Mastodon

Friday, March 13, 2015

Decoupling Views

One of the most common design mistakes that I see people make in mobile applications is coupling their views together. In this post I will explain why this is a problem and how to avoid it.

The most common form of coupling one view to another is by making it aware of where it fits within the whole flow of the app. For instance, a common navigational pattern in mobile apps is to launch with a login screen and then transition to a main screen after a successful login. A naive implementation of this pattern would be to have the code for the login page directly do the transition to the main page. However, this is the wrong approach.

Why is Coupling Views Bad?

First, for those who may not have taken a software engineering course, what is coupling? Coupling in its most basic form is when one component depends on another directly. As an example, if class A directly interacts with class B (i.e., by using variables of type B) then A is coupled to B. If B changes then A is affected, and if you want to replace B with C then you have to update A.

It is generally accepted in software engineering that coupling is a Bad Idea. I'm not going to try to give a detailed justification for this here because it's usually not controversial, but I do want to justify why I think specifically coupling of views is a Bad Idea. For that let me go back to the more concrete example from my introduction: consider an app with a login screen and a main screen. In your initial implementation you start with a login screen on launch, and then on a successful login the login screen itself transitions to the main screen. In Xamarin.Forms1 that may look like this:2

public class LoginPage : ContentPage
{
    // This method is called upon a successful login
    private async Task OnLoginSucceeded()
    {
        await Navigation.PushModalAsync(new MainPage());
    }
}

Given code like this here are some questions that we need to ask:

  1. Is modal navigation the right mechanism or is there another way to transition to the next page? Maybe a NavigationPage would be more appropriate, or perhaps just setting the MainPage property of the application.
  2. Is MainPage the only possible next page to show? Maybe in some cases you are taken directly to another page.
  3. Does a successful login always need to push a new page at all? Maybe in some cases the login page is itself presented modally, and it merely needs to be popped.

Each of these questions is a hint that the line of code above may not cover some use cases or may need to change in the future. Put another way, each of these tells us that we are coupling the login page to a specific set of assumptions. That coupling is a barrier to progress.

Writing Decoupled Views

Now that we've identified the problem we need to figure out how to solve it. The trick to avoiding coupling of views is to make the views as dumb as possible. Consider again the login page. What does a login page need to do? Just one thing: allow the user to enter credentials and verify those credentials3. There are typically only two possible results of a login page: (1) success or (2) failure. Therefore the login page only needs to provide the ability to communicate success or failure.

At this point you may be wondering "communicate to whom?" or "communicate how"? The simple, if unhelpful, answer to the first question is "whoever wants to know". Making assumptions about who wants to know about a successful login risks introducing yet more coupling. The real question then is how to communicate to an unknown interested party, and so the more important question is the second: how?

There are many ways of dealing with the problem of sending messages to unknown interested parties in software, and they mostly fall under the Observer Pattern. In C# the easiest way to implement this pattern is with C# events4. Applying that to our login example we would change our code to something like this:

public class LoginPage : ContentPage
{
    public event EventHandler LoginSucceeded;

    public event EventHandler LoginFailed;

    private void OnLoginSucceeded()
    {
        if (LoginSucceeded != null)
        {
            LoginSucceeded(this, EventArgs.Empty);
        }
    }

    private void OnLoginFailed()
    {
        if (LoginFailed != null)
        {
            LoginFailed(this, EventArgs.Empty);
        }
    }
}

Now we have a login page that knows nothing about what to do after logging in, but we still have to somehow do something in response to the user logging in. Where does that code go now? The most likely candidate is the code that presented the login screen in the first place. For instance, typically an app will start with a login screen and then after a successful login transition to a main page. That code might live in the Application class, and it might look like this:

public class App : Application
{
    public App()
    {
        var loginPage = new LoginPage();
        loginPage.LoginSucceeded += HandleLoginSucceeded;
        MainPage = loginPage;
    }

    private void HandleLoginSucceeded(object sender, EventArgs e)
    {
        MainPage = new MainPage();
    }
}

Later you may decide you prefer doing that transition as a modal push instead. To change that you just need to change the HandleLoginSucceeded method like this:

private async void HandleLoginSucceeded(object sender, EventArgs e)
{
    await MainPage.Navigation.PushModalAsync(new MainPage());
}

Or maybe you want to present the login page on top of the main page itself and then dismiss it after success:

private async void HandleLoginSucceeded(object sender, EventArgs e)
{
    await MainPage.Navigation.PopModalAsync();
}

The advantage is that this change doesn't affect any other possible uses of LoginPage (current or future). The code that knows how LoginPage is presented is the same code that knows how to transition away from the login page.

Summary

Implementing views that are decoupled gives you greater flexibility and makes it easier to reuse views for multiple use cases. You should try to avoid directly presenting a new view from within a view itself and consider moving that code outside of the view.

