Prism for Xamarin, dependency failed for INavigationService - c#

I am working on a sample Xamarin.Forms App for iOS, using PRISM.
But when I tried to use INavigationService in ViewModel, it is failed saying dependency failed for interface INavigationService
I dont know what wrong I am doing, how can we get around it or is it a bug in INavigationServices injection.
Thanks
Divikiran Ravela
Heres the code
public App()
{
// The root page of your application
Prism = new PrismBootStrap();
Prism.Run(this);
}
public class PrismBootStrap : UnityBootstrapper
{
protected override Page CreateMainPage()
{
return new NavigationPage(Container.Resolve<HomePage>());
}
protected override void RegisterTypes()
{
Container.RegisterTypeForNavigation<HomePage, HomePageViewModel>();
}
}
public class HomePage : ContentPage
{
public HomePageViewModel ViewModel { get; set; }
public HomePage()
{
ViewModel = App.Prism.Container.Resolve<HomePageViewModel>();
Content = new StackLayout
{
Children = {
new Label { Text = "Hello ContentPage" }
}
};
}
}
public class HomePageViewModel : BindableBase
{
private readonly INavigationService _navigationService;
public HomePageViewModel(INavigationService navigationService)
{
_navigationService = navigationService; //Fails here
}
}

The INavigationService depends on the use of the ViewModelLocator. If you don't use it, then the service cannot be injected. Instead of manually resolving your VM, use the ViewModelLocator.AutowireViewModel attached property. Besides, you don't want to be using your container like that. You only want one composition root when using DI.
Also, I recommend using the latest 6.1 preview. It has a ton of navigation improvements.

Related

Prism with Unity wrong instance gets passed into constructor

