MvvmCross first tab fragment was created and destroyed when loading the viewpager with navigate<>

Viewed 389

I am using Mvvmcross 6 with Xamarin Android. Just upgraded to Mvx 6.2 but the issue remains the same. I am following the sample Playground to load the viewpager. I have an activity holding a viewpager for 3 tabs. In the viewmodel for activity, tabs are added to the viewpager:

    var tasks = new List<Task>();
    tasks.Add(_navigationService.Navigate<SettingPreferenceViewModel>());
    tasks.Add(_navigationService.Navigate<SettingTrackingViewModel>());
    tasks.Add(_navigationService.Navigate<SettingCalorieViewModel>());
    await Task.WhenAll(tasks);

In the playground sample, tab fragments will not be created when building the viewpager by navigating to viewmodel of those tabs. Fragments will be created only when they are shown.

However, in my case, after Navigate< SettingPreferenceViewModel>(), SettingPreferenceFragment will be created. After created the second tab with Navigate< SettingTrackingViewModel>(), SettingPreferenceFragment will be destroyed. Later, SettingPreferenceFragment was created again when it is shown. I.e. the first tab was created twice. The first creation & destroy are unexpected.

For my second & third tabs, the codes are basically the same as the first one. None of them have the same issue as tab one. Fragments will not be created after navigated to the corresponding viewmodel.

What could be the possible reason that cause my first tab fragment to be created during loading the viewpager? Please kindly help.

Thanks,

Nick

SettingRootViewModel.cs

public SettingRootViewModel(
    IMvxNavigationService navigationService
    //IMvxMessenger messenger
)
{
    _navigationService = navigationService;

    ShowInitialViewModelsCommand = new MvxAsyncCommand(ShowInitialViewModels);
}


public IMvxAsyncCommand ShowInitialViewModelsCommand { get; private set; }

private async Task ShowInitialViewModels()
{
    var tasks = new List<Task>();
    tasks.Add(_navigationService.Navigate<SettingPreferenceViewModel>());
    tasks.Add(_navigationService.Navigate<SettingTrackingViewModel>());
    tasks.Add(_navigationService.Navigate<SettingCalorieViewModel>());
    await Task.WhenAll(tasks);
}

SettingActivity.cs

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

            SetContentView(Resource.Layout.activity_setting);

           …..

            ViewModel.ShowInitialViewModelsCommand.Execute();
        }

Activity.xml

  ……
  <LinearLayout
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.TabLayout
      android:id="@+id/tabs_fragment_setting_viewpager"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      local:tabIndicatorColor="@color/gray_700"
      local:tabGravity="fill"
      local:tabMode="fixed" />

    <android.support.v4.view.ViewPager
      android:id="@+id/viewpager_fragment_setting_viewpager"
      android:layout_width="match_parent"
      android:layout_height="match_parent"/>

  </LinearLayout>

SettingPreferenceFragment

BaseFragment is a MvxFragment. I tried implementing with MvxFragment directly but situation remains the same.

    [MvxTabLayoutPresentation(TabLayoutResourceId = Resource.Id.tabs_fragment_setting_viewpager, 
    ViewPagerResourceId = Resource.Id.viewpager_fragment_setting_viewpager, 
    Title = "P", 
    ActivityHostViewModelType = typeof(SettingRootViewModel))]
[Register(nameof(SettingPreferenceFragment))]

    public class SettingPreferenceFragment : BaseFragment<SettingPreferenceViewModel>
    {
        protected override int FragmentId => Resource.Layout.fragment_setting_preference;


        public override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
        }

        public override View OnCreateView(LayoutInflater inflater, ViewGroup container,
                                          Bundle savedInstanceState)
        {
            // view = BindingInflate(Resource.Layout.fragment_route_summary, null) is handled in base fragment
            var view = base.OnCreateView(inflater, container, savedInstanceState);

            return view;
        }

        public override void OnDestroy()
        {
            base.OnDestroy();
        }
    }
1 Answers

I had the exact same issue and I think I've managed to solve it for at least my case. (I'm on MvvmCross 6.2.3)

I was calling the ShowInitialViewModelsCommand from the ViewModel's ViewAppearing lifecycle callback (see https://www.mvvmcross.com/documentation/fundamentals/viewmodel-lifecycle), because it felt wrong to me that the Fragment had to worry about navigation. When I found this issue, I moved it back to OnCreate like in the Playground sample, and the problem was gone. With some further experimentation, it looks like I can call it from the ViewModel's ViewCreated callback, which gets called earlier than ViewAppearing.

Based on that, my theory is that there is some point in the initialization of the ViewPager where it creates its first Fragment if it doesn't already exist, and if that point happens before you run your Navigate calls, that Fragment will be created twice. Are you doing a lot of work in your OnCreate maybe?

But that's just a theory of course. Either way, this seems to work, maybe it'll help someone.

Related