Android MVVM Design Pattern Kullanımı

Google, Google I/O 18 ile MVVM patternini destekleyeceğini açıkladıktan sonra Android uygulama geliştiriciler için bu patternin nasıl bir yapı üzerine kurgulandığına yönelik bir makale paylaşma ihtiyacı duydum. Makalemizde öncelikle patternin avantajları ve nasıl kullanıldığını anlatacağım. Sonrasında da basit bir login işlemi ile konuyu pekiştireceğim. 

Öncelikle MVVM yapısı Model, View, ViewModel üzerine kurgulanmaktadır.
  • Model: Model, uygulamanın verilerini tutar. Doğrudan view ile iletişim kurmaz. Genel olarak, verileri Observables aracılığıyla ViewModel'e aktarır.
  • View: View, uygulamanın arayüzünü kapsamaktadır.
  • ViewModel: Model ve View arasında bir köprü görevi görür. Modelden gelen datanın View'e aktarılmasından sorumludur. Verilerin anlık aktarımı için hook veya callback metodları kullanılmaktadır.
MVVM Design Pattern Flow

MVP Patterni İle Arasındaki Fark

  • Middle Layer'de Presenter yerine ViewModel kullanılmaktadır.
  • Presenter, View'e ait referanslar tutar. Ancak ViewModelde buna gerek kalmamaktadır.
  • Presenter, View'i klasik yolları kullanarak güncellemektedir.
  • ViewModel, Model üzerinden gelen verileri iletebilmektedir.
  • Presenter ve View 1'e 1 (one to one) ilişki içindedir.
  • View ve ViewModelde ise 1'e çok yönlü (one to many) ilişki içindedir.
  • ViewModel, View'un onu dinlediğini bilmez.

Android'de MVVM'yi uygulamanın iki yöntemi bulunmaktadır:

  • Data Binding
  • RX Java

Makalemize Data Binding yöntemini kullanarak devam edelim. Data Binding, verileri doğrudan xml dosyalar üzerinden bağlamak için Google tarafından tanıtılan bir kütüphanedir.

Şimdi MVVM'i kullanarak email ve password içeren basit bir login sayfası oluşturalım. Uygulamamızda View'e ait herhangi bir referans tutulmaksızın ViewModel üzerinden iletilen verinin nasıl gösterildiğini göreceğiz.

İpucu: Herhangi bir referans tutulmadan bir sınıfa bildirimde bulunmak nasıl mümkün olabilir?

Bu işlem üç şekilde gerçekleştirilebilir;

  • Çift Yönlü Data Binding kullanarak
  • Live Data kullanarak
  • RxJava kullanarak

Çift Yönlü Data Binding Kullanımı

Çift Yönlü Data Binding, uygulama arayüzünde bulunan nesnelerin verilerle çift taraflı haberleşmesine imkan sunan bir tekniktir. Bu tekniğe göre layout üzerinden ViewModel'e ve ViewModel üzerinden layouta data aktarımı sağlanabilmektedir.

Bizim örnek projemiz ise ViewModel üzerinden veri aktarımı ve yaşanan değişikliklerin Observer yapısı üzerinden yakalanmasını kapsamaktadır.

Bunun için BindingAdapter ve XML'de tanımlanan custom attributelere ihtiyacımız var. Binding Adapter, custom attribute üzerinde gerçekleşen değişiklikleri izleyecektir.

Örnek Android MVVM Projeye ait Klasör Yapısı

Projemizde klasör yapısını aşağıdaki şekilde kurgulayacağız. Projemizde sadece login sayfası kurguladığımızdan dolayı User modeli, LoginViewModel sınıfı ve LoginActivity yeterli olmaktadır.

Android MVVM Project Structure

Data Binding Kütüphanesinin Eklenmesi

Aşağıdaki kod parçasını app klasörünüz altında bulunan build.gradle dosyasına ekleyelim.
android {

    dataBinding {
        enabled = true
    }

}
Eklenen kod parçası data bindingin projemizde aktif hale gelmesini sağlamaktadır.

Dependecylerin Eklenmesi

Aşağıdaki kod parçasını app klasörünüz altında bulunan build.gradle dosyasında dependecy alanına ekleyelim.
implementation 'android.arch.lifecycle:extensions:1.1.0'

Model

Uygulamamızda kullanıcıya ait email ve şifre alanlarını tutacak User modeli aşağıdaki şekilde oluşturalım.
public class User {
    private String email;
    private String password;

    public User(String email, String password) {
        this.email = email;
        this.password = password;
    }

    public void setEmail(String email) {
        this.email = email;
    }
    
    public String getEmail() {
        return email;
    }

    public void setPassword(String password) {
        this.password = password;
    }


    public String getPassword() {
        return password;
    }

}
Projemizde kullanılacak olan çift yönlü data binding işlemi yukarıda da dile getirdiğimiz gibi xml layout üzerinde tanımlanan viewlerin güncellenmesinde veya view üzerinde yapılan işlemin Model üzerine aktarımında kullanılmaktadır.

Çift yönlü data binding için kullanılan syntax @={variable} şeklindedir.

Layout

