Xamarin.Forms ile Katmanlı Mimariye Giriş – 2

Merhaba

Serimizin 2. yazısı ile beraberiz. Bu yazımızda kullanacağımız servisleri base projemizde geliştireceğiz. İlk yapacağımız işlem ise Dialog kullanımları için Acr.UserDialogs nuget paketini projelerimize eklemek olacak.

Genel amacımız kullanacağımız her 3rd party plugini wrap etmek olacak. Çünkü projenin geliştirme sürecinde t anında kullandığımız bir plugin’i artık kullanmak istemeyebiliriz veya değiştirmek isteyebiliriz. Bu yüzden genel yaklaşımımız bu tarz pluginlerin kullanımını hep ara bir katman üzerinden yapmak olacak.

IDialogService

public interface IDialogService : IServiceBase
    {
        Task<string> ActionSheetAsync(string title, string cancel, string destructive, CancellationToken? cancelToken = null, params string[] buttons);
        Task AlertAsync(string message, string title = null, string okText = null, CancellationToken? cancelToken = null);
        Task AlertAsync(AlertConfig config, CancellationToken? cancelToken = null);
        Task<bool> ConfirmAsync(string message, string title = null, string okText = null, string cancelText = null, CancellationToken? cancelToken = null);
        Task<bool> ConfirmAsync(ConfirmConfig config, CancellationToken? cancelToken = null);
        Task<DatePromptResult> DatePromptAsync(string title = null, DateTime? selectedDate = null, CancellationToken? cancelToken = null);
        Task<DatePromptResult> DatePromptAsync(DatePromptConfig config, CancellationToken? cancelToken = null);
        void HideLoading();
        IProgressDialog Loading(string title = null, Action onCancel = null, string cancelText = null, bool show = true, MaskType? maskType = null);
        Task<LoginResult> LoginAsync(string title = null, string message = null, CancellationToken? cancelToken = null);
        Task<LoginResult> LoginAsync(LoginConfig config, CancellationToken? cancelToken = null);
        IProgressDialog Progress(ProgressDialogConfig config);
        IProgressDialog Progress(string title = null, Action onCancel = null, string cancelText = null, bool show = true, MaskType? maskType = null);
        Task<PromptResult> PromptAsync(string message, string title = null, string okText = null, string cancelText = null, string placeholder = "", InputType inputType = InputType.Default, CancellationToken? cancelToken = null);
        Task<PromptResult> PromptAsync(PromptConfig config, CancellationToken? cancelToken = null);
        void ShowLoading(string title = null, MaskType? maskType = null);
        Task<TimePromptResult> TimePromptAsync(TimePromptConfig config, CancellationToken? cancelToken = null);
        Task<TimePromptResult> TimePromptAsync(string title = null, TimeSpan? selectedTime = null, CancellationToken? cancelToken = null);
    }

DialogService

