Xamarin.Forms ile Katmanlı Mimariye Giriş – 3

Merhaba

3. yazımızda mimarimizdeki VM navigation konusunu detaylandıracağız. İlk önce Navigation servisimizi biraz detaylandıralım ve hangi method/property ne iş yapıyor anlamaya çalışalım.

NavigationService

InitializeAsync methodunu zaten önceki yazımızda açıklamıştık fakat detaylandırmak gerekirse; ViewModelLocator sınıfı içerisindeki Init methodunu kullanarak uygulamamızı çalıştırdığımızda, ViewModelBase sınıfından miras almış bir sınıfı (VM’i) TViewModel parametresi olarak vermemiz gerekiyor. Çünkü NavigationService sınıfı içerisinde Shell mimarisini ihtiyaçlarımızı göre genişlettik ve tüm mimariyi ViewModel üzerine kurduk.

ViewModelLocator.Init<AppShellPageViewModel>(Assembly.GetExecutingAssembly());

Sırasıyla SetMainPage > CreatePage methodlarını izleyerek GetPageTypeForViewModel methodu ile VM type’ının namespace’ini kullanarak VM’in hangi page için olduğunu bulup, ufak bir replace işlemi ile sayfaya ulaşıyoruz. (viewName propertysini inceleyebilirsiniz.)

Geriye kalan işlem ise Activator sınıfı yardımıyla elimizdeki pageType ile sayfanın instance’ını almak oluyor.

Activator.CreateInstance(pageType) as Page;

Sırdaki işlem ise XF Shell mimarisinin direkt kullanıyoruz.

Shell.SetNavBarIsVisible(page, wrapInNavigationPage);
Application.Current.MainPage = page;

ViewModel kısmı ise tam da burada devreye giriyor. Çünkü VM ve page elimde. Tek yapmam gereken elimdeki sayfaya VM instance’ını BindingContext propertysi ile set etmek.

await (page.BindingContext as ViewModelBase).InitializeAsync(parameter);

NavigateToAsync

En önemli noktayı kesinlikle atlamamamız gerekiyor. Örneklendirmek gerekise; ListPage diye bir sayfa açıyorsam ViewModel adı ListPageViewModel olmak zorunda! Çünkü OnAutoWireViewModelChanged methodunda ViewModel ve Page instance işlemleri, GetPageTypeForViewModel methodun’da da göreceğimiz gibi path üzerinden replace edilip Reflection işlemi ile devam ediyor.

        protected Type GetPageTypeForViewModel(Type viewModelType)
        {
            var viewName = viewModelType.Namespace.Replace(GlobalSetting.Instance.ViewModelPath, GlobalSetting.Instance.PagesPath);
            viewName += $".{viewModelType.Name.Replace("ViewModel", string.Empty)}";
            var viewModelAssemblyName = viewModelType.GetTypeInfo().Assembly.FullName;
            var viewAssemblyName = string.Format(CultureInfo.InvariantCulture, "{0}, {1}", viewName, viewModelAssemblyName);
            var viewType = Type.GetType(viewAssemblyName);
            return viewType;
        }

2. önemli nokta ise yeni bir sayfa açtığımızda, açtığımız sayfayı geliştirdiğimiz mimari üzerinden çalıştırmak istiyorsak bazı xml ns eklemeleri yapmamız gerekiyor.

xmlns:viewModelBase="clr-namespace:MyXF.Client.mobilebase.ViewModels.Base;assembly=MyXF.Client.mobilebase"
viewModelBase:ViewModelLocator.AutoWireViewModel="true"

Amacımız ViewModelLocator sınıfı içerisindeki AutoWireViewModel methodunu tetikletip, viewName üzerinden replace ile ViewModel’in pathini yakalayıp, reflection ile instance’ını aldığımız VM’i page’in BindingContext’ine set etmek.

