An Introduction to Plugins
If you’re on my blog, you know I love Xamarin. Writing cross-platform applications and sharing as much code as possible is, in my opinion, the way to go. The Xamarin community has taken an awesome technology, and made it even more awesome by building tons and tons of plugins. These plugins allow us to access platform-specific functionality from shared code, which simply allows us as application developers to write even more code in our portable projects.
I ran into an interesting forum post talking about the structure of plugins made a standard by Xamarin. A lot of use these plugins, but of course, they are not made for everyone! I’ve seen many repositories riddled with issues asking for more and more features in the plugin. This post is going to show some ways to expand on those features with your own code! As well as show techniques to use these plugins with Inversion of Control and Dependency Injection, rather than through the Singleton that the standard ships with.
Suggested Resources Before Starting
It might be a good idea to read up on my blogs and watch my videos about calling platform specific code from a PCL. Check out the YouTube playlist for some different techniques. Specifically looking at the Singleton method and Dependency Injection method.
Resources for Examples
We are going to look at arguably my all time favorite plugin: UserDialogs by Allan Ritchie
If you haven’t used it yet, it’s a cool plugin that allows you to call platform specific pop ups and dialogs including alerts, toasts, prompts, action sheets, confirmation messages, and more.
We are also going to be using MvvmLight (as I tend to do) in order to use SimpleIoc
for our Inversion of Control and Dependency Injection.
Let’s first look at using IoC and DI, then look at extending the functionality.
Use Dependency Injection to Call Your Plugins
If we look at Singleton of UserDialogs, we see that it simply changes the Init
method based on the platform, and uses that to set the platform-specific implementation of IUserDialogs
. The core of the functionality comes from that implementation of IUserDialogs
. So rather than going through the Init
method of the Singleton, we can instead register the implementation to an IoC container, and inject it into the constructor of our ViewModel
or Service
that calls it!
Let’s create a ViewModel
that takes an IUserDialogs
in its constructor:
public class MyViewModel : ViewModelBase
{
...
private readonly IUserDialogs _userDialogs;
public MyViewModel(IUserDialogs userDialogs)
{
_userDialogs = userDialogs;
}
...
}
Now that our ViewModel
has a reference to an IUserDialogs
, we can make calls to it from ICommands
or methods.
public void ErrorToast(string message)
{
// show a toast message with a red background
_userDialogs.Toast(new ToastConfig(message).SetMessageTextColor(System.Drawing.Color.White).SetBackgroundColor(System.Drawing.Color.FromArgb(255, 213, 0, 0)));
}
Now all we need to do is register both our IUserDialogs
platform implementation and our MyViewModel
to our IoC container.
In our PCL or SharedLibrary:
...
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
...
SimpleIoc.Default.Register<MyViewModel>();
...
Then in our Android project:
...
ActivityLifecycleCallbacks.Register(activity);
var userDialogsImplementation = new UserDialogsImpl(() => ActivityLifecycleCallbacks.CurrentTopActivity);
SimpleIoc.Default.Register<IUserDialogs>(() => userDialogsImplementation);
...
And in iOS:
...
// note iOS doesn't require registering lifecycle callbacks like iOS does.
SimpleIoc.Default.Register<IUserDialogs, UserDialogsImpl();
...
Now when we create our MyViewModel
, we don’t do it through calling new MyViewModel()
, we will call to get the instance from the ServiceLocator
. For example, we can set the BindingContext
of a Page
:
...
public MyMainPage()
{
BindingContext = ServiceLocator.Current.GetInstance<MyViewModel>();
}
...
That’s all we need to do! As long as we make those Register
calls for our MyViewModel
and IUserDialogs
before the call to the MyMainPage
constructor, everything will be wired up and Injected.
Now that we have our application using Dependency Injection instead of the Singleton, it makes it even easier to override and extend the functionality of the plugin.
Changing Features and Functionality
There are only two things we need to change to our implementation from above.
- Create a new platform implementation of
IUserDialogs
that inherits from the UserDialogsImpl.
- Change the IoC Registration to use our new implementation.
Let’s create a new class called MyUserDialogsImpl
that will inherit from UserDialogsImpl
. In this class, we will override the Alert
method and do something like change the Tint color on iOS:
public class MyUserDialogsImpl : UserDialogsImpl
{
...
public override IDisposable Alert(AlertConfig config)
{
return this.Present(() =>
{
var alert = UIAlertController.Create(config.Title ?? String.Empty, config.Message, UIAlertControllerStyle.Alert);
// custom piece:
alert.View.TintColor = UIColor.Red;
alert.AddAction(UIAlertAction.Create(config.OkText, UIAlertActionStyle.Default, x =&amp;amp;amp;amp;gt; config.OnAction?.Invoke()));
return alert;
});
}
...
}
Now where we registered the IUserDialogs
before, we just substitute our new implementation!
...
// note iOS doesn't require registering lifecycle callbacks like iOS does.
SimpleIoc.Default.Register<IUserDialogs, MyUserDialogsImpl>();
...
So now our IoC set up will automatically set up our new implementation, so when we call the Alert
method, it will show the Tint color as Red.
Adding New Features to Plugins
Similarly to how we changed functionality, we can add completely new functionality in just a few steps:
- Create a new Interface that inherits
IUserDialogs
in our shared code.
- Add a new method to that interface
- Change our new implementation to also implement that new interface
- Change our IoC Registration to register our implementation as our new interface instead of
IUserDialogs
- Change our injected dependency in our
MyViewModel
to our new interface
In our shared code, let’s create an interface called ICustomDialogs
and add a method to its definition:
public interface ICustomDialogs : IUserDialogs
{
void SayHello();
}
Now, let’s update our MyUserDialogsImpl
to implement our new interface:
public class MyUserDialogsImpl : UserDialogsImpl, ICustomDialogs
{
public void SayHello()
{
this.Alert("Hello", "World");
}
}
Next, we need to update our IoC Register call:
...
SimpleIoc.Default.Register<ICustomDialogs, MyUserDialogsImpl>();
...
Lastly, let’s change our MyViewModel
to use our new interface:
public class MyViewModel : ViewModelBase
{
...
private readonly ICustomDialogs _userDialogs;
public MyViewModel(ICustomDialogs userDialogs)
{
_userDialogs = userDialogs;
}
...
}
Now we can call our SayHello
method from our ViewModel
too!
Recap
- Xamarin Plugins are awesome
- You don’t have to use Plugin Singletons!
- You can use dependency injection to inject your plugin implementations
- You can override methods in your platform specific implementations of your plugins
- You can create new functionality through another interface and updated injections!
Like this:
Like Loading...