[iOS] URL schema handling

Hi,
Is there any support/integration for the Cordova Custom URL Scheme Handling function in Ionic? Here is the Guide. Usually it’s a global function which should be called when the App is invoked via the URL scheme. I am looking for a good way making the URL parameter available in my page controller. Did anyone handle this function call in an Ionic App already?

Kind regards,
Alexander

Hmm, not really related to issues with ionic…but I found this article which maybe some help.

I have done some testing with this plugin

EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin

I’m assuming you’ve already got the URL scheme handling properly.

If not, the @aaronksaunders answer of the LaunchMyApp plugin is the best way to go. It works really well.

Next ->

I THINK your problem is that you can’t figure out how to integrate it into Ionic. As @mhartington said, this isn’t really an Ionic question. It’s more of an AngularJS question. Generally, speaking, you don’t want external code to be interacting with an Angular app. However, sometimes it is necessary. In this case, it’s necessary because the URL handler MUST be a global function.

So, you need to find some way to let the external, global function communicate with Angular. There’s a great SO post on that here : http://stackoverflow.com/questions/10490570/call-angular-js-from-legacy-code

Here’s how to make this work. Maybe I’ll do a blog post on this. The gist is that you have a controller that will always be present (don’t use on a controller that may not be active). The handler finds the element (body) and gets the controller scope of it. Then, it calls a method on that controller scope.

Put index.html:

<body ng-controller="AppLaunchedController">

Put this in your default view :

<h2 class="padding">Has Launched : {{test.hasLaunched}}</h2>

Create a controller like this :

.controller('AppLaunchedController', function($scope) {

	$scope.test = {
		hasLaunched : 'NO'
	};

	$scope.reportAppLaunched = function(url) {

		console.log('The app launched with URL : ' + url);
		
		$scope.$apply( function() {
			$scope.test.hasLaunched = 'YES';

		})
	}
})

Then perhaps at the bottom of your app.js

function handleOpenURL(url) {

    var body = document.getElementsByTagName("body")[0];
    var appLaunchedController = angular.element(body).scope();
    appLaunchedController.reportAppLaunched(url);

}

Thanks, I appreciate all your comments. The Cordova Plugin looks like a nice universal tool for Android and iOS but basically has the same “global function call” issue. I think Calendee is pointing into the right direction, so I’ll give this solution a try. Actually I dreamed of a integration similar to the ionic.Platform.ready(callback). But no clue if it’s feasible.

[UPDATE]
I can confirm that Calendee’s solution is working. I’m now using the EddyVerbruggen/LaunchMyApp-PhoneGap-Plugin and Calendee’s way to receive and add the URL to my BODY controller. It works okay despite some issues on Android with “when” and “with what data” the handleOpenURL(url) function is called. Thanks again for your help!

UPDATE : See @emaV solution below. Must better than mine.

Here’s the promised blog post with a sample app on GitHub.

http://calendee.com/2014/05/12/custom-urls-in-ionic-cordova-apps/

2 Likes

Thank you @Calendee, your solution works perfectly and is much better than my hack.

But for iOS I had to wrap the code in a setTimeout call with a timeout value of zero:

function handleOpenURL(url) {
    var body = document.getElementsByTagName("body")[0];
    var mainController = angular.element(body).scope();

    setTimeout(function() {
        mainController.reportAppLaunched(url);
    }, 0);
}

BTW: The URL of your blog post changed a bit:
http://calendee.com/2014/05/12/custom-urls-in-ionic-cordova-apps/

I tested a different approach using events.

Basically in my app.js my handleOpenURL function trigger a custom event with the url received as payload.

function handleOpenURL(url) {
  var event = new CustomEvent('SamklUrl', {detail: {'url': url}});
  setTimeout( function() {
      window.dispatchEvent(event);
    },
    0
  );
}

Then I added a run function to the main angular.module where I intercept the event.

.run(['$state', '$window',
  function($state, $window) {
    $window.addEventListener('SamklUrl', function(e) {
       if(e.detail.url==='zut://zut') {
         $state.go('invite', e.detail.url);
       }
    });
  }
])