public class DialogService : IDialogService
    {
        public Task<string> ActionSheetAsync(string title, string cancel, string destructive, CancellationToken? cancelToken = null, params string[] buttons)
        {
            return UserDialogs.Instance.ActionSheetAsync(title, cancel, destructive, cancelToken, buttons);
        }

        public Task AlertAsync(string message, string title = null, string okText = null, CancellationToken? cancelToken = null)
        {
            return UserDialogs.Instance.AlertAsync(message, title, okText, cancelToken);
        }

        public Task AlertAsync(AlertConfig config, CancellationToken? cancelToken = null)
        {
            return UserDialogs.Instance.AlertAsync(config, cancelToken);
        }

        public Task<bool> ConfirmAsync(string message, string title = null, string okText = null, string cancelText = null, CancellationToken? cancelToken = null)
        {
            return UserDialogs.Instance.ConfirmAsync(message, title, okText, cancelText, cancelToken);
        }

        public Task<bool> ConfirmAsync(ConfirmConfig config, CancellationToken? cancelToken = null)
        {
            return UserDialogs.Instance.ConfirmAsync(config, cancelToken);
        }

        public Task<DatePromptResult> DatePromptAsync(string title = null, DateTime? selectedDate = null, CancellationToken? cancelToken = null)
        {
            return UserDialogs.Instance.DatePromptAsync(title, selectedDate, cancelToken);
        }

        public Task<DatePromptResult> DatePromptAsync(DatePromptConfig config, CancellationToken? cancelToken = null)
        {
            return UserDialogs.Instance.DatePromptAsync(config, cancelToken);
        }

        public void HideLoading()
        {
            UserDialogs.Instance.HideLoading();
        }

        public IProgressDialog Loading(string title = null, Action onCancel = null, string cancelText = null, bool show = true, MaskType? maskType = null)
        {
            return UserDialogs.Instance.Loading(title, onCancel, cancelText, show, maskType);
        }

        public Task<LoginResult> LoginAsync(string title = null, string message = null, CancellationToken? cancelToken = null)
        {
            return UserDialogs.Instance.LoginAsync(title, message, cancelToken);
        }

        public Task<LoginResult> LoginAsync(LoginConfig config, CancellationToken? cancelToken = null)
        {
            return UserDialogs.Instance.LoginAsync(config, cancelToken);
        }

        public IProgressDialog Progress(ProgressDialogConfig config)
        {
            return UserDialogs.Instance.Progress(config);
        }

        public IProgressDialog Progress(string title = null, Action onCancel = null, string cancelText = null, bool show = true, MaskType? maskType = null)
        {
            return UserDialogs.Instance.Progress(title, onCancel, cancelText, show, maskType);
        }

        public Task<PromptResult> PromptAsync(string message, string title = null, string okText = null, string cancelText = null, string placeholder = "", InputType inputType = InputType.Default, CancellationToken? cancelToken = null)
        {
            return UserDialogs.Instance.PromptAsync(message, title, okText, cancelText, placeholder, inputType, cancelToken);
        }

        public Task<PromptResult> PromptAsync(PromptConfig config, CancellationToken? cancelToken = null)
        {
            return UserDialogs.Instance.PromptAsync(config, cancelToken);
        }

        public void ShowLoading(string title = null, MaskType? maskType = null)
        {
            UserDialogs.Instance.ShowLoading(title, maskType);
        }

        public Task<TimePromptResult> TimePromptAsync(TimePromptConfig config, CancellationToken? cancelToken = null)
        {
            return UserDialogs.Instance.TimePromptAsync(config, cancelToken);
        }

        public Task<TimePromptResult> TimePromptAsync(string title = null, TimeSpan? selectedTime = null, CancellationToken? cancelToken = null)
        {
            return UserDialogs.Instance.TimePromptAsync(title, selectedTime, cancelToken);
        }
    }

Kullandığımız plugindeki methodları incelediğimizde, kullanımlarının hem bir class ile hem de direkt property ile olduğunu görüyoruz.

        public Task AlertAsync(string message, string title = null, string okText = null, CancellationToken? cancelToken = null)
        {
            return UserDialogs.Instance.AlertAsync(message, title, okText, cancelToken);
        }

        public Task AlertAsync(AlertConfig config, CancellationToken? cancelToken = null)
        {
            return UserDialogs.Instance.AlertAsync(config, cancelToken);
        }

Yukarıdaki örnekte de olduğu gibi methodların 2 farklı kullanımının olduğunu görüyoruz. (Method Overloading) İlk methoddaki kullanım üzerinden örneklendirme yapacağım. İkinci kullanım ile gidersek kullandığımız plugini tüm projelerimizde içeri sokmuş oluruz ve zaten bu istemediğimiz bir olay.

Dikkat ettiyseniz IDialogService interface’i başka bir interface’i miras almış. Bunun sebebi ise tüm servisleri tek bir interface üzerinden register etmek. Autofac ile bunu AsImplementedInterfaces methodu ile yapacağız.

ViewModelLocator

Projemizin ilerleyen adımlarında tüm navigation ve uygulama viewmodel mimarisi üzerinden gideceği için base locator sınıfı kullanacağız.

