MVVM Design Pattern

Melih Kök
Mobillium
8 min readNov 19, 2020

--

Merhaba;
Uygulama geliştirirken kullandığımız tasarım desenlerinden, son zamanlarda çalıştığım projede de kullanmaya başladığım MVVM Design Pattern konusundan bahsedeceğim.

MVVM tasarım desenine geçmeden önce “Tasarım deseni nedir?”, “Neden tasarım deseni kullanmalıyız?” gibi sorular üzerinde düşünmeliyiz.

DESIGN PATTERN NEDİR?

  • Design Pattern kısaca; bir sorun için üretilmiş ve tekrar tekrar kullanılabilir çözümlerdir.
  • Projelerde sıklıkla karşılaştığımız sorunlar üzerinden yola çıkılarak zamanla tecrübeler sonucunda ortaya çıkmış yapılardır.

NEDEN DESIGN PATTERN KULLANMALIYIZ?

  • Tasarım desenlerini çalıştığımız projelere bir standart getirmek, daha iyi anlaşılmasını sağlamak, test edilmesini kolaylaştırmak, hataların bulunmasını ve çözülmesi için geçen süreyi azaltmak ve oluşabilecek hatalara karşı önceden önlem almak adına kullanmalıyız.

Bunu bir örnekle açıklayıp somutlaştırmak gerekirse:

https://www.raywenderlich.com/636803-mvvm-and-databinding-android-design-patterns

Bir banyoya girdiğinizi hayal edin. Bir bakışta duş kısmını, tuvaleti veya lavaboyu tanıyabiliyoruz. Ne işe yaradıklarını ve nasıl kullanmamız gerektiğini biliyoruz. Tabi bütün lavabolar aynı olmayabilir. Mesela bizde sıcak ve soğuk su aynı musluktan gelirken bazı lavabolarda farklı sıcaklığa sahip sular farklı musluklardan geliyor olabilir. Alıştığımız kullanıma uygun olmasa bile yine de bu lavaboyu nasıl kullanmamız gerektiğini biliyoruz. İşte bu, oluşturulan yapının aslında bir standarta uygun olduğunu aynı zamanda ne kadar farklı uygulansa da anlaşılabilir ve kullanılabilir olduğunu gösterir. Design pattern’lerin amacı da budur.

MVVM tasarım deseni muadili olan farklı yapılar da bulunmakta. Bunlar:

  • MVVM — Model — View — ViewModel
  • MVP — Model — View — Presenter
  • MVC — Model — View — Controller
  • MVI — Model — View — Intent

MVVM DESIGN PATTERN

Kullanıcı arayüzünü iş mantığı kısmından ayıran bir tasarım desenidir. Bu ayrım sayesinde daha kolay geliştirme yapılabilir, iş mantığı ve UI kısımları ayrı ayrı test edilebilir hale gelir. MVVM üç ana katmandan oluşur, bunlar:

Model Katmanı : Diğer bir deyişle veri katmanı. Herhangi bir View’a bağlı olmayan ve bu sayede tekrar kullanıma olanak sağlayan, uygulama içi verileri tutmamıza yarayan katmandır. Asıl görevi; ViewModel’ın veriye ihtiyacı olduğunda istenilen veriyi sağlamaktır. Model katmanı genelde Repository Sınıfları kullanılarak oluşturulur.

Görselde de gördüğümüz gibi, model katmanı Repository sınıfı kullanılarak oluşturulmuş burada. Hem lokaldeki verilere hem de uzak sunucudaki verilere erişim imkânı sağlayabiliyoruz model katmanında. Repository sınıflarına ileride değineceğim.

View Katmanı : Uygulamamızın kullanıcı arayüzünü oluşturan katmandır. Bu katman Activity, Fragment gibi yapıları içerir. İçerisinde hiçbir mantık bulundurmaz. Asıl görevi kullanıcının gerçekleştirdiği aksiyonları uygulamamıza taşır ve ViewModel’dan aldığı verileri kullanıcıya gösterir. View, XML içerisinde belirttiğimiz tag’ler yardımıyla observable şekilde tanımlanan alanlara erişebilir ve buradan gelen değerleri kullanabilir.

Aşağıda standart XML layout’umuzu tagler kullanarak etiketledik. Variable içerisinde belirttiğimiz name ismiyle bu XML sınıfımıza View sınıfımızdan erişebiliriz.

ViewModel Katmanı : MVVM tasarım deseninin temel katmanıdır. Tüm iş mantığı bu katmanda bulunmaktadır. Model katmanından aldığı veriyi işleyerek View katmanının kullanması için observable alanları hazırlar. Aynı zamanda View katmanından gelen isteğe göre Model katmanını günceller.

ViewModel’in asıl amacı, View ile Model arasındaki köprüyü oluşturmak. Ayrıca, bu ayrım sayesinde çok daha kolay test yazabiliyor oluruz. Çünkü iş mantığının çalışıp çalışmadığını test ederken UI ile alakalı herhangi bir şey ile uğraşmamız gerekmeyecek.

