Xamarin.Forms ile Katmanlı Mimariye Giriş – 1

Merhabalar,

Uzun zamandır Xamarin ile ilgili paylaşımlarda bulunuyorum ve bu paylaşımlarım çoğu zaman küçük haplar şeklinde oldu. (Açık Akademi eğitim serilerini saymazsak 🙂 ) Bu sefer ise yeni bir XF uygulaması geliştirmeye başladığımda “neyi nasıl yapmamız gerekiyor?” sorusuna en doğru cevapları doğru mimari yaklaşımlarla vermeye çalışacağım. Bir blog-post serisi şeklinde ilerleyeceğim ve ihtiyaçlarımızı çıkartıp, ilmek ilmek dokuyacağız 🙂

Temel İhtiyaçların Listelenmesi

  1. Web Servisler ile haberleşme yapısının kurulması
  2. Ortak kullanılacak mobil mimari servis yapılarının kurulması (DialogService, NavigationService gibi..)
  3. MVVM yapısının kurulması
  4. Behaviors yapısının kurulması ve ihtiyaç duyulan behavior sınıflarının yazılması
  5. Converters yapısının kurulması ve ihtiyaç duyulan converter sınıflarının yazılması
  6. Custom geliştirilecek olan (CustomRenderer) kontrollerin çıkartılması
  7. Platform spesifik ihtiyaçların (DependencyServices) çıkartılması
  8. Temel Helper ihtiyaçlarının çıkartılması

Artık temel ihtiyaçlarımızın neler olduğunu biliyoruz ve bu ihtiyaçlar doğrultusunda proje yapımızı oluşturacağız.

VS 2019 üzerinde boş bir Solution açıp resimdeki gibi bir proje oluşturdum.

Buradaki derdimiz her zaman, geliştirdiğim bir özelliği solution içerisine eklediğim başka XF projeleriyle de zahmetsiz bir şekilde paylaşabilmek olmalı. Bu yüzden aşağıdaki gibi bir .NETStandart Librar projesi ekliyorum. Bu proje mobile clientlar içerisinde tüketeceğimiz base projemiz olacak.

Genel olarak Behaviors, Converters, CustomControls, DependencyServices, Helper, Base Services ve ViewModel hizmetlerini oluşturacağım tüm projelerde kullanacağım. Temel olarak düşündüğümüzde bir navigation mimarisi kurduğumda her projeye ayrı ayrı eklemek doğru bir yaklaşım olmayacaktır. Bu gibi durumları tek bir yerden yönetmek her zaman geliştirme aşamasında bizleri birçok açıdan rahatlatacaktır. Bakım, yenilik gibi..

  • Behavior : Projelerimizde MVVM yapısı ile gideceğimiz için Command özelliği olmayan kontrollerin, kullanacağımız eventlerini commandlara convert etmek için kullanacağımız behavior sınıfını yazacağız. Projemiz geliştikçe ve ihtiyaçlarımız arttıkça burayı genişleteceğiz.
  • Converters : XF geliştiren arkadaşların en çok başvurduğu interface IValueConverter olabilir 🙂 proje ihtiyaçlarımız doğrultusunda gerekli converter sınıflarını yazacağız.
  • CustomControls : Render yapacağımız kontrolleri base özellikleriyle yazacağız.
  • DependencyServices : İhtiyacımız olan platform spesifik işlemlerin interface ve methodlarını burada yazacağız.
  • Helper : Farklı sorunlara hizmet veren helper sınıf/method larımız olacak.
  • Services : Hemen hemen her projede ve sayfada kullanacağımız dialog, navigation gibi temel servisleri burada yazacağız.
  • ViewModels : Projemizin MVVM mimarisini burada yazacağız.

Genel olarak yukarıdaki listemize baktığımızda tüm projeler için genel bir yapı oluşturacağız. Proje özelinde olacak işlemler için de projelerimizin içerisinde geliştirmeler yapacağız.

