• Marcio Granzotto Rodrigues
  • 4 ani în urmă
  • Categorii:Opinie, Tehnică
  • Tags:Android, Arhitectură, dezvoltare, iOS, kotiln, Mobile, VIPER

Începând ca dezvoltator Android și mai târziu lucrând și cu iOS, am avut contact cu mai multe arhitecturi de proiecte diferite – unele bune și altele rele.

Am folosit cu plăcere arhitectura MVP pentru Android până când am întâlnit – și am lucrat opt luni cu – arhitectura VIPER într-un proiect iOS. Când m-am întors la Android, am decis să adaptez și să implementez VIPER pe acesta, în ciuda faptului că alți dezvoltatori au sugerat că nu ar avea sens să folosesc o arhitectură iOS pe Android. Având în vedere diferența fundamentală dintre cadrele de lucru pentru Android și iOS, am avut câteva întrebări cu privire la cât de util ar fi VIPER pentru Android. Ar fi realizabil și ar merita efortul? Să începem cu elementele de bază.

Ce este VIPER?

VIPER este o arhitectură curată folosită în principal în dezvoltarea aplicațiilor iOS. Ajută la menținerea codului curat și organizat, evitând situația Massive-View-Controller.

VIPER înseamnă View Interactor Presenter Entity Router, care sunt clase care au o responsabilitate bine definită, urmând principiul responsabilității unice. Puteți citi mai multe despre aceasta în acest articol excelent.

Arhitecturi Android

Există deja câteva arhitecturi foarte bune pentru Android. Cele mai faimoase fiind Model-View-ViewModel (MVVM) și Model-View-Presenter (MVP).

MVVM are mult sens dacă îl folosești alături de data binding, și cum mie nu-mi place prea mult ideea de data binding, am folosit întotdeauna MVP pentru proiectele la care am lucrat. Cu toate acestea, pe măsură ce proiectele cresc, prezentatorul poate deveni o clasă uriașă cu o mulțime de metode, ceea ce îl face greu de întreținut și de înțeles. Acest lucru se întâmplă pentru că este responsabil pentru o mulțime de lucruri: trebuie să se ocupe de Evenimente UI, logică UI, logică de afaceri, rețea și interogări de baze de date. Acest lucru încalcă principiul responsabilității unice, lucru pe care VIPER îl poate rezolva.

Să-l rezolvăm!

Cu aceste probleme în minte, am început un nou proiect Android și am decis să folosesc MVP + Interactor (sau VIPE, dacă vreți). Acest lucru mi-a permis să mut o parte din responsabilitate de la prezentator la Interactor. Lăsând prezentatorului gestionarea evenimentelor UI și pregătirea datelor care vin de la Interactor pentru a fi afișate în View. Apoi, Interactorul este responsabil doar de logica de afaceri și de preluarea datelor din BD sau API-uri.

De asemenea, am început să folosesc interfețe pentru a lega modulele între ele. În acest fel, ele nu pot accesa alte metode decât cele declarate în interfață. Acest lucru protejează structura și ajută la definirea unei responsabilități clare pentru fiecare modul, evitând greșelile dezvoltatorilor, cum ar fi plasarea logicii în locul greșit. Iată cum arată interfețele:

 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) } } 

Și iată câteva coduri pentru a ilustra clasele care implementează aceste interfețe (este în Kotlin, dar în Java ar trebui să fie la fel).

 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!") }) } } 

Codul complet este disponibil pe acest Gist.

Vezi că modulele sunt create și legate între ele la pornire. Când este creată activitatea, aceasta inițializează Presenterul, trecându-se pe sine ca View pe constructor. Prezentatorul inițializează apoi Interactor, trecându-se pe sine ca InteractorOutput.

Într-un proiect VIPER iOS, acest lucru ar fi gestionat de Router, creând UIViewController, sau obținându-l dintr-un Storyboard, și apoi conectând toate modulele împreună. Dar pe Android nu creăm noi înșine activitățile: trebuie să folosim Intents și nu avem acces la activitatea nou creată de la cea anterioară. Acest lucru ajută la prevenirea scurgerilor de memorie, dar poate fi o pacoste dacă doriți doar să treceți date către noul modul. De asemenea, nu putem pune prezentatorul în extrasul Intent-ului, deoarece ar trebui să fie Parcelable sau Serializable. Este pur și simplu imposibil de făcut.

De aceea, în acest proiect am omis Router-ul. Dar este acesta cazul ideal?

VIPE + Router

Implementarea de mai sus a VIPE a rezolvat cele mai multe dintre problemele MVP-ului, împărțind responsabilitățile prezentatorului cu cele ale interactorului.

Cu toate acestea, vizualizarea nu este la fel de pasivă ca vizualizarea VIPER-ului din iOS. Acesta trebuie să se ocupe de toate responsabilitățile obișnuite ale vizualizării, plus rutarea către alte module. Aceasta NU ar trebui să fie responsabilitatea sa și putem face mai bine. Intrăm în Router.

Iată care sunt diferențele dintre „VIPE” și VIPER:

 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) } } 

Codul complet este disponibil aici.

Acum am mutat logica de rutare a vizualizării în Router. Acesta are nevoie doar de o instanță a activității pentru a putea apela metoda startActivity. Încă nu conectează totul împreună ca VIPER iOS, dar cel puțin respectă principiul responsabilității unice.

Concluzie

Având dezvoltat un proiect cu MVP + Interactor și ajutând un coleg să dezvolte un proiect VIPER Android complet, pot spune cu siguranță că arhitectura funcționează pe Android și că merită. Clasele devin mai mici și mai ușor de întreținut. De asemenea, ghidează procesul de dezvoltare, deoarece arhitectura face clar unde trebuie scris codul.

Aici, la Cheesecake Labs, plănuim să folosim VIPER la majoritatea proiectelor noi, astfel încât să avem o mai bună mentenabilitate și un cod mai clar. De asemenea, este mai ușor să trecem de la un proiect iOS la un proiect Android și invers. Desigur, aceasta este o adaptare în evoluție, așa că nimic aici nu este sculptat în piatră. Apreciem cu plăcere un feedback despre aceasta!

Articles

Lasă un răspuns

Adresa ta de email nu va fi publicată.