Xamarin.Forms 2.5 and Local Context on Android
Previously I wrote about Xamarin.Forms 2.5 and local context in Android custom renderers. I briefly mentioned that Xamarin.Forms.Forms.Context is now obsolete, and that in a custom renderer you can usually replace references to Xamarin.Forms.Forms.Context with a reference to the view’s context, which is passed into the custom renderer constructor.
So what do you do if your app references Xamarin.Forms.Forms.Context in a class that isn’t a custom renderer? For example, consider the GetVersionNumber method in the VersionHelper class:
using Android.Content; namespace DependencyServiceAndLocalContext.Droid { public class VersionHelper : IVersionHelper { public string GetVersionNumber() { var versionNumber = string.Empty; Context context = Xamarin.Forms.Forms.Context; if (context != null) { versionNumber = context.PackageManager.GetPackageInfo(context.PackageName, 0).VersionName; } return versionNumber; } } }
This code is executed by Xamarin.Forms DependencyService class, and references Xamarin.Forms.Forms.Context to get the local context. The problem is that as of Xamarin.Forms 2.5, it will result in a compiler warning - ‘Forms.Context’ is obsolete: ‘Context is obsolete as of version 2.5. Please use a local context instead.’
The solution is to get the local context without referencing Xamarin.Forms.Forms.Context. How this is achieved depends on whether your app has a single Activity, or multiple activities.
Note: my preferred approach is the technique documented for apps with multiple activities, as it’s also applicable to apps with only a single Activity. Therefore, this is the approach I’ve demonstrated in a sample app that can be found on GitHub.
Single Activity Apps
In a single Activity app the local context can be obtained from the MainActivity class. This can be achieved by adding an Init method to the VersionHelper class:
using Android.Content; namespace DependencyServiceAndLocalContext.Droid { public class VersionHelper : IVersionHelper { static Context _context; public static void Init(Context context) { _context = context; } public string GetVersionNumber() { var versionNumber = string.Empty; if (_context != null) { versionNumber = _context.PackageManager.GetPackageInfo( _context.PackageName, 0).VersionName; } return versionNumber; } } }
Then in the MainActivity class, call the VersionHelper.Init method, passing in the MainActivity instance as the context:
VersionHelper.Init(this);
Alternatively, create a static property in the MainActivity class, and set it to the MainActivity instance:
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity { internal static MainActivity Instance { get; private set; } protected override void OnCreate(Bundle bundle) { ... global::Xamarin.Forms.Forms.Init(this, bundle); Instance = this; Xamarin.Forms.DependencyService.Register<IVersionHelper, VersionHelper>(); LoadApplication(new App()); } }
Then, in the VersionHelper class, retrieve the local context from the MainActivity class:
using Android.Content; namespace DependencyServiceAndLocalContext.Droid { public class VersionHelper : IVersionHelper { public string GetVersionNumber() { var versionNumber = string.Empty; if (MainActivity.Instance != null) { versionNumber = MainActivity.Instance.PackageManager.GetPackageInfo( MainActivity.Instance.PackageName, 0).VersionName; } return versionNumber; } } }
There are other variations on this theme, but they all amount to the same idea – retrieving the local context from the MainActivity class.
Multiple Activity Apps
If your app uses multiple activities, and hence you need to ensure that the local context is accurate, the approach outlined for single Activity apps can be used and modified, but it soon becomes messy to ensure that classes have references to the correct local context.
A better approach is to create a MainApplication class that implements the IActivityLifecycleCallbacks interface:
using System; using Android.App; using Android.Content; using Android.OS; using Android.Runtime; namespace DependencyServiceAndLocalContext.Droid { [Application] public partial class MainApplication : Application, Application.IActivityLifecycleCallbacks { internal static Context CurrentContext { get; private set; } public MainApplication(IntPtr handle, JniHandleOwnership transfer) : base(handle, transfer) { } public override void OnCreate() { base.OnCreate(); RegisterActivityLifecycleCallbacks(this); } public override void OnTerminate() { base.OnTerminate(); UnregisterActivityLifecycleCallbacks(this); } public void OnActivityCreated(Activity activity, Bundle savedInstanceState) { CurrentContext = activity; } public void OnActivityDestroyed(Activity activity) { } public void OnActivityPaused(Activity activity) { } public void OnActivityResumed(Activity activity) { CurrentContext = activity; } public void OnActivitySaveInstanceState(Activity activity, Bundle outState) { } public void OnActivityStarted(Activity activity) { CurrentContext = activity; } public void OnActivityStopped(Activity activity) { } } }
The CurrentContext property is updated with a reference to the current Activity, whenever an Activity is created, started, or resumed. Therefore this property will always have a reference to the current Activity, which can then be used as the local context. Therefore, the GetVersionNumber method in the VersionHelper class becomes:
using Android.Content; namespace DependencyServiceAndLocalContext.Droid { public class VersionHelper : IVersionHelper { public string GetVersionNumber() { var versionNumber = string.Empty; if (MainApplication.CurrentContext != null) { versionNumber = MainApplication.CurrentContext.PackageManager.GetPackageInfo( MainApplication.CurrentContext.PackageName, 0).VersionName; } return versionNumber; } } }
By using this approach, any references to Xamarin.Forms.Forms.Context can simply be replaced with MainApplication.CurrentContext. In addition, while this approach is aimed at apps that contain multiple activities, it has the advantage that it can also be used by apps that contain only a single Activity.