Saturday, June 6, 2020

Xamarin, Android Receivers, and Intents

In Xamarin, a receiver is a class with an OnReceive method that is called by Android when something interesting happens. The receiver defines what it considers "interesting". This is a bare-bones demo of registering a receiver that is called whenever the battery status changes. The receiver grabs the battery level and displays it on a form.

The bulk of the fun occurs in the .Android project. The entire code that gets called when the battery state changes resides in the .Android project so this technique works for regular Xamarin too. However, iOS manages this functionality in it's own special way so you're on your own there.

I'm using Visual Studio version 16.6.1. Start a new Xamarin Forms project called BatteryMonitor. Deselect the iOS option.

Start a new Xamarin.Forms solution and call it BatteryMonitor
Chose the Blank option and deselect iOS.

Select the Blank option and deselect iOS.
In the BatteryMonitor.Android project, open MainActivity.cs.

We need to make it look like this.


using Android.App;
using Android.Content.PM;
using Android.Runtime;
using Android.OS;
using Android.Content;

namespace BatteryMonitor.Droid
{
    [Activity(Label = "BatteryMonitor", Icon = "@mipmap/icon", Theme = "@style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
    {
        protected override void OnCreate(Bundle savedInstanceState)
        {
            TabLayoutResource = Resource.Layout.Tabbar;
            ToolbarResource = Resource.Layout.Toolbar;

            base.OnCreate(savedInstanceState);

            Xamarin.Essentials.Platform.Init(this, savedInstanceState);
            global::Xamarin.Forms.Forms.Init(this, savedInstanceState);
            LoadApplication(new App());
            this.RegisterReceiver(new BatteryReceiver(), new IntentFilter(Intent.ActionBatteryChanged));
        }

        public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
        {
            Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);

            base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

    public class BatteryReceiver : BroadcastReceiver
    {
        public override void OnReceive(Context context, Intent intent)
        {
            BatteryMonitor.MainPage mainPage = Xamarin.Forms.Application.Current.MainPage as BatteryMonitor.MainPage;
            mainPage.BatteryLevel = intent.GetIntExtra(BatteryManager.ExtraLevel, 0);
        }
    }
}


As you can see, all we did was create the BatteryReceiver class which inherits BroadcastReceiver and implements the mandatory OnReceive method. Then we registered the class in the MainActivity.OnCreate method. As we registered it, we stated it should be called whenever the battery status changed. You can also provide a list of intents in the same registration. The receiver class would normally be in it's own class file, but I want to focus on functionality here.

When the application starts, and whenever the battery state changes, Android will call BatteryReceiver.OnReceive. The Intent parameter contains different information for different intents. We can easily get the current battery level from the intent.

Now we have to display it so we move to the BatteryMonitor project. Open MainPage.xaml and make it look like this.


<?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:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             x:Name="This"
             x:Class="BatteryMonitor.MainPage">
    <ContentPage.BindingContext>
        <x:Reference Name="This"/>
    </ContentPage.BindingContext>
    <StackLayout>
        <Label Text="{Binding Path=BatteryLevel}" HorizontalOptions="Center" VerticalOptions="CenterAndExpand" FontSize="Header"/>
    </StackLayout>
</ContentPage>

We have specified that the page will use the code-behind as its BindingContext. I chose to do that because it's simpler than using view models and this part of the demo is not central to the subject. We have also replaced the default Label with one that is bound to a property in the code-behind. Let's write that code-behind.

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Xamarin.Forms;

namespace BatteryMonitor
{
    [DesignTimeVisible(false)]
    public partial class MainPage : ContentPage
    {

        private int _BatteryLevel = 0;
        public int BatteryLevel
        {
            get { return _BatteryLevel; }
            set { SetProperty(ref _BatteryLevel, value); }
        }

        public MainPage()
        {
            InitializeComponent();
        }

        public bool SetProperty<T>(ref T Storage, T value, [CallerMemberName] string propertyName = null)
        {
            if (Object.Equals(Storage, value)) return false;
            Storage = value;
            OnPropertyChanged(propertyName);
            return true;
        }
    }
}


As you can see, the code in BatteryReceiver simply gets a reference to the page and modifies the BatteryLevel property.

If you run this using an emulator you can open the extended controls by clicking on the ellipsis at the bottom of the vertical control strip. Then you can move the Charge level slider and watch BatteryReceiver get called.



No comments:

Post a Comment