Of course you can inject any Service/Factory you need there…

4 Likes

@emaV Very nice! I’ve update my blog post to point people here. I like your solution much better.

Actually, it turned out that $state.go() is picky with $stateParams, so I suggest to use a factory like this to handle the url:

.factory('Invite', ['$q',
  function($q) {
    var _inviteCode = undefined;

    // check the url from event and set the _inviteCode
    // return true if the path is `invite/aaaaaa`
    function checkInviteURL(inviteURL) {
      var parser = document.createElement('a');
      parser.href = inviteURL;
      var res = parser.pathname.split("/");
      if(res[0]==='invite' && res[1]) {
        _inviteCode = res[1];
        return true;
      }
      else {
        return false;
      }
    }

    return {
      checkURL: checkInviteURL,
      get: function() { return _inviteCode;}
    }
  }
])

Then you can check on the .run function if the url is a valid url (you can add some more logic on the checkInviteURL) and use the get() function in your directive bypassing the $stateParams.

Your .run will read:

.run(['$state', '$window', 'Invite',
  function($state, $window, Invite) {

    $window.addEventListener('SamklUrl', function(e) {
      if(Invite.checkURL(e.detail.url)) {
        $state.go('tab.account');
      }
    });
  }
])

Finally your controller:

.controller('AccountCtrl', ['$scope', 'Invite',
  function($scope, Invite) {
    $scope.inviteCode = Invite.get();
  }
]);

This post proved to be quite useful.

However I discovered that Cordova has some issues opening an application with the handleOpenURL function if the application was previously closed.

This message is just to save you to pull your hair out of your head like I did. I investigated further and it’s reported in JIRA so, if you read this, follow the breadcrumbs!

https://issues.apache.org/jira/browse/CB-8028

Hey, I’ve now also implemented my functionality with a custom event, but now I just realized that on my Galaxy Nexus device running Android 4.3 the CustomEvent class is not available.
I get message “CustomEvent is not defined” in LogCat console.

Do you know if there is a workaround for this? It is very important that I can add data to the event dispatch.

Regards,
Alex

I found the solution by myself. You can polyfill the CustomEvent() constructor functionality with the following code:

(function () {
  function CustomEvent ( event, params ) {
    params = params || { bubbles: false, cancelable: false, detail: undefined };
    var evt = document.createEvent( 'CustomEvent' );
    evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
    return evt;
   };

  CustomEvent.prototype = window.Event.prototype;

  window.CustomEvent = CustomEvent;
})();

Got it from https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent

It works on both: Devices which have the class already and devices which haven’t got it.

2 Likes

where should I define handleOpenUrl in app.js?
Should it inside .config or .run ?

2 Likes

Hello. Is it possible that this error is still unsolved?
I have the
cordova-plugin-customurlscheme 4.1.5 "Custom URL scheme"
and, if the app is closed, it doesn’t work. I put a setTimeout() of 3seconds before change the page, to be sure the app is ready… but obviously is not an elegant solution.

I’ve just done it this way:

'use strict';

angular.module('MyCustomUrlHandler', [])

.run(['$rootScope', '$window', function($rootScope, $window) {
  $window.handleOpenURL = function(url) {
    $rootScope.$broadcast('customURL', url);
  };
}]);

You can then simply listen to the matching $rootScope.$on event after you’ve required the module:

angular.module('MyApp', ['MyCustomUrlHandler'])

.run(['$rootScope', function($rootScope) {
  $rootScope.$on('customURL', function(event, url) {
    alert('The URL is: ' +  url);
  });
}]);

Make sure handleOpenURL() is defined above cordova.js either inside index.html.
Or any js file, then in that case that js should be refered above cordova.js

Basically, Cordova framework (CDVHandleOpenURL.m) looks for handleOpenURL function defination in global js scope on app load…and adds deviceredy js function in cordova.js which will make call to handleOpenURL.

So now on ios app load (cold start), you will be able get url params.

Many thanks, your answer worked for me in a legacy angular 1/cordova project.

1 Like