Xamarin Forms and Forge Deep Linking

So things have been a bit quiet lately, I have been pretty busy at work and actually ended up a bit under the weather for a while, but am recovering now and want to continue on with this series of posts.

Last time I wrote about authenticating on Xamarin Form to Autodesk Forge using a web view to handle the authorization events. The use of webviews is being discouraged in some areas. It has been hard to get to the reasoning behind this, but some people are saying that it is possible for an attacker to fake in their own malicious site to replace the authentication page. I’m not sure how much of a worry this is, especially in mobile where both the app and the webview are sandboxed. Instead Google is recommending that you use the system browser directly. If nothing else, it will at least allow your user to use single-sign-on.

So how do we do this? First of all you have to modify your Autodesk Forge app to specify a different callback URL, one with your custom protocol. In my case, I used bbsw-fm://brainbucketsoftware.com.

Now when you start your authentication, instead of telling the webview to navigate you use the Device.Url command such as below (formatted for the blog, but obviously all one line).


string BaseUrl = "https://developer.api.autodesk.com/authentication/v1/authorize";
private const string ResponseType = "code";
private const string ClientId = "xxxxxxxxxxxxxxxxxxxxxxx";
private const string ClientSecret = "yyyyyyyyyyyy";
private const string Scope = "data:write%20data:read%20data:create";
private const string CallbackUrl = "bbsw-fm://brainbucketsoftware.com/";
private const string CodeParameter = "?code=";

string url = $"{BaseUrl}?response_type={ResponseType}&client_id={ClientId}&redirect_uri={CallbackUrl}&scope={Scope}";
Device.OpenUri(new Uri(url));

The next thing we need to do is override the OnAppLinkRequestReceived method in the App.xaml in your shared/portable project.


protected override async void OnAppLinkRequestReceived(Uri uri)
{
    base.OnAppLinkRequestReceived(uri);
    // same code as was used in the webview sample
    bool retrievedToken = await GetAccessTokenAsync(uri.ToString());
    if (retrievedToken)
        await NavigationService.NavigateAsync(Pages.Test);
}

The above function is supposed to be called automatically by the Xamarin Forms objects when your registered protocol is invoked on the device. However, I found that it only worked on Android and wasn’t being called on iOS or UWP (I believe there was a bug filed for this with Xamarin). So I created another entry point in my App object for those platforms which I ended up calling manually in the platform specific code.


public void UwpIOSOnAppLinkRequestReceived(Uri uri)
{
    OnAppLinkRequestReceived(uri);
}

Next post we will take a look at what we need to do for UWP platform code.

Xamarin Forms and Autodesk Forge

Introduction

I am going to change things up a bit and talk about working with Autodesk Forge on Xamarin Forms apps. If you are unfamiliar with Autodesk Forge, it is a set of web services that implement a range of functionality that in the past you might have only seen on desktop CAD programs. Forge gives you viewing, data management and some other services that may be of interest if you are in to processing designs. For the purposes of this article, we will look at authenticating a Xamarin app with Forge.

App Creation

The first thing you have to do is create an app with Forge. This is a pretty simple process. You login to developer.autodesk.com. Pull down the drop down menu showing your name and tap “My Apps”. You can then tap “Create App”.

ForgeCreateApp

Most of what you see is pretty straight forward. Pick the api’s that you are interested in and fill in the app name, description, callback url and website url.

Even if you are doing a mobile app, you still need to supply a callback url. This url will be used when the user authenticates.

ForgeappcreatedOnce the app is created, you should see something similar to what I have shown on the left. The important information is the client ID and the client secret. You will be needing this information for authenticating the user.

OAuth

Forge uses OAuth 2.0 throughout its services for authentication. There are a lot of mixed feelings about OAuth: some think it is very painful and clunky. To be honest, I don’t find it too bad and I think the effort is worth it as then you don’t have to worry about secure storage of user ids and passwords.

If you are unfamiliar with OAuth, it is a bit of a mix of separately authenticating outside of your app with a service and then authorizing your app to use the services in your name. Your app will use the client id and secret to connect to the service with requested access levels, the service will allow you to authenticate, and if all goes well, you will receive an access token that is used to consume the services. At no point does your app handle user ids and passwords. If you have ever given an app on your phone access to use your twitter account or Facebook account, you have probably used OAuth.

So lets take a look at how this would be implemented in your Xamarin app.

Setting Up Authentication

In your Xamarin app, you are probably going to want to connect to Forge as the very first thing, so you should navigate to that page upon app initialization. In my sample app, I am using the Prism framework and you can see samples of how that works in previous blog articles.

I have setup a ContentPage as the login page and it only contains a WebView control. On the WebView control, I am adding an event handler for the Navigating event. On the ContentPage, I am just overriding OnAppearing.