private static void OnAutoWireViewModelChanged(BindableObject bindable, object oldValue, object newValue)
        {
            if (!(bindable is Element view))
                return;

            var viewType = view.GetType();
            string viewName = "";
            if (view is PopupPage)
                viewName = viewType.FullName.Replace($".{GlobalSetting.Instance.ViewsPath}.",
                    $".{GlobalSetting.Instance.ViewModelPath}.");
            else if (view is Page)
                viewName = viewType.FullName.Replace($".{GlobalSetting.Instance.PagesPath}.",
                    $".{GlobalSetting.Instance.ViewModelPath}.");

            var viewAssemblyName = viewType.GetTypeInfo().Assembly.FullName;
            var viewModelName = string.Format(CultureInfo.InvariantCulture,
                "{0}ViewModel, {1}", viewName, viewAssemblyName);

            var viewModelType = Type.GetType(viewModelName);
            if (viewModelType == null)
                return;
            var viewModel = _container.Resolve(viewModelType);
            view.BindingContext = viewModel;
        }

MainPage

<?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:viewModelBase="clr-namespace:MyXF.Client.mobilebase.ViewModels.Base;assembly=MyXF.Client.mobilebase"
             viewModelBase:ViewModelLocator.AutoWireViewModel="true"
             x:Class="MyXF.Client.mobile.Pages.MainPage">
    
    <StackLayout>
        <Button Text="Dialog Service Test"
                Command="{Binding DialogCommand}"/>
        <Button Text="Go To ListPage"
                Command="{Binding GoToListPageCommand}"/>
    </StackLayout>
</ContentPage>

MainPage içerisine yeni bir button ekliyoruz.

MainPageViewModel

        public ICommand GoToListPageCommand => new Command(() =>
        {
            NavigationService.NavigateToAsync<ListPageViewModel>("Yiğit Özaksüt");
        });

MainPageViewModel içerisine de ilgili Command’ı yazıyoruz.
Parametreli ve parametresiz olarak navigation imkanı sunan NavigateToAsync methodunu kullanarak kolay bir şekilde

ListPage

<?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:viewModelBase="clr-namespace:MyXF.Client.mobilebase.ViewModels.Base;assembly=MyXF.Client.mobilebase"
             viewModelBase:ViewModelLocator.AutoWireViewModel="true"
             x:Class="MyXF.Client.mobile.Pages.ListPage">
    <ContentPage.Content>
        <StackLayout>
            <Label Text="Welcome to Xamarin.Forms!"
                VerticalOptions="CenterAndExpand" 
                HorizontalOptions="CenterAndExpand" />
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

ListPageViewModel

    public class ListPageViewModel : ViewModelBase
    {
        public override Task InitializeAsync(object navigationData)
        {
            return base.InitializeAsync(navigationData);
        }
    }

Yazımızın başında da bahsettiğimiz CreatePage yardımcı methodu ile olaylar aynı şekilde devam ediyor.

await Shell.Current.Navigation.PushAsync(page, animated);

Shell üzerinden navigation yapılıyor.

await (page.BindingContext as ViewModelBase).InitializeAsync(parameter);

ViewModelBase içerisindeki InitializeAsync methodu tetikleniyor ve parametre geçtiysek Navigate ettiğimiz ViewModel içerisindeki InitializeAsync methodu tetikleniyor.

Yapıyı da örneklendirdiğimize göre artık binding ve webservis işlemlerine başlayabiliriz. PopupPage navigation üzerinde durmayacağım zaten serimizin diğer yazılarında ihtiyacımız olacak ve kullanacağız. Yine aynı mantıkla ilerleyeceğimiz için ayrı bir örneklendirme yapmaya gerek duymadım. NavigatePopAsync gibi methodları zaten biliyoruz 🙂

Sorularınız olursa lütfen sormaktan çekinmeyin.

Yiğit

Xamarin Developer, Consultant & Architect. Community Leader and Director of Xamarin Türkiye

Post A Reply