Click here to Skip to main content
15,860,972 members
Articles / Mobile Apps / Xamarin

Xamarin Android Services Pattern on GPS Example

Rate me:
Please Sign up or sign in to vote.
4.61/5 (7 votes)
30 Jan 2017CPOL8 min read 12.6K   13  
Xamarin Android Services Pattern on GPS example

Introduction

In Xamarin, if you want to use many Android native service/mechanisms, you have to implement it in Activity class. Or at least that is how Xamarin documentation wants you to do it.

For example, if you want to use GPS to acquire current device location. According to the official documentation, you have to implement Android Activity similar to something below:

C#
public class MainActivity : FormsApplicationActivity, ILocationListener
{
    private LocationManager _locMgr;
    private string _provider = LocationManager.GpsProvider;
    private object _locationProvider;

    public void OnLocationChanged(Location location)
    {
    }

    public void OnProviderDisabled(string provider)
    {            
    }

    public void OnProviderEnabled(string provider)
    {            
    }

    public void OnStatusChanged(string provider, Availability status, Bundle extras)
    {            
    }

    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);

        Xamarin.Forms.Forms.Init(this, bundle);
        LoadApplication(new App());
        _locMgr = GetSystemService(LocationService) as LocationManager;
        var locationCriteria = new Criteria
        {
            Accuracy = Accuracy.Coarse,
            PowerRequirement = Power.Medium
        };
        if (_locMgr != null)
        {
            _locationProvider = _locMgr.GetBestProvider(locationCriteria, true);
            if (_locationProvider != null)
            {
                _locMgr.RequestLocationUpdates(_provider, 2000, 1, this);
            }
            else
            {
                throw new Exception();
            }
        }
    }

    protected override void OnPause()
    {
        base.OnPause();
        _locMgr.RemoveUpdates(this);
    }

    protected override void OnResume()
    {
        base.OnResume();
        _locMgr.RequestLocationUpdates(_provider, 2000, 1, this);
    }
}

Few words of explanation. ILocationListener is an Android interface that is used by OS to inform an application about changed GPS location of a device. In a most simple example, it is implemented inside Activity.
What is the problem with the above? When this kind of logic extends Activity class, it makes really it hard to share GPS functionality across your applications. Moreover, there are other Android system mechanisms that might be recommended to implement them this way (like GSM signal, network access, global layout, etc.). If done this way, it would make Activity class really bloated and hard to maintain. Also, it is problematic to use that kind of service implementation in view models. Unless you code even more features into the MainActivity class (new methods, events and static access to MainActivity instance, i.e.), which is not what we want to do.

Another bad idea is to use native Android views like this is done in Xamarin GPS documentation. It is funny considering that they advertise its own framework with the possibility of sharing code across platforms and then ask you to use native views to achieve something so basic on mobile like GPS location :). Yes, it would be possible to update Xamarin view with new location instead of native view, but I honestly do not see the point unless you are really in a hurry and the application has only one view. In more complicated applications, using of GPS in another view would force you to put another view-specific code inside Activity. Another bad idea. Other thing to point out is that if you are using XAML, you should also use MVVM pattern, for what XAML was designed and when it really shone. In such a case, doing GPS in the above way would made things even more complicated since after obtaining a Xamarin view, you would be forced to obtain view model and only then update view model with updated GPS location.

But, there is a better way to do things.

Reusable Android Service

The goal is to make all GPS related code inside class-interface combo. Registering both in IoC container and retrieving it from there (with maybe simple initialization before using it on some view) would be ideal. Then it is possible to place it in some library and share between applications.

Let us start with implementation of ILocationListener interface. It does not have to be implemented in Activity class. It can be any object that has base type of Java.Lang.Object. Of course, since this class is Android platform class LocationListener needs to be placed in the appropriate platform project.

C#
public class LocationListener : Java.Lang.Object, ILocationListener
{
    public void OnLocationChanged(Location location)
    {

    }

    public void OnProviderDisabled(string provider)
    {

    }

    public void OnProviderEnabled(string provider)
    {

    }

