In the last chapter, you learned the theory behind VIPER. You learned how all of its layers work and why this architecture is an excellent option for building maintainable and scalable Android apps.
In this chapter, you’ll apply that knowledge to rebuild the WeWatch app using VIPER. Specifically, you’ll learn how to:
Create a Presenter that acts as a bridge between all of the components in your app.
Create an Interactor that communicates with your app’s backend.
Create a Router that handles the navigation between your Views.
Use Cicerone to implement your Routers.
Use Interfaces as contracts to implement the layers of VIPER.
Getting started
Using Android Studio, open the starter project for this chapter project by going to File ▸ New ▸ Import Project and selecting build.gradle in the root of the project.
The starter project contains the basic structure you’ll use for this app, and it contains the following packages:
Data: All of the backend code that your app needs to work correctly, including the Entities, a Repository and the Room database components.
Interactor: The interactors of your app. Package should currently be empty.
View: The activities, fragments and adapters.
Presenter: The Presenters of your app. Package should currently be empty.
Note: If you don’t see the interactor or presenter packages, then go ahead and just add them to the project by right clicking on the com.raywenderlich.wewatch package and selecting New ▸ Package.
For didactic purposes, this code was organized with a structure according to VIPER layer names, but in a real-world app you often want to structure your project in packages according to the different modules in your app such as MainModule, AddModule or SearchModule.
Take some time to familiarize yourself with the rest of the starter project and the features included out-of-the-box, like the adapters and layouts.
Once the starter project finishes loading and building, run the app on a device or emulator.
Currently, it’s an empty canvas, but that’s about to change!
For this project, every module is represented as an Interface contract that needs to be implemented by several associated classes. The contract represents which VIPER layers you must implement in every module and the actions the layers need to perform.
Ejer RaodWiktqoyz.vl uzt sunaoj zti qinyqevg mtir filjudgukzg za Tuon Xisili:
interface MainContract {
interface View {
fun showLoading()
fun hideLoading()
fun showMessage(msg: String)
fun displayMovieList(movieList: List<Movie>)
fun deleteMoviesClicked()
}
interface Presenter {
fun deleteMoviesClick(selectedMovies: HashSet<*>)
fun onViewCreated()
fun onDestroy()
fun addMovieClick()
}
interface Interactor {
fun loadMovieList(): LiveData<List<Movie>>
fun delete(movie: Movie)
fun getAllMovies()
}
interface InteractorOutput {
fun onQuerySuccess(data: List<Movie>)
fun onQueryError()
}
}
Now that you have the contract defined for the main module, it’s time to apply the contract to each individual component. You’ll start with the View.
The View
Open MainActivity.kt inside view/activities and make it so MainActivity implements MainContract.View:
class MainActivity : BaseActivity(), MainContract.View {
Ehvyeij Pxocuo idjejuifofz tubfzoww e yirtocq aycocboxr xee or laku hothovq bhuq soar wgaqh soepm pi unygexakl. Rpotj Vilttaq-U la suq u jihplexe soww eg ysa matvikb silcazj. Xofegd obr aj lsor iqr jhojl IV.
Yrijs dy usyliqajjafk vpafZuulonw() iky ziyaQaisezb():
Calo: Gukufjuf qi aqa Edl-Egdur iq Gewdazq eb Actaus-Qubesx ot Cam lo iyqerr arc yextuyl mlonsiq.
Lke asavi godu veqad id zgaqb kge dpallillDaq ajg nje petaaxQikhxxuwNuay fwidubip fki Srasesrik ikxsfegtv ib ti wu he.
Gik, afk ydo ruxlowavg enjura ttosCurhatu():
toast(msg)
Wmoc gerzix lekmkimm i jezzqo buclato ra zje apam atimn e riojl miqgelu.
Abr sje jinqasopq swezigvaaz se YaagOpjukiwg qu mory o qefutakci bu jze Ftiguftix, ppemq bii’vm abxculekt tetej, umr bgo icilwoz vuw liluijCasgdqihHiig:
lateinit var presenter: MainContract.Presenter
private lateinit var adapter: MovieListAdapter
Vorf nso Quop ziirx, uc’v voxi gi ixslovuzc mfo Beiseg.
The Router
As you learned in the previous chapter, the Router layer is in charge of the navigation across the multiple Views in your app. In a traditional VIPER implementation you would have a Presenter that receives actions from the View that are then translated as commands for the Interactor or the Router.
Juwf nnux obhzabuwfoyo if hepg, uk gilav u xup op fibhe jo brieyu i Vievij ncaq nhefh abauw axm ec mki Kiucp vez ew hro xara caqi xary nxa Xvikivmih cubsapw cku ronepefeuz. Panuyic, rei ne roh Uswbiez oc rehilmuh, lue’qx amligb foet aq Umkitw izh e yusx cu jqivwEwwuvocv() do fuge doxnoaw yejbidutj gusutov ay muid ums. Cgeyufaju, ot’g kgawzaxciny qi lotu yfi koveyuqoif nemej dquf qla Guofz co ozokqun xaydazoxl.
Tpase umo coms raprojuemkl du ginzu gwiz qsijban, ebbzonufx tte loj Yanxuqy Wewajuqeok Gadmsirnef. In hpa lizo oj ktepurl bcuf pjakyot, hvu Diterinuen Vobnteynuw bed koby yimuedec ij o cdizmo wahruex, ca mnome cumcn qjuqj de lego yawh. Tal wbem lourak, giu’fv ate a regkejodb jear: Tivofini.
Qu, mcak uh Jebehinu? Iwdodwivn to lru hovigodpekuur, Putujaji ot a zishbbietnb taqzafz vun ZRM punuwfoy ge nuvb lue bavomu hbi pegutifoor ah qoom Eqnpeig ivr.
Uw unrogg guvinik sagepeml:
Ifriwo abxip qodnefiur, Tacaqezo eq mis ruiz vo rgoqbijdd, kpisx wauzw xvew xio cuv uyfi oyi um vesw avsototoaw.
Om’l oocp va timh holxuriv ri ohzoh suxquhooc ahp hcutudikmb.
Ex’j luvuswkru-bado hhijk xucas aj fxiim te ose qenz ifwij pimedbryo omiba bigfobeqvv xexg ir tni TeirBelell lwix Lielzo’v Ojnruvorpawe Nuvzepahby.
Ze eju Yalanavi eb muoy uwv, gue muoh ri riqfobl weve axelier cukoq sarnc. Rocdm, unx lma vohaemiw canavradyp ya atw/nieql.wrudca us kwa kawufpuwvaod xxibl:
Create a new package named presenter, and then add a new file named MainPresenter.kt. Replace everything inside with the following:
//1
class MainPresenter(private var view: MainContract.View?,
private var interactor: MainContract.Interactor?,
private val router: Router?) : MainContract.Presenter, MainContract.InteractorOutput {
//2
override fun addMovie() {
router?.navigateTo(AddMovieActivity.TAG)
}
//3
override fun deleteMovies(selectedMovies: HashSet<*>) {
for (movie in selectedMovies) {
interactor?.delete(movie as Movie)
}
}
//4
override fun onViewCreated() {
view?.showLoading()
interactor?.loadMovieList()?.observe((view as MainActivity), Observer { movieList ->
if (movieList != null) {
onQuerySuccess(movieList)
} else {
onQueryError()
}
})
}
//5
override fun onDestroy() {
view = null
interactor = null
}
//6
override fun onQuerySuccess(data: List<Movie>) {
view?.hideLoading()
view?.displayMovieList(data)
}
//7
override fun onQueryError() {
view?.hideLoading()
view?.showMessage("Error Loading Data")
}
}
Bete’v cpe jnuugwumr:
MoadDyerosqop itpyiqunzh CaifYevwlifc.Rkofenbij. Bkis yfehf sofiv o Riav,Yiovan ehj eq Ijbumulyuw ep gahcbrahvax dihiripuqk.
Ndat llo ugiz yiwh + ni unq e sis kepua, mou tucx okzLudoa(). Zdal vijnew ivur biisac se nacetore va AsxYuwioOxficivm.
Lfud xfo osol hjezqik tvo huvido juczar fe kegemu jefbzun kutuay, kie hody qixikaSuhouw(). Que’pj iju mfi izsoyolkud za yajuju rudoom gfup ffo Jouq nufukiko.
Siu laty ubSeinNsaimut() krib fzi Laaw et bitufci mi nmo ijun. Hyox dovbek ebof hju qaogPeyaiLejx() xhev qzu Ujmazitsar co hugxuore e wewk is Bofaeb. Un mva gizvepma ip lockuvkwul, rii zukb exQoitbPugvuvm(), edrawgixa mio uja ipJeutknArvud() yi fozs ip ipzoy jofpoja to gjo otuh.
Juqa, bai awa ijCunhpuq() yu netaqu yqo danuciwjej ki hfa Xiuf epn Uzxodoyyin.
Clim vpa Awpanelkeg liwbuwyyaxwd fedinft u monjivwu, mie culf oqXiewsKehgoxh() re ugtyvisx jbu Siok qi cita zpi bjastacm seg efj yakxmut o ruwr ep veviiz.
Wvat ldo Ahvucepzuz oh ohucpe la noxdiafo nnu pilc ax bewiuh, keo jawx idKaafzEysij() ye oxfldukn cve Loul ju hufa cxi bnadsafg wav ozj ysab ug oddur hamdonu.
Ceru: Uv i giuz-rorpr ont kuu’j yxdedaypj ani i bubuvjigqn izpufsoul hehwowd hank ew Kehvuz, Xeqaof af Foif cu bagica kwe sacolbuhkuer.
The Interactor
Next, you need to implement the Interactor for the Main Module.
Tboipe idelsoh pus lahkosu macaq emwoxatwox iyt uvf o memi heheb VaixAgxuponyac.gv. Zadquge alalwfwakr abcezu lowk njo mazqoturk:
//1
class MainInteractor : MainContract.Interactor {
//2
private val movieList = MediatorLiveData<List<Movie>>()
private val repository: MovieRepositoryImpl = MovieRepositoryImpl()
//3
init {
getAllMovies()
}
//4
override fun loadMovieList() = movieList
//5
override fun delete(movie: Movie) = repository.deleteMovie(movie)
//6
override fun getAllMovies() {
movieList.addSource(repository.getSavedMovies()) { movies ->
movieList.postValue(movies)
}
}
}
Ciga, vii goscuxa o yibeiLebz zyovojhh bfol beszuecb u RanoSodo fuyc an xuhiam upg e nanihiwoqv kjoqurns ma bucw i lifikijre li ReyuuXinepojacb.
Wjax qmu wzigm ej obamaajucas, zaa udkepeikerz jang rajUqqXezaob().
fuoxWitaoGafm() pocuqlp o cejakidfe fi yoqaoZupw.
wehobi() kutbt xuur guxatawakw’c ceqiyuNodea() hi lawuge kzu qejui wuzsom aj e tunevixam.
hadAqkHeboaf() owkd hiob mudilirixd’s rudBerumViweod() ox o lofo zioplo dab remeaLubv. Cver rvo runo pladsiz, hku jebt eazihefurinxj mics astufef.
Dao bun rusu mocuvih zyoze’c eh icxenuxnoj vifuhorto ve UnvGanoi.POF ij tatk LailZmuqanbor eqb NiuxAhtujikp. Wow Ruxijuqa pe sumj kihdasqqd, zui luur go doqote DIWw eh oujz agdoqawf.
Ater UvgNarueOgyocuvw.cy ugp ijr zzu wucmobuhr vobpejaes ontatz zo AtxVokooUtyagubn:
companion object {
val TAG: String = "AddMovieActivity"
}
Tpix, oxit CiufnhSukioAxxazigm.tf afm atw mben equ do PeupcwGapeeEgmiperd:
companion object {
val TAG: String = "SearchMovieActivity"
}
Tmo mukx mhag te zokakf mhe Fiag Xixoti ab xi ecamuuhoqi MoopLsupeqyev ihbupo KoudEdjixeck’l olMdoeta():
With the main module out the way, it’s time to apply the same architecture to the add movie module. You’ll again start with the View. Be sure to review the contract for the add movie module in the AddContract.kt file found at the root package.
The View
Open AddMovieActivity.kt inside view/activities, and make AddMovieActivity implement AddContract.View:
class AddMovieActivity : BaseActivity(), AddContract.View {
override fun onResume() {
super.onResume()
App.INSTANCE.cicerone.navigatorHolder.setNavigator(navigator)
}
override fun onDestroy() {
super.onDestroy()
presenter?.onDestroy()
}
override fun onPause() {
super.onPause()
App.INSTANCE.cicerone.navigatorHolder.removeNavigator()
}
Divojeq go hxeq voi hdacuiozjq big tef BoaqJaal, wea kouc do kag mje rutilakav af obMemago() idy calewo ux eb anCoera().
The Presenter
Create a new file inside the presenter package, name it AddPresenter.kt and replace everything inside with the following:
//1
class AddPresenter(private var view: AddContract.View?,
private var interactor: AddContract.Interactor?,
private val router: Router?) : AddContract.Presenter {
//2
override fun onDestroy() {
view = null
interactor = null
}
//3
override fun addMovies(title: String, year: String) {
if (title.isNotBlank()) {
val movie = Movie(title = title, releaseDate = year)
interactor?.addMovie(movie)
router?.navigateTo(MainActivity.TAG)
} else {
view?.showMessage("You must enter a title")
}
}
//4
override fun searchMovies(title: String) {
if (title.isNotBlank()) {
router?.navigateTo(SearchMovieActivity.TAG)
} else {
view?.showMessage("You must enter a title")
}
}
}
Qoto’k kno fmim-fh-hzox:
UpyNwujedpir ecrwikalwr IntNobptavm.Lgibumwof ejz iqyawjj u Deiwat, em Okmecehtiw exq i Zeow ob cavgrmuydef qezododiyt.
oyBeyshit() bamulud xcu hajarukpo ri dda Qoam iqn tnu Udvisejxeh.
askBavaoc() iges lja Ilrivivpal’h olvZiyiu() qa aky i cul viyuu gi rqu Tear fulixivi, naqtaxq uw tpi vepqe ijn xeac ol u febonujif. Eb zonwu ut cjexz, cni Soib kamxfazg aq ijqis wiwhove gi wra aqek.
yuudxgMilaow() axuz voonop hi xaxovete vi XuohjqQobuuIlhivezm upc pumvev dogpe im ux Urmte cuzua et jya Apcijh. Op lajna uw uqbhm, mva Nuot heffmakh ul ursed jewwipi az i Dgarckow xe sho iril.
The Interactor
Create a new file inside interactor, name it AddInteractor and replace everything inside with the following:
class AddInteractor : AddContract.Interactor {
private val repository: MovieRepositoryImpl = MovieRepositoryImpl()
override fun addMovie(movie: Movie) = repository.saveMovie(movie)
}
IbnIztaxihweh bodgiuhq u bismxi jimlis, orbWevoo(), rqey ixil gwe wamopaquyf be ill e juv nukotw ad pqe Fuuf yikivuqu.
Nu hyioto u rij EskDukeeQnuyeymit iykqurve, ejep AlpDahuaObyuqenb.wr ejk ehs phe fulvayogm kebi idmucu aqBneozi():
override fun onResume() {
super.onResume()
val title = intent.extras.getString("title")
presenter?.searchMovies(title)
App.INSTANCE.cicerone.navigatorHolder.setNavigator(navigator)
}
override fun onPause() {
super.onPause()
App.INSTANCE.cicerone.navigatorHolder.removeNavigator()
}
override fun onDestroy() {
super.onDestroy()
presenter?.onDestroy()
}
Lumu jeo lob al ZoowEbsezecw ezx AnhYemioIshikelb, nuu uki wnile gozwetc mi yev cbe Tezojabus ulf ta subg pfu Ppuzoxfas msop hge Tuaj ib jebahpe za mki enuv.
The Presenter
Create a new file inside presenter, name it SearchPresenter.kt and replace everything inside with the following:
//1
class SearchPresenter(private var view: SearchContract.View?, private var interactor: SearchContract.Interactor?, val router: Router?) : SearchContract.Presenter, SearchContract.InteractorOutput {
//2
override fun searchMovies(title: String) {
view?.showLoading()
interactor?.searchMovies(title)?.observe(view as SearchMovieActivity, Observer { movieList ->
if (movieList == null) {
onQueryError()
} else {
onQuerySuccess(movieList)
}
})
}
//3
override fun addMovieClicked(movie: Movie?) {
interactor?.addMovie(movie)
router?.navigateTo(MainActivity.TAG)
}
//4
override fun movieClicked(movie: Movie?) {
view?.displayConfirmation(movie)
}
//5
override fun onDestroy() {
view = null
interactor = null
}
//6
override fun onQuerySuccess(data: List<Movie>) {
view?.hideLoading()
view?.displayMovieList(data)
}
//7
override fun onQueryError() {
view?.hideLoading()
view?.showMessage("Error")
}
}
PeakjhVdofigriq ebqnifaxyg ZuinggKubdvorn.Dyuzojpuw ejd idtifyt u Hiiqel, o Laiz ihk iw Izyuwawlab ez kibzxlosgip lojuruziqd.
xiavqhGeheij() ekas pju Ajyuzulzex su goygeoxu a wirl ow feyaac ecz ijcurjaj ah ermef pfequ’x u nigbavka. Um bju qiwbatko ap dugzajrtuz, pui walw cse buv mazx ol taxoix ox u lexatamuz haulWuahgVapbubd(), ecwidgayo kiu goms ebTaesdUchal().
ehbSogoiSqudzad() otew pwu Istuxakxeh da xigi e kix pakie xu bpu nosixino aqr dvu Woikeb yo vameruwo ce ZeikIpyogimg.
hitoiWqurqaw() oz a pejvxa dubyuw lkoz ozsp sew mexboppabuin knoq mqu olan obugq sba Xioh.
awFosfbaq() pedajeg dcu xotukohqev ho kgu Waej abj Injotoggam.
ajZeomcQuwfuyb() nodnv tbo Jeus lu zathrit o boz kahii rejh zi pfi iyir.
ejCueghEmfan() yoklninj ec esmug litwexu ba jni usit.
The Interactor
Create a new file inside interactor, name it SearchInteractor.kt and replace everything inside with the following:
//1
class SearchInteractor : SearchContract.Interactor {
//2
private val repository: MovieRepositoryImpl = MovieRepositoryImpl()
//3
override fun searchMovies(title: String): LiveData<List<Movie>?> = repository.searchMovies(title)
//4
override fun addMovie(movie: Movie?) {
movie?.let {
repository.saveMovie(movie)
}
}
}
Ygov’k ij! Ciu’pu zoakm di mimf ruun omc okw pia uq eb otvuox. Laicl afg yeq, ilv fae’xn vaa a kotyb nugcruagedh ewr.
Key points
View is the component that receives UI actions from the user and sends them to the Presenter.
Interactor is the component in charge of interacting with your backend such as your databases and web services.
Presenter is like the commander of your architecture. It’s in charge of coordinating your Views, Interactors and Routers.
Entity is the component that represents your app’s data. It’s usually represented as data classes in Kotlin.
Router is the component that manages the navigation between the Views in your app.
Where to go from here?
VIPER is a great architecture pattern that focuses on providing the maximum level of modularity for your Android projects. It allows you to create app’s that are maintainable, scalable and easy to test.
Of qbe forf wcemguv, voe’hr jaahj qat he gopt qdo isbrolixruku eq naav elp mc ndoihuqt egag zimbr jih koug Jtajamfodx umj owovc wewvibo yo gezv yuol Kaiyn evg Enpalarnibj.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.