Home Xamarin.Forms: Native API Acces with Dependency Injection
Post
Cancel

Xamarin.Forms: Native API Acces with Dependency Injection

Dependency Injection is a great technique to leveraging native code in the context of your shared code. In Xamarin.Forms you typically create a custom renderer which gives you native access. If you need to run some native code from the shared context you can easily do this with the built-in Dependency Injection tooling.

Create the Contract

Start off by creating a simple contract that can be injected anywhere in the application. For our example we are going to create a IAppInfo which will provide us information about the installed application. The code provided are snippets I pulled from Xamarin Essentials to better demonstrate Dependency Injection. I recommend using Xamarin Essentials over the code samples here.

A control like this is useful if you want to display app name or version name via your shared code.

IAppInfo

1
2
3
4
5
public interface IAppInfo
{
    string Name { get; }
    string Version { get; }
}

Android Implementation

The Android implementation is broken up into 2 main parts

  • Android_AppInfo - The code that implements the contract
  • Registration code - Register the new manager with the Dependency Injection Container

Start off by creating an implementation of the contract, using the native Android APIs to access the application info when requested.

Android_AppInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Android_AppInfo : IAppInfo
{
    public string Name
    {
        get
        {
            var applicationInfo = Platform.AppContext.ApplicationInfo;
            var packageManager = Platform.AppContext.PackageManager;

            return applicationInfo.LoadLabel(packageManager);
        }
    }

    public string Version
    {
        get
        {
            var pm = Platform.AppContext.PackageManager;
            var packageName = Platform.AppContext.PackageName;

            using (var info = pm.GetPackageInfo(packageName, PackageInfoFlags.MetaData))
            {
                return info.VersionName;
            }
        }
    }
}

MainActivity

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MainActivity : FormsAppCompatActivity
{
    protected override void OnCreate(Bundle savedInstanceState)
    {
        TabLayoutResource = Resource.Layout.Tabbar;
        ToolbarResource = Resource.Layout.Toolbar;

        DependencyService.Register<IAppInfo, Android_AppInfo>();

        Forms.Init(this, savedInstanceState);
        LoadApplication(new App());
    }
}

iOS Implementation

Just like the Android implementation, the iOS implementation is broken up into 2 main parts

  • iOS_AppInfo - The code that implements the contract
  • Registration Code - Register the new manager with the Dependency Injection Container

Again, start off by creating the implementation of the contract. We will use the native iOS APIs to access the application info when requested.

iOS_AppInfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class iOS_AppInfo : IAppInfo
{
    public string Name
    {
        get => GetBundleValue("CFBundleDisplayName");
    }

    public string Version
    {
        get => GetBundleValue("CFBundleVersion");
    }

    string GetBundleValue(string key)
    {
        return NSBundle.MainBundle.ObjectForInfoDictionary(key)?.ToString();
    }
}

AppDelegate

1
2
3
4
5
6
7
8
9
10
11
12
public partial class AppDelegate : FormsApplicationDelegate
{
    public override bool FinishedLaunching(UIApplication app, NSDictionary options)
    {
        DependencyService.Register<IAppInfo, AppInfo>();

        Forms.Init();
        LoadApplication(new App());

        return base.FinishedLaunching(app, options);
    }
}

Shared Code Usage

Once you have your new platform specific code registered with the Dependency Injection Container you can get the native values from the shared code. Since Dependency Injection helps enforce a loose coupling of compontents we are going to build out a full ViewModel and utilize Data Binding to demonstrate this technique.

MainPage

Start by creating a basic view which will render the App Name and the Version in the center of the screen.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?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:Class="App.Views.MainPage">

    <StackLayout>
        <Label Text="Application Name"
           HorizontalOptions="Center"
           VerticalOptions="CenterAndExpand" />
        <Label Text="Version"
           HorizontalOptions="Center"
           VerticalOptions="CenterAndExpand" />
    </StackLayout>

</ContentPage>

MainViewModel

Once the MainPage is created, create a ViewModel which will use the DependenyService to resolve the native components

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MainViewModel
{
    protected IAppInfo AppInfo { get; }

    public MainViewModel()
    {
        // Xamarin.Forms doesn't support constructor injection
        // out of the box. We will manually resolve IAppInfo. There
        // are other options that go outside the scope of this article
        // for constructor injection. Scroll down to further reading
        AppInfo = DependencyService.Get<IAppInfo>();
    }

    public string Name { get => AppInfo.Name; }

    public string Version { get => AppInfo.Version; }
}

Add Data Binding to MainPage

Now that everything is configured correctly, you can add the ViewModel and Data Binding to the Page. Update the Text property of each label to include the Data Binding markup extension like the small example here:

1
<Label Text="{Binding Name}" />

Add MainViewModel to MainPage’s BindingContext

A ContentPage does not kow what View Model to instantiate for it’s BindingContext which is how Data Binding works. The most basic thing to do is configure the BindingContext in the XAML.

1
2
3
<ContentPage.BindingContext>
    <vm:MainViewModel />
</ContentPage.BindingContext>

Completed MainPage

Taking the Data Binding code snippets above we can put them together for the full MainPage.xaml.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?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"
             xmlns:vm="App.ViewModels"
             mc:Ignorable="d"
             x:Class="App.Views.MainPage">

    <ContentPage.BindingContext>
        <vm:MainViewModel />
    </ContentPage.BindingContext>

    <StackLayout>
        <Label Text="{Binding Name}"
           HorizontalOptions="Center"
           VerticalOptions="CenterAndExpand" />
        <Label Text="{Binding Version}"
           HorizontalOptions="Center"
           VerticalOptions="CenterAndExpand" />
    </StackLayout>

</ContentPage>

Conclusion

This article demonstrates the basic usage of the in-the-box Dependency Injection tooling built into Xamarin.Forms. Which uses the Service Locator Pattern to resolve dependencies. The Xamarin Community has many options for Depenency Injection which goes hand-in-hand with Model-View-ViewModel (MVVM).

When you pair a MVVM library with a Dependency Injection Container it can automatically resolve all dependencies in the constructor. Some popular MVVM Libraries

Constructor Injection

Constructor Injection is a Dependency Injection technique where dependencies are automatically resolved in the constructor. This simplifies having to manually resolve them like we did in the code example for the MainViewModel. If we were using a Dependency Injection Container that supported Constructor Injection our MainViewModel would look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MainViewModel
{
    protected IAppInfo AppInfo { get; }

    public MainViewModel(IAppInfo appInfo)
    {
        AppInfo = appInfo;
    }

    public string Name { get => AppInfo.Name; }

    public string Version { get => AppInfo.Version; }
}

Constructor Injection is considered a best practice with Dependency Injection which is why many teams choose to use a separate container over the out of the box DependencyService.

-Happy Coding

This post is licensed under CC BY 4.0 by the author.