In a future post I will take this idea further and explore how to manage more complex navigation flows with decoupled views.


  1. The examples in this post are for Xamarin.Forms, but the same advice applies to other frameworks as well, including direct iOS and Android programming (using Xamarin.iOS/Xamarin.Android or their native languages). In fact, this advice is one of the major reasons that I strongly discourage the use of Storyboards in iOS. 

  2. These examples put the navigational logic in the Page, which is contrary to my previous blog post. The same concepts apply regardless of whether the navigation is in the Page or the Page's view model. The examples are just simpler this way. 

  3. I am ignoring here another possible pitfall: the view itself (the LoginPage above) should not be doing any verification of credentials. In a proper MVVM architecture the view (the page in this case) should only be responsible for displaying information to the user and getting input from the user. The model should be doing the actual authentication. 

  4. In Xamarin.Forms you could also use MessageCenter to broadcast messages, but in more complex examples you may end up with multiple instances of the same kind of page in existence, and subscribers would then have to be sure to handle only the messages from the page they're actually interested in. Not only is that error prone, but in order to do that discrimination the subscriber has to have a direct reference to the sender in the first place, and then you may as well just be using events. Events are just easier for this use case. Use MessageCenter for cases where it really is a broadcast to the whole system. 

13 comments:

  1. Thanks for the post it came just in time. I started using Xamarin.Forms today and was wondering how navigation between XAML pages can be achieved correctly. I followed your example but the second page is not shown. From stepping through the code in XS I see that the second page constructor gets called in HandleLogonSucceeded but the page never gets replaced. What could be wrong? thanks

    ReplyDelete
    Replies
    1. Adding to that if I use the first option MainPage = new HomeHub(); navigation does not happen
      if I use the async option await MainPage.Navigation.PushModalAsync(new HomeHub());
      the app terminates after calling the constructor.

      Delete
    2. I've tested each of those versions on both iOS and Android, and they each worked for me. Make sure that you have the latest version of Xamarin.Forms (in each of your projects). I'm using 1.4.0.6341. If it still doesn't work then you may have to share some code. You can post your code somewhere on GitHub or make a post on forums.xamarin.com with a zip attached. Also let me know which platform you're testing on.

      Delete
    3. Hi,

      I am using 1.4.0.6341. Using XS on OS X 10.10 as the IDE. Trying to run the code on the iPhone Simulator.
      When I remove all other extra code around including the logon agains the web server your sample starts working.
      As soon as I add the authentication back the app closes when it the second page is in InitializeComponent. I will see if I can prep a sample and share that with you

      Delete
    4. For posterity, see this thread: https://forums.xamarin.com/discussion/35840/navigation-between-xaml-pages-fail

      Delete
  2. Hi Adam. Nice example. One thing I am concerned about is that you subscribe to an event using the += notation. But I don't see that you unsubscribe using -= Won't that lead to a memory leak? What I can't work out is where you would put that -= code.

    ReplyDelete
    Replies
    1. That's a good question. In general in C# you only have to worry about removing event handlers if the object that has the event outlives the object that has the handler. The delegate will hold a reference to the object that handles the event, and the object that has the event holds a reference to the delegate. That means the object that has the event indirectly references the object with the handler, possibly holding it in memory. However, if the object with the event isn't itself held in memory then it can't hold anything else in memory. That means if it isn't reachable from a root then it will be collected, and it won't cause anything else to leak.

      In this case the App object is going to live for the full duration of your application. It therefore obviously outlives the LoginPage object, and so you're not going to leak the App object. The LoginPage object won't be leaked either because having an event handler doesn't keep the object with the event in memory (the delegate references the handler, not the event source).

      Delete
  3. Hi Adam, very very brilliant article, but I cannot think of a way to implement this is in pure Xamarin.Android. I have successfully implemented this pattern with Xamarin.iOS, but with Android i'm stuck. How do you go about doing this personally ?

    ReplyDelete
  4. It's a lot harder on Android for sure. First, we tend to use fragments a lot for multi-screen UI sequences. When you use fragments you can treat them a lot more like view controllers, and you have access to them directly instead of having to communicate via Intents. Another thing that might help a bit is using some of the advice I give in my series of articles about the Android activity (part 1: http://blog.adamkemp.com/2015/04/taming-android-activity-part-1.html, and part 2: http://blog.adamkemp.com/2015/05/taming-android-activity-part-2.html). Part 3, when I eventually get around to writing it, will give you an even better way of dealing with multi-screen sequences, but if you use fragments for a whole sequence then the whole problem basically disappears. That would be my first recommendation if possible.

    ReplyDelete
  5. Thank you for the post! Very clearly explained. Looking forward to reading more Xamarin related posts :)

    ReplyDelete
  6. hi Adam!
    I just came here from xamarin forum. May be I'm wrong but listening for events can lead to memory leaks. Subscribe and Unsabscribe are expensive also.
    I'm really messed up with Xamarin forms MessagingCenter & Event mechanism...

    ReplyDelete
    Replies
    1. Events only cause leaks if the object that has the event outlives the object that has the handler. If you attach to an event on a page, and then later that page is removed from the screen and nothing refers to it anymore then the page will be collected, and the event won't hold anything in memory.

      I'm not sure what you meant by subscribe and unsubscribe being expensive. If you meant events then adding and removing a handler is not expensive at all. If you meant MessagingCenter then I doubt that's expensive enough to worry about either. That cost is going to be far less than most of the other stuff you're doing in a UI app, like creating the views. Regardless, IMO MessagingCenter is not an appropriate API for use cases like the one described in this blog post. Events are the best approach for this.

      Delete
  7. Hi, thanks for the Post, it helped me a lot. I had a question, how do I approach DisplayAlerts by following the same idea as in the Post.

    ReplyDelete