<?xml version="1.0" encoding="utf-8" ?>
<ContentPage     xmlns="http://xamarin.com/schemas/2014/forms"     xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"     xmlns:helpers="clr-namespace:Fusion.Mobile.Helpers;assembly=Fusion.Mobile"     xmlns:behaviors="clr-namespace:Fusion.Mobile.Behaviors;assembly=Fusion.Mobile"     xmlns:effecs="clr-namespace:Fusion.Mobile.Effecs;assembly=Fusion.Mobile"     x:Class="Fusion.Mobile.Views.LoginPage">

    <Grid>
        <WebView x:Name="_webViewer" Navigating="_webViewer_Navigating" />
    </Grid>

</ContentPage>

The OnAppearing override in the ContentPage is pretty straight forward and only serves to direct the WebView control to the Forge authorization page.


protected override void OnAppearing()
{
    base.OnAppearing();
    StartAuthentication();
}

The StartAuthentication method is used to set the WebView control to the Forge authentication page, and this is accomplished with a long url constructed from a number of parameters. It looks like the following:

https://developer.api.autodesk.com/authentication/v1/authorize
?response_type={resptype}
&client_id={clientid}
&redirect_url={callback}
&scope={scope}

I reformatted the above into multiple lines for readability, but normally, it is all one line. The {resptype} value is “code”. The {clientid} and {callback} values are the values from when you created your Forge app. And scope is the level of access you require to the data. In our case, we are just going to use “data:read”.


private const string BaseUrl = "https://developer.api.autodesk.com/authentication/v1/authorize";
private const string ResponseType="code";
private const string ClientId = "[your client id]";
private const string ClientSecret = "[your client secret]";
private const string Scope = "data:read";
private const string CallbackUrl="[your callback url]";
private const string CodeParameter = "?code=";

private void StartAuthentications()
{
    string url = $"{BaseUrl}?response_type={ResponseType}&client_id={ClientId}&redirect_url={CallbackUrl}&scope={Scope}";
    _webViewer.Source = new UrlWebViewSource { Url = url };
}

Let’s recap. We have our first page in our app, which contains a WebView control. After the app initializes and the first page is displayed, it executes our overridden OnAppearing method. This method constructs the url for the user to authenticate with Forge and authorize the app. It then passes the url to the WebView control and the user is then able to authenticate.

This is where it gets a bit tricky. You have to pay attention to the Navigating event. Once the user successfully authenticates, the WebView control will try and navigate to your callback URL. You can sniff that out by looking to see if the url that is being navigated to is your callback url. Take note: there could be multiple calls to this event handler as the WebView control handles multiple navigations. Once you find the url that has your callback url, grab it! You will need to pull out the code parameter at the end of the url. Then, cancel the navigation of the WebView. After that, we need to get the access token that is used to show that calls made from this app are authorized.


private const string CodeParam = "?code=";

private async void _webviewer_Navigating(object sender, WebNavigatingEventArgs e)
{
    if (e.Url.StartsWith(CallbackUrl))
    {
        int pos = e.Url.IndexOf(CodeParam);
        string code = e.Url.SubString(pos + CodeParam.Length);
        e.Cancel = true;

        await GetAccessToken(code);
    }
}

We are almost finished authenticating and authorizing! All we need to do now is retrieve our access token. To do this, we are just going to POST a REST call.


private async Task GetAccessToken(string code)
{
    using (HttpClient client = new HttpClient())
    {
        client.DefaultRequestHeaders.Clear();
        using (HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, "https://developer.api.autodesk.com/authentication/v1/gettoken"))
        {
            request.Content = new StringContent(
            $"client_id={ClientId}&client_secret={ClientSecret}&grant_type=authorization_code&code={code}&redirect_uri={CallbackUrl}");
            request.Content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/x-www-form-urlencoded");

            using (HttpResponseMessage message = await client.SendAsync(request))
            {
                string data = await message.Content.ReadAsStringAsync();
                if (message.IsSuccessStatusCode)
                {
                    Debug.WriteLine("retrieved access token!");
                    await _navigationService.NavigateAsync($"/{Pages.Master}/{Pages.Nav}/{Pages.Dashboard}");
                }
                else
                {
                    // show error message or something
                }
            }
        }
    }
}

Let’s talk about what is happening above. I am just using the standard REST functions in .NET, but you can use whichever library you want. The most important thing is what we put in the content of the REST call. This must be a url-like string that has the client id, client secret, the code we retrieved from the navigating event and the callback url and then post it up. If we aren’t successful, I am just restarting the process, but it would probably be a good idea to provide some feedback to the user.