    public void OnStatusChanged(string provider, Availability status, Bundle extras)
    {

    }
}

The only method we are really interested in is OnLocationChanged. We need to push this new location to our new service.

We can create a new PCL interface ILocationService that should work as interface that needs to be resolved from IoC container for its implementation on a specific platform.

C#
public interface ILocationService
{
    event EventHandler<LocationEventArgs> LocationChanged;

    void RequestLocation();

    void StopRequests();
}

We need event of course to notify other objects about changed location. Two other methods are used as a way to on and off GPS mechanism on device. This is needed because according to Google documentation, it is best to have GPS enabled only when you need it, so as soon as you will have good enough location, GPS should be disabled.

Ok, now to the implementation. Most of the code is copied from the previous implementation in activity and as an Activity class, it needs to be placed in Android project.

C#
public class LocationService : ILocationService
{
    public LocationListener Listener;
    private readonly LocationManager _locMgr;
    private readonly Criteria _locationCriteria;

    public LocationService(MainActivity activity)
    {
        _locMgr = activity.GetSystemService(Context.LocationService) as LocationManager;
        Listener = new LocationListener();
        _locationCriteria = new Criteria
        {
            Accuracy = Accuracy.Coarse,
            PowerRequirement = Power.Medium
        };
        if (_locMgr == null)
        {
            throw new Exception("No LocationManager instance!");
        }
    }

    public event EventHandler<LocationEventArgs> LocationChanged;

    public void RequestLocation()
    {
        var provider = _locMgr.GetBestProvider(_locationCriteria, true);
        if (provider == null)
        {
            throw new Exception("No GPS provider could be found for given criteria!");
        }
        _locMgr.RequestLocationUpdates(provider, 2000, 1, Listener);
    }

    public void StopRequests()
    {
        _locMgr.RemoveUpdates(Listener);
    }

    protected virtual void OnLocationChanged(LocationEventArgs e)
    {
        LocationChanged?.Invoke(this, e);
    }
}

There are a few changes though. The actual start of location updates is moved to RequestLocation method. Two errors are also thrown if there is a problem with GPS mechanism. Nothing major anyway.

Problem with the above is that there is no connection between location update in LocationListener and LocationService. The simplest way is to create, i.e., PushLocation method in LocationService and execute it from within LocationListener.

C#
public class LocationListener : Java.Lang.Object, ILocationListener
{
    private readonly LocationService _locationService;

    public LocationListener(LocationService locationService)
    {
        _locationService = locationService;
    }

    public void OnLocationChanged(Location location)
    {
        _locationService.PushLocation(location);
    }

    public void OnProviderDisabled(string provider)
    {

    }

    public void OnProviderEnabled(string provider)
    {

    }

    public void OnStatusChanged(string provider, Availability status, Bundle extras)
    {

    }
}

To use new constructor, we need to change how instance of LocationListener is created inside LocationService constructor.

C#
Listener = new LocationListener(this);

With instance of service inside LocationListener, we can use new PushLocation method.

C#
internal void PushLocation(Location location)
{
    OnLocationChanged(new LocationEventArgs(location));
}

This method will raise LocationChanged event and new location will be pushed to every handler. Pretty simple, but effective.

Ok. We should now test it in some application. The main page of the application can have just a simple label where location will be shown.

XML
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Service.MainPageView">

  <Label Text="{Binding Location}" 
         VerticalOptions="Center" 
         HorizontalOptions="Center"
         FontSize="40"/>

</ContentPage>

View model for the above view is also nothing fancy. Just implementation of INotifyPropertyChanged and constructor designed for dependency injection of location service.

C#
public class MainPageViewModel : INotifyPropertyChanged
{
    private readonly ILocationService _locationService;

    public MainPageViewModel(ILocationService locationService)
    {
        _locationService = locationService;
        _locationService.LocationChanged += _locationService_LocationChanged;
    }

    public string Location { get; set; }

    private void _locationService_LocationChanged(object sender, LocationEventArgs e)
    {
        Location = e.Location.Longitude + ", " + e.Location.Latitude;
        OnPropertyChanged("Location");
    }

