The Xamarin.Forms includes DependencyService to help shared code to resolve platform specific implementation. The important thing here is that DependencyService isn’t replace for Dependency Injection container. DependencyService is rather service locator than IoC container. I will show you how they can work together. Although I’m using Autofac as IoC, the proposed design and architecture should be applicable to any IoC Container.

The Idea

I will explain the idea on simple example. It is common to have class which is representing some settings. But not all settings are common for all platforms. Typical example is SQLite database connection which is on each platform different. On other side, database name isn’t platform specific and is same for all platforms.

image

We are ending up with IAppRuntimeSettings Interface with one property and one method: DatabaseFilename is representing the database file name and the method CreateSqlLiteConnection creates appropriate database connection, which is platform specific. The abstract class AppRuntimeSettingsBase inherits the interface and implements the platform unspecific aspects, in this case the database filename.

    public interface IAppRuntimeSettings
    {
        SQLiteConnection CreateSqLiteConnection();
        string DatabaseFilename { get; }
    }

    public abstract class AppRuntimeSettingsBase : IAppRuntimeSettings
    {
        public abstract SQLiteConnection CreateSqLiteConnection();
        public string DatabaseFilename { get; } = "Database.db3";
    }

Than, on other side are here platform specific implementations of AppRuntimeSettingsBase. Each platform implements, in this case, her own CreateSqlLiteConnection Method.

[assembly: Dependency(typeof(AppRuntimeSettingsDroid))]
namespace XamarinDiDsApp.Droid.Infrastructure.AppRuntimeSettings
{
    class AppRuntimeSettingsDroid : AppRuntimeSettingsBase
    {
        public override SQLiteConnection CreateSqLiteConnection()
        {
            var documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
            var path = Path.Combine(documentsPath, DatabaseFilename);
            var currentPlatform = new SQLitePlatformAndroid();
            var connection = new SQLiteConnection(currentPlatform, path);
            return connection;
        }
    }
}

[assembly: Dependency(typeof(AppRuntimeSettings_iOS))]
namespace XamarinDiDsApp.iOS.Infrastructure.AppRuntimeSettings
{
    class AppRuntimeSettings_iOS : AppRuntimeSettingsBase
    {
        public override SQLiteConnection CreateSqLiteConnection()
        {
            var documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
            var libraryPath = Path.Combine(documentsPath, "..", "Library");
            var path = Path.Combine(libraryPath, DatabaseFilename);
            var currentPlatform = new SQLitePlatformIOS();
            var connection = new SQLiteConnection(currentPlatform, path);
            return connection;
        }
    }
}

[assembly: Dependency(typeof(AppRuntimeSettingsWinPhone))]
namespace XamarinDiDsApp.WinPhone.Infrastructure.AppRuntimeSettings
{
    public class AppRuntimeSettingsWinPhone : AppRuntimeSettingsBase
    {
        public override SQLiteConnection CreateSqLiteConnection()
        {
            var path = Path.Combine(ApplicationData.Current.LocalFolder.Path, DatabaseFilename);
            var currentPlatform = new SQLitePlatformWP8();
            var connection = new SQLiteConnection(currentPlatform, path);
            return connection;
        }
    }
}

Be aware of Dependency Attribute which is necessary to be used above the class, otherwise the class will not be registered.

image

The solution structure (filtered by AppRuntime)

Register platform specific implementation

To enable to register platform specific objects you need to get them. Now come DependencyService into the game. DependencyService.Get<> returns back the appropriate instance.

private static IAppRuntimeSettings GetApplicationRuntimeSettings()
{
    var platformSpecificSettings = DependencyService.Get<IAppRuntimeSettings>();
    if (platformSpecificSettings == null)
    {
        throw new InvalidOperationException($"Missing '{typeof (IAppRuntimeSettings).FullName}' implementation! Implementation is required.");
    }
    return platformSpecificSettings;
}

Now you can register the object as usual.

public static void Initialize()
{
 var containerBuilder = new ContainerBuilder();
 var applicationRuntimeSettings = GetApplicationRuntimeSettings();

 RegisterPlatformSpecificObjects(containerBuilder, applicationRuntimeSettings);

 Container = containerBuilder.Build();
}

private static void RegisterPlatformSpecificObjects(ContainerBuilder containerBuilder, IAppRuntimeSettings applicationRuntimeSettings)
{
 containerBuilder.RegisterInstance(applicationRuntimeSettings).AsImplementedInterfaces().SingleInstance();

 var sqlLiteConnection = applicationRuntimeSettings.CreateSqLiteConnection();
 containerBuilder.RegisterInstance(sqlLiteConnection).AsSelf().SingleInstance();
}

Usage

Because of simplicity I just show path to the SQLite database file in this sample, which is on each platform different. Typically you will use the instance of SQLite Connection to inject repository.

public App()
{
    InitializeApplication();
    var sqliteConnection = DependencyInjection.Container.Resolve();

    // The root page of your application
    MainPage = new ContentPage
    {
        Content = new StackLayout
        {
            VerticalOptions = LayoutOptions.Center,
            Children = {
                 new Label {
                    XAlign = TextAlignment.Center,
                    Text = "Welcome to Xamarin Forms!"
                },
                new Label {
                    XAlign = TextAlignment.Center,
                    Text = $"SQLite Path:{sqliteConnection.DatabasePath}"
                }
            }
        }
    };
}

iOS DiDsApp

Result on iOS Simulator

image

Result on Android Emulator

image

Result on Windows Phone Emulator