public static class ViewModelLocator
    {
        private static IContainer _container;
        public static void RegisterDependencies()
        {
            try
            {
                var builder = new ContainerBuilder();

                builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
                       .AssignableTo<IServiceBase>()
                       .AsImplementedInterfaces()
                       .SingleInstance();

                if (_container != null)
                {
                    _container.Dispose();
                }
                _container = builder.Build();
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.ToString());
            }
        }
    }

Yavaş yavaş navigation mimarisi üzerinde geliştirme yapacağımız için ihtiyacımız olan sayfa tiplerini de belirlememiz ve gerekiyorsa pluginlerini projelerimize eklememiz gerekiyor.

Popup sayfalar için ben genelde Rg.Plugins.Popup pluginini kullanıyorum.

Genel olarak page, view, viewmodels gibi sınıflar her proje için aynı path üzerinde olmayabilir. Herkesin yoğurt yemesi farklıdır 🙂 Bu yüzden bu gibi değişkenleri proje içerisinde kullanmamız için yardımcı bir sınıf oluşturacağız.

GlobalSetting

    public class GlobalSetting
    {
        private static readonly GlobalSetting _instance = new GlobalSetting();
        public static GlobalSetting Instance
        {
            get { return _instance; }
        }
        public string LoadingText { get { return "Loading.."; } }
        public string BaseEndpoint { get; set; }
        public bool UseNativeHttpHandler => false;
        public string ViewsPath { get; set; } = "Views";
        public string PagesPath { get; set; } = "Pages";
        public string ViewModelPath { get; set; } = "ViewModels";
    }

Buralar daha çok gelişecek o yüzden şimdilik bu kadar property yeterli 🙂

INavigationService

public interface INavigationService : IServiceBase
    {
        ViewModelBase PreviousPageViewModel { get; }
        Task InitializeAsync<TViewModel>() where TViewModel : ViewModelBase;
        Task NavigateToAsync<TViewModel>() where TViewModel : ViewModelBase;
        Task NavigateToAsync<TViewModel>(object parameter) where TViewModel : ViewModelBase;
        Task NavigateToModalAsync<TViewModel>() where TViewModel : ViewModelBase;
        Task NavigateToModalAsync<TViewModel>(object parameter) where TViewModel : ViewModelBase;
        Task NavigatePopAsync();
        Task NavigatePopModalAsync();
        Task NavigateToPopupAsync<TViewModel>() where TViewModel : ViewModelBase;
        Task NavigateToPopupAsync<TViewModel>(object parameter) where TViewModel : ViewModelBase;
        Task RemovePopupAsync();
        Task RemoveLastFromBackStackAsync();
        Task RemoveBackStackAsync(bool includeLastPage = false);
        Task SetMainPageAsync<TViewModel>(bool wrapInNavigationPage = true, object parameter = null) where TViewModel : ViewModelBase;
        void SetShellCurrentTabItem(int index);
    }

Tüm Navigation mimarisi TViewModel üzerinden ilerleyecek ve TViewModel ViewModelBase sınıfından miras almak zorunda.

