Úprava zpětné navigace v UWP aplikaci s MvvmCross

WinUI

7 years ago

Při vývoji mobilních aplikací můžeme často narazit na potřebu manipulace s historií navigace mezi jednotlivými obrazovkami. Protože framework****MvvmCross obsahuje mnoho abstrakcí nad cílovým operačním systémem, neobsahuje přímo zabudovaný mechanismus pro úpravu zpětné navigace. Jak můžeme tento požadavek v MvvmCross implementovat?

Motivace

Uvedeme si dva velmi časté scénáře, kdy je velmi vhodné upravit zpětnou navigaci aplikace. Pokud má aplikace uživatelskou část, která je skryta za přihlašovací obrazovkou, pravděpodobně chcete, aby se uživatel po po přihlášení nevrátil zpětnou navigací znovu na přihlšovací stránku (protože již přihlášen je), ale o jednu úroveň výše. Řešením je vyjmout ze zásobníku zpětné navigace poslední stránku poté, co se uživatel přihlásí.

Login flow

Přihlašovací flow

Druhým případem vybízejícím k manipulaci se zpětnou navigací je talčítko "domů", které uživateli umožní vrátit se po víceúrovňové navigaci zpět na hlavní hub vaší aplikace. Toho lze docílit buď smazáním všech stránek v historii až na poslední nebo přechodem na hlavní stránku a následným smazáním celého zásobníku zpětné navigace.

Jumping home

Skok "domů"

101 zásobníku zpětné navigace v UWP

Univerzální platforma Windows používá ovladací prvek Frame (rámec) pro práci se navigaci mezi stránkami aplikace. Rámec slouží jako kontejner, který zobrazuje stránky a udržuje stav navigace. Pro nás je nejdůležitější vlastnost BackStack  jež je seznam implementující IList a obsahující instance PageStackEntry. Protože BackStack je IList , můžeme na něm provádět veškeré klasické "listové" operace. Můžeme si tedy vytvořit dvě jednoduché metody - první bude odebírat poslední položku zásobníku zpětné navigace a druhá jej kompletně promaže.

private void HandlePopBackstackHint()
{
    if (_frame.CanGoBack)
    {
        _frame.BackStack.RemoveAt(_frame.BackStackDepth - 1);
    }
}

private void HandleClearBackstackHint()
{
    _frame.BackStack.Clear();
}

Pro zjištění velikosti zásobníku jsme použili vlastnost BackStackDepth. Ekvivalentně lze použít i vlastnost BackStack.Count . Pro zvídavé - kromě zásobníku zpětné navigace udržuje Frame také zásobník dopředné navigace, díky čemuž je možné vyvořit v aplikaci navigační flow velmi podobné klasickému chování internetových prohlížečů s šipkami zpět a vpřed.

Integrace do MvvmCross

Logiku pro manipulaci se zpětným zásobníkem je nutné vyvolat ve ViewModelu. Bohužel to není přímo možné, protože manipulace se zásobníkem využívá platformně-specifický kód a ten nelze v Portable Class Library přímo používat. Přestože bychom mohli vytvořit službu, která toto bude umožňovat, MvvmCross nabízí čistější řešení ve formě presentation hintů. Presentation hinty jsou třídy, které dědí od MvxPresentationHint a reprezentují úmysl změnit způsob prezentace uživatelského rozhraní. Již jste se pravděpodobně nepřímo setkali se zabudovaným presentation hintem MvxClosePresentationHint. Ten je vytvořen když zavoláte ve ViewModelu metodu Close. V reakci na tento hint dojde ke zpětné navigaci.

Vytvoření presentation hintů

Hinty by měly být definovány v Core PCL projektu a konvence doporučuje je uložit do složky/namespace PresentationHints.

namespace MvvmCrossBackstack.Core.PresentationHints
{
    public class ClearBackstackHint : MvxPresentationHint
    {
    }

    public class PopBackstackHint : MvxPresentationHint
    {
    }
}

Oba naše hinty jsou pouze prázdné třídy, protože kromě jejich typu nepotřebujeme žádné doplňující informace. Nic vám však nebrání ve vytvoření komplexnějších hintů se speciálními vlastnostmi.

Vytvoření požadavku na změnu prezentace z ViewModelu

Třída MvxNavigatingObject (od které MvxViewModel dědí) má metodu ChangePresentation která má jeden parametr typu MvxPresentationHint. Lze ji použít velmi snadno:

private void GoToUserArea()
{
    //navigate to user area
    ShowViewModel<UserDashboardViewModel>();
    //pop the back stack to remove the login page from back stack
    ChangePresentation(new PopBackstackHint());
}

private void GoHome()
{
    //navigate to main page
    ShowViewModel<MainViewModel>();
    //clear the whole back stack            
    ChangePresentation(new ClearBackstackHint());
}

Zpracování požadavku na změnu prezentace

Presentation hint je vyvolán v PCL, ale musí být zpracován separátně pro každou cílovou platformu přímo v projektu dané aplikace. MvvmCross implementuje koncept view presenterů. Tyto implementují rozhraní IMvxViewPresenter které vypadá následovně:

public interface IMvxViewPresenter
{
    void AddPresentationHintHandler<THint>(Func<THint, bool> action)
        where THint : MvxPresentationHint;
    void ChangePresentation(MvxPresentationHint hint);
    void Show(MvxViewModelRequest request);
}

Jak si můžete všimnout, view presentery již mají zabudovanou podporu presentation hintů. Každá platforma má ve frameworku implementováný výchozí view presenter, As you can see, the view presenters already have a built-in support for presentation hints. Each platform implements a default view presenter. Pro Windows máme MvxWindowsViewPresenter, který implementuje výchozí chování navigace pomocí Frame. Můžete implementovat vlastní složitější view presentery, pokud to vaše aplikace vyžaduje. V našem případě vystačí už ten výchozí, protože můžeme přímo použít metodu AddPresentationHintHandler pro registraci handlerů našich požadavků. Nejprve vytvořme třídu, která v konstruktoru očekává IMvxWindowsFrame  (což je obal okolo systémového prvku Frame) a obsahuje handlery jednotlivých hintů:

/// <summary>
/// Handles back stack presentation hints
/// </summary>
public class BackStackHintHandler
{
    private readonly Frame _frame;

    public BackStackHintHandler(IMvxWindowsFrame rootFrame)
    {
        _frame = (Frame)rootFrame.UnderlyingControl;
    }

    public bool HandleClearBackstackHint(
        ClearBackstackHint clearBackstackHint)
    {
        _frame.BackStack.Clear();
        UpdateBackButtonVisibility();
        return true;
    }

    public bool HandlePopBackstackHint(
        PopBackstackHint clearBackstackHint)
    {
        if (_frame.CanGoBack)
        {
            _frame.BackStack.RemoveAt(_frame.BackStackDepth - 1);
            UpdateBackButtonVisibility();
            return true;
        }
        return false;
    }

    private void UpdateBackButtonVisibility()
    {
        SystemNavigationManager.
            GetForCurrentView().AppViewBackButtonVisibility =
                _frame.CanGoBack ? 
                    AppViewBackButtonVisibility.Visible :
                    AppViewBackButtonVisibility.Collapsed;
    }
}

Kromě manipulace se zpětnou navigací aktualizaujeme viditelnost tlačítka zpět v titulku okna a vracíme true nebo false pokud byl hint zpracován resp. nebyl zpracován. To je užitečné, pokud chceme mít zaregistrovaných více handlerů pro jeden konkrétní presentation hint. Nakonec handlery přímo zaregistrujeme. Toho lze docílit overridem virtuální metody CreateViewPresenter ve tříde Setup v UWP projektu.

protected override IMvxWindowsViewPresenter CreateViewPresenter(
    IMvxWindowsFrame rootFrame )
{
    var viewPresenter = base.CreateViewPresenter(rootFrame);
    var backStackHandler = new BackStackHintHandler(rootFrame);
    viewPresenter.AddPresentationHintHandler<ClearBackstackHint>
       (backStackHandler.HandleClearBackstackHint);
    viewPresenter.AddPresentationHintHandler<PopBackstackHint>
       (backStackHandler.HandlePopBackstackHint);
    return viewPresenter;
}

Necháme framework vytvořit výchozí view presenter a přidáme mu dva presentation hint handlery. Tímto způsobem budou zaregistrovány a spuštěny vždy, když ViewModel indikuje požadavek na změnu prezentace.

Zdrojový kód

Ukázkový kód k tomuto příspěvku je dostupný na mém GitHubu. Obsahuje i spustitelnou ukázkovou aplikaci.

Shrnutí

Ukázali jsme si, jak můžeme manipulovat se zpětným zásobníkem v UWP aplikací s MvvmCross. Abychom to dokázali, využili jsme presentation hinty, které byly následně zpracovány view presenterem naší aplikace.