If we are successful, we will get some JSON back in the response content that looks like the following:


{
    "token_type": "bearer",
    "expires_in: 3600,
    "refresh_token": "xxxxxxxxxxx",
    "access_token": "xxxxxxxxxxxxxxxxxxxxxxxxx"
}

You will need the access token value for each of your calls to the Forge API. You need to include it in the HTTP header as:

Authorization: Bearer xxxxxxxxxxxxxxxxx

When the token expires, you can use the refresh token to get a new one without having to get the user to authenticate.

On the next post, we will look at how to the token refresh.

Visual Studio, Xamarin Forms and Intellisense Errors

This is a bit of an odd one … I was working away on a Xamarin Forms project and suddenly started getting dozens of the red squigglies in the code editor. I could compile the project, but intellisense kept showing all of these errors.

I tried cleaning the solution, manually deleting all the bin and obj folders in all the projects, reloading projects, re-referencing projects. It didn’t matter, whatever I did still showed all kinds of errors, most of them being of the type “the type or namespance name could not be found”.

Finally I was able to track it down. The solution was to find the offending project in the solution (i.e. the one that contains the types that are throwing the errors) and remove it as a reference from the other projects. Then add the removed reference back in to each of the projects (Note you only have to do this for the projects that are showing the errors). After that, the intellisense is reset and all of the squigglies are gone!

 

Xamarin Forms Master Detail Pages Tip

Sometimes you just end up banging your head on things and can’t see what is directly in front of you …

In my case, I was setting up an app with a MasterDetailPage. And no matter what I did with it, it kept crashing on start up. It didn’t matter if I put the Master content in a separate page or embedded all of the XAML right in the MasterDetailPage itself. Crash.

It ended up being the fact that the Title property on the ContentPage in the Master section needed to be filled.


<MasterDetailPage>

<!-- ... -->

<MasterDetailPage.Master>

<ContentPage Title="FILL THIS IN">

<!-- master section content goes here -->

</ContentPage>

</MasterDetailPage>

Now that I have written it down, I will remember that for next time.

Custom Map Renderer: iOS

It Finally Happened. I picked up my first Mac computer in January. I am now the proud owner of a MacBook Pro 13, no touch bar and the regular keyboard. I went into the local Apple Store and tried out the newer keyboard and the older keyboard and have to say that I liked the feel of the old one better. I really didn’t like how loud the new keyboard was. But the old keyboard was good, trackpad was good (but not as good as I thought it was going to be) and the screen was nice. Overall a nice computer if expensive. But the important thing is that I could finally install XCode and build iOS apps!

And that is the subject of the this blog post. In the previous blog post, we looked at creating a custom renderer for Android. Now we will look at doing the same with iOS. With iOS, things are simpler in at least one regard: you don’t have to go get an access key for your app.

One thing I want to make clear after some questions from others. This information isn’t a whole lot different than what you can get from developer.xamarin.com. But it builds on those examples by making the custom map renderer updatable from your view model … if you are using data binding of course. If you aren’t using data binding, you can skip all of this and just follow the guide in the above link.

If you are still here …

I didn’t change anything in my CustomMap class in the shared project. All updates are in the iOS project. In here I have defined two classes: CustomMKAnnotationView and CustomMapRenderer. Let’s talk about CustomMKAnnotationView class first: it is the simpler.

The CustomMKAnnotationView class derives from MKAnnotationView. It adds a couple of new properties for the Id of the marker and a URL associated with the marker. Otherwise there is not a lot to be excited about. So lets move onto the CustomMapRenderer class.

The CustomMapRenderer class derives from MapRenderer. The first thing is that we have to override the OnElementChanged method. Just like Android, this either hooks up the native map to the cross platform map class in the shared project or unhooks it.


protected override OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.View> e)
{
    base.OnElementChanged(e);

    if (e.OldElement != null)
    {
        //... unhook events, native conrols etc
    }

    if (e.NewElement != null)
    {
        _customMap = (CustomMap) e.NewElement;
        _nativeMap = Control as MKMapView;
        _customPins = _customMap.CustomPins;

        _nativeMap.GetViewForAnnotation = GetViewForAnnotation;
        _nativeMap.CalloutAccessoryControlTapped +=
            OnCalloutAccessoryControlTapped;
        _nativeMap.DidSelectAnnotationView +=
            OnDidSelectAnnotationView;
        _nativeMap.DidDeselectAnnotationView +=
            OnDidDeselectAnnotationView;
    }
}

The most important things above are that you get a reference to our CustomMap object that was defined in the shared project; and also get a reference to the native map object (MKMapView).

Using the native map object from above, we can hook into events for it. We use these events to display the pins on the map and the info window when a user taps one of the pins.

