Навигация и DI - PullRequest
       8

Навигация и DI

0 голосов
/ 18 мая 2018

Я пытаюсь создать стандартный код для использования в моих приложениях xamarin.forms.То, что я хочу сделать, - это иметь способ навигации между моделями представления и вей, чтобы правильно реализовать внедрение зависимостей.Что я сейчас делаю для навигации:

await Navigation.PushAsync(new SecondPageView());

А для DI:

 var test = DependencyService.Get<ITestService>();
 WelcomeMessage = test.GetSystemWelcome();

Я знаю, что правильный способ реализации Di - это создание интерфейса и переход от этого шага, нопроблема в том, что когда я пытаюсь, мне не удается создать хорошую навигационную систему (например, зарегистрировать представление и модель представления в отдельном файле).

У кого-нибудь есть пример, на который я могу посмотреть?Или, может быть, какие-то указания, чтобы продолжить?

PD: Я пытаюсь избегать таких фреймворков, как MvvMcross.

Заранее спасибо!

1 Ответ

0 голосов
/ 18 мая 2018

(я постараюсь максимально упростить все примеры кода).1. Прежде всего нам нужно место, где мы могли бы зарегистрировать все наши объекты и опционально определить их время жизни.Для этого мы можем использовать контейнер IOC, вы можете выбрать один самостоятельно.В этом примере я буду использовать Autofac (это один из самых быстрых доступных).Мы можем сохранить ссылку на него в App, чтобы он был доступен глобально (не очень хорошая идея, но необходимая для упрощения):

public class DependencyResolver
{
    static IContainer container;

    public DependencyResolver(params Module[] modules)
    {
        var builder = new ContainerBuilder();

        if (modules != null)
            foreach (var module in modules)
                builder.RegisterModule(module);

        container = builder.Build();
    }

    public T Resolve<T>() => container.Resolve<T>();
    public object Resolve(Type type) => container.Resolve(type);
}

public partial class App : Application
{
    public DependencyResolver DependencyResolver { get; }

    // Pass here platform specific dependencies
    public App(Module platformIocModule)
    {
        InitializeComponent();
        DependencyResolver = new DependencyResolver(platformIocModule, new IocModule());
        MainPage = new WelcomeView();
    }

    /* The rest of the code ... */
}

2. Нам понадобится объект, отвечающий за получениеPage (просмотр) для определенного ViewModel и наоборот.Второй случай может быть полезен в случае установки корневой / главной страницы приложения.Для этого мы должны договориться о простом соглашении, что все ViewModels должны быть в каталоге ViewModels, а Pages (Views) - в каталоге Views.Другими словами, ViewModels должен находиться в [MyApp].ViewModels пространстве имен и Pages (Views) в [MyApp].Views пространстве имен.В дополнение к этому мы должны согласиться, что WelcomeView (Page) должен иметь WelcomeViewModel и т. Д. Вот пример кода маппера:

public class TypeMapperService
{
    public Type MapViewModelToView(Type viewModelType)
    {
        var viewName = viewModelType.FullName.Replace("Model", string.Empty);
        var viewAssemblyName = GetTypeAssemblyName(viewModelType);
        var viewTypeName = GenerateTypeName("{0}, {1}", viewName, viewAssemblyName);
        return Type.GetType(viewTypeName);
    }

    public Type MapViewToViewModel(Type viewType)
    {
        var viewModelName = viewType.FullName.Replace(".Views.", ".ViewModels.");
        var viewModelAssemblyName = GetTypeAssemblyName(viewType);
        var viewTypeModelName = GenerateTypeName("{0}Model, {1}", viewModelName, viewModelAssemblyName);
        return Type.GetType(viewTypeModelName);
    }

    string GetTypeAssemblyName(Type type) => type.GetTypeInfo().Assembly.FullName;
    string GenerateTypeName(string format, string typeName, string assemblyName) =>
        string.Format(CultureInfo.InvariantCulture, format, typeName, assemblyName);
}

3. Для случая установки рутастранице нам понадобится вид ViewModelLocator, который установит BindingContext автоматически:

public static class ViewModelLocator
{
    public static readonly BindableProperty AutoWireViewModelProperty =
        BindableProperty.CreateAttached("AutoWireViewModel", typeof(bool), typeof(ViewModelLocator), default(bool), propertyChanged: OnAutoWireViewModelChanged);

    public static bool GetAutoWireViewModel(BindableObject bindable) =>
        (bool)bindable.GetValue(AutoWireViewModelProperty);