ViewModel katmanında dikkat edilmesi gereken önemli bir konu, bu katman ile View katmanını tamamen ayırmaktır. Yani ViewModel’ın, etkileşimde bulunduğu View hakkında herhangi bir bilgiyi içerisinde bulundurmaması gerekir. Bunun bir getirisi olarak; bir ViewModel’ı birden fazla View için kullanabiliriz.

  • Bir ViewModel’ı birden fazla View için kullanmaya bir örnek vermemiz gerekirse; adımlar şeklinde oluşturulmuş bir kayıt ol senaryosu düşünebiliriz.
  • Kullanıcı ilk sayfada isim soy ismini girebilir.
  • İkinci sayfada mail adresini ve şifresini girebilir.
  • Üçüncü sayfada ise doğum tarihini girip varsa sözleşmeleri onaylama işlemini yapabilir olsun.
  • Böyle bir senaryo içerisinde tüm bu UI yapısını desteklemek için oluşturduğumuz sınıfları tek bir ViewModel ile kullanabiliriz.
  • Her adımda kullanıcıdan aldığımız verileri ViewModel içerisinde tutabilir ve son adımda kullanıcı kayıt ol butonuna bastığında ViewModel içerisinde tuttuğumuz bu değerleri aynı ViewModel içerisinde bulunan bir metod ile servise gönderebiliriz.

- Burada iki fragment’in aynı ViewModel objesini kullanabilmesine örnek verilmiş mesela.

- Master Fragment ve Detail Fragment bağlı oldukları Activity’nin life cycle’ını kullanarak aynı ViewModel’i kullanabilir ve veri aktarımında bulunabilir.

- Bu iletişim sırasında Activity’nin herhangi bir işlem yapmasına gerek kalmıyor. Ayrıca fragment’ler de birbirleri hakkında herhangi bir bilgiye sahip de değiller. Tek ortak noktaları bağlı oldukları ViewModel objesi. Her fragment’in kendi yaşam döngüsü de devam ediyor. Birbirlerinin yaşam döngüsünden etkilenmiyorlar. Biri diğerini replace etse dahi, herhangi bir UI problemine yol açmadan çalışmaya devam edebiliyor bu fragment.

Burada biraz önce anlattığım katmanların topluca gösterimi bulunmakta. Her katman bir altındaki katmana ulaşabilir durumda. View katmanının ViewModel Katmanına, ViewModel katmanının da Model Katmanına erişim yetkisi bulunuyor. View, ViewModel içerisindeki bir metodu çağırabilir veya ViewModel içerisinde Observable olarak tanımlanmış bir alanı dinleyebilir. Aynı şekilde ViewModel’da Model katmanında Observable olarak tanımlanan alan varsa onu dinleyebilir konumda olacak.

MVVM’in temel yapısından bahsettim. MVVM tasarım modelini DataBinding ile kullanmamız bizler için büyük kolaylıklar sağlayacaktır.

DataBinding

  • Jetpack kütüphaneleri ile beraber gelen, kod sınıflarımızla XML sınıflarını birbirine bağlamamıza yardımcı olan bir kütüphane.
  • Bu kütüphane sayesinde tekrarlanan kod blokları yazmamıza, cast işlemi yapmamamıza ve veriyi XML sınıfı içerisinde yönetebilmemize olanak sağlamakta.

Aşağıda gözüktüğü gibi, View sınıfımız içerisinde, ViewModel’da Observable olarak tanımlanmış bir alanı dinliyoruz. ViewModel içerisindeki alan her güncellendiğinde View sınıfı içerisindeki bu kod bloğu da bilgilendirilecek. Ve bu kod bloğu çalıştığında bind işlemi yapılacak ve UI sınıflarımız da DataBinding sayesinde otomatik olarak güncellenmiş olacak.

Gene MVVM ile oldukça fazla kullanılan ve benim de bu sunumda birkaç defa üzerinden geçtiğim LiveData’dan bahsedeceğim. MVVM konusunu anlatırken bu konulara da değinmem gerektiğini düşünüyorum çünkü beraber kullanıldığında faydası çok fazla olan kütüphaneler bunlar.

LiveData

  • Kısaca gözlemlenebilir bir veri tutma sınıfıdır.
  • Observer mimarisini örnek aldığı için içerisinde her zaman güncel veri bulundurur. Ama Observer mimarisinden farklı olarak yaşam döngüsüyle eş zamanlı çalışır.
  • Bağlandığı View’ın yaşam döngüsüne göre hareket ettiği için oluşabilecek memory leak gibi, yaşam döngüsü sonlanmış View’lar yüzünden ortaya çıkabilecek hatalar gibi pek çok duruma karşı güvenli bir yapı sunar.
  • Kullanıcı arayüzünü her defasında güncellemek yerine bir değişiklik olduğunda bu LiveData’yı dinleyen yer de değişiklikten haberdar olur ve güncellenir.

Örneğin:

ViewModel içerisinde böyle bir alan tutarak View sınıfımız içerisinde,

şeklinde bu alanı dinleyebilir ve gelişmelerden anlık olarak haberdar olabiliriz.

