How to properly use Fragments inside TabLayout with MvvmCross 6.x

无人久伴 提交于 2020-01-04 15:51:07

问题


Problem

I'm trying to make a very simple Proof of Concept with TabLayout and Fragments using MvvmCross 6.1.2. For this, I implemented an activity with a TabLayout and a ViewPager, which should have two tabs - each one containing a different fragment with just one TextView.

But I'm receiving an exception followed by a crash on runtime, when this activity should be displayed:

The type MvxTabLayoutPresentationAttribute is not configured in the presenter dictionary

Code

This is what my code looks like, which I implemented following the Playground example and the Documentation:

AppStart.cs:

public class AppStart : MvxAppStart
{
    private readonly IMvxNavigationService _mvxNavigationService;

    public AppStart(IMvxApplication app, IMvxNavigationService mvxNavigationService)
        : base(app, mvxNavigationService)
    {
        _mvxNavigationService = mvxNavigationService;
    }

    protected override void NavigateToFirstViewModel(object hint = null)
    {
        Mvx.Resolve<IMvxNavigationService>().Navigate<TabLayoutViewModel>();
    }
}

TabLayoutViewModel.cs

public class TabLayoutViewModel: MvxViewModel
{
    public override async Task Initialize()
    {
        await base.Initialize();

        var tasks = new List<Task>();
        tasks.Add(Mvx.Resolve<IMvxNavigationService>().Navigate<FragmentTab1ViewModel>());
        tasks.Add(Mvx.Resolve<IMvxNavigationService>().Navigate<FragmentTab2ViewModel>());
        await Task.WhenAll(tasks);
    }
}

FragmentTab1ViewModel.cs (and FragmentTab2ViewModel.cs likewise)

public class FragmentTab1ViewModel : MvxViewModel
{
    public override Task Initialize()
    {
        return base.Initialize();
    }
}

TabLayoutViewController.cs

[MvxActivityPresentation]
[Activity(Label = "", ScreenOrientation = ScreenOrientation.Portrait, LaunchMode = LaunchMode.SingleTask, Theme = "@style/LoginTheme")]
public class TabLayoutViewController: MvxAppCompatActivity<TabLayoutViewModel>
{
    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);

        SetContentView(Resource.Layout.TabLayoutView);

        var set = this.CreateBindingSet<TabLayoutViewController, TabLayoutViewModel>();

        set.Apply();
    }
}

TabLayoutView.axml

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:alwaysDrawnWithCache="false"
    android:background="#f4f4f4"
    android:minWidth="25px"
    android:minHeight="25px">
  <android.support.design.widget.TabLayout
      android:id="@+id/tabsTeste"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:paddingLeft="16dp"
      app:tabGravity="center"
      app:tabMode="scrollable" />
  <android.support.v4.view.ViewPager
      android:id="@+id/viewpagerTeste"
      android:layout_width="match_parent"
      android:layout_height="match_parent" />
</android.support.design.widget.CoordinatorLayout>

FragmentTab1ViewController.cs (and FragmentTab2ViewController.cs likewise)

[MvxTabLayoutPresentation(ActivityHostViewModelType = typeof(TabLayoutViewModel), ViewPagerResourceId = Resource.Id.viewpagerTest, TabLayoutResourceId = Resource.Id.tabsTest, Title = "Tab A")]
[Register("smartSolution.coleta.droid.view.FragmentTab1ViewController")]
public class FragmentTab1ViewController : MvxFragment<FragmentTab1ViewModel>
{
    public override Android.Views.View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        base.OnCreateView(inflater, container, savedInstanceState);

        var view = this.BindingInflate(Resource.Layout.FragmentTab1View, null);

        inflater.Inflate(Resource.Layout.FragmentTab1View, container, true);

        var set = this.CreateBindingSet<FragmentTab1ViewController, FragmentTab1ViewModel>();

        set.Apply();

        return view;
    }
}

(FragmentTab1View.axml and FragmentTab2View.axml are just LinearLayouts with a TextView)

Questions

  1. What is the cause of the exception thrown?
  2. Is this the recommended way to implement a TabLayout with Fragments?
  3. What can be done to solve this issue, following MvvmCross 6.x good practices?

回答1:


That exception is thrown because that attribute is not registered in the AttributeTypesToActionsDictionary of the Presenter.

In the code you can see that in the method RegisterAttributeTypes it's registered but take into account that it is in the MvxAppCompatViewPresenter. Furthermore in the docs it states that that attribute only works on AppCompat.

Given that you are getting that exception I can assume that the non-AppCompat presenter is being used, therefore you are using MvxAndroidSetup.

To solve this make sure you are using AppCompat classes, in particular inherit from MvxAppCompatSetup if you have a custom Setup that is where the MvxAppCompatViewPresenter is set. Also make sure you are using MvxAppCompatApplication so if forces you to use the AppCompat version of the Setup.


Update regarding the comment on the exception MvvmCross.Exceptions.MvxException: ViewPager not found

I think that the problem is that you are navigating to the children viewmodels in the Initialize instead of doing this after the tabs view is created so the ViewPager may not be initialized yet when you try to navigate to the children, therefore it is not found.

So as in the Playground Viewmodel you should have a command that calls a method to do the navigation on your ViewModel:

...
ShowInitialViewModelsCommand = new MvxAsyncCommand(ShowInitialViewModels);
...

public IMvxAsyncCommand ShowInitialViewModelsCommand { get; private set; }

...

private async Task ShowInitialViewModels()
{
    var tasks = new List<Task>();
    tasks.Add(Mvx.Resolve<IMvxNavigationService>().Navigate<FragmentTab1ViewModel>());
    tasks.Add(Mvx.Resolve<IMvxNavigationService>().Navigate<FragmentTab2ViewModel>());
    await Task.WhenAll(tasks);
}

And as in the Playground View you should call the command in your TabLayoutViewController:

protected override void OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);

    SetContentView(Resource.Layout.TabLayoutView);

    var set = this.CreateBindingSet<TabLayoutViewController, TabLayoutViewModel>();

    set.Apply();

    if (bundle == null)
    {
        ViewModel.ShowInitialViewModelsCommand.Execute();
    }
}

HIH



来源:https://stackoverflow.com/questions/52748984/how-to-properly-use-fragments-inside-tablayout-with-mvvmcross-6-x

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!