Calling native platform code in your portable class library (PCL) is
achievable via Dependency Injection. It’s a common question for people
starting out, who are using a PCL or .NET Standard Library for
developing their Xamarin apps. Dependency Injection involves creating an
interface that can be commonly applied across all native platforms,
then coding the actual implementation, or the native platform code, on
each platform. This interface and instance is then given to a container.
The PCL or .NET Standard Library, then goes to the container with only
the interface known, to retrieve the instance. It doesn’t know where the
instance came from, just that it has a specific interface.
To create the container, implement the following code.
Interface
The first thing we must do to enable dependency injection is define an interface. For this example, lets say we have to get a unique identifier for the mobile phone. There are specific calls you can make on a each platform to get this, but its not available in our PCL. Hence we would define this interface to return a string of unique identifier.public interface IDeviceInfo { string GetUniqueIdentifier(); }This interface would be placed in your PCL and hence accessible from your PCL and each platform project.
Implementation
Next we need to actually define the implementation of this interface in each platform.iOS
public class DeviceInfo : IDeviceInfo { public string GetUniqueIdentifier() { return UIDevice.CurrentDevice.IdentifierForVendor.AsString(); } }
Android
public class DeviceInfo : IDeviceInfo { public string GetUniqueIdentifier() { return Android.Provider.Settings.Secure.GetString(Xamarin.Forms.Forms.Context.ContentResolver, Android.Provider.Settings.Secure.AndroidId); } }
UWP
public class DeviceInfo: IDeviceInfo { public string GetUniqueIdentifier() { var token = HardwareIdentification.GetPackageSpecificToken(null); var hardwareId = token.Id; var dataReader = Windows.Storage.Streams.DataReader.FromBuffer(hardwareId); byte[] bytes = new byte[hardwareId.Length]; dataReader.ReadBytes(bytes); return BitConverter.ToString(bytes); } }
Dependency Injection Container Registration
Next we want to register this implementation with the Dependency Injection Framework. For this example, I will just use Xamarin Forms inbuilt dependency injection container. Place the following above each implementation of the DeviceInfo, above the namespace declaration.[assembly: Xamarin.Forms.Dependency(typeof(DeviceInfo))]
namespace Mobile.Platform
{
...
You can also register it via a line of code, and this is actually
need for UWP, as the assembly registration has issues with UWP under
Native Compilation.Xamarin.Forms.DependencyService.Register<DeviceInfo>();
Retrieve Dependency
Lets now retrieve that dependency in code. All we need to do is call this line, referencing the interface we want the implementation of.DependencyService.Get<IDeviceInfo>().GetUniqueIdentifier();
Now we can call the GetUniqueIdentifier and it will return a string.
The PCL or .NET Standard Library doesn’t know anything about the
platform, and just interact with the instance via the interface
definition.AutoFac
The Xamarin Forms Dependency Service is a nice simple dependency injection framework, but it lacks many features, including the ability to do constructor injection. Using something more advanced such as AutoFac offers you many more features. There are many other Dependency Injection Frameworks out there, and they all provide very similar functionality.To create the container, implement the following code.
ContainerBuilder _builder = new ContainerBuilder();Now you can add dependencies as needed.
_builder.RegisterInstance<IDeviceInfo>(new DeviceInfo()).SingleInstance();Finally you need to call the Build function to finish the building of the container.
_builder.Build();The Container should be accessible in the PCL and native platforms, as is the interface. Similar to Xamarin Forms implementation, you can also get the
Constructor Injection
The benefits of using a Dependency Injection Framework such as AutoFac, is you can inject dependencies into a constructor. For example.public class MyViewModel { private readonly IDeviceInfo _deviceInfo; public MyViewModel(IDeviceInfo deviceInfo) { _deviceInfo = deviceInfo; } }If you had registered your ViewModel and DeviceInfo, in AutoFac, when AutoFac creates an instance of your ViewModel, it will automatically detect constructors with parameters and try to fill them in as needed. This has immense benefits for readability and unit testing. No longer is a dependency in your class, hidden via a line of code, its clearly apparent, as you can only create the class, if you inject the dependency into it.
No comments:
Post a Comment