4

Is it possible to have a constructor in the ViewModel, which initializes the data service?

My data service is accessing the web-service of the data storage in a manner similar to this:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Collections.ObjectModel;
    using Cirrious.MvvmCross.ViewModels;
    using Cirrious.MvvmCross.Commands;
    using MobSales.Logic.DataService;
    using MobSales.Logic.Base;
    using MobSales.Logic.Model;

    namespace MobSales.Logic.ViewModels
    {
        public class CustomersViewModel:MvxViewModel
        {
            ICustomerService custService;
        public CustomersViewModel(ICustomerService custService)
        {
            this.custService = custService;
            if (custService != null)
            {
                custService.LoadCustomerCompleted += new EventHandler<CustomerLoadedEventArgs>(custService_LoadCustomerCompleted);
            }
            loadCustomerCommand = new MvxRelayCommand(LoadCustomer);
            loadCustomerCommand.Execute();
        }


    private ObservableCollection<Customer> customers;

    public ObservableCollection<Customer> Customers
    {
        get { return customers; }
        set
        {
            customers = value;
            FirePropertyChanged("Customers");
        }
    }


    private CustomerViewModel customer;

    public CustomerViewModel Customer
    {
        get { return customer; }
        set
        {
            customer = value;
            FirePropertyChanged("Customer");
        }
    }


    private MvxRelayCommand loadCustomerCommand;

    public MvxRelayCommand LoadCustomerCommand
    {
        get { return loadCustomerCommand; }
    }

    public void LoadCustomer()
    {
        custService.LoadCustomer();
    }

    void custService_LoadCustomerCompleted(object sender, CustomerLoadedEventArgs e)
    {
        if (e.Error != null)
        {
            return;
        }

        List<Customer> loadedCustomers = new List<Customer>();
        foreach (var cust in e.Customers)
        {
            loadedCustomers.Add(new Customer(cust));
        }

        Customers = new ObservableCollection<Customer>(loadedCustomers);
    }

}

I am getting an exception but can only see the following partial description:

Cirrious.MvvmCross.Exceptions.MvxException: Failed to load ViewModel for type MobSales.Logic.ViewModels.CustomersViewModel from locator MvxDefau…

The binding from View to ViewModel is realized as I've shown in this post: MVVMCross Bindings in Android

Thanks!

plr108
  • 1,201
  • 11
  • 16
Martin
  • 235
  • 1
  • 4
  • 12
  • My understanding of ViewModels is that it really is not made to have anything to do with fetching data etc. – Styxxy May 09 '12 at 07:52
  • 1
    Yes that's right, i would only initialize (wrong word... approve would be the better one) my data service. fetching the data would happen somewhere else. You can see, i have an event for loading completion... – Martin May 09 '12 at 07:53
  • 1
    If it doesn't happen in your ViewModel, why does your ViewModel have to have this Data Service? – Styxxy May 09 '12 at 07:54
  • 1
    only for this custService_LoadCustomerCompleted and this LoadCustomer – Martin May 09 '12 at 07:56

1 Answers1

5

One of the unusual (opinionated) features of MvvmCross is that by default it uses ViewModel constructor parameters as part of the navigation mechanism.

This is explained with an example in my answer to Passing on variables from ViewModel to another View (MVVMCross)

The basic idea is that when a HomeViewModel requests a navigation using:

private void DoSearch()
{
    RequestNavigate<TwitterViewModel>(new { searchTerm = SearchText });
}

then this will cause a TwitterViewModel to be constructed with the searchTerm passed into the constructor:

public TwitterViewModel(string searchTerm)
{
    StartSearch(searchTerm);
}

At present, this means that every ViewModel must have a public constructor which has either no parameters or which has only string parameters.

So the reason your ViewModel isn't loading is because the MvxDefaultViewModelLocator can't find a suitable constructor for your ViewModel.


For "services", the MvvmCross framework does provide a simple ioc container which can be most easily accessed using the GetService<IServiceType>() extension methods. For example, in the Twitter sample one of the ViewModel contains:

public class TwitterViewModel
    : MvxViewModel
    , IMvxServiceConsumer<ITwitterSearchProvider>
{
    public TwitterViewModel(string searchTerm)
    {
        StartSearch(searchTerm);
    }

    private ITwitterSearchProvider TwitterSearchProvider
    {
        get { return this.GetService<ITwitterSearchProvider>(); }
    }

    private void StartSearch(string searchTerm)
    {
        if (IsSearching)
            return;

        IsSearching = true;
        TwitterSearchProvider.StartAsyncSearch(searchTerm, Success, Error);
    }

    // ...
}

Similarly, you can see how the conference service data is consumed in the Conference BaseViewModel


If your preference is to use some other IoC container or some other construction mechanism for your ViewModels, then you can override the ViewModel construction within MvvmCross.

Take a look at this question (and answers) for ideas on how to do this - How to replace MvxDefaultViewModelLocator in MVVMCross application

e.g. if you want to, then it should be fairly easy for you to adjust the MyViewModelLocator example in that question to construct your ViewModel with your service.

Community
  • 1
  • 1
Stuart
  • 66,722
  • 7
  • 114
  • 165
  • ok, understood (after reading it multiple times). So i don't use the constructors of the ViewModels in MVVM. If i got it right the constructor is for the data passing purpose and thereby only for strings. – Martin May 09 '12 at 09:14
  • In MvvmCross yes, that's the default behaviour - but if you want different behaviour then it's easy to override the default - write the code you feel is best for your app :) – Stuart May 09 '12 at 09:19
  • No i will not rewrite them, i don't want to leave the convention, to be sure to have it as portable and as reusable as possible. Thanks so far. – Martin May 09 '12 at 09:29
  • May I ask if in general, writing constructor methods in viewmodels is a bad idea? My simple example is when a list needs to be populated in the view model based on a key passed to it. I feel the constructor of the VM should perform this task, instead of a separate controller doing it. (Or at least it calls the Repository to get its data based on a key). (This is somewhat unrelated to this question) – Worthy7 Jun 20 '16 at 08:31