• Marcio Granzotto Rodrigues
  • 4 évvel ezelőtt
  • Kategóriák:vélemény, technikai
  • Címkék:Android, architektúra, fejlesztés, iOS, kotiln, mobil, VIPER

Android fejlesztőként kezdtem, majd később iOS-szel is dolgoztam, és számos különböző projekt architektúrájával kerültem kapcsolatba – néhány jó és néhány rossz.

Boldogan használtam az MVP architektúrát az Androidhoz, amíg nem találkoztam – és nyolc hónapig nem dolgoztam – a VIPER architektúrával egy iOS-projektben. Amikor visszatértem az Androidhoz, úgy döntöttem, hogy adaptálom és implementálom rajta a VIPER-t, annak ellenére, hogy néhány más fejlesztő azt javasolta, hogy nincs értelme iOS architektúrát használni Androidon. Tekintettel az Android és az iOS keretrendszerei közötti alapvető különbségre, felmerült bennem néhány kérdés, hogy mennyire lenne hasznos a VIPER Androidon. Megvalósítható lenne és megérné az erőfeszítést? Kezdjük az alapokkal.

Mi a VIPER?

A VIPER egy tiszta architektúra, amelyet elsősorban iOS-alkalmazások fejlesztésénél használnak. Segít tisztán és szervezetten tartani a kódot, elkerülve a Massive-View-Controller helyzetet.

VIPER a View Interactor Presenter Entity Router rövidítése, amely olyan osztályok, amelyeknek jól meghatározott felelőssége van, az Egyetlen felelősség elvét követve. Erről bővebben ebben a kiváló cikkben olvashatsz.

Android architektúrák

Már van néhány nagyon jó architektúra az Androidhoz. A legismertebbek a Model-View-ViewModel (MVVM) és a Model-View-Presenter (MVP).

A MVVM-nek akkor van sok értelme, ha adatkötéssel együtt használod, és mivel én nem nagyon szeretem az adatkötés ötletét, mindig MVP-t használtam azokban a projektekben, amelyeken dolgoztam. Azonban ahogy nőnek a projektek, a prezenter egy hatalmas osztállyá válhat rengeteg metódussal, ami megnehezíti a karbantartást és a megértést. Ez azért történik, mert rengeteg dologért felelős: kezelnie kell a UI eseményeket, a UI logikát, az üzleti logikát, a hálózatépítést és az adatbázis-lekérdezéseket. Ez sérti az Egyetlen felelősség elvét, amit a VIPER képes orvosolni.

Hozzuk helyre!

Ezeket a problémákat szem előtt tartva kezdtem el egy új Android projektet, és úgy döntöttem, hogy az MVP + Interactort (vagy ha úgy tetszik, a VIPE-t) használom. Ez lehetővé tette számomra, hogy a felelősség egy részét az előadótól az Interactorra helyezzem át. A prezenterre hagyva az UI események kezelését és az Interactortól érkező adatok előkészítését a View-n való megjelenítésre. Ezután az Interactor csak az üzleti logikáért és az adatok DB-kből vagy API-kból való lekérdezéséért felelős.

Az interfészek összekapcsolására is elkezdtem interfészeket használni a modulok összekapcsolására. Így nem tudnak más metódusokhoz hozzáférni, mint amiket az interfészen deklaráltak. Ez védi a struktúrát, és segít az egyes modulok egyértelmű felelősségének meghatározásában, elkerülve az olyan fejlesztői hibákat, mint például a logika rossz helyre helyezése. Így néznek ki az interfészek:

 class LoginContracts { interface View { fun goToHomeScreen(user: User) fun showError(message: String) } interface Presenter { fun onDestroy() fun onLoginButtonPressed(username: String, password: String) } interface Interactor { fun login(username: String, password: String) } interface InteractorOutput { fun onLoginSuccess(user: User) fun onLoginError(message: String) } } 

