2017. június 21.

Android Architektúrák – MVVM

10 perc olvasási idő

Android Architektúrák – MVVM

A Model-View-Presenter(MVP) mellett leginkább ismert tervezési minta a Model-View-ViewModel, ami a 2015 Google I/O-n bemutatott Databinding könyvtárral lett népszerű.

Az MVP-hez nagyon hasonló feépítésről van szó. A különböző szerepkörök szétválasztását célozza meg, így segítve a tesztelést és karbantartást. Ilyen szerepkör a felület és az üzleti logika.

Model-View-Binding néven is emlegetik, mivel a kulcs ebben a mintában a felület és az adatok között létrejövő kapcsolat. Ha változás történik az adatokban, az megjelenik a felületen is.

MVVM Model View Presenter diagram

Model

Az üzleti logika itt található. Itt van a helye a Java POJO osztályoknak, az adatbázis és API kezelőknek is. Ezeknek a felépítésével nem foglalkozik az MVVM.

View

A felület, ami meg fog jelenni, azaz layout XML-ek. Ide tartoznak az Activity és Fragment osztályok is.

ViewModel

A ragasztó az üzleti logika és a felület között. Segít a Model változásairól értesíteni a felületedet databinding használatával. Feliratkozik és értesül a felület eseményeiről, azaz itt helyezkedik el a felület logikája is.

Hogyan kell használni?

Egy egyszerű Gradle beállítással engedélyezd az egészet:


android { 
    dataBinding {
        enabled = true
    }
}

Az XML layoutokat vedd körül egy új XML taggel:


<layout xmlns:android="http://schemas.android.com/apk/res/android">
  ...eddigi layout...
</layout>

A databinding könyvtár generál a layout fájlból egy osztályt, amit a kapcsolatra használni kell. A neve a layout fájl neve lesz. Például main_activity.xml-ből MainActivityBinding osztály fog keletkezni, mivel az _ (alulvonás) utáni karaktereket nagybetűvel kezdi és végére csap a névnek egy Binding szót. Ezt elérheted az onCreate() vagy onViewCreated() metódusokban:


//Fragment vagy RecyclerView esetén
FragmentListBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_list, container, false);

//Fragment vagy RecyclerView másik lehetőség
FragmentListBinding binding = FragmentListBinding.inflate(inflater, container, false);

//Activity esetén
MainActivityBinding binding = DataBindingUtil.setContentView(this, R.layout.main_activity);

//Activity másik lehetőség
MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());

A következő lépésben létrehozod a felülethez tartozó ViewModel osztályodat.
Ebben lesznek a felületet és Modelt érintő metódusok, változók:


public class ViewModel {
  ...
}

Hivatkozol az XML layoutodban a ViewModel osztályodra, mint adatra:


<layout xmlns:android="http://schemas.android.com/apk/res/android">
  <data>
    <variable
            name="viewModel"
            type="hu.fps.example.ViewModel"/>
  </data>
  
  ...eddigi layout...
</layout>

Létrehozod és csatolod a ViewModelt kódban is. Ezt a generált databinding osztályon keresztül lehet megtenni. A generált osztályban lesz egy setVáltozóNév() metódus. A „változó név” az előbbi példából a <variable name="változó név">:


@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main_activity);

  //Binding létrehozása
  MainActivityBinding binding = MainActivityBinding.inflate(getLayoutInflater());
  
  //ViewModel létrehozása
  ViewModel viewModel = new ViewModel();

  //ViewModel csatolása a view-hoz databindinggal
  binding.setViewModel(viewModel);
}

Hogy a felületet feltöltsd adatokkal, hivatkoznod kell rájuk XML-ben. Ezt a becsatolt változón keresztül tudod megtenni, aminek látod a metódusait, változóit:


   <TextView
    ...
    android:text="@{viewModel.exampleText}" />

A fenti példában tehát az XML deklarált a viewModel változó exampleText metódusára vagy változójára mutatunk. Magától keresi meg melyikről van szó. Szóba jöhetnek a következők:

  • publikus exampleText változó
  • getExampleText() metódus
  • exampleText() metódus

A felhasználótól jövő bemenetre is lehet figyelni, de azt másképp kell megírni az XML-ben:


   <EditText
    ...
    android:text="@={viewModel.userName}" />

Ekkor a ViewModel-ben a setUserName() metódust fogja meghívni a rendszer.

A ViewModel-ban történt változásokra akkor tud feliratkozni a felület, ha az Observable interface-t implementálod. Szerencsére vannak wrapper osztályok, amikkel megfigyelhető lesz egy változó:


  private ObservableField exampleText;

A fenti exampleText változóban elérhető egy set(String value) metódus, ami beállítja azt és értesíti a felületet a változásról.

Ezek mind alapvető dolgok, de elkezdeni így lehet. Ha belevágsz még több mindennel fogsz találkozni:

  • Saját XML attribútum létrehozása az összekötéshez:
    Ha használsz képletöltőt (Picasso, Glide stb.), akkor az XML-hez egy app:url="" attribútumot hozhatsz létre, ahol csak az URL-t kell megadnod. Egy úgynevezett BindingAdapter-ben lesz a kód, ami letölti és beállítja a képet.
  • RecycleView kezelése
  • XML-ben lévő kód:
    Itt egész bonyolult dolgokat lehet létrehozni, ugyanis logikai operátorok, lambda kifejezések, és még sok minden rendelkezésre áll.

Amikre figyelned kell

  • Logika az XML-ben:
    Figyelni kell, hogy ne rakjon logikát az XML-be senki. Habár erre hiába figyelsz, valaki a csapatból úgyis oda fog rakni valamit.

  • Hibakezelés:
    Az esemény vezéreltség miatt nehezebb keresztül lépkedni a kódon. Egy databinding nevű fekete dobozon keresztül állítod be a felületi elem tulajdonságait, ami tovább nehezítheti a hibakeresést.
    Kisebb, javítható hiányosságok is vannak, amik idegesítőek. Például hibás XML esetén az Android Studio csak sor és oszlopszámot dob ki jelenleg, nem tud odaugrani a hiba helyére.

És most?

Ez csak egy kis segítség a kezdéshez. Ha elkezded alkalmazni, a developer portál databinding cikkében jó segítséget találsz a pontos működéshez.

Amennyiben érdekelnek az ehhez hasonló tervezési minták, akkor ajánlom olvasásra Martin Fowler Patterns of Enterprise Application Architecture című könyvét.

Vaszil Ádám

Már sok-sok éve szeretne programozó lenni, de mivel ennyi nyelvet nehéz megtanulni, ezért inkább csak a Javara koncentrál.

Vaszil Ádám

Hozzászólások