Introduction
In Xamarin if you want to use many Android native service/mechanism 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 official documentation you have to implement Android Activity similar to something below.
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 Android interface that is used by OS to inform application about changed GPS location of a device. In most simple example it is implemented inside Activity.
What is problem with above? When this kind of logic extends Activity class, it makes really hard to share GPS functionality across your applications. Moreover there are other Android system mechanism 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 this is problematic to use that kind of service implementation in view models. Unless you code even more features into 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 possibility of sharing code across platforms and then ask you to use native views to achieve something such 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 hurry and application have 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 shine. In such case, doing GPS in 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
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 do not have to be implemented in Activity class, It can be any object that have base type of Java.Lang.Object. Of course since this class is Android platform class LocationListener need to be placed in appropriate platform project.
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 new PCL interface ILocationService that should work as interface that needs to be resolved from IoC container for its implementation on specific platform.
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 then when you need it, so as soon as you will have good enough location GPS should be disabled.
Ok, now implementation. Most of the code is copied from previous implementation in activity and as an Activity class it need to be placed in Android project.
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 few changes though. 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 above is that there is no connection between location update in LocationListener and LocationService. Simplest way is to create i.e. PushLocation method in LocationService and execute it from within LocationListener.
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.
Listener = new LocationListener(this);
With instance of service inside LocationListener, we can use new PushLocation method.
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. Main page of application can have just simple label where location will be shown.
<?xml version="1.0" encoding="utf-8" ?> <ContentPage x_Class="Service.MainPageView"> <Label Text="{Binding Location}" VerticalOptions="Center" HorizontalOptions="Center" FontSize="40"/> </ContentPage>
View model for above view is also nothing fancy. Just implementation of INotifyPropertyChanged and constructor designed for dependency injection of location service.
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 do 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 share library, you are using inside all of your projects and then use it without a hassle 🙂
Anyway inside Android activity class you can place initialization of service implementation.
[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 🙂
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 we can at last test if everything works inside our new application.
Above test was performed on an emulator, which may look a little strange with two windows etc., but I think it shows quite good 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 basic implementation of GPS service. Of course there is possibility to implement more sophisticated logic. In example it would be nice to add to ILocationService some platform independent way of requesting specific accuracy of GPS updates. In above code there is use or 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 possibility of implementation more complex algorithm of requesting location, so that it conserve battery power with best result possible like it is recommended in Android documentation.
Summary
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 much more sane thing to do. You cannot in 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 force creating another mechanism just for that. Even more complicated would be to push 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 much more simpler and cleaner way.
You can download code for this article from link below or you can find it on GitHub.
Correction: The Method is actually "isInTouchMode()"
https://developer.android.com/reference/android/view/View.html#isInTouchMode%28%29
This works when IsTouchMode=True but if it is not then the Hide event handler doesn't register the keyboard hide event.
An example of this not working is using either the hardware back button (Which turns into a hide keyboard button if it's a software back button on screen and the keyboard is visible)
Or another example is using a Escape or back button on a bluetooth keyboard connected to a phone running the xamarin forms application.