LiveData’nın yaşam döngüsüyle beraber çalışması ne demek; LifeCycleOwner (burada View oluyor) hangi state içerisinde bulunuyorsa LiveData sınıfı da bu state içerisinde bulunmakta. Yani eğer view sınıfımız bir Activity veya fragment ise, onPause durumunda LiveData’da pause durumuna geçiyor ve veri aktarımını durduruyor. OnDestroy olduğunda ise LiveData da View ile beraber siliniyor.

Yukarıdaki görselde ViewModel’in yaşam döngüsünü görebiliriz. ViewModel, View’ın oluşturulmasından itibaren ayağa kalkar ve View onDestroy olduğunda da onCleared metodu ile temizlenmiş olur. Burada önemli bir nokta da ekranı yan çevirme gibi olaylardan sonra da sayfada bulunan veriler kaybolmaz. Ekran tekrar çizdirildiğinde veriler tekrar yazdırılır. ViewModel içerisinde LiveData ile tuttuğumuz alanlar sayesinde en son güncel veriye View sınıfımız kill edilmediği sürece ulaşabiliriz.

Repository Katmanı

  • Sorumlulukların ayrılması prensibine dayanarak veri katmanını diğer sınıflardan ayırmamız gerekiyor.
  • Repository sınıfı, veri katmanı ve uygulamanın geri kalanı arasında bir köprü görevi görmesi için kullanılan bir yapıdır.
  • Soyut bir katman oluşturarak, uygulama içinde herhangi bir veriye ihtiyaç duyulan sınıfta, verinin nereden ve nasıl alınacağını düşünmeden geliştirme yapmamıza olanak sağlıyor.
  • Repository sınıfı içerisinde verilerin lokalden mi, uzak sunucudan mı yoksa cache’den mi geleceğini belirtebiliriz.
  • ViewModel, nesnesini ürettiği Repository sınıfından bir istekte bulunur ve bu isteğin cevabını dinlemeye başlar.
  • Repository sınıfı gelen bu isteği değerlendirir ve belirtilen koşullara göre veriyi elde ettiğinde ViewModel’ı bilgilendirir.
  • Repository sınıfları, uygulamada kullanacağımız verilerin tek gerçek kaynağı olmalı. Yani tüm verileri bu sınıflar üzerinden elde ediyor olmalıyız.
  • Repository sınıfında, LiveData sınıfından tanımlanmış dönüş tipi bulunan metotlar kullanabiliriz.
  • Bunun sayesinde Repository sınıfımız da LifeCycleAware özelliği kazanır.

Disposable

  • Observer, herhangi bir Observable alana bağlandığı zaman ortaya bir akış çıkmış olur.
  • Disposable; Observer ile Observe edilen alanlar arasında tek kullanımlık akışlar oluşturan sınıftır.
  • Potansiyel Memory Leak’leri engellemek adına bu akışı durdurmak için Disposable’ları kullanıyoruz.

Örneğin:

Örnek olarak yaptığımız servis sorgularını verebiliriz. Bir servise istek atarken aynı zamanda bu isteği dinlemek için isteği gönderdiğimiz yere Observe oluyoruz. Servisten cevap geldiğinde Observer olan yer uyarılıyor ve cevabı elde etmiş oluyor. Aradaki bağlantıyı kapatmazsak akış kapanmayacağından hatalarla karşılaşabiliriz.

Composite Disposable

  • Bir View’ın yaşam döngüsü içerisinde birden fazla yere istek atabilir ve birden fazla Disposable akış oluşturabiliriz.
  • Bu akışları tek tek kontrol etmektense hepsini bir sınıfta toplayıp View’ın yaşam döngüsüyle beraber hareket etmesini sağlayabiliriz.
  • Burada BaseViewModel içerisine yazılmış olan composite disposable’dan türemiş bir değişkenimiz var. Her attığımız istek için açılan akışı bu sınıf içerisinde topluyoruz.
  • Gene BaseViewModel içerisine yazdığımız bu metot sayesinde LifeCycle öldüğünde, aktif olan akışlar da temizlenmiş oluyor.

MVVM Avantajları

  • Sorumlulukların ayrılması ilkesine göre her sınıfın kendi işini yapmasına olanak sağlayan bir yapı sunar. Bunun bir getirisi olarak geliştirme yapmak da kolaylaşır.
  • Asıl amacı View sınıfını soyutlaştırmak olduğu için ViewModel içerisinde View’a ait bir şey barındırmaz ve bu nedenle daha kolay test edilebilirdir. Bütün logic ViewModel’de bulunur.

Dezavantajları

  • Bazı geliştiricilere göre basit arayüze sahip olan uygulamalarda verilen eforun karşılığını vermeyebilir.
  • Aynı şekilde büyük uygulamalarda da ViewModel içerisindeki logic kısmını çok şişirebilir ve geliştirme yapılmasını zorlaştırabilir.
  • Kompleks bir şekilde DataBinding kullanıldığında Debug yapılması zorlaşabilir.

Okuduğun için teşekkür ederim. Umarım küçükte olsa bir yardımı dokunur bu yazımın. İyi şanslar! :)

--

--