    public void OnAppearing()
    {
        _locationService.RequestLocation();
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public void OnDisappering()
    {
        _locationService.StopRequests();
    }
}

Location event handler (_locationService_LocationChanged) just glues two coordinates and sets new string representation of location with notification of update to view.
How inject LocationService into view model? There is a really nice library called TinyIoC. In my previous article, I explained how to add PCL support to this library and this version, which will be used in this example. But if you want, you can use TinyIoC official package from Android project. Personally, I think this is much better, to put IoC container inside PCL and not worry about it in platform project (which is much easier since you do not need to create another interface which would work as proxy from PCL to TinyIoC inside platform project). You can even place it in some shared library that you are using inside all of your projects and then use it without a hassle. Smile

Anyway, inside Android Activity class, you can place initialization of service implementation.

C#
[Activity(Label = "Service", Icon = "@drawable/icon", 
 MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : FormsApplicationActivity
{
    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);
        Xamarin.Forms.Forms.Init(this, bundle);
        RegisterServices();
        LoadApplication(new App());
    }

    private void RegisterServices()
    {
        var container = TinyIoCContainer.Current;
        container.Register(this);
        container.Register<ILocationService, LocationService>();
    }
}

Whole IoC magic happens in RegisterServices method, where LocationService is registered with its PCL interface. This in fact still forces you to do some work inside activity, but you must admit that it is much more convenient to enable some functionality with one line of code instead of whole interface implementation. And if you really do not like this way of registering services, there is Auto register feature inside TinyIoC. Also, it is possible to add your own auto registration of some specific assemblies that are available in domain or some classes marked with attribute, etc.

And now, finally, it is possible to create Xamarin application and set main page to well, MainPage. :)

C#
public partial class App
{
    public App()
    {
        InitializeComponent();

        var mainPageViewModel = TinyIoCContainer.Current.Resolve<MainPageViewModel>();
        MainPage = new MainPageView
        {
            BindingContext = mainPageViewModel
        };
    }
}

As you can see, creation of view model instance from IoC container takes care of dependency injection of ILocationService.
Now at last, we can test if everything works inside our new application.

Image 2

The above test was performed on an emulator, which may look a little strange with two windows, etc., but I think it shows quite well how update of location of device updates label on MainPage. And I did not have to run around with tablet to change location. :)

Points of Interests

It is really a basic implementation of GPS service. Of course, there is the possibility to implement more sophisticated logic. In the example, it would be nice to add to ILocationService some platform independent way of requesting a specific accuracy of GPS updates. In the above code, there is use of Criteria class to select location provider. It is possible to set it to GPS or NETWORK or both by some switch. Also, example code is set to constant time intervals (2s as 2000 milliseconds passed to RequestLocationUpdates method of LocationManager class) or minimum distance difference between updates of 1 meter (1 passed to RequestLocationUpdates method). There is also the possibility of implementation of more complex algorithm of requesting location, so that it conserves battery power with best results possible like it is recommended in Android documentation.

Image 3

Summary

In the same way, we can create Android services for more mechanisms: accelerometer, network state, software keyboard visibility, etc. without implementing all of this inside Activity class, which would made it bloated and code would be impossible to share easily between applications. And after all, it is much better to write something once and then reuse it, instead of copy-pasting the same code everywhere.

View model services injection is also a much more sane thing to do. You cannot in an easy way, update Xamarin views from Activity and this is how it is done in Xamarin official documentation. Even more mind boggling is that Xamarin documentation involves modification of Android native views for showing GPS location :). What is the point of using Xamarin if you do everything in native Android views anyway. Yes, it is possible to obtain Xamarin current view page inside Activity class and update it from there, but it forces creating another mechanism just for that. Even more complicated would be to push a new location into view model, where application view logic should be located anyway. It would be just bad pattern to do things like this. Separating that kind of platform services and injecting their interfaces inside view models is a much more simpler and cleaner way.

You can download the code from the link at the top of this article or you can find it on GitHub.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Poland Poland
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --