Android — MVVM Yapılması/Yapılmaması Gerekenler

Furkan Aşkın
4 min readAug 22, 2019

Merhabalar,

Bir süredir mvvm mimarisinde kod yazıyorum ve gün geçtikçe kendimi geliştirmeye çalışıyorum. Bu esnada öğrendiğim bazı bilgileri sizlerle paylaşmak istedim, hazırsak başlayabiliriz!

❌ ViewModel’iniz içerisinde hiç bir Android frameworkü hakkında bilgi barındırmayın.

Bildiğiniz üzere MVVM’de temel amaçlardan birisi view içerisine business logic yerleştirmemektir. Bu ; yönetilebilir bir koda , test edilebilir bir yapıya sahip olmamızı sağlar. Aynı şekilde, viewModel içerisinde herhangi bir Android Framework’ü barındırmamak da önemli bir yere sahiptir. Örneğin,

import android.widget.TextView;

gibi bir importa sahip bir viewModeliniz varsa bununla ilgili işlemi UI katmanına almanız gerekecektir.

✅ Activity ve Fragment içerisindeki logic’i minimum düzeyde tutun.

Activity ve fragmentların içerisinde mümkün olduğunda logic barındırmamaya özen gösterin. Bu logicler if-else koşulları, veri düzenlemeleri veya döngüler olabilir. Bu logicleri viewModel içerisinde barındırmak daha doğru olacaktır. Böylece kodunuz test edilebilir, modüler ve yönetilebilir bir hale gelecek.

❌ View referanslarını viewModel’da barındırmayın.

Activity veya fragmentlar, viewModel’a göre daha farklı bir lifecycle’a sahiptir. ViewModel yaşıyorken activity herhangi bir lifecycle state’ine sahip olabilir. ViewModel activity-fragmentların hangi state’e sahip olduğunu bilemez.

viewModel Scope

Herhangi bir view(activity veya fragment) referansını viewModel’a taşımak ciddi bir risktir. ViewModel’in bir network isteğinde bulunduğunu ve bir süre sonra bu istekten cevap aldığını varsayalım. Bu esnada viewModel içerisinde bulunan view referansı artık visible olmayabilir veya destroy edilmiş olabilir. Bu durumda memory leak veya bir crash yaşanması muhtemeldir.

✅ View referansını viewModel’a aktarmak yerine, değişiklikleri Observer Pattern ile observe edin.

ViewModel’da view referansı barındırmanın risklerinden yukarıdaki maddede bahsetmiştim. O halde değişikliklerden view’ın nasıl haberi olacak? Observable kullanarak bunu rahatlıkla sağlayabiliriz. Ufak bir örnek ile açıklamak gerekirse;

HomeViewModel içerisinde bir adet encapsulation uygulanmış LiveData mevcut.

getBestPodcasts ise temel bir network isteği, Status.Success olduğunda LiveData’ya post value ile data veriyorum. Gelelim view kısmına ;

Fragment içerisinde ilk önce viewModel’ın getBestPodcasts fonksiyonunu çağırıyorum. Yukarıda bu fonksiyonun ne gibi bir iş yaptığını açıklamıştım, ardından viewModel içerisindeki bestPodcastsLiveData’yı observe ediyorum. Böylelikle bu LiveData’da herhangi bir değişiklik olduğu zaman view’ım bundan haberdar olmuş oluyor.

❌ Fragmentları LifeCycleOwner olarak LiveData’ya vermeyin.

Hazır LiveData’dan bahsetmişken, fragment’da LiveData değişikliklerini observe ederken

viewModel.livedata.observe(this,Observer<Data>{})

şeklinde bir kullanım yaparsanız this diyerek fragmentı işaret edersiniz ve lifeCycleOwner olarak fragmentı göstermiş olursunuz. Ancak bu observe işlemini onCreateView’da yaptığınızı varsayarsak her onCreateView çalıştığında yeni bir observer yaratılacak ve memory leak’a sebep olacak. Bu sebeple observe ederken fragment’ı göstermek yerine yukarıdaki örnekte olduğu gibi viewLifecycleOwner veya viewLifecycleOwnerLiveData kullanmamız daha doğru olacaktır. Eğer bunları kullanırsanız, fragment destroy edildiğinde observerlar silinecek ve memory leak’a sebep olmamış olacak.