MyXF.Client.mobilebase projemize Xamarin.Forms yüklemeyi unutmayalım 🙂

Behavior

Yukarıdaki listemizde de yaptığımız tanıma uygun olarak istediğimiz eventlerin command karşılığı olmadığı yerlerde eventleri commandlara convert edeceğiz.

    public class BindableBehavior<T> : Behavior<T> where T : BindableObject
    {
        public T AssociatedObject { get; private set; }

        protected override void OnAttachedTo(T visualElement)
        {
            base.OnAttachedTo(visualElement);

            AssociatedObject = visualElement;

            if (visualElement.BindingContext != null)
                BindingContext = visualElement.BindingContext;

            visualElement.BindingContextChanged += OnBindingContextChanged;
        }

        private void OnBindingContextChanged(object sender, EventArgs e)
        {
            OnBindingContextChanged();
        }

        protected override void OnDetachingFrom(T view)
        {
            view.BindingContextChanged -= OnBindingContextChanged;
        }

        protected override void OnBindingContextChanged()
        {
            base.OnBindingContextChanged();
            BindingContext = AssociatedObject.BindingContext;
        }
    }

BindableObject sınıfından miras alarak yazacağımız EventToCommandBehavior sınıfına yardımcı bir sınıf yaratmış olacağız. Generic BindableBehavior sınıfımızın temel amacı; Gelen T objesinin BindingContextChanged eventi yönetimi.

public class EventToCommandBehavior : BindableBehavior<View>
    {
        public static BindableProperty EventNameProperty =
            BindableProperty.CreateAttached("EventName", typeof(string), typeof(EventToCommandBehavior), null,
                BindingMode.OneWay);

        public static BindableProperty CommandProperty =
            BindableProperty.CreateAttached("Command", typeof(ICommand), typeof(EventToCommandBehavior), null,
                BindingMode.OneWay);

        public static BindableProperty CommandParameterProperty =
            BindableProperty.CreateAttached("CommandParameter", typeof(object), typeof(EventToCommandBehavior), null,
                BindingMode.OneWay);

        public static BindableProperty EventArgsConverterProperty =
            BindableProperty.CreateAttached("EventArgsConverter", typeof(IValueConverter), typeof(EventToCommandBehavior), null,
                BindingMode.OneWay);

        public static BindableProperty EventArgsConverterParameterProperty =
            BindableProperty.CreateAttached("EventArgsConverterParameter", typeof(object), typeof(EventToCommandBehavior), null,
                BindingMode.OneWay);

        protected Delegate _handler;
        private EventInfo _eventInfo;

        public string EventName
        {
            get { return (string)GetValue(EventNameProperty); }
            set { SetValue(EventNameProperty, value); }
        }

        public ICommand Command
        {
            get { return (ICommand)GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }

        public object CommandParameter
        {
            get { return GetValue(CommandParameterProperty); }
            set { SetValue(CommandParameterProperty, value); }
        }

        public IValueConverter EventArgsConverter
        {
            get { return (IValueConverter)GetValue(EventArgsConverterProperty); }
            set { SetValue(EventArgsConverterProperty, value); }
        }

        public object EventArgsConverterParameter
        {
            get { return GetValue(EventArgsConverterParameterProperty); }
            set { SetValue(EventArgsConverterParameterProperty, value); }
        }

        protected override void OnAttachedTo(View visualElement)
        {
            base.OnAttachedTo(visualElement);

            var events = AssociatedObject.GetType().GetRuntimeEvents().ToArray();
            if (events.Any())
            {
                _eventInfo = events.FirstOrDefault(e => e.Name == EventName);
                if (_eventInfo == null)
                    throw new ArgumentException(String.Format("EventToCommand: Can't find any event named '{0}' on attached type", EventName));

                AddEventHandler(_eventInfo, AssociatedObject, OnFired);
            }
        }

        protected override void OnDetachingFrom(View view)
        {
            if (_handler != null)
                _eventInfo.RemoveEventHandler(AssociatedObject, _handler);

            base.OnDetachingFrom(view);
        }

        private void AddEventHandler(EventInfo eventInfo, object item, Action<object, EventArgs> action)
        {
            var eventParameters = eventInfo.EventHandlerType
                .GetRuntimeMethods().First(m => m.Name == "Invoke")
                .GetParameters()
                .Select(p => Expression.Parameter(p.ParameterType))
                .ToArray();

            var actionInvoke = action.GetType()
                .GetRuntimeMethods().First(m => m.Name == "Invoke");

            _handler = Expression.Lambda(
                eventInfo.EventHandlerType,
                Expression.Call(Expression.Constant(action), actionInvoke, eventParameters[0], eventParameters[1]),
                eventParameters
            )
            .Compile();

            eventInfo.AddEventHandler(item, _handler);
        }

        private void OnFired(object sender, EventArgs eventArgs)
        {
            if (Command == null)
                return;

            var parameter = CommandParameter;

            if (eventArgs != null && eventArgs != EventArgs.Empty)
            {
                parameter = eventArgs;

                if (EventArgsConverter != null)
                {
                    parameter = EventArgsConverter.Convert(eventArgs, typeof(object), EventArgsConverterParameter,
                        CultureInfo.CurrentUICulture);
                }
            }

            if (Command.CanExecute(parameter))
            {
                Command.Execute(parameter);
            }
        }
    }

EventToCommandBehavior sınıfımızın amacı ise tamamen ilgili eventin commandlara attach edilmesidir.

Tabi olay sadece bu iki sınıf ile bitmiyor. Bu sınıfları kullanacak Converter sınıflarını yazmamız gerekiyor.

Converters

    public class TextChangedEventArgsConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (!(value is TextChangedEventArgs eventArgs))
                throw new ArgumentException("Expected TextChangedEventArgs as value", "value");

            return eventArgs.NewTextValue;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return null;
        }
    }