    public static void SetAutoWireViewModel(BindableObject bindable, bool value) =>
        bindable.SetValue(AutoWireViewModelProperty, value);

    static ITypeMapperService mapper = (Application.Current as App).DependencyResolver.Resolve<ITypeMapperService>();

    static void OnAutoWireViewModelChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var view = bindable as Element;
        var viewType = view.GetType();
        var viewModelType = mapper.MapViewToViewModel(viewType);
        var viewModel =  (Application.Current as App).DependencyResolver.Resolve(viewModelType);
        view.BindingContext = viewModel;
    }
}

// Usage example
<?xml version="1.0" encoding="utf-8"?>
<ContentPage
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:viewmodels="clr-namespace:MyApp.ViewModel"
    viewmodels:ViewModelLocator.AutoWireViewModel="true"
    x:Class="MyApp.Views.MyPage">
</ContentPage>

4. Наконец нам понадобится NavigationService, который будет поддерживать ViewModel First Navigation подход:

public class NavigationService
{
    TypeMapperService mapperService { get; }

    public NavigationService(TypeMapperService mapperService)
    {
        this.mapperService = mapperService;
    }

    protected Page CreatePage(Type viewModelType)
    {
        Type pageType = mapperService.MapViewModelToView(viewModelType);
        if (pageType == null)
        {
            throw new Exception($"Cannot locate page type for {viewModelType}");
        }

        return Activator.CreateInstance(pageType) as Page;
    }

    protected Page GetCurrentPage()
    {
        var mainPage = Application.Current.MainPage;

        if (mainPage is MasterDetailPage)
        {
            return ((MasterDetailPage)mainPage).Detail;
        }

        // TabbedPage : MultiPage<Page>
        // CarouselPage : MultiPage<ContentPage>
        if (mainPage is TabbedPage || mainPage is CarouselPage)
        {
            return ((MultiPage<Page>)mainPage).CurrentPage;
        }

        return mainPage;
    }

    public Task PushAsync(Page page, bool animated = true)
    {
        var navigationPage = Application.Current.MainPage as NavigationPage;
        return navigationPage.PushAsync(page, animated);
    }

    public Task PopAsync(bool animated = true)
    {
        var mainPage = Application.Current.MainPage as NavigationPage;
        return mainPage.Navigation.PopAsync(animated);
    }

    public Task PushModalAsync<TViewModel>(object parameter = null, bool animated = true) where TViewModel : BaseViewModel =>
        InternalPushModalAsync(typeof(TViewModel), animated, parameter);

    public Task PopModalAsync(bool animated = true)
    {
        var mainPage = GetCurrentPage();
        if (mainPage != null)
            return mainPage.Navigation.PopModalAsync(animated);

        throw new Exception("Current page is null.");
    }

    async Task InternalPushModalAsync(Type viewModelType, bool animated, object parameter)
    {
        var page = CreatePage(viewModelType);
        var currentNavigationPage = GetCurrentPage();

        if (currentNavigationPage != null)
        {
            await currentNavigationPage.Navigation.PushModalAsync(page, animated);
        }
        else
        {
            throw new Exception("Current page is null.");
        }

        await (page.BindingContext as BaseViewModel).InitializeAsync(parameter);
    }
}

Как вы можете видеть, существует BaseViewModel - абстрактный базовый класс для всех ViewModels, где вы можете определить такие методы, как InitializeAsync, которые будут выполняться сразу после навигации.И вот пример навигации:

public class WelcomeViewModel : BaseViewModel
{
    public ICommand NewGameCmd { get; }
    public ICommand TopScoreCmd { get; }
    public ICommand AboutCmd { get; }

    public WelcomeViewModel(INavigationService navigation) : base(navigation)
    {
        NewGameCmd = new Command(async () => await Navigation.PushModalAsync<GameViewModel>());
        TopScoreCmd = new Command(async () => await navigation.PushModalAsync<TopScoreViewModel>());
        AboutCmd = new Command(async () => await navigation.PushModalAsync<AboutViewModel>());
    }
}

Как вы понимаете, этот подход сложнее, сложнее в отладке и может привести к путанице.Однако есть много преимуществ, и вам не нужно реализовывать это самостоятельно, так как большинство сред MVVM поддерживают его «из коробки».Пример кода, который демонстрируется здесь, доступен на github .Есть много хороших статей о ViewModel First Navigation подходе, и есть бесплатные Шаблоны корпоративных приложений, использующие электронную книгу Xamarin.Forms , которая подробно объясняет эту и многие другие интересные темы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...