És itt van néhány kód az interfészeket megvalósító osztályok illusztrálására (Kotlinban van, de Java-ban ugyanígy kell lennie).

 class LoginActivity: BaseActivity, LoginContracts.View { var presenter: LoginContracts.Presenter? = LoginPresenter(this) override fun onCreate() { //... loginButton.setOnClickListener { onLoginButtonClicked() } } override fun onDestroy() { presenter?.onDestroy() presenter = null super.onDestroy() } private fun onLoginButtonClicked() { presenter?.onLoginButtonClicked(usernameEditText.text, passwordEditText.text) } fun goToHomeScreen(user: User) { val intent = Intent(view, HomeActivity::class.java) intent.putExtra(Constants.IntentExtras.USER, user) startActivity(intent) } fun showError(message: String) { //shows the error on a dialog } } class LoginPresenter(var view: LoginContracts.View?): LoginContracts.Presenter, LoginContracts.InteractorOutput { var interactor: LoginContracts.Interactor? = LoginInteractor(this) fun onDestroy() { view = null interactor = null } fun onLoginButtonPressed(username: String, password: String) { interactor?.login(username, password) } fun onLoginSuccess(user: User) { view?.goToNextScreen(user) } fun onLoginError(message: String) { view?.showError(message) } } class LoginInteractor(var output: LoginContracts.InteractorOutput?): LoginContracts.Interactor { fun login(username: String, password: String) { LoginApiManager.login(username, password) ?.subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) ?.subscribe({ //does something with the user, like saving it or the token output?.onLoginSuccess(it) }, { output?.onLoginError(it.message ?: "Error!") }) } } 

A teljes kód elérhető ezen a Gist-en.

Láthatjuk, hogy a modulok indításkor létrejönnek és összekapcsolódnak. Az Activity létrehozásakor inicializálja a Presentert, átadva magát View-ként a konstruktoron. A Presenter ezután inicializálja az Interactort, átadva magát, mint a

InteractorOutput.

Egy iOS VIPER projektben ezt a Router kezelné, létrehozva a UIViewController-t, vagy megkapva azt egy Storyboardból, majd összekötve az összes modult. Androidon azonban nem mi magunk hozzuk létre az Activity-ket: Intenteket kell használnunk, és az újonnan létrehozott Activity-hez nem férünk hozzá az előzőből. Ez segít megelőzni a memóriaszivárgást, de fájdalmas lehet, ha csak adatokat akarunk átadni az új modulnak. Az Intent extráira sem tehetjük rá a Presentert, mert ahhoz Parcelable vagy Serializable kellene. Egyszerűen nem kivitelezhető.

Ez az oka, hogy ennél a projektnél kihagytam a Routert. De vajon ez az ideális eset?

VIPE + Router

A VIPE fenti implementációja megoldotta az MVP legtöbb problémáját, megosztva a Presenter és az Interactor feladatait.

A View azonban nem olyan passzív, mint az iOS VIPER View-ja. Meg kell kezelnie az összes szokásos View responsabilityt, plusz a más modulokhoz való útválasztást. Ez NEM az ő felelőssége kellene, hogy legyen, és ezt jobban is tudjuk csinálni. Enter the Router.

Itt vannak a különbségek a “VIPE” és a VIPER között:

 class LoginContracts { interface View { fun showError(message: String) //fun goToHomeScreen(user: User) //This is no longer a part of the View's responsibilities } interface Router { fun goToHomeScreen(user: User) // Now the router handles it } } class LoginPresenter(var view: LoginContracts.View?): LoginContracts.Presenter, LoginContracts.InteractorOutput { //now the presenter has a instance of the Router and passes the Activity to it on the constructor var router: LoginContracts.Router? = LoginRouter(view as? Activity) //... fun onLoginSuccess(user: User) { router?.goToNextScreen(user) } //... } class LoginRouter(var activity: Activity?): LoginContracts.Router { fun goToHomeScreen(user: User) { val intent = Intent(view, HomeActivity::class.java) intent.putExtra(Constants.IntentExtras.USER, user) activity?.startActivity(intent) } } 

A teljes kód elérhető itt.

Most a View routing logikát áthelyeztük a Routerbe. Csak az Activity egy példányára van szüksége, hogy meg tudja hívni a startActivity metódust. Még mindig nem drótoz össze mindent, mint az iOS VIPER, de legalább tiszteletben tartja az Egyetlen felelősség elvét.

Következtetés

Miáltal, hogy fejlesztettem egy projektet MVP + Interactorral, és segítve egy munkatársamat egy teljes VIPER Android projekt fejlesztésében, nyugodtan mondhatom, hogy az architektúra működik Androidon, és megéri. Az osztályok kisebbek és karbantarthatóbbak lesznek. A fejlesztési folyamatot is irányítja, mert az architektúra egyértelművé teszi, hogy hova kell írni a kódot.

Itt a Cheesecake Labsnél azt tervezzük, hogy a legtöbb új projektnél VIPER-t fogunk használni, így jobb karbantarthatóságot és áttekinthetőbb kódot kapunk. Emellett megkönnyíti az átugrást egy iOS-projektből egy Android-projektbe és fordítva. Természetesen ez egy fejlődő adaptáció, így itt semmi sincs kőbe vésve. Örömmel fogadunk néhány visszajelzést ezzel kapcsolatban!

Articles

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.