NavigationService

    public class NavigationService : NavigationServiceHelper, INavigationService
    {
        private bool animated => Device.RuntimePlatform == Device.Android ? false : true;
        public ViewModelBase PreviousPageViewModel
        {
            get
            {
                var stack = Shell.Current.Navigation.NavigationStack;
                var viewModel = stack[stack.Count - 2].BindingContext;
                return viewModel as ViewModelBase;
            }
        }
        public Task InitializeAsync<TViewModel>() where TViewModel : ViewModelBase
        {
            return SetMainPageAsync<TViewModel>(true, null);
        }
        public Task NavigateToAsync<TViewModel>() where TViewModel : ViewModelBase
        {
            return InternalNavigateToAsync(typeof(TViewModel), null);
        }
        public Task NavigateToAsync<TViewModel>(object parameter) where TViewModel : ViewModelBase
        {
            return InternalNavigateToAsync(typeof(TViewModel), parameter);
        }
        public Task NavigateToModalAsync<TViewModel>() where TViewModel : ViewModelBase
        {
            return InternalNavigateToModalAsync(typeof(TViewModel), null);
        }
        public Task NavigateToModalAsync<TViewModel>(object parameter) where TViewModel : ViewModelBase
        {
            return InternalNavigateToModalAsync(typeof(TViewModel), parameter);
        }
        public async Task NavigatePopAsync()
        {
            await Shell.Current.Navigation.PopAsync(animated);
        }
        public async Task NavigatePopModalAsync()
        {
            if (Shell.Current.Navigation.ModalStack.Count > 0)
                await Shell.Current.Navigation.PopModalAsync();
        }
        public Task NavigateToPopupAsync<TViewModel>() where TViewModel : ViewModelBase
        {
            return InternalNavigateToPopupAsync(typeof(TViewModel), null);
        }
        public Task NavigateToPopupAsync<TViewModel>(object parameter) where TViewModel : ViewModelBase
        {
            return InternalNavigateToPopupAsync(typeof(TViewModel), parameter);
        }
        public Task RemovePopupAsync()
        {
            try
            {
                if (PopupNavigation.Instance.PopupStack.Count > 0)
                {
                    PopupNavigation.Instance.PopAsync(animated);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.ToString());
            }
            return Task.FromResult(true);
        }
        public Task RemoveLastFromBackStackAsync()
        {
            var stack = Shell.Current.Navigation.NavigationStack;
            Shell.Current.Navigation.RemovePage(stack[stack.Count - 2]);
            return Task.FromResult(true);
        }
        public Task RemoveBackStackAsync(bool includeLastPage = false)
        {
            var stack = Shell.Current.Navigation.NavigationStack;

            var count = includeLastPage ? 0 : 1;
            for (int i = 0; i < stack.Count - count; i++)
            {
                var page = stack[i];
                Shell.Current.Navigation.RemovePage(page);
            }

            return Task.FromResult(true);
        }
        public async Task SetMainPageAsync<TViewModel>(bool wrapInNavigationPage, object parameter) where TViewModel : ViewModelBase
        {
            Page page = CreatePage(typeof(TViewModel));
            Shell.SetNavBarIsVisible(page, wrapInNavigationPage);
            Application.Current.MainPage = page;
            await (page.BindingContext as ViewModelBase).InitializeAsync(parameter);
        }
        public void SetShellCurrentTabItem(int index)
        {
            if (Shell.Current.CurrentItem.Items.Count >= index)
                Shell.Current.CurrentItem.CurrentItem = Shell.Current.CurrentItem.Items[index];
        }
    }

NavigationServiceHelper

    public class NavigationServiceHelper
    {
        private bool animated => Device.RuntimePlatform == Device.Android ? false : true;
        protected async Task InternalNavigateToAsync(Type viewModelType, object parameter)
        {
            Page page = CreatePage(viewModelType);

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

            await (page.BindingContext as ViewModelBase).InitializeAsync(parameter);
        }
        protected async Task InternalNavigateToPopupAsync(Type viewModelType, object parameter)
        {
            PopupPage page = CreatePopupPage(viewModelType);
            await PopupNavigation.Instance.PushAsync(page);
            await (page.BindingContext as ViewModelBase).InitializeAsync(parameter);
        }
        protected async Task InternalNavigateToModalAsync(Type viewModelType, object parameter)
        {
            try
            {
                Page page = CreatePage(viewModelType);
                await Shell.Current.Navigation.PushModalAsync(page);
                await (page.BindingContext as ViewModelBase).InitializeAsync(parameter);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.ToString());
            }
        }
        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;
        }
        protected Type GetPopupPageTypeForViewModel(Type viewModelType)
        {
            var viewName = viewModelType.Namespace.Replace(GlobalSetting.Instance.ViewModelPath, GlobalSetting.Instance.ViewsPath);
            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;
        }
        protected Page CreatePage(Type viewModelType)
        {
            Type pageType = GetPageTypeForViewModel(viewModelType);
            if (pageType == null)
                throw new Exception($"Cannot locate page type for {viewModelType}");
            return Activator.CreateInstance(pageType) as Page;
        }
        protected PopupPage CreatePopupPage(Type viewModelType)
        {
            Type pageType = GetPopupPageTypeForViewModel(viewModelType);
            if (pageType == null)
                throw new Exception($"Cannot locate popup page type for {viewModelType}");
            return Activator.CreateInstance(pageType) as PopupPage;
        }
    }

