Question

I'm trying to use a TabActivity with MvvmCross but I am getting a NullReferenceException in the framework code because the viewModelLoader passed in to the OnViewCreate is null

namespace Cirrious.MvvmCross.ExtensionMethods
{
    public static class MvxViewExtensionMethods
    {
        public static void OnViewCreate<TViewModel>(this IMvxView<TViewModel> view, Func<TViewModel> viewModelLoader)
            where TViewModel : class, IMvxViewModel
        {
            if (view.ViewModel != null)
                return;

            var viewModel = viewModelLoader();
            viewModel.RegisterView(view);
            view.ViewModel = (TViewModel)viewModel;
        }

I suspect that this is because I am trying to load the views drirectly rather than via a ViewModel. The code in my TabHost Activity looks like:

[Activity(Label = "TabHost")]
    public class TabHostView : MvxBindingTabActivityView<TabHostViewModel>
    {
        protected override void OnViewModelSet()
        {
            SetContentView(Resource.Layout.Page_TabHostView);
            var tabHostWidget = this.TabHost;

            TabHost.TabSpec spec;     // Resusable TabSpec for each tab
            Intent intent;            // Reusable Intent for each tab
            // Create an Intent to launch an Activity for the tab (to be reused)
            intent = new Intent(this, typeof(HomeView));
            intent.AddFlags(ActivityFlags.NewTask);

            // Initialize a TabSpec for each tab and add it to the TabHost
            spec = tabHostWidget.NewTabSpec("home");
            spec.SetIndicator("Home", Resources.GetDrawable(Resource.Drawable.icon_home));
            spec.SetContent(intent);
            tabHostWidget.AddTab(spec);
//... more tabs

How would I get around this problem?

Also my ViewModels are set up so that the TabHostViewModel has a property for each tab page ViewModel. These are lazy in that they only get the data from the Model when the get accessor for the property is called.

So if I have data binding in my tab page axml layouts, presumably the path has to assume that the TabHostViewModel is the Context (root)?

Many thanks, Jason

Was it helpful?

Solution

At a trivial level I think you might be able to solve your current problem by asking the framework to create the intent for you:

        TabHost.TabSpec spec;     // Resusable TabSpec for each tab
        Intent intent;            // Reusable Intent for each tab
        // Create an Intent to launch an Activity for the tab (to be reused)
        intent = base.CreateIntentFor<HomeViewModel>();
        intent.AddFlags(ActivityFlags.NewTask);

At a more complete level...


There is more than one way to tackle tabbed pages - both in Android and in MvvmCross.

In Android, you choose to tackle a TabActivity by using a layout which directly contains all the axml for the views inside each individual tabs. If you work with this method, then I believe that you can just using databinding directly as you would "normally" - each individual tab should just work just like its a normal child Android Widget/View... I have read that there are performance advantages in working this way with Android tabs, but generally I don't work like this.

Secondly - the way I generally work - you can choose to tackle a TabActivity by treating each Tab as a separate Activity, and linking each of those Activities to a child ViewModel of the main tab ViewModel. (I'll try to draw a picture of this structure and upload it later today!)

If you choose to do this, then a good example to follow is the conference one - https://github.com/slodge/MvvmCross/blob/master/Sample%20-%20CirriousConference/Cirrious.Conference.UI.Droid/Views/HomeView.cs

What happens in this conference example, is that the tab specifications are initialised using spec.SetContent(intent) where the intent is created using a tab activity base class method CreateIntentFor - here's the relevant code :

    protected override void OnViewModelSet()
    {
        SetContentView(Resource.Layout.Page_Home);

        TabHost.TabSpec spec;     // Resusable TabSpec for each tab
        Intent intent;            // Reusable Intent for each tab

        // Initialize a TabSpec for each tab and add it to the TabHost
        spec = TabHost.NewTabSpec("welcome");
        spec.SetIndicator(this.GetText("Welcome"), Resources.GetDrawable(Resource.Drawable.Tab_Welcome));
        spec.SetContent(CreateIntentFor(ViewModel.Welcome));
        TabHost.AddTab(spec);

        spec = TabHost.NewTabSpec("sessions");
        spec.SetIndicator(this.GetText("Sessions"), Resources.GetDrawable(Resource.Drawable.Tab_Sessions));
        spec.SetContent(CreateIntentFor(ViewModel.Sessions));
        TabHost.AddTab(spec);

        spec = TabHost.NewTabSpec("favorites");
        spec.SetIndicator(this.GetText("Favorites"), Resources.GetDrawable(Resource.Drawable.Tab_Favorites));
        spec.SetContent(CreateIntentFor(ViewModel.Favorites));
        TabHost.AddTab(spec);

        spec = TabHost.NewTabSpec("tweets");
        spec.SetIndicator(this.GetText("Tweets"), Resources.GetDrawable(Resource.Drawable.Tab_Tweets));
        spec.SetContent(CreateIntentFor(ViewModel.Twitter));
        TabHost.AddTab(spec);
    }

where the corresponding top-level ViewModel is a bit like:

public class HomeViewModel
    : MvxBaseViewModel
{
    public HomeViewModel()
    {
        Welcome = new WelcomeViewModel();
        Sessions = new SessionsViewModel();            
        Twitter = new TwitterViewModel();
        Favorites = new FavoritesViewModel();
    }

    public FavoritesViewModel Favorites { get; private set; }
    public WelcomeViewModel Welcome { get; private set; }
    public SessionsViewModel Sessions { get; private set; }
    public TwitterViewModel Twitter { get; private set; }
}

...

As a variation on that second alternative (and it looks like this is what you are trying to do in this question), if your individual tab ViewModel's aren't really related to each other or to the 'parent' TabHost ViewModel, then you can link each tab to a new ViewModel created on demand. I don't think any of the public samples do this, but if you need it, then the format for this would be:

        spec = TabHost.NewTabSpec("newTab");
        spec.SetIndicator("new tab text"), Resources.GetDrawable(Resource.Drawable.Tab_Icon));
        spec.SetContent(CreateIntentFor<NewViewModel>(new { constructorParameter1 = "value1", constructorParameter2 = "value2" }));
        TabHost.AddTab(spec);

Note that there is an underlying philosophy going on here - it's part of MvvmCross's opinion - the navigation/linking in mvx is always about ViewModels, never about Views. The idea is that the applications are built ViewModel-first - each client platform simply decides how to represent those ViewModels on the screen. This is likely to continue and evolve even further in iPad and WinRT development where splitviews, popups, etc will be common.

One further note... if ever you have complicated ViewModel construction or if you need to use special lifetimes for some of your ViewModels (e.g. singletons), then it's also possible to use custom ViewModelLocators within your "core application" - these would then allow you to change/control the dynamic ViewModel creation if you want to... but I suspect that this isn't the case for this question.


Thanks for the detail in your question - sorry for throwing in advanced options. I'll try to enhance this answer with a bit better explanation later today or this weekend.


Mvx is also still open to ideas... so we could modify the code to work with you original code... it wouldn't be that hard to do and I can see some justification for it... I'll have a think about it...

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top