Search code examples
cross-platformxamarinmvvmcrossportable-class-library

Is there a nice way to simulate Android's Fragment Pattern in iOS?


We're using MvvmCross in our apps. In our Android app, we use the NavigationDrawer for our Menu. We load our HomeView which contains the NavigationDrawer and the ContentFrame.

<android.support.v4.widget.DrawerLayout >

    <!-- The main content view -->
    <FrameLayout android:id="@+id/content_frame" />

    <!-- The navigation drawer -->
    <ListView android:id="@+id/left_drawer" />

</android.support.v4.widget.DrawerLayout>

When the OnCreate() method is triggered, we load an AudioPlayerFragment into the content_frame based on a database query. In that same method we setup a listener that waits for the user to click one of the ListItem's in the navigation drawer.

protected override void OnCreate(Bundle bundle) {
   // do stuff to build the nav drawer
    _topDrawerList = FindViewById<MvxListView>(Resource.Id.left_drawer);
    _topDrawerList.ItemClick += (sender, args) = > SelectItem(args.Position, null);

    if(bundle == null)
        SelectItem(0, null);
}

private void SelectItem(int position, UserViewModel currentUser) {
    SupportFragmentManager.BeginTransaction()...
}

Our app is working great, and I like how the HomeView is the only View for the app. Everything else is a Fragment.

In our Core library, we have a ViewModel for the HomeView, and we also have a ViewModel for each Fragment in the app. Again, this is working well for us.

  • PCL
    • ViewModels
      • HomeViewModel
      • AudioPlayerViewModel <-- vm for fragment
      • LoginFragmentViewModel <-- vm for fragment
  • Droid.Ui
    • Fragments
      • AudioPlayerFragment
      • LoginFragment
    • Views
      • HomeView <-- Just a container View as the entry point to the app. Everything else is fragmented... AudioPlayerFragment is loaded instantly.

Now I'm trying to build a matching iOS app, but I can't figure out how to structure it. Essentially I'd like the exact same behavior where HomeView is the entry point and can contain both the SlidingPanel bits and some form of a ContentFrame that other "Views" can be loaded into.

Unfortunately, right now I've got this stupid MenuView to deal with, and a corresponding MenuViewModel... and I really don't want either.

  • PCL
    • ViewModels
      • HomeViewModel
      • AudioPlayerViewModel <-- vm for fragment
      • LoginFragmentViewModel <-- vm for fragment
  • Touch UI
    • Views
      • AudioPlayerView
      • HomeView
      • LoginView
      • MenuView <-- UGLY!!!
    • ViewModels
      • MenuViewModel <-- UGLY!!!

I really don't want to create a custom View/ViewModel for the Menu. I'm using SlidingPanels for my iOS "version" of the Navigation Drawer, and the tutorials I'm seeing are requiring an additional View/ViewModel for the menu.

Is there any way to do this and keep continuity with regards to ViewModels so that they can all be reused in a PCL? Is there a nice (read Clean) way to build the layout structure in a similar way to Android?


Solution

  • The approach I've settled on is to use the MvxViewController as my base entry point into the app.

    public class HomeView: MvxViewController
    {
        public override void ViewDidLoad()
        {
            base.ViewDidLoad();
    
            var myEntryFragement = new MyEntryFragment();
            Add(myEntryFragment);
        }
    }
    

    And then use MvxView's as "Fragments"

    public class MyEntryFragment: MvxView // essentially a UIView
    {
    
        private MyEntryViewModel _viewModel;
        public MyEntryFragment()
        {
            // manually construct the _viewModel (or alternatively inject it in the constructor)
            // setup all your UI controls in here
        }
    
    }
    

    Then I just load and unload my "Fragment's" as needed.