✅ Datayı yönetmek adına bir CallbackWrapper kullanın.

Network requesti attıktan sonra gelen datayı yönetmek adına, Jetpack dökümantasyonunda olduğu gibi bir Wrapper kullanabilirsiniz. Örnek vermek gerekirse:

Bu şekilde bir resource class’ı oluşturun.

Veriyi elde ettikten sonra Status’e göre gerekli işlemleri yapın.

✅ View yeniden oluşturulduğunda her seferinde istek atmak yerine, onSaveInstanceState() kullanabilirsiniz.

onCreate içerisinde viewModel üzerinden istek attığınızı varsayalım. Bu durumda activity yeninden yaratıldığında tekrar tekrar istek gidecek veya aynı durum fragment için de geçerli. Bu gibi durumlarda tekrar tekrar istek atmak istemiyorsanız;

gibi bir kullanım yapabilirsiniz veya buraya göz atabilirsiniz.

❌ Her yaşanan olayı takip etmek için LiveData kullanmayın.

Bazı olayları sadece bir defa observe etmek isteyebilirsiniz, bu gibi durumlarda LiveData kullanmak gereksiz. Bunun yerine SingleLiveEvent kullanmak daha faydalı olacaktır. Detaylı bilgi için buraya bir göz atın derim.

✅ ViewModel içerisindeki LiveData’ya encapsulation uygulayın.

Bunu yazmamdaki sebep şu : View’ın LiveData’ya erişip onun ile ilgili bir işlem yaparsa kendi state’ini değiştirmiş olacak. Ancak bu viewModel’ın sorumluluğunda olan bir durum, view sadece bu LiveData’yı observe etmeli diye konuşmuştuk, bu sebeple LiveData’ya encapsulation uygulamamız gerekiyor.

Backing Özelliği hk.: https://proandroiddev.com/backing-properties-in-kotlin-cb78dfebfd90

❌ Eğer Navigation kullanıyorsanız, onBackPress’te göstermek istemediğiniz ekranlar için onBackPress’i activity’de override etmeyin.

Navigation component’i ilk kullanmaya başladığımda activity’de onBackPress’i override ederek bir çözüm arıyordum.

Ancak bunun yerine Navigation popUpTo ve popUpInclusive kullanarak daha basit bir çözüm yaratabiliriz. Örnek üzerinden ilerleyelim:

  • FragmentA
  • FragmentB
  • FragmentC

şeklinde 3 fragmentımız olsun. Burada kullanıcı C ekranına geldiğinde A ekranına geri yollamak istiyor olalım(A-B-C-A). Bunu yapmak için sadece popUpTo kullanmamız yeterli.

Senaryo şu şekilde olacak : A →B → C→ Geri Tuşu Basıldı → A →Geri Tuşu Basıldı → activity onBackPress

Diğer bir örnekte A-B-C-A-B-C-A-B-C şeklinde bir senaryomuz olsun, burada tüm B’leri kaldırmak istiyorsak bu sefer popToInclusive kullanmamız yeterli olacaktır.

Senaryo şu şekilde olacak: A → B → C → A → B →C → A → B → C → Geri Tuşu Basıldı → A→ Geri Tuşu Basıldı → C→ Geri Tuşu Basıldı → A→ Geri Tuşu Basıldı → C→ Geri Tuşu Basıldı → A → Geri Tuşu Basıldı → activity onBackPress

✅ Navigation kullanıyorsanız data taşımak için Navigation SafeArgs kullanabilirsiniz.

Navigation SafeArgs sayesinde Primitive değişkenleri ve Parcelable’ları rahatlıkla taşıyabilirsiniz.

nav_graph.xml’de eklemek istediğiniz veriyi Arguments olarak ekleyin:

Ardından aşağıdaki gibi göndermek istediğiniz Fragment’ın generate edilmiş Directions class’ına erişip taşımak istediğiniz veriyi action’a setleyip, navigate edin.

Kullanmak istediğiniz Fragment’ın içerisinde navArgs tanımlayıp taşıdığınız veriyi kullanabilirsiniz.

Bu yazım burada sonra eriyor, vakit ayırıp okuduğunuz için teşekkür ederim, beğendiyseniz alkışlamayı unutmayın!

--

--