Nick's .NET Travels

Continually looking for the yellow brick road so I can catch me a wizard....

Intercepting the Android Software Back Button in Xamarin.Forms

Recently an issue was raised on MvvmCross that claimed there was an issue intercepting the back button in a Xamarin.Forms + MvvmCross application running on Android. I spent a bit of time investigating the issue as I didn’t believe MvvmCross was doing anything that would prevent an application from intercepting the back button. In this post I’ll walk through my investigation and demonstrate a solution that I came up with. Just for clarity, the issue was referring to intercepting the software back button that appears in the navigation bar.

In the comments someone had referenced a solution proposed by Udara Alwis (https://theconfuzedsourcecode.wordpress.com/2017/03/12/lets-override-navigation-bar-back-button-click-in-xamarin-forms/) which states that the OnOptionsItemSelected method can be overridden in the Activity that hosts the Xamarin.Forms application. Further comments indicated that this solution didn’t work for a Xamarin.Forms application that was using MvvmCross. My starting point was to of course validate this assertion. Using the Playground sample in the MvvmCross source code I attempted to override the OnOptionsItemSelected method in the MainActivity of the Android application. I ran the application and sure enough, the OnOptionsItemSelected method does not get invoked when tapping the software back button.

Having validated that the MvvmCross Playground was indeed broken, I next wanted to test to see if it was an issue with Xamarin.Forms itself. To do this, I started by creating a new Xamarin.Forms application (I’m just going to build for Android, since this is just a sample).

image

The Blank application template comes with a single page, MainPage. I added a second page to the application:

image

Onto the MainPage I added a Button and in the Clicked event handler added code to navigate to the page I just added:

private async void Button_Clicked(object sender, EventArgs e)
{
     await Navigation.PushAsync(new Page1());
}

In order to get the navigation bar to appear, I changed the App constructor to make use of a NavigationPage:

public App()
{
     InitializeComponent();

    MainPage = new NavigationPage(new MainPage());
}

Lastly, I needed to add the OnOptionsItemSelected override to the MainActivity.

public override bool OnOptionsItemSelected(IMenuItem item)
{
     return base.OnOptionsItemSelected(item);
}

Unfortunately, after doing all this, the OnOptionsItemSelected method was still not called. Looks like the issue sits with Xamarin.Forms, so it’s time to start trawling through the source code.

After an initial inspection it appears that the FormsAppCompatActivity does override the OnOptionsItemSelected method, but that doesn’t explain why overriding it in my application wouldn’t work. However, further inspection revealed that the NavigationPageRenderer (the renderer for the NavigationPage) implements the IOnClickListener interface and registers itself with the ActionBar (which corresponds to the navigation bar). Doing this prevents the call to the OnOptionsItemSelected method, which is why we were not seeing it called.

I figured that I would try extending the default renderer and provide my own implementation of the IOnClickListener interface:

[assembly: ExportRenderer(typeof(NavigationPage), typeof(HackBackButton.CustomNavigationPageRenderer))]
namespace HackBackButton
{
     public class CustomNavigationPageRenderer : NavigationPageRenderer, IOnClickListener
     {
         public CustomNavigationPageRenderer()
         {
         }

        public CustomNavigationPageRenderer(Context context) : base(context)
         {
         }

        public new void OnClick(Android.Views.View v)
         {
             Element?.PopAsync();
         }
    }
}

This works and the OnClick method is invoked when the software back button is tapped. Of course, we then need to route this to the current page so that it can decide if it should allow the back navigation.

Turns out that it’s nothing to do with MvvmCross and that the issue lie entirely with Xamarin.Forms. There needs to be a better way to intercept the back navigation. Someone has already listed a issue with Xamarin.Forms, so add a comment and make sure this issue gets some attention.

No se aceptan más comentarios
Application Development Using States and Transitions

Nick's .NET Travels

Continually looking for the yellow brick road so I can catch me a wizard....

Application Development Using States and Transitions

Having worked with each of the different XAML based technologies Microsoft has released (WPF, SL, WP, Win, UWP...), I've long been a proponent of the use of visual states. Jerry Nixon discusses using visual states to build Windows 8.1 application. I've written articles that talk about using visual states for changing page layout to reflect states within a view model. Let me talk briefly about why I favour visual states and why the visibility property is often misused.... then we'll get onto discussing using states for building applications.

Developers new to working with XAML discover that they can hide/show items on the page by data binding a Boolean property on the view model (this assumes they're using mvvm/data binding) with the visibility property on an element. This of course requires a Boolean to visibility converter. It works nicely and assuming the developer has implemented INotifyPropertyChanged and is raising the PropertyChanged event correctly, the elements on the page will hide/show as required.

Whilst there isn't anything wrong with this approach, it makes it difficult to design the layout in Blend. In order to hide/show elements, you have to modify the design time data (assuming you've bothered to create design time data). The alternative is to use visual states to describe how the page looks for each state. Blend for Visual Studio includes a States window which allows for one or more states to be invoked, making it easy to create, view and edit visual states, at design time!


Ok, time to move on an talk more about using states as a foundation for application development. Unfortunately we can't jump there yet as we need to first look at different levels of how an application hangs together. Rather than get into a discussion on the similarities and differences between application platforms, let's just look at the Windows platform (this should apply to Windows Phone, Windows 8.x and Windows 10):

- An application typically has a frame, within which it navigates between pages

- A page can be made up of any number of visual state groups, and can have a single visual state selected at any time from each visual state group. Animations can be run as part of transitioning between states.

- Controls (including Usercontrols) can contain their own visual states that determine how they look under various conditions (pressed, checked, selected etc).

What's interesting about this is that the navigation between pages is actually the odd one out. Most application developers devote a large portion of the application development process to ensuring the navigation between pages is correct. They spend very little time working through the visual states of pages (often hacking away at hiding/showing elements on the screen). So, what happens if we make the assumption that each one of these scenarios can be expressed as a series of states?

As a starting point, we'd need a definition of a state. In the past, to keep the states of a view model separated from the visual states on a page I typically create an enumeration which has values that have the same names as the visual states. The view model then keeps track of which state is currently selected. Changing the current state raised a StateChanged event, which is handled by the page where it invokes the GoToState method on the VisualStateManager. My basic definition of a state will be an extension of this where the IStateDefinition is based on an enumeration type (well, at least a struct):

public interface IStateDefinition<TState> where TState : struct
{
    TState State { get; }
}

with a simple implementation:

public class BaseStateDefinition<TState> : IStateDefinition<TState>
    where TState : struct
{
    public TState State { get; set; }
}

And we can then define an interface for an entity which maintains current state, allows for a change in state and raise an event when the state changes:

public interface IStateManager<TState> : INotifyPropertyChanged
    where TState : struct
{
    event EventHandler<StateEventArgs<TState>> StateChanged;

    TState CurrentState { get; }

    IDictionary<TState, IStateDefinition<TState>> States { get; }

    Task<bool> ChangeTo(TState newState, bool useTransition = true);
}

The first instinct would be for the view model to implement this interface, but this won't work since the view model would have to implement the interface for each state group that it needs to track. This means we need an implementation of this interface that tracks current state for each state group:

public class BaseStateManager<TState> :
    NotifyBase, IStateManager<TState>
    where TState : struct
{
    public event EventHandler<StateEventArgs<TState>> StateChanged;

    public TState CurrentState { get; private set; }

    public IDictionary<TState, IStateDefinition<TState>> States { get; set; }


    public async Task<bool> ChangeTo(TState newState, bool useTransitions = true)
    {
        var current = CurrentState;
        if (current.Equals(newState)) return true;

        CurrentState = newState;

        try
        {
            StateChanged?.Invoke(this, new StateEventArgs<TState>(newState, useTransitions));
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
            // Ignore any errors caused by the event being raised, as
            // the state change has still occurred
        }
        return true;
    }
}

We'll use this as the basis from which we can track states at an application, page and control level.







Pingbacks and trackbacks (1)+

Comments are closed