Temel amacımızın ortak bir proje geliştirmek ve kullanacağımız tüm XF, plugin vb. kaynakların t anında gelişime açık değişime kapalı olmasını sağlamak ve bağımlılıkları da tek bir yerden yönetmek olduğu için XF Shell mimarisini de wrap ediyoruz.

Burada önemli olan kısım elimizden geldiğince XF’un navigation mimarisini ihtiyaçlarımız doğrultusunda genişletmek olacaktır. SetMainPageAsync methodunu ele alırsak eğer TViewModel üzerinden sayfamızı oluşturup, uygulamanın ana sayfası olarak set ediyoruz ve ViewModel base içerisindeki InitializeAsync methodunun yardımıyla, varsa paslamak istediğimiz bir object onu paslıyoruz. Uygulamamızın ilerleyen zamanlarında Navigation servisini daha çok kullanacağımız için şimdilik üzerinde fazla durmuyorum. Zaten kodları detaylı bir şekilde incelerseniz yaptığı olay tamamen XF’un Shell mimarisini ihtiyacımızı göre geliştirmek olduğunu göreceksiniz.

Daha önce de söylediğimiz gibi tüm mimari ViewModel üzerinden gideceği için base viewmodel sınıfımızı oluşturalım.

ViewModelBase

    public class ViewModelBase : ExtendedBindableObject
    {
        private bool _isBusy;
        public bool IsBusy
        {
            get
            {
                return _isBusy;
            }
            set
            {
                _isBusy = value;

                if (IsBusy)
                    DialogService.ShowLoading();
                else
                    DialogService.HideLoading();

                RaisePropertyChanged(() => IsBusy);
            }
        }
        protected readonly IDialogService DialogService;
        protected readonly INavigationService NavigationService;
        public ViewModelBase()
        {
            DialogService = ViewModelLocator.Resolve<IDialogService>();
            NavigationService = ViewModelLocator.Resolve<INavigationService>();
        }
        public virtual Task InitializeAsync(object navigationData)
        {
            return Task.FromResult(false);
        }
    }

Dialog ve Navigation servislerimiz hemen hemen tüm viewmodel sınıflarımızda kullanılacağı için, bu servisleri ViewModelBase sınıfı üzerinden açıyoruz.

InitializeAsync methodu burada çok önemli. Çünkü ViewModeller üzerinden navigation yaptığımızda data gönderiminde bize yardımcı olacak.

IsBusy propertysi tamamen kullanım kolaylığı olması açısından yazıldı. Sürekli DialogService üzerinden Loading methodunu kullanmak yerine böyle ufak yardımcı propertylerin olması her zaman faydalı olacaktır.

ExtendedBindableObject

    public abstract class ExtendedBindableObject : BindableObject
    {
        public void RaisePropertyChanged<T>(Expression<Func<T>> property)
        {
            var name = GetMemberInfo(property).Name;
            OnPropertyChanged(name);
        }

        private MemberInfo GetMemberInfo(Expression expression)
        {
            MemberExpression operand;
            LambdaExpression lambdaExpression = (LambdaExpression)expression;
            if (lambdaExpression.Body as UnaryExpression != null)
            {
                UnaryExpression body = (UnaryExpression)lambdaExpression.Body;
                operand = (MemberExpression)body.Operand;
            }
            else
            {
                operand = (MemberExpression)lambdaExpression.Body;
            }
            return operand.Member;
        }
    }

ExtendedBindableObject yardımcı sınıfı ile ViewModel içerisinde kullanacağımız propertyler için OnPropertyChanged işlemini yapmış oluyoruz. Her ViewModel için INotifyPropertyChanged interface’ini kullanmak zorunda kalmıyoruz.