Login sayfasına ait arayüz için aşağıdaki kodu activity_login.xml dosyası içerisine yapıştıralım.
?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:bind="http://schemas.android.com/tools">

    <data>

        <variable
            name="viewModel"
            type="yazilimdersi.info.example.samplemvvm.viewmodels.LoginViewModel" />
    </data>


    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_margin="8dp"
            android:orientation="vertical">

            <ImageView
                android:id="@+id/yazilimdersiImageView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_alignParentBottom="true"
                android:layout_centerHorizontal="true"
                android:padding="10dp"
                android:src="@drawable/icon_app_logo" />

            <EditText
                android:id="@+id/inEmail"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Email"
                android:inputType="textEmailAddress"
                android:padding="8dp"
                android:text="@={viewModel.userEmail}" />


            <EditText
                android:id="@+id/inPassword"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="Password"
                android:inputType="textPassword"
                android:padding="8dp"
                android:text="@={viewModel.userPassword}" />


            <Button
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="8dp"
                android:background="@color/colorPrimaryDark"
                android:textColor="@color/colorWhite"
                android:onClick="@{()-> viewModel.onLoginClicked()}"
                android:text="LOGIN"
                bind:toastMessage="@{viewModel.toastMessage}" />


        </LinearLayout>

    </ScrollView>

</layout>
Data Bindingde arayüzüne yönelik işlemlerde sayfanın en üst kısmında layout taglerinin tanımlanması gerekmektedir. Yukarıda yapılan tanımlamalar sonrasında ViewModel dataları direkt olarak arayüze bind edebilmektedir. Kodlamada gördüğünüz üzere ()-> viewModel.onLoginClicked() fonksiyonu LoginViewModel sınıfı içerisinde tanımlanmıştır. Kullanıcı login butonuna bastığı anda bu bilginin ViewModel e iletilmesi sağlanmaktadır. Ayrıca ViewModel üzerinden gelen verilerin view e aktarımına örnek olarak (bind:toastMessage="@{viewModel.toastMessage}" kullanımını inceleyebiliriz. toastMessage verisinde herhangi bir değişiklik olduğu takdirde view üzerinde bu mesajın gösterimini sağlanmaktadır.

View Model

Aşağıdaki kod parçasını LoginViewModel.java sınıfı içerisine yapıştıralım.
import android.databinding.BaseObservable;
import android.databinding.Bindable;
import android.text.TextUtils;
import android.util.Patterns;

import com.android.databinding.library.baseAdapters.BR;
import yazilimdersi.info.example.samplemvvm.model.User;

public class LoginViewModel extends BaseObservable {
    private User user;


    private String successMessage = "Başarılı şekilde giriş yapıldı";
    private String errorMessage = "Email veya Şifre hatalı";

    @Bindable
    private String toastMessage = null;


    public String getToastMessage() {
        return toastMessage;
    }


    private void setToastMessage(String toastMessage) {

        this.toastMessage = toastMessage;
        notifyPropertyChanged(BR.toastMessage);
    }


    public void setUserEmail(String email) {
        user.setEmail(email);
        notifyPropertyChanged(BR.userEmail);
    }

    @Bindable
    public String getUserEmail() {
        return user.getEmail();
    }

    @Bindable
    public String getUserPassword() {
        return user.getPassword();
    }

    public void setUserPassword(String password) {
        user.setPassword(password);
        notifyPropertyChanged(BR.userPassword);
    }

    public LoginViewModel() {
        user = new User("","");
    }

    public void onLoginClicked() {
        if (isInputDataValid())
            setToastMessage(successMessage);
        else
            setToastMessage(errorMessage);
    }

    public boolean isInputDataValid() {
        return !TextUtils.isEmpty(getUserEmail()) && Patterns.EMAIL_ADDRESS.matcher(getUserEmail()).matches() && getUserPassword().length() > 5;
    }
}

LoginViewModel sınıfımızı ViewModel üzerinden extend edebiliriz. Ancak amacımız login butonuna tıklanıldığında Toast mesajı gösterecek bir kurgu olduğundan BaseObservable sınıfını extend etmemiz daha iyi olacaktır.

toastMessage ile alakalı olarak ViewModel sınıfımızda getter ve setter methodlarını tanımlamamız gerekmektedir. settter methodu içerisinde notifyPropertyChanged metodunu çağırmamız gerekmekte. Böylelikle datamızın değiştiği bilgisini View ile paylaşmış oluyoruz. View de yaptığımız tanımlamalara göre aksiyon alacaktır. BR sınıfı proje rebuild edilirken otomatik olarak data binding kütüphanesi tarafından oluşturulmaktadır.

LoginActivity.java sınıfı içinde aşağıdaki kod parçasını yapıştıralım.
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.BindingAdapter;
import androidx.databinding.DataBindingUtil;

import yazilimdersi.info.example.samplemvvm.R;
import yazilimdersi.info.example.samplemvvm.databinding.ActivityLoginBinding;
import yazilimdersi.info.example.samplemvvm.viewmodels.LoginViewModel;

public class LoginActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityLoginBinding activityLoginBinding = DataBindingUtil.setContentView(this, R.layout.activity_login);
        activityLoginBinding.setViewModel(new LoginViewModel());
        activityLoginBinding.executePendingBindings();
    }

    @BindingAdapter({"toastMessage"})
    public static void runMe(View view, String message) {
        if (message != null)
            Toast.makeText(view.getContext(), message, Toast.LENGTH_SHORT).show();
    }
}

DataBinding librarysi sayesinde ActivityLoginBinding sınıfı otomatik olarak oluşturulmaktadır. @BindingAdapter methodunda tanımlı olan toastMessage güncellendiği anda içerisinde bulunan kod parçası trigger etmektedir.

Böylelikle ViewModel, yukarıdaki kodlamada görüldüğü üzere View üzerindeki işlemleri izleyerek Model'i güncellemektedir. Ayrıca Model, notifyPropertyChanged metodunu kullanarak View'i trigger edebilmektedir.

Uygulamamıza ait ekran görüntüsü aşağıdaki şekildedir.

comments powered by Disqus