I am trying to understand/learn Prism with Unity
I created following classes:
==========================================
Seperate Assembly containing a "Module":
using GlobalContracts;
using Prism.Ioc;
using Prism.Modularity;
namespace ModuleA
{
[Module(ModuleName = MyModuleA.NAME, OnDemand = true)]
public class MyModuleA : IModule
{
public const string NAME = "MyModuleA";
public void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.Register<MyControlA>();
containerRegistry.Register<IView, MyControlA>(NAME);
containerRegistry.Register<PluginViewModelBase, MyControlViewModel>(NAME);
}
public void OnInitialized(IContainerProvider containerProvider)
{
}
}
}
==========================================
A ViewModel
using GlobalContracts;
namespace ModuleA
{
public class MyControlViewModel : PluginViewModelBase
{
public MyControlViewModel(IView view) : base(view)
{
}
}
}
==========================================
The Host Application (other assembly):
public partial class App : PrismApplication
{
private Shell mShell;
private ShellViewModel mShellViewModel;
protected override IModuleCatalog CreateModuleCatalog()
{
return new DirectoryModuleCatalog(){ModulePath = #"..\..\..\..\ModulesOutput"};
}
protected override void RegisterTypes(IContainerRegistry containerRegistry)
{
containerRegistry.Register<IView, Shell>();
containerRegistry.Register<IViewModel, ShellViewModel>();
}
protected override Window CreateShell()
{
mShellViewModel = Container.Resolve<ShellViewModel>();
mShell = (Shell)mShellViewModel.View;
return mShell;
}
(...)
Now my question is:
How do I tell Prism to resolve the IView-Parameter passed to the
constructor of the ViewModel properly?
It resolves it as "Shell" and not as "MyControlA".
Further tips regarding my code are welcome
I found some sources in the web but they used "RegisterType" method of a container. And for now I do not have dependencies to Unity in my ModuleA and I would not know how to get the container to call the "RegisterType". All sources are outdated in the web..
By default, it resolves the default registration, which in your case is Shell.
Registering a type with a name does not mean that that name is automatically used to resolve dependencies. You have to do that manually, with parameter override, injection factory or the like. But I'd try to avoid that as it makes things a bit fragile and tedious.

View is not opening after implementing new interface in viewModel class in MVVMCross

I am using MVVMCross for creating an android app, I have a viewModel class which is working fine. After I have implemented a new interface into this the view is not opening.
The interface and viewmodel is given below.
Viewmodel:
public sealed class PuCreationViewModel : BaseDataScreenViewModel
{
private readonly IProjectPuManager _puManager;
public PuCreationViewModel(
IProjectPuManager puManager )
{
_puManager = puManager;
}
}
Interface:
public interface IProjectPuManager
{
string CreatePu(string projectId, PuEntity entity);
}
Implementation class:
public class ProjectPuManager : IProjectPuManager
{
private readonly IFirebaseRepository<PuEntity> _puRepository;
public ProjectPuManager(IFirebaseRepository<ProjectPuEntity> puRepository)
{
_puRepository = puRepository;
}
public string CreatePu(string projectId, PuEntity entity)
{
_puRepository.CreateReference(
$"{AppConstants.Firebase.Key.ProjectPi.Root}/{projectId}");
return _puRepository.Create(entity, true);
}
}
Registered the interface in app.cs
Mvx.RegisterType<IProjectPuManager, ProjectPuManager>();
In my case I missed create the the mapper file for converting the entities. I thought the mapper is not needed to run the app but the mapper is mandatory for the manager. Without that the app doesn't open and it won't show any error to you.

Multiple navigation control through dependency injection

My base content class. I used this class as a theme for my project. I do not know this info relevant or not. In here I create an abstract method that would overload the navigation method.
public abstract class BaseContentPage : ContentPage
{
public readonly BaseViewModel BaseViewModel;
public BaseContentPage(BaseViewModel baseViewModel)
{
BaseViewModel = baseViewModel;
}
public abstract void Navigate(SelectedItemChangedEventArgs e);
}
In my locator where I build the dependency Injection public class Locator. in this class mainly focus on adding this class to the container to make the all code loss coupling
private readonly ContainerBuilder _builder;
public Locator()
{
_builder = new ContainerBuilder();
RegisterTypes();
Container = _builder.Build();
}
public IContainer Container { get; set; }
private void RegisterTypes()
{
_builder.RegisterType<WardListService>().As<IWardListService>();
_builder.RegisterType<WardListPageViewModel>();
_builder.RegisterType<WardListPage>();
_builder.RegisterType<PatientService>().As<IPatientService>();
_builder.RegisterType<PatientListPageViewModel>();
_builder.RegisterType<PatientListViewPage>();
_builder.RegisterType<PatientDetailsPageViewModel>();
_builder.RegisterType<PatientDetailsViewPage>(); }
In my app.Xaml.Cs file
public App()
{
InitializeComponent();
Locator locator = new Locator();
Container = locator.Container;
MainPage = new NavigationPage(Container.Resolve<WardListPage>());
}
public static IContainer Container;
I used this method for navigation in my view code behind page
public async override void Navigate(SelectedItemChangedEventArgs e)
{
PatientDetailsViewPage patientDetailsViewPage = App.Container.Resolve<PatientDetailsViewPage>();
patientDetailsViewPage.BaseViewModel.SelectedPatient = e.SelectedItem as PatientViewModel;
await Navigation.PushAsync(patientDetailsViewPage);
}
This code is working perfectly but this only can navigate to one page.meaning as an example on one page we have two buttons that navigate to two different pages. I don't know how to implement this task using above navigate overloader. How to do it can anyone give suggestion to overcome the problem?. Also, I used autofac for dependency injection Thank you
You can define container in your CustomNavigationPage and use in every navigation page instance.
public class CustomNavigationPage : NavigationPage
{
public static IContainer Container;
public CustomNavigationPage()
{
Locator locator = new Locator();
locator.RegisterTypes();
Container = locator.Container();
}
}
It is dummy code what i mentioned.
You creating a navigation page that customized. So you can use this navigating your pages for example:
CustomNavigationPage.PushASync(new TestPage(Container.Resolve<WardListPage>())):
If use this your custom navigation page will be resolve your dependencies every call.
To improve performance you can register your dependencies with
singleton pattern. When the app started, dependencies will be registered.
After you use this registered dependencies.
There is an improvement : You define a static locator with singleton pattern it registers dependencies in app.cs
public sealed class Locator
{
private static Locator locator = null;
private static readonly object padlock = new object();
Locator()
{
//your registries
}
public static Locator Locator
{
get
{
lock (padlock)
{
if (locator == null)
{
locator = new Locator();
}
return locator;
}
}
}
}
And your app.cs :
public App()
{
InitializeComponent();
Locator locator = new Locator();
Container = locator.Container;
.
.
}
public static IContainer Container;
This way you only one time register your dependencies. There is no duplication of code. Only one instance will be used.

How to navigate between pages using Prism in Xamarin.Forms?

I'm trying to inject INavigationService into my ViewModel, so I can navigate between pages, but I don't know how to register ViewModel with parameter. Here's my App:
<prism:PrismApplication xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Unity;assembly=Prism.Unity.Forms"
x:Class="PrismDemo.App" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
d1p1:Ignorable="d" xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006">
<prism:PrismApplication.Resources>
<ResourceDictionary>
</ResourceDictionary>
</prism:PrismApplication.Resources>
</prism:PrismApplication>
and...
public partial class App
{
INavigationService _navigationService;
public App(IPlatformInitializer initializer = null) : base(initializer) { }
protected override void OnInitialized()
{
InitializeComponent();
_navigationService = NavigationService;
NavigationService.NavigateAsync("MainNavigationPage/Start");
}
protected override void RegisterTypes()
{
Container.RegisterTypeForNavigation<MainNavigationPage>();
Container.RegisterTypeForNavigation<StartPage, StartPageViewModel>("Start");
Container.RegisterTypeForNavigation<MiddlePage, MiddlePageViewModel>("Middle");
Container.RegisterTypeForNavigation<LastPage, LastPageViewModel>("Last");
}
}
How can I inject _navigationService into ViewModels?
Thanks for help
You don't actually have to register the ViewModel directly as long as you have followed the convention {MyProjectName}.Views.{MyPage} and {MyProjectName}.ViewModels.{MyPageViewModel}
To use INavigationService in the ViewModel simply add it to the Constructor. Remember it is a named service so it must be named navigationService. You can check out several examples in the Samples Repo.
public class MyPageViewModel
{
INavigationService _navigationService { get; }
public MyPageViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
NavigateCommand = new DelegateCommand<string>(OnNavigateCommandExecuted);
}
public DelegateCommand<string> NavigateCommand { get; }
public async void OnNavigateCommandExecuted(string path) =>
await _navigationService.NavigateAsync(path);
}
If you are using Prism, than you should inherit your App from PrismApplication:
instead of public partial class App should be public partial class App : PrismApplication.
No need to declare INavigationService _navigationService; in your App class, because it's already declared in PrismApplication as NavigationService property.
Prism uses IUnityContainer as Dependency Injection framework, which means for you, that all, that you will register in container:
Container.RegisterType<ISomeService, SomeServiceImplementation>();
Unity container will automatically inject in constructor:
Define a constructor in the target class that takes as a parameter the concrete type of the dependent class. The Unity container will instantiate and inject an instance.
PrismApplication registers INavigationService in a container, so, as I understand, you don't need to register your own instance and you could just add parameter of type INavigationService in constructor of view model and unity container will inject instance of registered type. The important thing, that you should give an exactly the navigationService name for your parameter, because Prism "expects" such parameter name for navigation service.

How is this viewModel being created?

I have a simple application here but I am unsure of how my ViewModel is being created. I am assuming it's from the unity container but I am unsure and curious. The module initializes and registers the view with the region. The view's code behind has the ViewModel initialized in it's constructor and the ViewModel calls some services that were previously registered.
My question is how is the ViewModel created in the View's code behind when I've never registered the ViewModel type with the unity container? Is there some magic happening in the RegisterViewWithRegion method?
AlarmsModule.cs: This simply registers the view with the region
[Module(ModuleName = "AlarmsModule")]
public class AlarmsModule : IModule
{
[Dependency]
public IRegionManager regionManager { get; set; }
public void Initialize()
{
regionManager.RegisterViewWithRegion("AlarmsRegion", typeof(AlarmPanel.View));
}
}
View.xaml.cs:
public partial class View : UserControl
{
public View(ViewModel vm)
{
InitializeComponent();
DataContext = vm;
}
}
ViewModel.cs
public class ViewModel: DependencyObject
{
IEventAggregator _eventAggregator;
public ObservableCollection<IAlarmContainer> AlarmList { get; set; }
IAlarmService _alarmService;
public ViewModel(IAlarmService alarmService)
{
//Adding an alarm from the alarm service, which is injected into this viewModel
AlarmList = alarmService.AlarmList;
}
}
The view model is created by the unity container in the DoGetInstance method of the UnityServiceLocatorAdapter class in the Prism.Unity assembly which is in turn called by the RegisterViewWithRegion method through some other methods of the RegionViewRegistry class.
Unity is able to resolve the view model type automatically provided that it has a default parameterless constructor.
You could verify this yourself using the following code:
var view = unityContainer.Resolve(typeof(View), null); //will automatically resolve the view model type and inject the view with an instance of it

Categories