Skip to content

Android Navigation

Introduction

The idea behind this navigation mechanism is similar to how WPF/UWP's <Frame> element works. Namely we are have one root page that hosts all other pages. These pages are managed by library within this frame.

Setup

Layout

All pages are hosted as Fragments, I'm assuming using single activity pattern. Given a single android activity with layout:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">

  <FrameLayout android:layout_width="match_parent"
               android:layout_height="match_parent"
               android:id="@+id/RootView" />
</LinearLayout>

Manager

Now we will want to initialize library from code. The RootView FrameLayout will be hosting all our pages within itself.

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

    SetContentView(Resource.Layout.main);

    var pageDefinitions = new Dictionary<PageIndex, IPageProvider<NavigationFragmentBase>>
    {
        // cached
        {PageIndex.WelcomePage, new CachedPageProvider<WelcomePageFragment>()},
        // oneshots
        {PageIndex.SignInPage, new OneshotPageProvider<SignInPageFragment>()},
    };

    var manager = new NavigationManager<PageIndex>(
        fragmentManager: SupportFragmentManager,
        rootFrame: RootView,
        pageDefinitions: pageDefinitions)
}

Hint

You can also used attribute based navigation. See more here.

This is also the place to pass IViewModelResolver

Back navigation forwarding

One last thing left to do is to give the library the way to know that user pressed back button. Given that App.Current.NavigationManager is the instance of our freshly created navigation manager we can override OnBackPressed() in MainActivity like so:

public override void OnBackPressed()
{
    if (!App.Current.NavigationManager.OnBackRequested())
    {
        MoveTaskToBack(true);
    }
}

If OnBackRequested() returns true it means that the navigation was handled by the library, if not then it means that there's nothing on backstack.

Now we can happily use our INavigationManager<TPageIdentifier> in our ViewModels!

Additional configuration

Adding new pages

As we can see there are two pages defined WelcomePageFragment and SignInPageFragment. I'm providing base clases for fragments so it's very easy to add new content.

Type hierarchy:

  • INavigationPage

    • NavigationFragmentBase

      • FragmentBase<TViewModel>

NavigationFragmentBase inherits from Android's Fragment class and wraps its functionality. Let's say we want to create new page SplashPage. 1. Create new class called for example SplashPageFragment 2. Make it inherit from FragmentBase<TViewModel>

public class SplashPageFragment : FragmentBase<SplashViewModel>
{
    public override int LayoutResourceId { get; } = Resource.Layout.splash_page;

    protected override void InitBindings()
    {

    }
}
  1. There are 2 required elements:
  2. LayoutResourceId which indicates which layout the fragment is associated with.
  3. InitBinings method in which we will define our bindings to ViewModel.
  4. Add new entry in pageDefinitions

Transition animations

If you want to include transition animations when navigating you will want to use Action<FragmentTransaction> interceptTransaction parameter of NavigationManager's constructor. It will expose FragmentTransaction so you change whatever you want before actually committing new page. For example:

private void InterceptTransaction(FragmentTransaction fragmentTransaction)
{
    fragmentTransaction.SetCustomAnimations(
        Resource.Animator.animation_slide_bottom,
        Resource.Animator.animation_fade_out);
}

Bindings

When we are talking navigation we are talking bindings lifecycle too. FragmentBase class handles them too. Since the library is based on MVVMLight library we are using its bindings.

You will want to add all of them to Bindings which is of type List<Binding>, they will be properly attached and reattached when needed.

You are supposed to add all your bindings in InitBindings method which is called once per fragment instance or when bindigs need to be recreated.

If you don't have any bindings added to Bindings, yet you don't want the method to be fired again you can call this constructor:

public NavigationFragmentBase(bool hasNonTrackableBindings = false);

Example:

protected override void InitBindings()
{
    Bindings.Add(this.SetBinding(() => ViewModel.Toggle).WhenSourceChanges(() =>
    {
        ToggleValue.Text = ViewModel.Toggle ? "ON" : "OFF";
    }));

    Bindings.Add(this.SetBinding(() => ViewModel.Value, () => Value.Text));
}

Disclaimer: Value and ToggleValue are TextViews.

Faq

References to bindings objects should be preserved so that GC doesn't sweep them away.

Notes

  • The MainActivity will have to inherit AppCompatActivity.