As mentioned earlier, each IoC and dependency injection library implements the patterns slightly differently. In this section, we will use Ninject to start adding dependency injection capabilities to our TripLog app. Ninject allows you to create Modules, which are responsible for adding services to the IoC container. The modules are then added to a Kernel that is used to resolve the services by other areas of the app.
You can create a single Ninject Module or many, depending on how your app is structured and how you want to organize your services. For the TripLog app, we will have a Ninject Module in each platform project that is responsible for registering that platform's specific service implementations. We will also create a Ninject Module in the core library that will be responsible for registering dependencies that live in the core library, such as ViewModels and data access services, which we will add later in Chapter 6, API Data Access, when we start working with live data.
We will start by creating Ninject Modules in each of the platform projects that will be responsible for registering their respective platform's specific service implementations, as shown in the following steps:
TripLog.iOS
and TripLog.Droid
.TripLog.iOS
project named Modules
, and within it, create a new class named TripLogPlatformModule
that inherits from Ninject.Modules.NinjectModule
:public class TripLogPlatformModule : NinjectModule { // ... }
Load
method of the NinjectModule
class and use the Ninject Bind
method to register the iOS-specific implementation of ILocationService
as a singleton:public class TripLogPlatformModule : NinjectModule { public override void Load () { Bind<ILocationService> () .To<LocationService> () .InSingletonScope (); } }
TripLog.Droid
project named Modules
, and within it, create a new class named TripLogPlatformModule
that inherits from Ninject.Modules.NinjectModule
:public class TripLogPlatformModule : NinjectModule { // ... }
Load
method of the NinjectModule
class and use the Ninject Bind
method to register the Android-specific implementation of ILocationService
as a singleton:public class TripLogPlatformModule : NinjectModule { public override void Load () { Bind<ILocationService> () .To<LocationService> () .InSingletonScope (); } }
We can also use our IoC container to hold our ViewModels. It is a slightly different model than the one used to register concrete implementations of our service interfaces—instead of mapping them to an interface, we are simply registering them to themselves.
Because our ViewModels are in our core library, we will create another Ninject Module in the core library, a core module, that will handle registering them, as shown in the following steps:
Modules
, and within it, create a new class named TripLogCoreModule
that inherits from Ninject.Modules.NinjectModule
:public class TripLogCoreModule : NinjectModule { // ... }
Load
method of the NinjectModule
class and use the Ninject Bind
method to register each of the ViewModels:public class TripLogCoreModule : NinjectModule { public override void Load () { // ViewModels Bind<MainViewModel> ().ToSelf (); Bind<DetailViewModel> ().ToSelf (); Bind<NewEntryViewModel> ().ToSelf (); } }
In the previous chapter, we created a custom navigation service and used the Xamarin.Forms DependencyService to register and resolve it. Now that we have introduced Ninject, we can swap Xamarin.Forms DependencyService out and use a Ninject Module to register the navigation service instead so that it can be resolved and used just like our platform-specific services.
assembly
attribute that was added above the class's namespace:// Remove assembly attribute // [assembly: Dependency(typeof(XamarinFormsNavService))]
We originally instantiated the navigation service and registered view mappings all within the core App
class. We can now move all of that logic into a new Ninject Module. However, in order for us to instantiate our navigation service, we require an instance of Xamarin.Forms.INavigation
, so we will have to set this new Module up to take that in as a constructor parameter, and then its overridden Load
method will handle creating the service, creating the view mappings, and then registering the service into the IoC.
Modules
folder named TripLogNavModule
that inherits from NinjectModule
:public class TripLogNavModule : NinjectModule { // ... }
TripLogNavModule
to take in a Xamarin.Forms.INavigation
parameter:public class TripLogNavModule : NinjectModule { readonly INavigation _xfNav; public TripLogNavModule (INavigation xamarinFormsNavigation) { _xfNav = xamarinFormsNavigation; } }
Load
method of the NinjectModule
class to instantiate a new XamarinFormsNavService
object:public override void Load () { var navService = new XamarinFormsNavService (); navService.XamarinFormsNav = _xfNav; }
App
class and put them into the TripLogNavModule
Load
override method:public override void Load () { var navService = new XamarinFormsNavService (); navService.XamarinFormsNav = _xfNav; // Register view mappings navService.RegisterViewMapping ( typeof(MainViewModel), typeof(MainPage)); navService.RegisterViewMapping ( typeof(DetailViewModel), typeof(DetailPage)); navService.RegisterViewMapping ( typeof(NewEntryViewModel), typeof(NewEntryPage)); }
TripLogNavModule
Load
override method to use the Ninject Bind
method to register the XamarinFormsNavService
object as a singleton:public override void Load () { var navService = new XamarinFormsNavService (); navService.XamarinFormsNav = _xfNav; // Register view mappings navService.RegisterViewMapping ( typeof(MainViewModel), typeof(MainPage)); navService.RegisterViewMapping ( typeof(DetailViewModel), typeof(DetailPage)); navService.RegisterViewMapping ( typeof(NewEntryViewModel), typeof(NewEntryPage)); Bind<INavService> () .ToMethod (x => navService) .InSingletonScope (); }
Now that our platform services, navigation service, and ViewModels have all been registered with the IoC, we need to add the Ninject Modules that we created to the Ninject Kernel. We will do this in our main Xamarin.Forms.Application
class, App
.
In order to get our platform modules into the App
class, which is in our core library, we will simply update the App
constructor to take in INinjectModule
parameters. Then, each platform-specific project will be responsible to pass in its respective Ninject module when it loads the App
at startup.
App
constructor to take in INinjectModule
parameters:public App (params INinjectModule[] platformModules)
{
// ...
}
IKernel
property named Kernel
to the App
class:public class App : Application
{
public IKernel Kernel { get; set; }
// ...
}
App
constructor. In the previous section, we moved the bulk of the existing App
constructor logic into the navigation Ninject Module. Now the App
constructor should only be responsible for creating the main page and initializing the Ninject Kernel with the various modules we have created:public App (params INinjectModule[] platformModules) { var mainPage = new NavigationPage (new MainPage ()); // Register core services Kernel = new StandardKernel ( new TripLogCoreModule (), new TripLogNavModule(mainPage.Navigation)); // Register platform specific services Kernel.Load (platformModules); // Get the MainViewModel from the IoC mainPage.BindingContext = Kernel.Get<MainViewModel> (); MainPage = mainPage; }
Notice how we get an instance of the MainViewModel
from the IoC container and use it to set the ViewModel of the MainPage
. In the next section, we'll update the navigation service to do this same thing each time we navigate to the other pages in the app.
App
instantiation in the AppDelegate
class of our iOS project to pass in a new instance of TripLog.iOS.Modules.TripLogPlatformModule
:LoadApplication (new App (new TripLogPlatformModule()));
MainActivity
class of the Android project.Currently in the TripLog app, each page is responsible for creating its own ViewModel instance. However, because we provide a ViewModel's dependencies through its constructor, we would have to manually resolve each dependency within the Page
class and pass them into the ViewModel instantiation. Not only is this going to be messy code, it is also difficult to maintain and doesn't promote loose coupling. Because we have registered our ViewModels in our IoC, we can completely remove the ViewModel instantiations from our Pages
and set our navigation service up to handle resolving the ViewModels from the IoC, automatically supplying their dependencies through constructor injection.
BindingContext
property to a new ViewModel instance.NavigateToView
private method in the XamarinFormsNavService
to handle setting the ViewModels of the Pages automatically as they are navigated to. After the Page (view) is created using the Invoke
method, simply get a new instance of the specified ViewModel and set it to the BindingContext
of the Page:async Task NavigateToView(Type viewModelType) { // ... var view = constructor.Invoke (null) as Page; var vm = ((App)Application.Current).Kernel.GetService (viewModelType); view.BindingContext = vm; await XamarinFormsNav.PushAsync (view, true); }
3.149.250.11