Let’s take a look at the GetViewAnnotation method. This is the method that is used to show a custom marker on the map. In it we are passed in a reference to the marker. We use our GetCustomPin method to look up the information associated with the marker. In an effort to conserve memory, the map can reuse the view: and that is what is happening with the call to DequeueReusableAnnotation. If we can’t reuse, then we create a new one, specifying the image for the pin itself, and customizations for the callouts.


private MKAnnotationView GetViewForAnnotation(
    MKMapView mapView, IMKAnnotation annotation)
{
    if (annotation is MKUserLocation)
        return null;

    MKAnnotationView ret = null;

    var pointAnnotation = annotation as MKPointAnnotation;
    var customPin = GetCustomPin(pointAnnotation);
    if (customPin == null)
        throw new Exception("My pin not found");

    ret = mapView.DequeueReusableAnnotation(customPin.Id.ToString());
    if (ret == null)
    {
        ret = new CustomMKAnnotationView(
            annotation,
            customPin.Id.ToString(), customPin.Url);
        ret.Image = UIImage.FromFile("custompin2.png");
        ret.CalloutOffset = new CoreGraphics.CGPoint(0, 0);
        ret.LeftCalloutAccessoryView =
            new UIImageView(UIImage.FromFile("custompinimage.png"));
        ret.RightCalloutAccessoryView =
            UIButton.FromType(UIButtonType.DetailDisclosure);
        ((CustomMKAnnotationView) ret).Id = customPin.Id.ToString();
        ((CustomMKAnnotationView) ret).Url = customPin.Url;
    }

    ret.CanShowCallout = true;

    return ret;
}

Next we can take a quick look at the OnDidSelectAnnotationView. This is called when the user taps on the pin. You have the option of doing something custom … or just select the default that was setup in the GetAnnotationView function. In our case, we are going to see if the marker is associated with ID 1, and if it is, add in a custom image. Otherwise, we are just going to accept the default.


private void OnDidSelectAnnotationView(
    object sender,
    MKAnnotationViewEventArgs e)
{
    var customView = e.View as CustomMKAnnotationView;
    _customPinView = new UIView();

    if (customView.Id == "1")
    {
        _customPinView.Frame = new CoreGraphics.CGRect(0, 0, 200, 84);
        var image = new UIImageView(new CoreGraphics.CGRect(0, 0, 200, 84));
        image.Image = UIImage.FromFile("custommapimage.png");
        _customPinView.AddSubview(image);
        _customPinView.Center =
            new CoreGraphics.CGPoint(0, -(e.View.Frame.Height + 75));
        e.View.AddSubview(_customPinView);
    }
}

The OnDidDeselectAnnotationView method is called when the extended annotation is displayed and the user taps somewhere on the map, it will get rid of the extended information and free up the resources.


private void OnDidDeselectAnnotationView(
    object sender,
    MKAnnotationViewEventArgs e)
{
    if (!e.View.Selected)
    {
        _customPinView.RemoveFromSuperview();
        _customPinView.Dispose();
        _customPinView = null;
    }
}

Finally, let’s take a look at OnCalloutAccessoryControlTapped. After the user taps on the pin, extra data is displayed about the pin. If you tap on the extra data, this function is called. You can look at the information associated with the pin and act on it. In our case, we are going to grab the url and navigate to it.


private void OnCalloutAccessoryControlTapped(
    object sender,
    MKMapViewAccessoryTappedEventArgs e)
{
    var customView = e.View as CustomMKAnnotationView;
    if (!String.IsNullOrWhiteSpace(customView.Url))
    {
        UIApplication.SharedApplication.OpenUrl(
            new Foundation.NSUrl(customView.Url));
    }
}

And now just for a recap. If we want to make the custom map respond to our view model we have to listen for changes from the data binding system. This is almost exactly the same as the Android implementation where we override the OnElementPropertyChanged and listen for our property.


protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    base.OnElementPropertyChanged(sender, e);

    if (Element == null || Control == null)
        return;

    if (e.PropertyName == View.CustomMap.CustomPinsProperty.PropertyName)
    {
        UpdatePins();
    }
}

private void UpdatePins()
{
    if (_nativeMap == null)
        return;

    foreach (var p in _customMap.CustomPins)
    {
        MKPointAnnotation pa = new MKPointAnnotation
        {
            /// check git hub
        }

        _nativeMap.AddAnnotation(pa);
    }
}

Don’t forget, the CustomPins is bound in our XAML page.

I find it pretty amazing that Xamarin Forms is able to share so much of the code. Really all we are doing in the separate projects is the presentation: all of the business logic is shared between all. Very cool.

Find all the code in my GitHub under the XamarinPrism repository. Find it under the MapRendererIos solution.