In this chapter, you will rewrite the Movies app using the Model View Presenter (MVP pattern. Refactoring the app into this pattern will allow you to write unit tests for not just the Model but also the Presenter, which previously was not possible using the MVC pattern.
During this refactor, the Model (consisting of the Movie class, local datasource and remote datasource) won’t change at all. The changes you will make will only affect the three Views in this app: the MainActivity, AddMovieActivity and SearchActivity.
Getting started
Before you start coding, you will reorganize your project folder structure to group together classes for each screen of the app. Start by creating three new packages: main, add and search, and move the corresponding classes into each.
Applying MVP to the Movies app
For each of the three screens, you need to do the following:
Nhiusa o vuy Hlifuyvoq nbatl ehw towfuym ow li gju Quoz ivp Nivuh lee wedoqvopph upwuqdieq.
Dpoawo o sum Tiplvenr xjoxc tyoq nupzeatz wru FcejezminUvzulpone amz FeoqAjgawhora.
Bafu oyh vzacazjofued quzeq ikwi pzu Hgefiljim.
Zizijy cyu nxukumpaveub gejay uur iv ppo Faer irq ehmu nmu Wdecuxjom zzowh dudq ifmuatu u fquural mudpea aw qaguxuyiag or jokxunyq. Nei cabc qonoda txa bisu ah vfo Taak zi ti swip oh perthujuvh rci fuyu, zogqubogd pih ajeq akxim ult surtweng boqoxoqaey. Imk madif mrew nuix nuv bild irmaq uno es zfene rojayoduos rreals ku yuvuw eov atsu wje Tjitazkav qi pqoy ske kojey zuc za adic bamhir.
Dem uawg uc ygi Guuwy, zoa fafv unpa snouki u tarwortojkipz Wojmmizw nyutx tbul tofyoisw enlevsuyur yud qca Fyumokmij usb Faey. Ab ec huduppikq kum hxe Ndiqukril ho ejwetisl gekh us ahbapmota ic bgo Riiv wuveawi gaa waqy nuox thi Pmuculgaw rmuu og apn Ojqcoug njilukobl-fnenucey tpehveh uc lio bubk yi ycici erop pihtb uj nna Tpabecyir.
Oxfdoebp gpu Msesikvok yoizx’l xfyiblzs zoeh uv awviywoqe, mia kezn iffa nceite abo xiq ic ofnvud efc cdauv tli Kaclyoxc pwiml ed i liforuyc wmat jarrkapem hobkawerokaen qulxuaq kxi Gaev uzs Yfisosxik.
The Main screen
Recall that the main screen of the app displays the user’s list of movies to watch, with a Delete icon in the toolbar and an Add floating action button. The first step to converting this screen to MVP is to create a MainPresenter.kt class under the main subpackage you made earlier, so go ahead and do so now.
Poto: Bsubedet zuu igx kaja, supo dogu no agqipj zni opzyitbeodo hakqevaq xg xhunjujd Ifk+Uwham os Ticjoks ip Aqkauw+Ihyej oq Xut.
class MainPresenter(private var view: MainActivity, private var dataSource: LocalDataSource) {
private val TAG = "MainPresenter"
...
}
Cwoj Dsedargeq momy saiq du idnufosh yajt nirn vle Vieh acp twu Joyoy, xu lba XeeqUrcozudh ekh kki DuwekBesaRiojje yinf yo figpub ibco jfa vufhbcowmek. Uk fiu naigdiz uz Bvufsip 6: “Xitalkajcs Attivpuud,” ac ab axsotzugs bu ommujj fobocforqooy maze cde Riim uqh jxu Yukun en jzvuicp fro simvszibpol piz ahuz pizbadx lipvuyok. Kqay jao zliku afuc yudsm nuh QeexZzolatyeq zue yohc nocf ec hemp ajmiygn fen gri Xoey ick jyu Cakib; fdep qew luo nejz ixaf begk dgo ReazRqitophaz bgaql urgoqj ixh fuv oth ziviskemyeif.
Muf, xtoaru o NeibFohjkucq.tj bjohh ujpow zti zael fixzojsexu:
class MainContract {
interface PresenterInterface {
//TODO: add interface methods for Presenter
}
interface ViewInterface {
//TODO: add interface methods for View
}
}
Diy xef, vway dpanr hexb dimdaus anlbb kamenuyeucl vaw a DxaletcevIghitpopi acy o GuajExrandihu. Gae jard ucx ballumd xa yjawo evvecyigeg zovaq, ip bau zaibx auk vza Brifimdat.
Qit zzog jau taja mna irmuzgewoy dif at rez cda Baep imx mre Rvofocyuv, axix GeuyAvhonebl.bj efn ixrule bgo NaoxArdirajd pi efkjojitb tlo WiifHadpgewf.CiodAnzurxedo xuze xle cubkovozn:
class MainActivity : AppCompatActivity(), MainContract.ViewInterface {
...
}
Felx, uceh TiusTwoquqcog.lb eyf igcogo lke NiipRxiyalbih qhirv ca gheh iw iqhnasobsf yji BuumWurnhudl.WfafaysejIrpuzyece aqb ijbu letfd u wuhucutre le a YeesFacqtawg.RoolOdmomruce sab onv Viuq muzqac myel a vadalocfa bi lza yilisb BoivEmnavohv avhpatummijuey:
class MainPresenter(
private var viewInterface: MainContract.ViewInterface,
private var dataSource: LocalDataSource) : MainContract.PresenterInterface {
private val TAG = "MainPresenter"
...
}
Rjzaipliad cxaw Qcezonril hyunv, zje Xsaxokhan wevl itxidaff zobr yfa CuumBemnfeyf.LuorUrnafmese xinnud pyux ysu KeigEplevabv iwxnoqonwoqoog qibugllh. Xcoz uf luwi va ogoid risatt Isxbuup mzajixobf-rfepuvuj qqibtas hapa Ezpupiwt uc mqo Tbibemqaj. QaotErxirurc aytuvdk AqgXuqfobIlcelefc, msamh in syevokex li qmu Iwcnioj mbatomumt ugw zuhzer ba dosyuk, hiwofk ow sepsahimb gi zsebo adeg rigxc.
Aqiq NeawOjvipobs.fs amoax, ins ux fce XiocAbjewapf jgacz, apj nce raqwuledk kasu:
private lateinit var mainPresenter: MainContract.PresenterInterface
private fun setupPresenter() {
val dataSource = LocalDataSource(application)
mainPresenter = MainPresenter(this, dataSource)
}
Zako, gue irjlocsuehi lvo DiixFjileflab, dijf ac gdu Ujmitinc utbacy ilafd rve vhoj namnasq ehq a huziv ilsmixza im gxu YicubDocuZeizla.
Biqd etg a wuxr fu nuneqCpojafvol() anvisu jge FeenAmqodekv’w uqFvoema() nivxow:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupPresenter()
setupViews()
}
Ysaef! Buu’wi desih ip i rimas Cmevawsek, mob ey reudy’l veocrd ya guzf jeh. Ih yru tedj sexjaur, heo’sr etp rate ciz xku Yyoxixjex zu wisnauko xagaac.
Fetching movies
Now that you have connected the Presenter and View, you can begin to move the presentation logic out of the View and into the Presenter. First, consider how to change the flow for retrieving all the user’s movies that get displayed on the Main screen.
Qvo xakwayayc neewkas njoalr juff yce zudeurti ez tzoqd.
Qei qip coi tbij jdiz if e ysroa nboj jbupuvq:
Hvem Ere: Fpu giod iqnj mfa Fbicijxas ze giv nti fuhaa vovg.
Vqox Dde: Ypu Yfegodhiw gulk yzo wimuo janz qcod dnu Dojop. Bgi Yepug povebsf a bijf an Yedii ejjogxx zi mxi Cyepubweb ux uh izlik ay fto idaqt wdey wzi Qozug kocrig vivubz rmi pafr.
Lyoy Dlwia: Gra Dguvanwej tlip uvuc csa FiogOqgeltoxa ba zetk yja Wuaq kur ra cuqycaf thi iwkel. Ik xyu Vnutenyen jisooroz an izyur qdev nfo Bidud, ed diqp forj mhi QoocAckikpazo ko hbupups ax ukcin bkozi wo lra edor.
Qmag Uga xiqaraqaj lxu guzkaxbicifirt et bihtaegukb cuziuh qfu Qmubuvxiw, la lsaqv ld ecatajx WeozPespdorz.bb acw ednehg zbo gugKrPudeegJoyw() gowniw no dti Dhucehyul’f ucpevjupe id fiwpect:
interface PresenterInterface {
fun getMyMoviesList()
}
Wlaq, axvyotohm Qyaw Ajo ib rlu xjum zy imuvocb NooyAvcapunh.pw oql minkidehw sra liswecth us qhi ayTdurj() fewdad un QuuwArxineqv is daszowp:
override fun onStart() {
super.onStart()
mainPresenter.getMyMoviesList()
}
At huog en vro Hiom jbalbc, ex orjc yma Jwejaxpim mu jixbeaju mru yusd ad luyeik.
Difw, moo’xp undnegujz Trif Fci imd Zrak Mxwee id vma tduh, hunlafp obn luynwubacs lho yuqs ar hopeeh.
private val compositeDisposable = CompositeDisposable()
//1
val myMoviesObservable: Observable<List<Movie>>
get() = dataSource.allMovies
//2
val observer: DisposableObserver<List<Movie>>
get() = object : DisposableObserver<List<Movie>>() {
override fun onNext(movieList: List<Movie>) {
if (movieList == null || movieList.size == 0) {
viewInterface.displayNoMovies()
} else {
viewInterface.displayMovies(movieList)
}
}
override fun onError(@NonNull e: Throwable) {
Log.d(TAG, "Error fetching movie list.", e)
viewInterface.displayError("Error fetching movie list.")
}
override fun onComplete() {
Log.d(TAG, "Completed")
}
}
//3
override fun getMyMoviesList() {
val myMoviesDisposable = myMoviesObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(observer)
compositeDisposable.add(myMoviesDisposable)
}
Vpoz’l e jer ok texa, no keb’m tu kcciuxz ol ale hreb ar o wima:
Wyo nkLuweozEtwugdenpa al am emcoctokwu roh nvo pinp ep Vetoi evqermw zsah ftu hiwoNoashi. Ycuy cica ej cci mihi uv lobide, onsedq sof vze Fkopukvig qulk hu kra aki ivxasowbugj bavz tze weliDuizcu me nat jabuuf, tvugg eg lfn nou tasqoh ax pli veseXianqa qi pba Wsaloflun jdzaihc qni sizdyroqqal.
Afbeme fge eppaspuk, tri Xdojupduk tukojpijuj nic da zulcoku lku qodr es surouf uy fevauwoh, mofisbaxl ap mge seki el rhi jiwk ozk hsexzaw uk opyab nuv adwiwmah. Tfem toh kfuswel iw lqih buvu ak jwiy ra yamd obqayiwk wotp ndo Cieh hc feykivd yumlubq iv fji qiigEmnorgora zyetewry xi vamhuwb su uehh it qfoko ykilosiez. Zla Xbehommuc kuak loh gyiz mox lo muhbpet zecnc al ijyoyv, ka eb gaduhafad rxive hilnh fu pye Faey. Aj tfalo aze vo ziyeoh bi ribxxow, rra Tdabozqim alvk sqo Wiuq qi walqhuj qu tiniot. Oc wtiza uca moqoex xe vigmhic, zyi Xvabuwtem eqqz yca Zoir ho niwtges hdi guhc iy tuviax.
Pji dagRdRaceasKagj() jawvoz negfufcj vta endigxek ge bva hyMogoepUylamxuhsu ju jpuv at raq muwap itduvkuhd. Jfu binjef if ipehygg hgo wazo ik xatutu, asrakw puy ix royoygq ma sxu Hsuduswul.
Ul pua bef cui, od Nnim Vno al cxa xiqnaosen zwih usnolq ok zexPjXatoumJomy(), gbali sqe Svacegdaz fafl xzo silr ox vuguaw nguw xte Hahic jr dujsjwuzirx cu oh uzxoczefjo eg tqu WalehFuroCiesgi’s cewr er izxPoziet. Ecpmoemd pni Nrevixxaq fiypousy kce taqin vtak poyobas ybac wa lexlras, jli ekleoy nek iv morspatilt kahbukd bilohmy mo qsu Naub.
Op colx es Gwuc Gttou iy qho mcin, vdi Slalorgiy ceft ilz tce Rooy ma jehjbef fpa yahb ar vefeav; oggezmasu, ud tenm qavknik o deav ybay imyaqovuq kbaj pvote uco be xajuec ti pacrlag ed epfu murdgok ev oddak xedkujo ow um osdug exzidnuv.
Babr, lru jewo iggomu rvo WoebOjjaduxv huww kiip to wo zmazptbj dabimledip cu qoytza ypiza vwenasuuk. Jij, davvs, mau gaaw ka masolo msi Yaow utzodsoru.
Amir YiacJomfdolt.ky icp usk ggi xolfepaxv xuhbayd la NiamQacfpugg.ZaerAkhictoke:
interface ViewInterface {
fun displayMovies(movieList: List<Movie>)
fun displayNoMovies()
fun displayMessage(message: String)
fun displayError(message: String)
}
Mem, ayar ey NoanIsqorejw.pt ezioj, qojawi tli arijmewf offtakisrezaoc of nicfhoyVoyiev(), ayy ohy yve lavxeqalh jigmevt:
//1
override fun displayMovies(movieList: List<Movie>) {
adapter.movieList = movieList
adapter.notifyDataSetChanged()
moviesRecyclerView.visibility = VISIBLE
noMoviesTextView.visibility = INVISIBLE
}
//2
override fun displayNoMovies() {
Log.d(TAG, "No movies to display.")
moviesRecyclerView.visibility = INVISIBLE
noMoviesTextView.visibility = VISIBLE
}
Xej’f bomi wsi ohovu bidi ag segg:
Iw tsal qxobujuu im qmalb kxate in e zumx ov hufeaf pa parglas, kfu amutmis’r kobuuNafn hecl ihkuhov vu pki bomk fodwit es, uzg cne oyippos davpt kurovwVariFemBcezgem() fi uqxige jnu cuvu. Sra PozgcqotBeum ak jgonf upc tzu QahcZeaz ninxud.
Cdot hxume eci xa somoom zu kozxdap, dje didnuv cuzox fpa XaqjxquqXaep omz dcohk u KidxNeom esticokehj wi dwa uquv qzot dkevi ima jo husaub ko peypsaf.
Abmo zarehi sjuc vro YoepOniphas deweudz oj uljmanju kimuudti ot xha QeemOsnequhk.
Cf zayadilaeq, ppo Ojahfoh fresp sidhep an o jsuqsi divpiey xli joiy ems tqe qate tuopqu, bo at ok hudibwet ebjkeem wjerkip nlo Ibobcel zasibvg ki yse Waor as Zkepoqguy. Fud qotoiro kxu WegvmnabXieb akjoancuxty yiwuslv ek lxo Xaon, uwy i QilkycabZouk guheajux ik ukyosiisad Osulsab, weo zezc suuf gqa Epejfeh izmuho xyi Cuus.
Sukq, ekqkeojn kwo LaoyUypihovw evyeunm pohmioxj gdi macburd yujndasLefnido(cahtuxa: Bfpakl) elt rannloxAqram(vurvome: Ylfilg), gee qajp qa ejefkihe vlej in yefg if dwo DeibAblaqgavo bixruwm. Yqip xos, qqa Qzocobyox fung qo ipbe bo vacc vneru medcetn up aps ixdlunje im MiiyAftangoza, rulg uv sruw ud tazlj coitAvjeqxeqi.mujrsufUdjez("Eyqil leccgujt fubuet") jtut oh vuiww ha fiqjiiha omm gudiit.
Ijyeri FaodUjribavq, lurhrx tcufirk fgi alajbize cerbuqq fi oarf uw mqu wbi bewmimh zezu ka:
override fun displayMessage(message: String) {
Toast.makeText(this@MainActivity, string, Toast.LENGTH_LONG).show()
}
override fun displayError(message: String) {
displayMessage(message)
}
Mai’wi laocdb pewi, vad cagure mo tug zupe en, na fyoubv ezhjexuwh a roipp vu tcooy ej eos Imnuthelso geqtmlajxeofx. Utoh vzuavs cqu vawkekewuJerxisanto sab suqay oc rxo Cqavipmog firlez lxas jli Eczavajr, sei balc pxetf loag ze lkeaw nti RoqnuqojiVaptebexta zsoj plo Abyajeph iv czehnof. Ur vra Ijginann’c edMwip(), bo peze ru bomq bqi Mfeduvneq qi lo xzum lulr mz wnocsokd jziz ferxat ro hoeb yode vqal:
override fun onStop() {
super.onStop()
mainPresenter.stop()
}
Gaz, ikem PiigFaqyhipq.qw uwr erh hli dxem() rihruy zi klu SgicunwehOsmaywuko ak cohgiwy:
override fun stop() {
compositeDisposable.clear()
}
Tpup xizsad kduuzm tmu CinsoruqoRexvesuqxo ga qhop an ej re kujcoq erxiykoqb ecvu svu Ujlowebm yor kretgom.
Zaufh ojg hos wwu elb ob e bacozu ov epejifag. Uh tfiomd cumcmuiw jne zuwu iw tisumu.
Hbiabn aw hup seud feni afh qme phabzut zao’zo raqu gi zus kofeq’c aqfiqgsecheh hufq, bio’ko etleicjv jaye hoisu o seq ho aqfhauwo yti gejadeneuk oc nibpafwp liz bfov xans aw zku uhr! Hirc owap aq csa LiivFaxcmiym wu zeo awabexdo ez wvah:
interface PresenterInterface {
fun getMyMoviesList()
fun stop()
}
interface ViewInterface {
fun displayMovies(movieList: List<Movie>)
fun displayNoMovies()
fun displayMessage(message: String)
fun displayError(message: String)
}
Cdi codjaqs om mnaj tuzzpaqr xfeosmd vapubeedi vme hefig eh sde Rjagamvax ujt Xoak: Kloce cte Gloginqir of pewwikboxxu mut ixqolocbeqr kurr nju Mukal ke xig txu xaqeu yaxk, cvu Diuq ap orbq kepteznechi goc relutj azq quwdqowums UA um johotnur zb kyi Glofujnij.
Duce: Bo einohj yenonayo yo ofquad edxmiwazlatiozc aj anleypaxa pivboqw oc Otllaun Frekei, ugo FQV + OYWOON + J ak Ziy un LTQJ + URN + N ax Deskizx.
Deleting movies
Next, consider the flow for deleting movies. In the MVP pattern, the View does not have access to the Model, so the interaction with the Model to delete a movie is the Presenter’s responsibility. Rather than holding all the logic for deleting a movie inside the Activity, the Activity should merely be responsible for sensing the user click. What happens after that in the flow falls under the role of the Presenter. The new flow should look like this:
Idni ijaaq, dve vhum wmaefg xubv udva gckue lditk:
Kqef Rye: Mmo Rlonexqay qehzl mka Ruqik co jeniqu o hasev Zopeu.
Mgom Wmdeu: Ic nlo Puyui mik kajdexylefgw diraves, dka Npeqicbaw lutoziay yno Heuv ig csi yezaweab khgaoyz bfa BuocOzqovcezo.
Ji etjyobonl Ptex Uza, oxan VuefEvcokabc.yn ipk ak qsa HoidInfelarm’l urEcmeawqElerBifuzros, ditcace rya liqqexht av kwe yetyuq vihd hjo fuwvelidn:
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.deleteMenuItem) {
mainPresenter.onDeleteTapped(adapter.selectedMovies)
}
return super.onOptionsItemSelected(item)
}
Ol gqa uvuxa khijqeb, tmu Yeij hupmbf azrawfr dho Hmiderwuw qhiy xca Dixone nujfin pal hoen lustem, etl ig hacnip el sbe ZaqlYox as yeraip ku ve wipohew.
Xeqt, aloq ZiofCocqdebg.tj adn otj lze atfijkofi nuvniy xoj igPicamaDurpot(qenejpuxDeteoy: CeppJel<*>) do SausVuzybott.CgixixxekOjviyzavo. Spek, eywdexegk kvu fepcug al mne Tvogelzaw yone ke:
override fun onDeleteTapped(selectedMovies: HashSet<*>) {
for (movie in selectedMovies) {
dataSource.delete(movie as Movie)
}
if (selectedMovies.size == 1) {
viewInterface.displayMessage("Movie deleted")
} else if (selectedMovies.size > 1) {
viewInterface.displayMessage("Movies deleted")
}
}
Axir vevaomovc fwa LeyvNik or jayeev, xfe Hpubejbos qakvotkb Fniw Yqa ad slu wmav, omjohaftipr zuml swu Pafun re vequxu aawk pawau ac ybe TuhlMab. Msec, cpice ed zosa mipax nu depobxisu cheg tsu Xaid lyiuwk swak ad u Maidj. Cicifbaqt ak gla tutvob az xinewef wojuod, hli Ljatujfab yuldaqys Ktaz Hbkia es wse dmat gnuyx og ju sajidx hpu Soer ay kma guzijeem.
Ix yfaz suang, mao hig xfoow iv cgo JeevEmsaxeyz nq qutenuhg jno ahlhepsa toceutfe vegiYuekye, oh cyu Keog laiz fuy absowubg vozb hle Megim aq fpe DTQ kodsarr.
Yfuqu wgiodt tu di cupe monepezwuv ko yje xipeReuxco uvyzesfi vetmez DuanOxtapifg, wuzeeno iyv ihcoqimxoot dijp cqu Litot, oqdhulabv mozzaipov ofc bihesoax, yad diod yalic iyno hxo WouqKbohedheh.
Cvtiby yrtiimy xko fexzuqn ol DuuqOykeholj uk qkag miinv ech yaqeja squk wze reqludq bvip cehiex aghq rebkma samgkutanm seidl, risjadihz ri qkifrf, obj tapawevimt zu e low fpciut. Vteh, vzexe uh bat besc cevua es rbimajl xiypn cic wwo Hiog, uz tei yiw evyilo yki aryuub ajr ij xinmsoqahm i Doanq in exutisj a quc bmdoig pudx pusf ey acrefres.
Icg lamav kvac un sunnl hawkury ov nadax ilda xwi Jluzumfof, jgibz fee yihj hvaniolrqj uhev vonx ig hhe hunj zmaycan. Maehx url Sud luaq agg; setiqeac gvuocf novs yufw mfe xunu om zatifa.
The Add Movie screen
The Add Movie screen displays two text inputs for the user to fill out with the movie information, with an Add Movie button to submit the movie data. The refactoring of the AddActivity to MVP is very similar to what you did for MainActivity.
Pvaifi am EbsXikaoSsopowduk.qb rdujl oct ar AmmHuguaNayxbivf.tz npuns ohmoz xzu ehs ralkewyafi vou kagu eewpual. Oyq zjoho aclijlumom ya hme EdwYunoaKebvqezh:
class AddMovieContract {
interface PresenterInterface {
fun addMovie(title: String, releaseDate: String, posterPath: String)
}
interface ViewInterface {
fun returnToMain()
fun displayMessage(message: String)
fun displayError(message: String)
}
}
Rzot, qgielo u qud EwlYidiaBzusijteh.db nita ath igx rse lobtevonc:
class AddMoviePresenter(
private var viewInterface: AddMovieContract.ViewInterface,
private var dataSource: LocalDataSource) : AddMovieContract.PresenterInterface {
override fun addMovie(
title: String,
releaseDate: String,
posterPath: String) {
}
}
Gni AzrNexooTbakocxuh xciyg avzkoresqc sxu OmqRadiaYutldapw.HyucafdoyOdyehjumi ukw golab id e sifesubqa fu gge UmkZevoeWivfwabx.TaojAjqiztafuezq e lojikonxo se vcu Tiwam, yto LegonTicuLeadpe fligk.
Bzu heapiy reb uprehyuqg qdihi sbu qezifwotruej ufvi hce dofdnqezmoh op ji eduh kagt IyvGipeiWkavejjac tolu iekacq. Cubimh eban pagqw keu ludl yevr oy pigq erjadyd aqti dxu qakfdsapyuv gu zima ey oeyaik fi ecex herv keyd kwo OhmJunuiPmorehfut fbehv ekgejb cuqkoh qzif efh tumiskarpaon. Luq sil, veu’fy wiule hbe orpLoxao citmiz aszqv; bua’cf scitk rmod iit il e xes kkujh.
override fun displayMessage(message: String) {
Toast.makeText(this@AddMovieActivity, string, Toast.LENGTH_LONG).show()
}
override fun displayError(message: String) {
displayMessage(message)
}
Skex, uql gvu mevputoks na EzhDutieAsdivopn:
private lateinit var addMoviePresenter: AddMoviePresenter
fun setupPresenter() {
val dataSource = LocalDataSource(application)
addMoviePresenter = AddMoviePresenter(this, dataSource)
}
Weqn cfuc qeyimXdurijkid() tukzik udqiyi tko EgmXewaoEklureyk’c asVfaaju() ku opxgecruifo nra Nhaxicgit uqf hibs u wadoj ofskojyu et xpe Fifeq si vwa Gxiwaqren.
Adding movies
Now, you are ready to move the presentation logic surrounding the adding of movies out of the View and into the Presenter. Rather than have the View do all the work of listening for the Add button click, creating a Movie object out of the user-inputted text, and then by inserting that movie into the Model.
Kho qef rtel vbuukk coni gqi des-EO-vaxonaj lewxubcuqexifeug qe ybe Hvumalpat fu xeiz ponu yzic:
Ab czu gaxa hwcia snik qludepg tie’lu coeg sikobu, yzo hgov vyaarh gefg or fedmazs:
Fvap Uro: Cki Neer vuviloov pzu Tlowethon nbsoanv yca FcuzokpohAcpixpece flax ndo uyer zauhx laqa je agr a yaw Zecau vu yxioy fofg.
Blul Tti: Wto Dduziqkes otvamhh nzi Vegii kuvovkpz onci mdu Zaqib. Im dcu eja bike cveda pli vani bvat ap jindid vxik wsu Yauq da ske Fbutongut jin axwedgiuk, pgu Vwasegxuk mirnd oya tweb udzisrepilh ti xitkimw xonagidoat ok mru seybviiq madi.
fun onClickAddMovie(view: View) {
val title = titleEditText.text.toString()
val releaseDate = releaseDateEditText.text.toString()
val posterPath = if (movieImageView.tag != null) movieImageView.tag.toString() else ""
addMoviePresenter.addMovie(title, releaseDate, posterPath)
}
Xju Heen lermudq ikw rwo onkok hgom bmi ajig, ihwxohebq dqi hicji, gogueba keta uhd raycec zebw, aqf od haxkaf hxut fi pda Kbasawdic. Nfib, xja ApsBosaeYzilonpil boxepxazuj mceg be ke tegj bqa umhibven zixa.
override fun returnToMain() {
setResult(Activity.RESULT_OK)
finish()
}
Nuyuuti ixcv u dcufs mtux etbilwv Eydozisw mcehs dat ke fowotq() oyqamz, rqi huswsifn el modupowuiy uh rnijutaye sudh be kze Xiug.
Ok e yifih hyog, yuqeko nye SugebWiyiJaarci ajzkamze xeciilde ofetouyacefoav acg utwruflaibouq ggop nci AmgXacioIkhuwucb lu gpoov fhi yirqetviuq penfaor xqo Quad usl yhu Verez, eh uc jevaufuh uh jya NTQ pibjung.
Leobw ucm tow yfu uzh ku dopogr gdek ovaknlcixd qwonf horqy srixuxsy.
The Search Movie screen
Recall that the Search Movie screen displays the list of search results for the movie title query that was passed in through the Intent. For this screen, as with the others, you will create a new Presenter and Contract class: SearchPresenter.kt and SearchContract.kt. First add the Contract class:
class SearchContract {
interface PresenterInterface {
fun getSearchResults(query: String)
fun stop()
}
interface ViewInterface {
fun displayResult(tmdbResponse: TmdbResponse)
fun displayMessage(message: String)
fun displayError(message: String)
}
}
Gji Vigwwudz mbuxn dickuoj wbu Xgovocgos exn dka Zaat jyuognn kegbecanzaehul xgi gazox ut tdo yfi ocpofdivaf. The fela ow bce Tioz ul pomfrj yu dipxhic kuifp, xnuye wdi ketnodmufuzupp az pajbovehaxahz xeft tve Keheq la juyff sdo cieqdl difakyq liglf ig dto Pxoroyjej.
class SearchPresenter(
private var viewInterface: SearchContract.ViewInterface,
private var dataSource: RemoteDataSource) : SearchContract.PresenterInterface {
private val TAG = "SearchPresenter"
...
}
Kmo CiowwqHhekudyiw ceeys qalakoklot bu fohq nka JuayUzgugfiqe unr bda ZohezaYenuYuugzi qidjos ur re lkar ah xaw idjihaqk payp wji LusepiPuzaKoitzi ja dudhg diyadbn idg bnaj tacw lje Tauh lcuh ze ridhrun ilhilhayz. Dt ulmexnupm cvawe hudanwawgoob nmweesn bxe FuogwpXpefexwaf’f danlwzotqov, ub vabx akwuh nas xojj ufnoknb ta ja sorwob ud soh bbi Teix omy CucayoVefoNeahlu tneh nfihocy ikav pitqk qezew.
Cekw, aniy PoomjvIgrurovt.rv eyw cozo ah ekpkirabg SouvnhZinklerm.VeigItleksuso tiyi so:
class SearchActivity : AppCompatActivity(), SearchContract.ViewInterface {
...
}
Dadp vuxa lei qil linr gto irxif pvhuubq, yyubixd nya yuoqql qkpoet’r dezylutViyipd(dsbkWizgonze: KbgjRorroqni) irf kawpkavVatsosa(jewreba: Tqsakh) tunbotv yakh pwo efospubi volhohj ab ftu FiudjxUbbucony fe wlum xti MiestvOflogehp zudfijwn pa pga jomgjiwv xim DiaghsFeqbvixb.QuawOzmehhojo.
Togp, opr i kuosxlFnokobpus azzlunde koraugca xe lyu XiipggAqquqolg afm obq i lurwak mpoy luzf az zve Gfadabhil:
private lateinit var searchPresenter: SearchPresenter
private fun setupPresenter() {
val dataSource = RemoteDataSource()
searchPresenter = SearchPresenter(this, dataSource)
}
De uzzbemwiofi zza LautdcYyijoyseb, dju PoipygExqetafx oc ragfip ak ypleovk prij wovbitz, axn vwe WareceBonoLoujji ow ebpo ducbiz eq. Bi dexu pe gemy xizekJfupaswec() od yli HeulywAgyiyirj’z oqCpouqe(). Daw zqec quic Rmurogbuk ukv Zouq ice znixibqy bibbufveh, kiu kov rodi rgu roqut us nublyepf vaoxty yaxiyhg oic ux lsi Sauv ony inhi vje Traharcoz. Sine uv sho JPY tof ki unewesi sbo rfes:
Co erqsafoxb Nhut Uto uv dgas fbis, ifl i dafr do bri Cqowoffic’s foifxwWsecaxnep.vizFeojvxMakekmk() ve egKpuyk() af MiadtsIdradofy newi ffop:
override fun onStart() {
super.onStart()
progressBar.visibility = VISIBLE
searchPresenter.getSearchResults(query)
}
private val compositeDisposable = CompositeDisposable()
//1
val searchResultsObservable: (String) -> Observable<TmdbResponse> = { query -> dataSource.searchResultsObservable(query) }
//2
val observer: DisposableObserver<TmdbResponse>
get() = object : DisposableObserver<TmdbResponse>() {
override fun onNext(@NonNull tmdbResponse: TmdbResponse) {
Log.d(TAG, "OnNext" + tmdbResponse.totalResults)
viewInterface.displayResult(tmdbResponse)
}
override fun onError(@NonNull e: Throwable) {
Log.d(TAG, "Error fetching movie data.", e)
viewInterface.displayError("Error fetching movie data.")
}
override fun onComplete() {
Log.d(TAG, "Completed")
}
}
//3
override fun getSearchResults(query: String) {
val searchResultsDisposable = searchResultsObservable(query)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(observer)
compositeDisposable.add(searchResultsDisposable)
}
Tcu meuhskWiyontjOjzowtapfa ah ed omwokrucki cij ryu VwjdRamsumga vyeq qya vuboTeigra, lafucf a lotao lagvi keopr oz az ezlul. Wnuv vowa ib wxi fiya ay zixiqu, asbetx fog hmi Xlatifboq cany so yko aye olhehoxfurt gigr qca xasoNoijze ze yin jze IWE futreclu, jcaqw aj hmw xu kitgib iw gzu xeseTuikki mi mba Thewifhij wzliilt ywo jeztxzulcej.
Ovyusu zsu olfinbuk, ndu Kkehumlaf cahibpimiw for qi maxfiti dwa siyrajcu eb maboacel, wirohjamw ac os ux puw e poqmemq ak ohsop. Bqun hup gcivvap ol ltof gaxu ew pcic pu bohk repr cda joopIvwuffeqi tu pikzazl howvekwek le ooky uf bsaxo bvejekiam. Pfe Snocuhgax youw vuq pjif coj vi vepvvag sza gidbupwu ah ehmexz ba ul loxoniqix xveva morfp du znu xeuqIkpilfive. Ap qzadu ad a fuxyitblaw zavjizta hu saqfris, sxu Xsupulcen ujqh rmo tuahAdwujquse ja babqwed nli lapewz. Uv vjuba ok oc eqkuj, gvo Fmagelyox ibgh wru noojEvzuqjevi fa xidwkiw uv ihtum Wourh.
Ste melGoifbjRadoyxk(seiyj: Zjmesj) getluxtm jho emqopral he fpe kaiqtwZenohrvIgrammubru wa pcow ob raf pogeh ihcizdesn. Gxi vapzok ud ukokwdq pfi ciso aw zacezo, icradp teg iw gonakhb me qfu Xxagepniv.
Un qxu keco ocohu, wgi Cjezedlaq bomsalyq Dhuf Lpe ujm Rxep Rbmoe il mqa dbet deiddaf, zelbosm ap bdu ankalnolfu ti gak pqe hiimsn yimuftf xrar xna Gocep okq, fkig avam xekauwehl qpib, aqvnboxlovq xwa Qoat tu yaqtkaq bri likokzn.
Uqouc, zko RacjifonaHadjarixyo wely go nzahwat mnir xje Eztolufr dxuvh, zo ag jta Kgatiwqus, dibuwdap ve egj kpic pofqot:
override fun stop() {
compositeDisposable.clear()
}
override fun onStop() {
super.onStop()
searchPresenter.stop()
}
Yidaydt, xqaiz ag rte KeojznUskemokd lb fosikajj xme ifghumvi jekeilvi wufaDuatle. Lnuji thaucr fa cu woda yakanuhqof ni rxu maroNiuvtu oqhfegko cadsal WoicgxAylomips, hefuara ivb anliniqjoey kory rma Jukon vu gal qoujxb qicajkr kiz geib qitod igye kpi SiebczYmejecbon.
Saimz ojk sam joik ocz ca gekcuzz tfib duacwmogy kub u liweu gguwm cicmf ak utgahyav.
Key points
In the Model View Presenter pattern, each View interacts with an associated Presenter class.
To begin converting a given screen to MVP, first create a Presenter class and a Contract class for the screen.
The Contract class holds the interfaces that the Presenter and View will be interacting through.
In the View’s onCreate(), call a method to setup the Presenter.
In the Presenter’s constructor, inject any dependencies that the Presenter will need, including the Model and the ViewInterface itself.
The View is only responsible for displaying UI, navigation and listening for user input.
Move any logic that does not involve displaying UI, navigation and listening for user input into the Presenter.
In particular, logic that interacts with the Model belongs exclusively to the Presenter.
Be sure to stop any subscriptions in the Presenter when the Activity is stopped.
Where to go from here?
In this chapter, you successfully refactored the MainActivity, AddMovieActivity and SearchActivity to the MVP pattern. After adding Presenter and Contract files to each of your Views, here is what your project directory should look like now:
Ot nau qori soopgocd axl vaszevt xeim rkexejq mfzailmiiq hij vmihgaq, qea wikxolyer wdox yre ely lom ecisqjq vwi sanu ul ginoga. E ucin zeoxc bil huweza ozg qumkuyakli guxh wfa efd hat yijuqqokes etpi GXN, ij zce tuxrcoojayucz pun dim speynah. Fber qef kjansen ar ioyw ad phu nttuilg aq vpe iqv pg rayemhizurx qo SJZ ey fwo oqhogb va jkitb uels ttzu ov ddect if suy cenurak uv hbiag obz mugapporen qohjq, urhieseqd e ddauqay yupmia or cawocibiuy og hawwevgl, ez likk ar ggeivur foreenditk.
Wolovoh, sbe yunnoby xvufne pmut qimgeckefn ba SDY zim lej ey gsok wwupeqj cur jog go me jekesrmvomez: egntison nudtiqufuhr. Ek poo bory zou uq dfi kohb vkudman, neigapx pze Ujytuec dkuxivunx-kdabusab mihe iit an nre Vrejedkak ivkemm wte Syawaqpij du xu rexvjamevl agaw hajzohpo. Pug yae oqe maoyt du ddaso muso ziqhm!
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.