Custom Map Renderer on Android: Part 3

This is the final post in the series about custom map renderers on Android. To recap, we added a map page to our application and then customized the appearance of the map markers and the info window that is shown when the user taps a marker.

Now we are going to make the map a bit more usable by making it possible to update the pins from the view model, and making it easy to consume services in the Android specific project.

In the previous post, we defined a CustomMap control that only had one property:


public class CustomMap : Map
{
    public List<CustomPin> CustomPins { get; set; }
}

The property above is only available from the ContentPage code behind. We are going to change this to be a bindable property that can be bound in the view model itself.


public class CustomMap : Map
{
    public static readonly BindableProperty CustomPinsProperty =
        BindableProperty.Create(
            "CustomPins",
            typeof(List<CustomPin>),
            typeof(CustomMap),
            new List<CustomPin>(),
            BindingMode.TwoWay);

    public List<CustomPin> CustomPins
    {
        get { return (List<CustomPin>) GetValue(CustomPinsProperty); }
        set { SetValue(CustomPinsProperty, value);
    }
}

Now that CustomPins is a bindable property it can participate in the data binding system. We can setup a property in our view model and attach it in our XAML.


    <view:CustomMap x:Name="cusmap"
IsShowingUser="True" MapType="Street"
CustomPins="{Binding CustomPins, Mode=TwoWay}" />

And here is the property in the view model:


/// other properties
private List<CustomPin> _customPins = new List<CustomPin>();
public List<CustomPin> CustomPins
{
    get { return _customPins; }
    set { SetProperty<List<CustomPin>>(ref _customPins, value); }
}

Finally, we need to get our CustomMapRenderer to react to these changes. The way we do that is to override the OnElementPropertyChangedEvent to list for changes in the CustomPins property.


public class CustomMapRenderer : MapRenderer, GoogleMap.IInfowindowAdapter, IOnMapReadyCallback
{
    // .... all the other code

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

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

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

Finally, I want to show how you can use that data binding technique to pass some services through to your Android project. For example, when a user taps a marker on the map, the info window will show. We can listen for when the user taps on that info window and use our Prism navigation service to pull up a new page. Let’s add the new bindable property to the CustomMap class.


public class CustomMap : Map
{
    public static readonly BindableProperty CustomPinsProperty =
        BindableProperty.Create(
            "CustomPins",
            typeof(List<CustomPin>),
            typeof(CustomMap),
            new List<CustomPin>(),
            BindingMode.TwoWay);

    public List<CustomPin> CustomPins
    {
        get { return (List<CustomPin>) GetValue(CustomPinsProperty); }
        set { SetValue(CustomPinsProperty, value); }
    }

    public static readonly BindableProperty NavigationServiceProperty =
        BindableProperty.Create(
            "NavigationService",
            typeof(Prism.Navigation.INavigationService),
            typeof(CustomMap),
            null);

    public Prism.Navigation.INavigationService NavigationService
    {
        get { return (Prism.Navigation.INavigationService) GetValue(NavigationServiceProperty); }
        set { SetValue(NavigationServiceProperty, value); }
    }
}

Let’s make sure the navigation service is exposed in our view model and then bind it in XAML. Here’s the view model:


public class MapViewModel : BaseViewModel
{
    public MapViewModel(INavigationService navigationService)
        : base()
    {
        NavService = navigationService;
    }

    /// other properties, such as CustomPins

    private INavigationService _navService = null;
    public INavigationService NavService
    {
        get { return _navService; }
        set { SetProperty<INavigationService>(ref _navService, value); }
    }

    /// other code is here
}

And now the XAML:


    <view:CustomMap x:Name="cusmap"
IsShowingUser="True" MapType="Street"
CustomPins="{Binding CustomPins, Mode=TwoWay}"
NavigationService="{Binding NavService}" />

And now we can get use the navigation service from our CustomMapRenderer like this by adding in our InfoWindowClick handler. Remember that we wired it up in the OnMapReady function. Now let’s put some functionality in the handler:


private void OnMapInfoWindowClick(object sender, GoogleMap.InfoWindowClickEventArgs e)
{
    if (_customMap.NavigationService != null)
    {
        _customMap.NavigationService.NavigateAsync(DevDaysSpeakers.PageKeys.Speakers);
    }
}

You can find all of this code in my GitHub repo in the MapRenderer solution. If you run the project, you can tap on the “Map” button on the main page. It will bring up the map and display a marker. If you tap the test button, it will update the marker to a new location. Tapping on the marker will show a custom info window, and tapping that window will use the Prism navigation service to navigate back to the main page.

Sounds like I am finally going to pick up a Mac laptop, so I should be able to expand these posts to the iOS platform. Looking forward to that.

Lots of stuff! Hope you find some of it useful.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s