ViewModelLocator sınıfımızda bazı değişiklikler yapmamız gerekiyor..

    public static class ViewModelLocator
    {
        private static IContainer _container;
        public static readonly BindableProperty AutoWireViewModelProperty =
            BindableProperty.CreateAttached("AutoWireViewModel", typeof(bool), typeof(ViewModelLocator),
                default(bool), propertyChanged: OnAutoWireViewModelChanged);
        public static bool GetAutoWireViewModel(BindableObject bindable)
        {
            return (bool)bindable.GetValue(AutoWireViewModelProperty);
        }
        public static void SetAutoWireViewModel(BindableObject bindable, bool value)
        {
            bindable.SetValue(AutoWireViewModelProperty, value);
        }
        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;
        }
        public static T Resolve<T>()
        {
            return _container.Resolve<T>();
        }
        /// <summary>
        /// Use GlobalSetting before Init
        /// </summary>
        /// <param name="assembly"></param>
        public static void Init<TViewModel>(Assembly assembly) where TViewModel : ViewModelBase
        {
            var builder = new ContainerBuilder();

            builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
               .Where(t => t.Namespace.Contains(GlobalSetting.Instance.ViewModelPath));
            builder.RegisterAssemblyTypes(assembly)
               .Where(t => t.Namespace.Contains(GlobalSetting.Instance.ViewModelPath));

            builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
                   .AssignableTo<IServiceBase>()
                   .AsImplementedInterfaces()
                   .SingleInstance();

            if (_container != null)
                _container.Dispose();

            _container = builder.Build();

            var navigationService = Resolve<INavigationService>();
            navigationService.InitializeAsync<TViewModel>();
        }
    }

Init methodu uygulama ilk ayağa kalkarken gerekli dependency injection işlemlerini yapacak ve Navigation servisini ilk açılacak sayfanın ViewModel sınıfı üzerinden çalıştıracak. Init methoduna Assembly parametresinin geçilmesinin amacı ViewModel sınıflarının olduğu pathteki tüm ViewModel sınıflarını register etmek.

OnAutoWireViewModelChanged methodu ise generic bir şekilde sayfa adı üzerinden viewmodel sınıfının adının çıkartılması ve container ile resolve edilen viewmodel instance’ının ilgili sayfaya bindingcontext olarak set edilmesini yapacak.

Örnek Kullanım

AppShellPage

<?xml version="1.0" encoding="utf-8" ?>
<Shell 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"
       xmlns:pages="clr-namespace:MyXF.Client.mobile.Pages"
       x:Class="MyXF.Client.mobile.Pages.AppShellPage">

    <ShellContent ContentTemplate="{DataTemplate pages:MainPage}"/>

</Shell>

AppShellPageViewModel

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

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}"/>
    </StackLayout>
</ContentPage>

MainPageViewModel

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

        public ICommand DialogCommand => new Command(() =>
        {
            DialogService.AlertAsync("Dialog Service", "Test", "Ok");
        });
    }

Burada dikkat etmemiz gereken nokta sayfalarımızın xml ns’lerine eklediklerimiz.

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

Kurduğumuz yapı gereği page, view, viewmodel gibi sınıflarımızın generic bir şekilde geliştirdiğimiz navigation mimarisi üzerinden ilerlemesi. Bu yüzden AutoWireViewModelProperty ile OnAutoWireViewModelChanged methodunu tetikleterek bunun olmasını sağlıyoruz.

        public App()
        {
            InitializeComponent();

            GlobalSetting.Instance.PagesPath = "Pages";
            GlobalSetting.Instance.ViewsPath = "Views";
            GlobalSetting.Instance.ViewModelPath = "ViewModels";
            ViewModelLocator.Init<AppShellPageViewModel>(Assembly.GetExecutingAssembly());
        }

Gerekli GlobalSetting ayarlarımızı yapıp Init yardımcı methodu ile uygulamamızı başlatıyoruz 🙂

Genel itibariyle yapımızı oluşturduk gibi. Bundan sonraki geliştirmelerimiz ana çatıyı geliştirmek olacak 🙂

Kaynak kodlar : https://github.com/ozaksuty/MyXF

Yiğit

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

Post A Reply