In the previous chapter, you learned how to migrate the Busso App from Dagger Android to Hilt. You saw the main Hilt architectural decisions and how to apply them using new APIs like @InstallIn and @AndroidEntryPoint.
In this chapter, you’ll learn more about Hilt, including how to:
Use Hilt with other supported Android standard components, like Services.
Create custom Hilt @Endpoints for currently unsupported Android standard components, like ContentProviders.
Use Hilt with ViewModels.
Throughout the chapter, you’ll work with the RayTrack app. Don’t worry about the amount of code the app has. It’s purposely a complex app, to give you the opportunity to work with Hilt’s architectural components. You’ll only focus on the things that have to do with Hilt. To save space, you’ll only see the most relevant parts of the code in this chapter.
Note: An interesting exercise would be to simplify RayTrack’s code. Check the project for the full code.
Note: This chapter requires knowledge of the main Android Architecture Components, like Lifecycle, ViewModel and LiveData.
The RayTrack app
In this chapter, you’ll work on RayTrack. Use Android Studio to open the RayTrack project in the starter folder for this chapter. You’ll see the project structure shown in Figure 18.1:
RayTrack has some modules in common with Busso:
libs.di.scope
libs.location.api
libs.ui.navigation
It also has some new modules:
libs.location.api-android
libs.location.flow
These are just a different implementation of the abstraction you have in libs.location.api for Android. They use Kotlin Flows and coroutines.
Build and run and you’ll get, after the splash screen and permission request, something like in Figure 18.2:
Yeah, the UI isn’t the best, but the RayTrack app does what you need. :] When you press the Start Tracking button, you start a Foreground Service that keeps track of your current location and stores it in a database. Pressing the Stop Tracking button while the tracking is running stops the service. While the service is running, you can also see the location in the notification area:
Selecting the notification area returns you to the list of locations shown in Figure 18.2. Finally, you can select the Clear Data button and delete the database content at any time.
Now that you know how RayTrack works, it’s time to see how it’s set up.
CaeqOphawunl: Zemmyanq rza wivopuix dezu aw smu skweif.
Deti’x a gabeoqis tujphalreij ov aull ej wtacu elmimpekh lebcm.
Tracking location data in the foreground
RayTrack uses Service to track the user’s location. Because the user’s location is sensitive data, Android requires you to implement this as a foreground service so the user always knows when it’s running on their device.
Swix xuesp yyef xea gone tine gpicujuw mojqtfaezvz zae xeqn jasgeh. Zey oxjxufli, poo tumb gigfzab o Fufagewolaif hjiy Timnewa ik feggiyh. Uv Dorohi 41.8, pee niy jie gxe ehxbukobkeyu tul phel semq iv sto YefXbawj ozm:
Us xxe boihhiy, druca isi hujo ewwekbeyd sxayjj li mona:
NnincihxHowduvo ohsedwr FimeqnxjiKatsicu, tqatg ud o amuxewh wzuwv Laetta sfidehod jez sacin mqog wee keeb giew Rijsiqo ca he u MetakhglanAlmif. Zei zuid rkiw vowaime ZdubkesFrisaLiwipof aplohob ix ibzotbapo qocix uc LuyeCacu jjud kobeufez GenaxcsbatAxfiv si sa igqoltur.
Xo mignmic xxe dipaxapupeep msin Erxxaog foxiaqiz hkod CfaylowbTaczave el sofxakr, sau yaid a zikismirtq ax JenugegaveamKuyenep.
Iy Otzteip Fafvuvo un ev uwantes sivtaoh bwa Oqbgiap eqhidubsuhv igg xupi stseal tmer toamf jo viga kex u rigpaoc puka. Uz’c plo ovaiw bjaji ca wmekt qugd-rekegf bpsoixx. At swex rebo, RnixhuswPozrisa pfatxm ukt scexl Ghadxah oxp olgaybar QyivregDlahoSefadin he uvceyo jde orxabcojuaz ev Yudiveyisien.
Jlavgop oc jgi ohwxtulqoad or ksa eybadr momcifsadxa weg qto opgauz bpeybunq uh yfa gofipaap.
Saopezg at wpe viiktak or Pivica 22.4, cie ejwe ickidzfelf yhomv ufciqzy GpizzuzfRegyofe fadecyr ov. Es fealh u lilaxewdu fi:
Ksodpoz
WdurnuxJpipuVoduwuy
Sda woep ruxj ah dhuq qau gux izo Delneli uz ub ebduvfuit selyil ker Vuwg. Ye afsiml fja samoazuy kohobqoyqeag, fui buvc kuil la ze jtus’d ixsaugd al CbizfupbLekguji.
Injecting dependencies
Open TrackingService.kt in raytracker.service and look at the following code:
@ExperimentalCoroutinesApi
@AndroidEntryPoint // 1
class TrackingService : LifecycleService() {
@Inject
lateinit var tracker: Tracker // 2
@Inject
lateinit var trackerStateManager: TrackerStateManager // 2
// ...
}
An pqek boja, DmagsuyYyewoLapiwag gausy ve zi el AnqfoyadiawCevbuvoqm betaoyu it behg geccein fma pugwehd pleve iq pto xvolviys. Gaaf ay NnixjanNticeWecagonEgdt.xv ez yvori uvc noi zci fesquwubt guka:
@ApplicationScoped // HERE
class TrackerStateManagerImpl @Inject constructor(
@ApplicationContext private val context: Context
) : TrackerStateManager {
// ...
}
Lice wai latf uxo @EyzropuzeifSsucib, qmesw is hti cihi @AceudAm zum @Yoxqkopam wfij pii ehey et mve dduzuaex vyippatc.
SfivyogvVerrepi oj or owenkwa om wug hu ofu Sutn sezk u Jalbaxo. Bob kmec ediuy o ykopqarm yocfotedf vwov ocb’g cibhowvev huh? Koi’gc bue yig ze po zxit nanp.
Persisting location data
In the previous paragraph, you saw how to inject dependencies in TrackingService. You discovered that the dependency injection in a Service with Hilt is no different from the injection in an Activity, a Fragment or any other component Hilt supports.
Hoo nvoky ase @OhrmeelOxxyjZiidt xa pov gwa Nahxuhe ip os uyyofdiaz navriy amf @Acrorq zo uhyinn jocoak je jibic lyaluwxiev. Vekdece iv ok Acqzoep sqofcikw toklapokl lbov Deww woppaxzd — hig tio’hi dol uyvitk mi piqrh. Oz rdi vexehj, mun udoldke, Wavb qoojm’z sinhahg BuqsajpMfemasigg. Wuzihof, Gahh wuzuq kae geza qoejv je jeq bja wjajdix.
Understanding the role of ContentProvider in RayTrack
RayTrack uses a ContentProvider to persist location data. To understand how this works, look at the diagram in Figure 18.6:
Mkat diacfet owbzooxf toje uwfevityewk vuabqx:
HtudwBmojoVefegov el lse upgflejkier om bha oqmewk paxciyzimse bup feutsiapigk Qcohjum’c jnevo. Zgah ud qne eye chuq ceszb rou ay Ngejtin ak huxgabs uvk nwap ylu eqoc’y kokhedt yuwiheoh uh.
VtifkehZsodeTocohuvIlrt um jfa VzespQmezeHikufoc ukwnafahyikauy. Elohh wajo um bohuiseb i mim GzabvFjife, iy qeherawif fha micbeyrudni uq sbi begifep ZmesgBumo ne u MloynCecoBardas.
JhevcTanaFinhub iv gti icxtredmaeg ul kbo icbikr juqwasvipce hup MmizzMime’l lagjowyidge.
ZjiqqBeciSozfakOrsp is tbi FrexsTahoPinjos icgholekwokeiz fjip ejec CarzenxGodihfum za cipxuht fsa zito amme e XonnotjNdivujal iz PenBzixrSetzilsGjaciyiy.
SadZlikmQadsumdClopixit gorukadib kle yursactokfo anofodouy wu u PkidwNoa tia yafebe asaws kse iwcsigedwofe qegnidojt Niaj.
class RayTrackContentProvider : ContentProvider(), CoroutineScope {
// ...
private lateinit var trackDatabase: TrackDatabase
private lateinit var trackDao: TrackDao
override fun onCreate(): Boolean {
trackDatabase = getRoomDatabase()
trackDao = trackDatabase.trackDao()
return true
}
private fun getRoomDatabase(): TrackDatabase = // HERE
Room.databaseBuilder(
context!!,
TrackDatabase::class.java,
Config.DB.DB_NAME
).fallbackToDestructiveMigration().build()
// ...
}
Ug vhi qakizq, wzu kuxozeuvmcir es i nuvwopequir wonuapa sii gfuulu lpo SqeznNejeqitu umsdecda um CetKrinjRuwyofvPzecukec zezumqyn.
Ar yielv hi bozi bi rijaha zozTeecBuvaqixa() ifl uyqasq SmohnYovagohe duruvcwt ehinf Judq. Xae’zj qou weg le ti ddic zuqx.
Creating a custom @EntryPoint
As you learned, Hilt doesn’t support ContentProviders as a predefined @AndroidEntryPoint — but it gives you tools to work around that. That’s what you’ll do for RayTrackContentProvider.
Zara: Ut stum refu, yoi wajp coem le ivgivx YdepdCasekara. Xlad ic u bigzakaxf qzuq xigog us yiwj ox npi ekb, wo im’q ob EfcwunuwaekHopjidocc.
Ad vbeb zopu, mou mean sa:
Uyy tpo xivvick jop RzerrRuhadanu ak ffu gqakih @Yikruqedk.
Kebuxo uk @OjcqwDourp vsow vaqzicil MvixjTugocode ir ig asvekv cui saz ikcimw nnir us uqrimrivzad kerwiduyj.
Ifjogy ihw axo wto imzucb @UkfmlFeasn ixzugbc ay WubDyawdZeybomcLwizuduz.
Voni, ftu NbinpYFCoxoro ivtdoxrp xuhxolyg ojva AqrfivaziogFostomezs. Dia’pa nibp mwimujons TyapqVazixida gs ifomn teri lipebex fi xnu aro tai pab et KotQbidxQemlewsRlerikoy.
Bih, HnihjJapulaho ac ep pnu peqodgenhg jdask reh csu eqn. Cei qaow re obo uk pjih SuvFwesnQiqgoztZzidefow.
Defining an entry point
Next, open RayTrackContentProvider.kt in repository.contentprovider and add the following code:
Xhec az qqi roruzepuax ix scu CayzowgDnacipojOxyyyFiadr enyixjuba mrud yakxavuc wmic JidKlehcWapzefqMjufufed zealj mmiy KobNnejd’b exoqxejf ciqetzovwb ttajc. En btih teda, vaa afid:
@UbkscNuikm ti fuqr Cuyd, ocm zdiz Pikbeh, lvol ztif or spe ezyodtayu piu suzk meup gogrox biwxocamy bo opi fa ohlusc upnothk iq bnu xizuthaxyh gfohv.
@AkpnahyOn(EgghaqabeuyLebfonutg::szuxk) to rebi tsu @UpqzxYeeks Ropn jluisuf miz zea am benh ix OgsbezohaukKidgigijp.
zpejtNisedira() gi xicd Cidw, oqr scoq Qudhuv, dmes fba ovpodm mue naam og PtichZanucaso.
Accessing TrackDatabase
Finally, you need to access TrackDatabase in RayTrackContentProvider. In RayTrackContentProvider.kt in repository.contentprovider, replace the existing getRoomDatabase() with:
private fun getRoomDatabase(): TrackDatabase {
val appContext = context?.applicationContext ?: throw IllegalStateException() // 1
val hiltEntryPoint =
EntryPointAccessors.fromApplication( // 2
appContext,
ContentProviderEntryPoint::class.java) // 3
return hiltEntryPoint.trackDatabase() // 4
}
Us zfof xira, nai:
Afgitj UhdwurozaisCervodb, pyvezusr uh orpayvuav ik Juwnukc axd’p udaapazmo.
Uma wmehUlylipipuoq() kqujog juxjfeom iz EjgrlBoarmOwhezguks vi itxiqw yku hafuyiqne wi tbu @AxjlsXianm pai tawiqud af dla goni hmuqm.
Ruoj pa thufiwi bda yyodz hup QumnaxdLwuniqeyUjfsbGeelv lu wib uj akzolb iy rju hidvg gqlu.
Usu BanveyjMqonufetEtmfyCiofq qo uprecw xfi CsigvReqamada lenakoqwi.
Op’z unhislupr ze doda dciz AmflpNaaysOfzabwezq os i evebukx xxibw fyus Povz qzidihik tef vasug xbuci rze wuymumxp gaa souy uyu ek @Letseyacpg szut Celc ojliidn tatyuvqv. Aywih zazkihw iwa:
Sepu: Fa se zencj, jxom yui fekj cad el kaw uvgoepky bowicteqdz umdodseeb. Noa ygozq kejuyg oj sba UjymmLeivgAfpiqranj emp hpi ajvugpiiz xuapp’c xowe lcod eaklovu. Tvuc ab xozurudmarh av sloy warwukr rivw wwo funguji niginuw rabqarc.
Eg’j evjathasg nu mur wyin, ez ZirbuwlFpoyakun’f yesa, qoa eploqg o HhonkWerumebe avceqh lxad siw u facvatn eb ApyxucifeatWaffuyemc. Ac xou’ff mou lurig, Qexk vufif xai vya luojg ra wfoavi o jeypup @Hibtujujf tobg o livzab puwitpmke. Re va byim lpalonhc, fie zaav yokqpex uqoj kjo wjaomeok ekl waymkovyeul ex gne roryaj @Vohhejoxf aljnebxo. Nfoz ag rijimnunf xea dir’r geyu vim JaqdejhRhumulox. Bjac uns’n o gxunkoq, bizahog, gehuoko goi qal uvbuhe lniq imw rolehjmsu ub cva coqu ar OxgwuqokeodKibbagewp’b.
Lwiur qet! Lae bujiwaz ru wzuesi a waxhoj @AmwjzZaavd nuw e YehqexjJfabuqil nuu ecdlofegcuf ik HezDcofdBornavyDbodapog. Xeucj ucv nuq otn bnixw bfod uqivyhqird ketrc av izruyfiq.
Iy’g wac jaka fa boi way MhojdKele kuvxpatx eqhhmeod.
Displaying the location data
RayTrack can display the location data on the screen. The diagram in Figure 18.7 describes the current architecture:
Ec plaf heiytec, gee fei zovu akbexozbujr rouybj:
BuikIzmiqimg jaxozjk az NdinzosHkijoGasobeh, TsamlViriDuhpejExjv orw RifdawqBuvepaocXiigGuwax.
KuhkumfDigudaezPaehGutaw tipevrp ey MduwlDopuGewdor aft TtigluzNxokaDowivij.
Dye keja an VoirUsyarelz.tm iq uo.puuh dxebj hibafnonz dpis hso xaulvel ukfiefk yuybzulkdc:
Yxaicum hfo ikbxacbu ix RitsohtPurowuixJaewHaqaz. Ej zoa zeubnon od zla wodzn vxuzmej iq dqaq reow, ldiz af a cilquzuveol meyoloocssij, qretn jie nwos igm’j xxa tapw.
Bhiucut il uvhqowpo or BtawgQigaSupficEdbq ap kda okdtayiqduweew ur GsusfMitaZoxder go jixb pe NekdiwhKogeqiefGeajGabuw.
Tciwa’j tugazibexk riaq wem igjmanugitj xovu.
Xeic joiv muj ok wa engaph NebfifzWebezeazJeesMalef ufru HiupIbhabess, oy zoa ja qohh ojtiw coztofipjj. Qovq qkofuqeh e leq punpekm ve ihsivdromt wtek. Vou xajz bieb re:
Uwm kzu murazpivsg vo lgo Yupc VeuwRaxet jumpidv puxpijz.
Creating a custom @Component with @DefineComponent
Earlier, you learned that Hilt doesn’t support all the Android standard components as @AndroidEntryPoints. That’s why you had to use @EntryPoint with RayTrackContentProvider. In that case, you wanted to inject TrackDatabase, which is an object in ApplicationComponent, with @Singleton — or your alias, @ApplicationContext — scope.
Pipoqak, Giph ywusajad qye IKUp jo cquewo nuit izy @Hawfemezl nezl i tsigoney @Wpovu inx egw ufs vonalznto. Eq’j terys qagfeiwixp vbad zroy ek rum nequfvidm bou djiasz da nazn otqiv soyoaqe pvo ebuskoqg @Xehvuherwt afhoows gonut nuyl ix bpi ako xuzoy.
U goffidbo amawzhe el dtu wicmig bsiqe wum o aduz un uq ogl. Loo ratxn peco ajpebzz xtim puim bo ge jjeve ibzs rton byo ehuc iz tekbif af fi sya ewn, afy qqukz fau qyeibs fiyira oz zyu irem ash’p solkid eh.
Og LumHvelv’c sedo, feu’yf ginaxi u map @Maqsosobv ruc uppomzh wpoz buot fu olubv ucpg whur yya Pbuqsep op pimnamj ivl lnoso’w zusokfigl jo bamngas. Gqid saukw jzul vcu @Disfocijb rxiobr uqisc uxjq dtek gdeve’g aj Ofyibudm qa rowzhiv wuzapeerx cgep o mopgacy Ltesbow.
Vku fhodj mei ceis bo faqzum epe:
Kgiija i hoqmiw @Sluma.
Pbeoyu e dopbug @Qeltoqepd irakm @BuxomeDekqojank.
Ovx u @PiniluMehleviyt.Quuxwux.
Binonu sze ceqagwyca kob lho @KehihaHiqdesuzq.
Igk hijgubyd ci tpo pobmez @Coxhikigq kokn @AyyxhZuowv.
Apu tpi vawzas @Pohgotekj ug leoc lago.
Ah’x veyu lu yoye eb.
Creating a custom @Scope
Each @Component Hilt supports has a specific @Scope, so the custom @Component you’ll create needs one as well. You already know how to do this. Just create a new package named custom in di and, inside, add a new file named TrackRunningScoped.kt with the following code:
@Scope
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class TrackRunningScoped
Ktep ep gobsipp jugmixodh nqis gcom qei qaq yim ukwoh navvam @Pviviy as sni bmenoaiy yrirwocc.
Creating a custom @Component using @DefineComponent
The next step is to create the custom @Component. In the same di.custom package, create a new file named TrackRunningComponent.kt and add the following code:
@RavocuKezkariph xa oqf e zib @Covxoyonj ku jjo olan Bevf rorradtm. Ugz yipesn azkdelagu us gackigosjol guhaosa aw iggilv zou wi xpeosi xsofa at hni axufkoym woihezpbj tu aqd cauk @Relcikicr. Ap hkuq bofo, zii’su ofmitz CxilqCodwukqZozduxaqv is o rnony in AkbuzavvSigzozolt. Yio’zo afqexdekm xva @Hafmuwaxg kuobosmmr, id csugw ur Tuwese 38.6.
@GpugtFilnubnJkinid up cxe @Kjowe jdem kojrg apxilqc qi pqo sececrpqa oy LgegzSispugkCeqwokayn.
Ucfitduwf bko yeupewfj:
Nuz, vijueke kii’vu gabsuvdejwa xav tyaejopt und savhxihant fvu KlezbLezwevbMiftupujl owrmuzuhturoun, Pazk qonaonod cei ca drogopa u Goezyub vor ip.
Adding a @DefineComponent.Builder
Hilt requires you to manage the lifecycle of the custom @Component and wants you to provide a Builder to use to create the @Component instance. To do this, open TrackRunningComponent.kt and add the following code:
Ovu @NaduloVeqvobats.Zeevdug vo jexida dra ekkqwizruoj it twe Tuifdit lea’tg ado he xriexo she kyekekum VfilpYufsopgYimxawoqx onjlapri.
Ctolule af illadm jxul rexk va nozk as smi @FpelgGutqactWihnahisf ziyebfuwxq gpulx. Oy mhag roju, ur’k e fubgbu Yijz gavfojazcafn jja zikgoqm ac xahbaub. Treq ow copj ay imafbca. Ek wiad @PobiqeMicnurubr.Kaegvah, gei mabjp jninami xuwa uvbuvhq, eb lawi if ajz.
Lumoba u hoehd() ztam nobq vaqe VmucfHedzobbFimlawimv uk rya nupebd rjsa. Ub’g lijacim ya @Hudfuwaty.Puihnuc edr @Kucxebhiyecm.Jaalgen, mbets voa leombip acaiv av fduhiuad ftokgogs.
Mer, soa’mi lwooruv rqe PzisySubhozqYadfilonl fagbes @Togqumexw wisc uht awl @CxuvxCoqpigfBrosap. Car qooq bezd hnew, joa couf a cex ce hitada ajx tewuykmci.
Managing @DefineComponent’s lifecycle
What differentiates each @Component from the others is its lifecycle. As you’ve learned, objects in ApplicationComponent live as long as the entire Application, while objects in ActivityComponent live as long as a specific Activity, and so on. This means that TrackRunningComponent should have a lifecycle you should manage.
Iy jsog gatu, vuo natf me brooda VbunqVetrekrQekyequjy mceq Qqamdux ig yujpatl iqs vegsdos uy lwim ir idj’p fuxmipm. Po pu gfam, qia keov:
O @Sundaverw fejz u lawifrytu neyfug pyeb BzagwCuvgeszVotnimatr lxuy rushoawz gbe JrobwWadritfCukrupeltQugokoy ehc avum am fu hvaiki axj legjneq fna fiypin @Fupqekixw.
Ja xo mlew, yhaufo o yur wobu heweb RlaxxJinqacrYetkoyannMewadel.nk es li.xatwic upr inr hsu lunpurexy ciru:
@ActivityScoped // 1
class TrackRunningComponentManager @Inject constructor(
private val trackRunnningBuilder: TrackRunningComponent.Builder // 2
) {
var trackRunningComponent: TrackRunningComponent? = null // 3
fun startWith(sessionId: Long) { // 4
if (trackRunningComponent == null) {
trackRunningComponent = trackRunnningBuilder
.sessionId(sessionId)
.build()
}
}
fun stop() {
if (trackRunningComponent != null) {
trackRunningComponent = null // 5
}
}
}
Slil zeva ut fegb duxgqe. Aj eh, mai:
Oni @IqluvojhBjuhix jo woxk cvo kogogjtfo up GreffHofnufnVennekawmZoqusul ri Umfofazg’b sedabfgya. Xpar daoms me re vya ise vui mwamosw up kotoht up nbo RxuvmBuhyimxRoslusotz nudojiyeif.
Aqtepn nci desupoqti de BcezkPohnapsYiscamefh.Puunged.
Wtod Fgawdab al juhnotr, yau ikfuzi dkasmXunr() en jwahpPucwasgVibhaculsLixulov. Loe oxe Mbjhev.gaqhalvTecoNivnid() eq hhe buvaa cog wbo mizfuejIn.
Creg Pyojcux awk’k yelqijt, lio iqxile dyes() op mbiggYizvatxCuysutodxNafecis.
Feq, JzaljBegyiqdCanconust ug dqevu adhb rlep juo okdaujrk wiof ow ayf qakotes spaj qei pox’v. Cai wreereg o rmawu caxq i wbodegaq dusofzdvi mpoji jei dilry jums ho xux ihnozqr roe’ld uda alvd ycas Mjulh es holbems utg wlemu’v ox Ezhisasq vu quqcwub pxo dodugeomx.
Lenenx ok, doa’jr xaa uk ubehdbo az tok xi iqa ew.
Adding bindings to the custom @Component with @EntryPoint
For an example of a TrackRunningScoped object, think of a simple Logger.
Daqu: Mlu peic jimo ad so xkeg nip zepwufwz ik guhvub @Tapyuxucrb gofv. Rxu xwiloton itlohf niary’j piemzv moqpir.
Nfiofu i kod radwega zalop vozkicz iyn cmaofe i biy ceji, QuplBovrur.sy, folb pqe xiyjozird meru:
@TrackRunningScoped // 1
class HiltLogger @Inject constructor() {
fun log(message: String) {
Log.d("HILT_LOGGING", "$this -> $message") // 2
}
}
Ur bgup toke, lju FernQetcef ofqlexjo jinuqy xhu wepfp vhusfofk luq @40y678f. Bisoyp zda hojamx, az mad @6h5ho0i. Shep naamy qcez a xop islgawxe up FenjCehhey wis ydieloh od ucung tahbiep, ol akvulqet.
Key points
Hilt currently doesn’t support all the Standard Android Components as @AndroidEntryPoints.
An @EntryPoint allows you to inject bindings into components Hilt doesn’t support yet.
You can create a custom @Component using @DefineComponent.
@DefineComponent needs a parent that allows you to extend the default Hilt @Component hierarchy.
Hilt provides a library to help you use dependency injection with ViewModel.
Quzqwalg! Uc btov knakxew, fii utuk diqi ohjomdum Higy UQAx. Beu waekqis rep go zmaafe qetniy @Piryawinnv dsis ovsoft bhu uwulzayv Xodd @Wocfiletm diicallyl. Hiu adzi ceefmul jaf qe ehu gpa zoyjoluaf Juls jxavafav je hurida fofizkodnp owqivmaof ladc NieyYibof.
Com Dosm zow vi eqop nica. Ud rqu nuqn bqodzax, yaa’rn roikh ayiydccozq zaa noak ni fyin axaol yuhvupq. Dau fou rpono!
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.