Convert methodunun aldığı value objesine eventargs’ı gönderiyoruz.

Kullanım örneği;

xmlns:behaviors="clr-namespace:MyXF.Client.mobilebase.Behaviors;assembly=MyXF.Client.mobilebase"
             xmlns:converters="clr-namespace:MyXF.Client.mobilebase.Converters.EventToCommandConverters;assembly=MyXF.Client.mobilebase"
             xmlns:viewModels="clr-namespace:MyXF.Client.mobile.ViewModels"

xamlns’lerimizi ekliyoruz.. Burada dikkat etmemiz gereken nokta ise base projemizi MyXF.Client.mobile projemize referans proje olarak ekledik ve xmlns:behaviors olarak ns eklerken “MyXF.Client.mobilebase.Behaviors” base projenin path’ini kullanmamız. Aynısı converters için de geçerli. Gözlerden kaçabilir dikkat edelim 🙂

    <ContentPage.BindingContext>
        <viewModels:MainPageViewModel />
    </ContentPage.BindingContext>
    
    <ContentPage.Resources>
        <ResourceDictionary>
            <converters:TextChangedEventArgsConverter x:Key="TextChangedEventArgsConverter" />
        </ResourceDictionary>
    </ContentPage.Resources>
        <Entry>
            <Entry.Behaviors>
                <behaviors:EventToCommandBehavior EventName="TextChanged"
                                                  EventArgsConverter="{StaticResource TextChangedEventArgsConverter}"
                                                  Command="{Binding TextChangedCommand}"/>
            </Entry.Behaviors>

Artık projemizde istediğimiz event bizim için bir command 🙂

Sonraki yazımda “Helper” ihtiyaçlarımız ile devam edeceğim.

Kodları takip edebileceğiniz repo : https://github.com/ozaksuty/MyXF

Yiğit

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

1 Comment

You can post comments in this post.


  • Çok katmanlı mimariyle ilgili bir video çeksen çok süper olur.

    SAİM HASANUSTA 1 hafta ago Reply


Post A Reply