I am beginning using MVVMLight with Xamarin.Forms and need some help to get the IOC set up to create my first ContentPage in App.GetMainPage.
My ViewModels have constructors like so -
public class NewsType1ViewModel : ViewModelBase
{
private readonly IContentService contentService;
private readonly INavigation navigation;
public List<ContentModel> ContentModels { get; set; }
public NewsType1ViewModel (INavigation navigation, IContentService contentService)
{
this.contentService = contentService;
this.navigation = navigation;
}
My ContentPages have constructors like so -
public partial class NewsType1CP : ContentPage
{
private NewsType1ViewModel vm;
public NewsType1CP (NewsType1ViewModel vm)
{
InitializeComponent ();
I am using a ViewModelLocator class as follows -
public class ViewModelLocator
{
public ViewModelLocator ()
{
ServiceLocator.SetLocatorProvider (() => SimpleIoc.Default);
// Services
if (App.StubMode) {
SimpleIoc.Default.Register<IContentService, ContentServiceStub> ();
} else {
SimpleIoc.Default.Register<IContentService, ContentService> ();
}
// How do I wire up INavigation?
// I could not just register default NavigationPage() as it has 2
// possible constructors so tried [PreferredConstructor] on my derived class
SimpleIoc.Default.Register<INavigation, AppNavigationPage> ();
// View Models
SimpleIoc.Default.Register<NewsType1ViewModel> ();
SimpleIoc.Default.Register<NewsDetailsViewModel> ();
}
public NewsType1ViewModel NewsType1ViewModel {
get {
return ServiceLocator.Current.GetInstance<NewsType1ViewModel> ();
}
}
public NewsDetailsViewModel NewsDetailsViewModel {
get {
return ServiceLocator.Current.GetInstance<NewsDetailsViewModel> ();
}
}
public static void Cleanup ()
{
// TODO Clear the ViewModels
}
}
public class AppNavigationPage : NavigationPage
{
[PreferredConstructor]
public AppNavigationPage ()
{
}
}
My App.cs is "in progress" as follows -
public static class App
{
public static AppNavigationPage Nav;
public static ViewModelLocator Locator = new ViewModelLocator ();
public static bool StubMode = true;
public static Page GetMainPage ()
{
try {
// Want to return a Nav with NewsType1CP as the starting Page
NewsType1CP newsType1CP = new NewsType1CP(ServiceLocator.Current.GetInstance<NewsType1ViewModel> ());
Nav.PushAsync (newsType1CP);
return Nav;
} catch (Exception ex) {
//
Exception baseexception = ex.GetBaseException ();
Debug.WriteLine (baseexception.Message);
}
return null;
}
}
My latest exception is -
Cannot cast from source type to destination type.
Am I barking up the wrong tree trying to supply an INavigation to each of my ViewModels like so?
Update: After a couple of answers showing other ways of controlling the Navigation in Xamarin Forms, I think it would help if someone could clarify why attempting constructor injection for Navigation is not such a good thing.
I think my example is a bit confusing with AppNavigationPage as a static, ideally I would like this to be in the IOC also, so I could just call return ServiceLocator.Current.GetInstance< AppNavigationPage >(), but I had a go with various factory methods and am in the middle of debugging this, so the code is obviously half baked ...
If you want a ready to use solution, instead of MVVM Light, you could try to use Xamarin Forms Labs ViewModel base, it injects a Navigation propety in your ViewModel:
that way you cold do something like this:
public Command NavigateToViewModel
{
get
{
return navigateToViewModel ?? (navigateToViewModel = new Command(
async () => await Navigation.PushAsync<NewPageViewModel>(),
() => true));
}
}
I'm not using MvvmLight, but I can tell you that yes, you're barking up the wrong tree in trying to supply an INavigation to each ViewModel.
The easiest way to achieve what you're trying to do is to use a public static on your App.
public static INavigation Navigation
{
get;
set;
}
public static Page GetMainPage()
{
var firstPage = new NavigationPage(new MyRootPage())
{
Navigation = firstPage.Navigation;
return firstPage;
}
}
Now this falls apart when you're using a MasterDetail page because you need your Navigation to wrap your DetailPage, not your MasterDetailPage. Therefore, don't set Navigation in the GetMainPage method, but instead from within the MD Page.
var master = new MainMenuPage();
var detail = new NavigationPage(new FirstPage());
if (App.Navigation == null)
{
App.Navigation = detail.Navigation;
}
Master = master;
Detail = detail;
Oh ok, when looking better at your code i maybe spotted the problem:
SimpleIoc.Default.Register<INavigation, AppNavigationPage> ();
Shoult it be:
SimpleIoc.Default.Register<INavigation, AppNavigationPage.Navigation> ();
Related
When i try to run the project, i am getting this kind of error: "System.NullReferenceException: Object reference not set to an instance of an object"
pointing in this code:
sqliteconnection = DependencyService.Get().GetConnection();
This is my Class for DB actions:
namespace DevoApp.DevoAppFinal.Helpers
{
public class DatabaseHelper
{
static SQLiteConnection sqliteconnection;
public const string DbFileName = "Devotion.db";
public DatabaseHelper()
{
sqliteconnection = DependencyService.Get<ISQLite>().GetConnection();
sqliteconnection.CreateTable<Devotion>();
}
// Get All Contact data
public List<Devotion> GetAllDevotionsData()
{
return (from data in sqliteconnection.Table<Devotion>() select data).ToList();
}
//Get Specific Contact data
public Devotion GetDevotionData(int id)
{
return sqliteconnection.Table<Devotion>().FirstOrDefault(t => t.devotionalId == id);
}
// Delete all Contacts Data
public void DeleteAllDevotions()
{
sqliteconnection.DeleteAll<Devotion>();
}
// Delete Specific Contact
public void DeleteDevotion(int id)
{
sqliteconnection.Delete<Devotion>(id);
}
// Insert new Contact to DB
public void InsertDevotion(Devotion contact)
{
sqliteconnection.Insert(contact);
}
// Update Contact Data
public void UpdateDevotion(Devotion contact)
{
sqliteconnection.Update(contact);
}
}
}
When using the DependencyService, you have to implement the interface in each targeted platform project.
In this case, you should have the ISQLite interface implemented on the platforms you're targeting, i.e. iOS and Android.
To make Xamarin find it at runtime, you will have to register the implementation with the Dependency attribute above the namespace. Observe the following example based on a few assumptions of your project.
In your shared library you have declared the interface:
public interface ISQLite
{
// Members here
}
Nothing fancy going on there. Then for each platform, you want to run the app on, do something like this:
[assembly: Xamarin.Forms.Dependency (typeof (SQLiteImplementation_iOS))]
namespace DevoApp.DevoAppFinal.iOS
{
public class SQLiteImplementation_iOS : ISQLite
{
// ... Your code
}
}
From the error, it looks like you forgot to add the attribute
When I navigate from page A to page B, I need to remove page A.
How can I do this with Prism's navigation service in Xamarin Forms?
There are a few scenarios that people run into on this one.
As a common example say you have a LoginPage, and once the user successfully logs in you want to Navigate to the MainPage. Your code might look something like the following:
public class App : PrismApplication
{
protected override async void OnInitialized()
{
await NavigationService.NavigateAsync("LoginPage");
}
protected override void RegisterTypes()
{
Container.RegisterTypeForNavigation<LoginPage>();
Container.RegisterTypeForNavigation<MainPage>();
}
}
public class LoginPageViewModel : BindableBase
{
public DelegateCommand LoginCommand { get; }
private async void OnLoginCommandExecuted()
{
// Do some validation
// Notice the Absolute URI which will reset the navigation stack
// to start with MainPage
await _navigationService.NavigateAsync("/MainPage");
}
}
Now if what you're looking for is some flow where your navigation stack looks like MainPage/ViewA and what you want is MainPage/ViewB and you don't want to reinitialize MainPage, then this is something that we are currently evaluating and wanting to improve this so you could do something like _navigationService.NavigateAsync("../ViewB"). In the mean time what I might suggest is something like this:
public class ViewAViewModel : BindableBase
{
public DelegateCommand ViewBCommand { get; }
private async void OnViewBCommandExecuted()
{
var parameters = new NavigationParameters
{
{ "navigateTo", "ViewB" }
};
await _navigationService.GoBackAsync(parameters);
}
}
public class MainPageViewModel : BindableBase, INavigatedAware
{
public async void OnNavigatingTo(NavigationParameters parameters)
{
if(parameters. GetNavigationMode() == NavigationMode.Back &&
parameters.TryGetValue("navigateTo", out string navigateTo))
{
await _navigationService.NavigateAsync(navigateTo);
return;
}
}
}
Given: "NavigationPage/ViewA/ViewB/ViewC/ViewD"
Navigate from ViewD with:
NavigationService.NavigateAsync("../../../ViewE");
Results in: "NavigationPage/ViewA/ViewE"
Referred from here
Need Prism >= 7.0
Another approach would be to have your page implement INavigationAware and in the OnNavigatedFrom, call Navigatin.RemovePage(this).
I do it that way, it's simpler.
navigationService.NavigateAsync("../PageB");
I am using Prims 7.0.0.396.
I have a problem with this sample from Mvvmcross5 documentation :
public class MyViewModel : MvxViewModel
{
private readonly IMvxNavigationService _navigationService;
public MyViewModel(IMvxNavigationService navigation)
{
_navigationService = navigationService;
}
public async Task SomeMethod()
{
var result = await _navigationService.Navigate<NextViewModel, MyObject, MyReturnObject>(new MyObject());
//Do something with the result MyReturnObject that you get back
}
}
public class NextViewModel : MvxViewModel<MyObject, MyReturnObject>
{
public async Task Initialize(MyObject parameter)
{
//Do something with parameter
}
public async Task SomeMethod()
{
await Close(new MyReturnObject());
}
}
It seems to work with a simple Xamarin app with Mvvmcross project. Now, i need to use it with Xamarin.Forms : the close method is called but after showing the previous page, nothing happen :/
In output : "Requesting presentation change" maybe a clue ?
Is there a way to do this using DI? I tried IScopedInstance<Controller> but this gives me null. Poked around aspnet's source code but didn't win. Any ideas?
I have a controller that accepts different IPaymentMethods. The IPaymentMethod can be a ViewComponent that can render Views. If the IPaymentMethod is a ViewComponent, I want it to use MVC's built-in model binding on post back.
public class XController : Controller
{
// ctor, props, ...
public IActionResult Checkout()
{
return View(new Model
{
PaymentMethodId = 1,
PaymentMethodType = typeof(MyPaymentMethod) // The razor file will use this type to render it as a ViewComponent
});
}
[HttpPost]
public IActionResult Checkout(Model model)
{
var paymentMethod = _paymentService.GetPaymentMethodById(model.PaymentMethodId);
paymentMethod.ProcessPayment();
// ..
}
}
This is where I need the controller to be injected. I wanted to make use of the built-in MVC validation and model binding.
public class MyPaymentMethod : IPaymentMethod
{
private Controller _currentController;
public MyPaymentMethod(IScopedInstance<Controller> controller)
{
_currentController = controller.Value;
}
public void ProcessPayment()
{
var model = new PaymentModel();
_currentController.TryUpdateModel(model, typeof(PaymentModel), null);
if (!_currentController.ModelState.IsValid)
{
return; // or exception
}
// Process Payment using model
}
public Task<IViewComponentResult> InvokeAsync()
{
// returns View
}
}
public interface IPaymentMethod
{
void ProcessPayment();
}
Since the model instance is required in the ProcessPayment method, why not simply pass it as a parameter?
[HttpPost]
public IActionResult Checkout(PaymentModel model)
{
var paymentMethod = _paymentService.GetPaymentMethodById(model.PaymentMethodId);
if (!ModelState.IsValid)
{
return; // or exception
}
paymentMethod.ProcessPayment(model);
// ..
}
public void ProcessPayment(PaymentModel model)
{
// Process Payment using model
}
Your service is taking on responsibilities that belong to the controller - namely checking ModelState.IsValid.
public interface IPaymentMethod
{
void ProcessPayment(PaymentModel model);
}
You may wish to also pass just the properties that are needed from the payment model, or you may wish to make an IPaymentModel interface to decouple your model from your PaymentService. In that case, your IPaymentModel would go into a shared layer.
public interface IPaymentMethod
{
void ProcessPayment(IPaymentModel model);
}
This no longer works with beta7
At this time of writing (beta6), this probably isn't supported and there is a good reason for it: Controllers in ASP.NET 5 does not need to inherit from the Controller class. I have, however, found a way for this to work using ActionFilters.
public class ScopeControllerActionFilterAttribute : ActionFilterAttribute
{
private readonly IScopedInstance<Controller> _controller;
public ScopeControllerActionFilterAttribute(IScopedInstance<Controller> controller)
{
_controller = controller;
}
public override void OnActionExecuting(ActionExecutingContext context)
{
if (_controller.Value == null)
{
_controller.Value = context.Controller as Controller;
}
}
}
Note that depending on the stage of the http request lifecycle, the Value of IScopedInstance<Controller> may still be empty.
I am stuck with this and I wonder what is the best way to approach this problem. I have a WebApi controller where I want to inject ICommand instance but I can know what is the instance I need once I inspect the Post request data. I'll give an example to be more clear but my question also applies to Winform events where you receive an event argument and depending of this event arg you want to have different implementation injected.
public class TestController : ApiController
{
public object Post(int id)
{
ICommand command = null;
if(id = 1)
{
command = new Id1Command();
}
else
{
command = new Id2Command();
}
return new object();
}
}
The only thing I can think of is creating a factory that accepts unity container as argument and inside that factory to call container.Resolve with named instance.
My problem with that is that I am taught that you shouldn't register or resolve outside your composition root and that is violation of the good practices (according Mark Seemann). I am looking for the best design for this problem in general.
I would use a CommandFactory and pass it to the TestController:
public class TestController : ApiController
{
private readonly ICommandFactory mCommandFactory;
public TestController(ICommandFactory CommandFactory)
{
mCommandFactory = CommandFactory;
}
public object Post(int id)
{
ICommand command = null;
if(id = 1)
{
command = CommandFactory.CreateId1Command();
}
else
{
command = CommandFactory.CreateId2Command();
}
return new object();
}
}
Now you have to make sure that Unity is creating the TestController. To do so, you have to implement, configure and set an IDependencyResolver. Check Dependency Injection in ASP.NET Web API 2.
Edit to your comment:
For this scenario you can use an autofactory using a functor that takes an int:
public class TestController : ApiController
{
private readonly Func<int, ICommand> mCommandFactory
public TestController(Func<int, ICommand> CommandFactory)
{
mCommandFactory = CommandFactory;
}
public object Post(int id)
{
var command mCommandFactory(id);
return new object();
}
}
The registration should look like this:
container.RegisterType<Func<int, ICommand>>(new InjectionFactory(
c => new Func<int, ICommand>(
id =>
{
if (id == 1)
{
return new Command();
}
else
{
return new Command2();
}
})));
Note: You still have to set the DependencyResolver!