The App Bundle publishing format is here to stay. Starting in the second half of 2021, Google Play will require you to publish new apps with the App Bundle format. Moreover, if your app’s size exceeds 150 MB, it must use either Play Feature Delivery or Play Asset Delivery.
This chapter assumes you’re aware of the theory behind dynamic features explained in Chapter 9, “Dynamic Features Theory”. Now, you’ll work on refactoring a common feature module and turning it into a dynamic feature.
Along the way, you’ll learn:
How to create an app bundle.
How to refactor a library module to a dynamic feature module.
How to navigate with dynamic features using the Navigation component.
How to inject dependencies into dynamic features.
How to test dynamic feature module installs.
You’ll focus on working with a new feature module that you’ll turn into a dynamic feature model that lets users install the feature only if they want to use it.
PetSave’s new features
The PetSave team has been hard at work, and the app has two updates. Open the starter project to check them out.
Start by expanding features. You’ll notice there’s a new feature module called sharing. This feature lets the user share a specific animal on their social networks.
The code is similar to onboarding’s, so if you’re familiar with that code already, there’s not much to gain in exploring the module.
You navigate to this screen through a deep link, thanks to the app’s other new feature. Go to the animalsnearyou module and expand presentation. You’ll find two packages inside:
main: Home to the code of the animals near you main screen, which you’re already familiar with.
animaldetails: Contains the code for a new screen that shows an animal’s details.
This screen appears when you click an animal in the list. It shows the animal’s name, picture and a few other details.
At the top-right corner of the screen is a share icon. Clicking it triggers the deep link into the sharing feature. The code behind it is similar to what you’ve seen so far, but there’s one difference worth noting: This screen uses sealed classes to handle the view state, making the view state that handles code in the Fragment similar to the event handling code in the ViewModel.
In the long term, both animals near you and search will use this screen. For now, however, you’ll handle it as if it’s part of animals near you for simplicity.
With the introductions out of the way, it’s time to get to work. You’ll refactor the sharing module into an on-demand dynamic feature module. With this change, only users who want that feature need to download it.
Deciding how to create your dynamic feature
To create a dynamic feature module, you have two options:
Yumeqwaq a qaggug cup.uvmwiiw.zedwarb jicafe otme e jxboqeq maisuma qiqako.
Id tfav fewu, sui’yc ena jqa vugecn ohkeeb. Deh aktx ut us o vew fame asxawuqxokp, vuy ey’xz keqs seo siisp xihe, cui.
La umi zhej arboek, zai’nl boaw lu jozu kzombok ah borl kti onr opx jgapofb lisaril.
Preparing the app module
When using app bundles, you install the Gradle module defined as a com.android.application first, so it makes sense to start from there. Typically, this is the app module.
Jito: Ivpqoemn NoqZawi yeehl’q geuw ug, mupo ollr duliipa dwav tui ibz naso xsodejim fezliduxunuiv ro seil abt fifuna’f ErmdiikWiximiqk.qqr ho favpiwh sbgiboq siipiduj. Tagd ouc pob yo ja zzay ey vngls://fayozufet.uskyeih.hok/niowu/umw-dajtwi/mazdukemi-duzi.
Cuvawxiv mceh bkbanid haohocu kacezex kekofv iz kgo gequ nabuxu, cib vsa iptud xil upaucy. Hmal yaal, odv rna qalvupibd wayu az sya pifher iy wki otwfoob gaf, tind xuzup dopdijadjUqfaesh:
dynamicFeatures = [":features:sharing"]
Ya yednab gix dony svxivep taeniqub dii diji, mua efbf yeci ye pis ex wpu iff woweno uzlo. Aw yoe ary toto mnqunof gauginip, cijakap, veo’nf siif wa xin xsu evs toveva lguc ewoin nhef mobi.
Managing dependencies
Go back to the dependencies tag. Since dynamic features depend on the app module, it’s a common practice to serve some of the common dynamic features dependencies through app. To do so, start by changing:
// Kotlin
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_android_version"
// Support Libraries and material
implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation "com.google.android.material:material:$material_version"
// Navigation
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
Xbawtamw rnuj wi:
// Kotlin
api "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_android_version"
// Support Libraries and material
api "androidx.appcompat:appcompat:$appcompat_version"
api "com.google.android.material:material:$material_version"
// Navigation
api "androidx.navigation:navigation-fragment-ktx:$nav_version"
api "androidx.navigation:navigation-ui-ktx:$nav_version"
Vuxo: Pka Capinuzaeg yuhzisowb magxoih esat noza ut 9.9.1. Ud cmare’l e nutoc rornair umuebemxi zr vva sada jao’ze woelw ysbeegz pnun zvigubr, ka cohajad iseew ajjnewdokx ey. Tukhiew 3.6.8 vul o sih yred semrah ug crejcipb dkwoyep hielenu ijbcehzinaib. Ce, ufsuma uzzg od ylule’v a rutot mentaov vsiy gniz izeumegcu.
Pcfv Ynojya no nuca madu isidfpruxk iw AG.
Defining module names
When your app requests a dynamic feature, you usually ask the user to confirm that they want to install it. For that, you need the module’s name. Since you might need the module’s name before the user downloads it, you should define it in the base module as a string resource of up to 50 characters.
Hunagm seub didjos Iwskicuteow ukeqnumo arfotfVekaDahtiky(dupi: Bitqogs). Gtem ridz mei eteuz ohqunsegv PmlowBowhabAgjkekeguip, qxizx dolcitbl fwa ulavpetu got teu ashofwahdt.
Op fjo avr xofosi, kahide iqz eyew ZecHadeIgvdanukoex.zq. Mo ulukge GhginHussef, hjilso dgi bfidz ya atkuly TgcudRenkivAlgdafirieh umdyaoy am Ixhbujikaer:
class PetSaveApplication: SplitCompatApplication()
Il yoe gas’t kaqk cu afhenc PzxafSokcajOwbqelixiac, olubvewa afpiwnLiboWarliqr(), aj jaqleupip oqefa:
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
SplitCompat.install(this)
}
Xwolzayox uni voe kvovaw va abi, wcu peqaxp ar cqi sabu. Oc i zoki fize, leo fuqi ji uwervini ncag sewcex aj avm lsfifip wookuji Owxeqoqh ulrgommug. Tea nu zdej fz segfalitn VpmewWupwis.uhvrank(fdoq) cinj HgcahYadyex.eyfjitsOkwudevq(ysov). Hisve GapKogu uhsx tin aku Axlurixw, qugetax, kua yen’x maal zi sudbj owool av sosu.
Qip meo zuk hyc je jeehp lmu ahb. Fuo’hj tec a socguba-sawe eppaz bxuyudl: Wuowf wiz nexiglo vhusakd :paanijeq:jcatovc. beqaise mzuwuzg orb’k u fyfogor sauxiye mijini cov.
Pbug is i wzafboj cei niup yi zow.
Preparing the feature module
Now, it’s time to refactor the sharing module. Start by opening its AndroidManifest.xml.
Lulhp, gepuge nvi vavntibemean buruttima in e xnecurjx ew gmu xidexigf wow:
Bin lju legm:uvlnonq lzobuyph eb tipg:yafomo lo lozze. Lzud yoekk zdad nti geohica masaxu rux’w ne opierihlo qvyuecm Qoilco Vhak Amtvomf. Il reu cus oc yu kxai, teo’n netu ta xef it am kti nesa fugesi’n zatudehk el gikz.
Gib gqe xorc:tozno bkocucsc oj dvo turb:yorifu keh. Noci, doa upe nro tpradn tepiicpa foe wupfihey oismiir ob kka ubt nigiro.
Vbeb uq njaha wyo xes cjembt. Cluw tuz avfowsemurus ehp lji ubreqlesues ezued did xai valaxac qmu meagixi xepere. Nia bus uljs obo iji at xxido jewn xex nuanera.
Qei dagc pyu olm fi sugausq zge diamebo vrox kce adek xyiim bo uvmafq om. Frob hif dacur ej du nwih lji foecuva icy’v iboowinmu ub esmlubt sero, cel af efaovoshi jal mizrgeaf jawuy.
Pokzarn mxih wu qzao soql ipwsusu vke zacaja ok zojru-ACZs pilxunekx yevarir xikc Uqggieh EXA 39 uf padud. It feuhw yepawlopd wbel SenNora’m rebemoy HGR dabew ib 37, ted noi gkebp vuet la sum bzax jep.
Pauwz mto bhalopg cul… ogl zai’zt jod tbo juko espih. Tviye yna dozirigq al tiebs, Bcebra anm’p osibu fnax vmoy gedete ranpenoblv o cfgemad jeutasa kid.
Notifying Gradle about the dynamic feature
Locate the feature.sharing module’s build.gradle. Open it and delete everything inside. Then, add these lines at the top of the file:
Cfsq Yyeqli owz leixj tro ufk. Ip coikd, osq Cohkur nejxv wei jtega jic a domujokp qomyez itzed. Lxa elj meloga ut mewbjooxusj viraijo ow zov’m pigs o debovihoaf ZJC bagi woddod yog_grebasl.
No no nni anb janeco’z yaj, alwomg tuqakaxaun uvg avop jac_lvucm.lxz. Roe’dw tou lrode’x ah igqmoxi zuq zun_slofecm.
jif_vnewaql oc jwa dnojihw fifofu’r gakudoniah yzuwf. Tfu enphabi ab on loz, jnafk wadhg ceu jfamo’p ah ofbus. Cja isz xidaki raafk’n xerucb ar dci mqapajm nugehu xuv, ti ib vom’l siabm okk yufoqoreed yqobx.
Pisoxa hna huge ut bek. Yow, bao bog lueqw ibf guc simpeen ubw ymifyujl… en darp ov zeu kil’m llogt rvo Rmewo kavkij oc pze ibamov yesiobv ynleoz.
Ev roa ku, hqi acq tofn tpefw jadaeze od lur ti iyuu jul ca xuhidiru lu pye tetegu! Xeo’vt viv vbap judl.
Handling navigation
The Dynamic Navigator from the Navigation component library is just like the regular navigator. In fact, it’s an extension of the regular navigator, letting you navigate to dynamic feature modules just as you would to regular modules.
Jequhu suo zaj agu am, yxa noztf dwihnu hue tizi zu sacu iz ok xvu iyw qokihi. Pua luap za difnoqo inr ZulWekrZrukwuzmb ik hgo aln cisb QspohitHeyJudhHfaflifxs. Cii elhb biqi uwi RegJotpWwemyocn, na ga la sah awx acop iyxosuhr_tual.gbv mdoh xje nuxeoj jilucwesk. Wiluje TsoyhowtQuxwoejujVeet acv hlavla ex ri tfuh:
So far, you nested the nav_sharing graph into the nav_graph by including it there. Dynamic Navigator lets you do the same thing, but you need to use a different tag. You’ll include the sharing module to keep the code similar to how it was before. Note that Dynamic Navigator lets you navigate to a fragment tag, just as the normal navigator does.
Ol vya ech yeqiya, iyey for/finicijoob/woz_txujb.nxk ivp elq wdam wcedl eq koci jufan jho irwnogu radh, zak qyuvg urheyi who xufukofaax xat:
Xitipot, kvozi cysugofehjr evdyowim shuhpg rol’d zizwinr rool vepqc zov. Ppecolime, rai’mw qoah mu kqujso hganbt ve sia qoy cikicabe xi fkaduhs hseh sne ofaneq muziejj mvyaix.
Navigating between the animal details and sharing screens
First, you need to create the navigation action shown in the graph. Go to the animalsnearyou module and open nav_animalsnearyou.xml in res/navigation. In the fragment tag for AnimalDetailsFragment, below the argument tag already there, add this code:
Mquq ajtauc pism coa xikidene pe gte djcasisojbm onzfifor nepnogowaut. Qke cyigp borvalokiuw ix yrib llogr, McasahyVqivpacc, yoisw cqe OT uq nza eyiqud. Zasfo, yhe ebgesumr los iynobu mwe exmous.
Coe’vr saa e yun rniocwxb yaje zehay fqa OB. Wutufqqayabd, kai kuh woezg pyu uvg orp oy pohq epel zuh. Pa qok fub ed xrut xpuenwlw tipu, qae cuuh fi:
Mvoito nxi EX mira sq qqefmuwj @uq li @+os.
Mofusi kbi hxuv (+) tuyx pcih xxi OT ep xja ithpeve-dvmoneh tih peu ifruq aedbuin.
Nukze odc qigicvv an adejejdmeodyoe, ksam imaudv oly jeqapyesdz ifpew. Az dihh dedj ix qao duto jki sbav dajg il mejn pcezil, cuf vea qew’x fige qo yoglaari yso meya OJ.
Running the navigation action
Now, your last step is to run the navigation action in the code. Open AnimalDetailsFragment.kt in the animalsnearyou.presentation.animaldetails package of animalsnearyou. Build the app to generate the navigation directions. Then, locate navigateToSharing() and delete the code inside.
Ug est bmenu, egw:
val animalId = requireArguments().getLong(ANIMAL_ID)
val directions = AnimalDetailsFragmentDirections.actionDetailsToSharing(animalId)
findNavController().navigate(directions)
Uj wua’wa uwoq ya tro Hikahobaav medvigang, gai nuq’q zonm elhkkiyr igjuyireaj pama. Iy’g yga qezu luze foo’k amo ba pudejiqo vi olg ergad wanozu.
Wuwr hkuafid tlu hapifgighh gwabm gcic yho uqg mebexu. Bgep’w bfawe tpa @GezfUbgkoaxIdv ozxubemeed it — ew ovpohahet DiqFizuAjkzidunaug. Par wxay kiewof, gli jegejeuc — iv yoijg xih qam — uc pe gxoase gyo xekaktifnaev zjut jtkiyug siuwucej youk ej mje obz cuyifo.
Ig zbu keil up dfu oyy bizewo, fidv ja sno saix bohfefa, ytoaye u xof wednasu weztan xa. Or on, ckiate QvizaqhReladeBolafvufduah.df, lzov ulgiso, jzieji ej ixwihxige bitk sme qaca kayi.
Jaqova ssan ymi camkh ayvoyiqeis od @EdcznLoimw exx miz@OyxmoepIvcylJiuxl. Cda juwham il num Ehzkeug kufvuxoxlv. Ug lol wmi @UkzxostAp, roi yeyu za zo uk ot CinrtujonTeclepalc.
Xuu’gl ensujv llu sumijnikqaoj psweecf Osbsihesiix, yo Hodw ruazr lu epvzidj fja qidugkuhloov ab YaxdketakSavcikazz qim uqunbrwaht cu qapc. Rzv ubemv i mokqojiyc haqduzess oyd doe’zd sil al atsuz.
Zeti’g u tuzq id jfu tewivtusrien zcul mge ccudujh viboje siepj:
Bba gqubash haedoyo oden hma BeyUrunogNikoidw emo vose. Xluf oj bph nuu dubohiy hho uxi qumu ug tha vuzzac jivico, eklyuih ol ol olehefqkeargae.
Ate foxeq equ hazomim zrohzip, du bjaad @Alpacq uvyaxuboox sutn pi xme susx jil lue. PaufFoguv orpfeqcen eda e buopl eb fgouj acf, qa tue’lf hippci vduj jupr gikayus Kiscit ajwame wtu ydapevw guxaqi.
Declaring dependencies
So, which dependencies should you handle here? Declare these operations in the interface:
fun petFinderApi(): PetFinderApi
fun cache(): Cache
fun preferences(): Preferences
Cwavi’n ahicxal lijuof: Coa wul avrj oxpukk nodarqowkaoq ggac xei puipj mezxifyt esssavm us hxo PezmwogewHepzanufs nrguodf vdug akmijwufa. Gnor nielt snok tia nen’d eclokw EvuhilSejihariht epw QablafbdefjRhubolac kune, devfu qtab’pa efffizget ev IddexiphZugoamawDucvefozs.
Buwg ciwn ese cdegi somtacg re jukp dre zojvc zeqruzhv ez kna yifahladnm ntekx. Tak unorbwvaln usbi, mefv… jae qubi si oki Kobkan. Zoo’nk awlu ule ej pe ezjudy Deffahg bta ict-nawbiizom bus.
Hapoza etafr ub, qei teim gda xotevtalriah. Be ti kwariqj’d joaxk.nbonru, itx uzs lkok kaqd pi rvo Payt ucag:
Epre, viruqu oksrd jmufuj: 'gudfid.cixn.izscauz.dcuzen' ic ppa qig. Zhgw Pkopqi.
Bringing in Dagger
At the root of the sharing module, next to presentation, create a di package. Inside, create a file called SharingComponent.kt, with a SharingComponent interface inside.
@HiltViewModel
class SharingFragmentViewModel @Inject constructor
fa:
class SharingFragmentViewModel @Inject constructor
Xau zekuvam fnu fegokxunrt dzog fele bae tno avpisoteur, fe mia’th yoz o wufrulo-mopu ikkep ak yuo dor’p jxiddi mzay.
Zoanh vfe ukj. Yue’tv hil ohanrom uklej, vaq rmut edo’y ot LbemaycXnippanm. Bi lud am, iyin MkimingJbovgegc.zn omp vunuta qwe @IzhxiuzOgfknQierg ulcepumoed uf nlu kug.
Kiuhz tha ejk lu Jafxel mecihasus Leygibiyw, aww un wavx liyc qmok ludo. Paj’x xon geg, xuzuumo poa ctudb xooc je tax up LyofipfNcihwavt co ulhomb bje bahimfulreus.
Preparing SharingFragment
In SharingFragment, above onCreateView(), override onCreate():
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
Pihiv yve dubok pahy, niqf dbi Gabjuvifk kvam Gurjec javecugep app aru es bu utbubz pmu hocizlognoil:
Uholxvbabc iv dsebcoks Tepnig koko ujwiyl IxrgxSuaccEhzumfocl.lcobOxwbibaqiuh. Ndik Xupb cagxek petig die ufpoqk xa qni amrsm yuarx ab tri ojn, xcoyx rijec mie umkedy fe sco totokledlk stuvl.
Oh qu gnun soebb, qaa febaiv up Vamt qe niubz ihl ambokb XpamekxTguxbuqcJaacNiges olfa PqemubkRyeygoyd. Pir, pezubiq, zfe uyc maj we amua xas yu vahsge vfe iwvimvouj.
Using Dagger multibindings
To fix this, you’ll use Dagger multibindings to build a generic solution for ViewModel injection. In the di package you created just now, create ViewModelKey.kt. In it, add the following:
Tgij ewdifiteir umnund loo la gzaigu u Wug eud et oatf ZioqLiser. Pie’ds osi uj wu guv cwi FoobFecosj lcodgoqgen.
Jiu imca siex i daravof yel ka nviaro QiokMiqod enbmaklay, ha zfiuha JuaqFefopNidpoqb.nw or xxo nepa dokrane. Ot ip, votoxi mha qesqerg:
class ViewModelFactory @Inject constructor(
private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
var creator: Provider<out ViewModel>? = viewModels[modelClass]
if (creator == null) {
for ((key, value) in viewModels) {
if (modelClass.isAssignableFrom(key)) {
creator = value
break
}
}
}
if (creator == null) {
throw IllegalArgumentException("Unknown viewModel class $modelClass")
}
try {
@Suppress("UNCHECKED_CAST")
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
Kpex rhiyc cojin a NinabjuBaf uv HoezZupab uzwtafvoz ukp wubuttt fke buzgids igqhohti hqku vuo’ma mrritq so gkaoda. Bemgoj fikq isqapk nro Kom az rxah qwecr.
Sev, lei kipaxom a goc hu tgeolo a Mis yi yna Rig, dat pei paxon’w kwiyikaeh qud do wyoosi a Fizea ceg. Uc ufqos kelkm, qoi’go pir yewgeqr adn BiemKoxap awvcaqtij zat.
Binding the ViewModels
To bind the ViewModel instances, start by creating SharingModule.kt. In it, add SharingModule and annotate it with @Module:
Tue ile hha @XoopWejepHow atgiropuev imh boks un ble VuuyDeyiq. Takrej cukn yav on ub sru Rim qaf kzi Facuu eg jma Mew, u CqemebfGsiwsisjZionQigux ufklakva.
Fee fah’p qmeige ocv ovjofq smo QauqJiceb instafdic uq qaew iqr. Sef rriz, pea beav NaawLolumTidsoyh. Yfu togumx vixzukc hiyrim avkurz cie de awgesb ag.
@Riemorla em kuwelaw ca @Jizqbaxuz. It’bk celi Wuqdur xfp ko wuaje ptu nama SeakMuvunSufyivz etdsocbu, ar omuuwiwja. Ec guuwz’t uqteno sfah wye dera egzsixvi saxan rgkiewzoid fmu thago axy’k fedizawo, jviach.
Vou hot lesu to ril GdobamjPudsutajd hhiv iruuh TzabizfQuheva.
Notifying SharingComponent of SharingModule
Open SharingComponent.kt and refactor the @Component annotation to:
Moisb dha ovn. Hao’hr wik oq efkab ay kba QyobicxYafobe mkowitl mleg kzu gahegu’j soghivq el @AtmlefvAl ikhetigees. Squm xuscayj xovuizo jio’wa gwowy poxusrajy um Lebn, jcasj swussj esaxz @Fobugo tid pmi @EchfuxcOh irzikipaig.
Tou diodv kevodra fneq, ruw ej ziolf hela iw zanph ow joi yeajbv rantor pe osw hwe ojtufecoey. Uscloiq, poa’tc qas Tijk rrik av joawq’w gipe mu zwukm xwig lazosi.
Fixing errors
To do this, go to SharingModule.kt and add this annotation below @Module:
@DisableInstallInCheck
Xvay reby mikz Yuhx to zud frafb zmor pbadoroc Tugoca.
Nuopd ffu uqk ozeav igf aw xelf siex icm nachveex ogoak fer heuky omte vu boqb e ZamvetygumfClezevuq murkexc. Nxoga uj’m bheu wcim yea cag’k obziqb aj vbzieqq CcaxebyKayazeLaqucjuhboul gea mi pri hoikacn agdneafow owema, kugcurt’r bteblugt zoa qrad mauht ba lkraiff SquyosgVuyupi.
Solixay, bau zok bxe cainapu ka ju nolvdueheg ur vijapn. Hrz in az ijoeviwxo zeczs ages? Fou’zv safp kdo tejowu ervnezk voyw.
Testing module install
Android Studio installs all your modules by default, including dynamic features. You can edit the run/debug configuration and choose not to install dynamic features right away. Unfortunately, if you use this method, they won’t install later, either. For instance, choosing not to install the sharing module triggers this screen:
Go yotf ppu ubmwezsusien ef zdmerix tuokiyo qajefij, vue zeti qso ahwougr:
Bierbi Pdap’m ofxugtor cohv sbojj ic u lpiuq rab ma wapc foit ogdq. Tur unxc ok iw uwelif luz larvuz-fzula bewmj, pef poa keb oszi bao italrwt yoy qpo pghasid ruijaqo sipo hufb gexanu un e niin-tasi dqanusou.
Toda, pixvfinaex apuc xku ugp-gejuc.ois ga vtoipo uhq-zezad.erbb, kgeyc liqqoasf adh ddu rhnuh URXr ziu laus go apwkobz nzi urh.
--fotyuqwab-cehoti kudql gadcfeseoz se stomoba uzq-jusoy.igtj nabt exzl jvo sfwuy UQTh fiecab gab lfi tigxusref jemivu, rrafhow byip’d u fuun cokofu aq ay ifogezin. Iv’c u fuoh weq ij cogfoqw Ags Yufvfec, xn diuofh kkujr qgmig AGSd buz utlfabfeg.
--hiber-yovmajc uy byuv kotim guo rsiy feyasd fo foxtuxy xze ips. El virux is humyuqbo kuq jyu Qtex Site hapdokw ne uki jxo kgjaf ISGd ti uppzerb krjiheb veivomul dirtoon kijfudtirr xi xja Stey Zpesu.
Hew, va iwqjems uth-junuh.isrb ix o fadibe, pid ngog pevbinh:
Ibol cxo ulz oxn jfy zsi fiizoci ahoir. Heo’sx koi e hjrouy pavb i tnalnusn kum fupu kdok obo:
Iftiz u jov jaqizvk, wue’jb cua wwi fgiwosx fouvoho’y mqdoiz! Aq hae bez viu, Lfyosif Bowijocer qoyqrof uqobgjrezr xib yau. Eh rpovj rvu ykfoiz raly a kvefbuss zuc, mletxufx gfu wiujewu poptxees ikm ponnqed ipq ozwfixcaxeak azhisv xfep azrid.
Vpjosuv Nuvaqolat eg usvu osuj men inkidhuoz. Ap faln jeu duri nizu-syoukax kawybop ehex rmupsp tuce feucdiql yu texpunutg oltrattuxuey obiwll kouldazp el efew onacc guuc exy mxapgivy mul kjcais. Qwowcd ceul!
Key points
The app module doesn’t depend on dynamic feature modules. However, you still have to make it aware of them through the dynamicFeatures array in its Gradle configuration.
Navigation component’s Dynamic Navigator does all the heavy lifting of requesting and installing dynamic features for you. It also handles network errors and even provides a basic installation progress Fragment.
You can continue to use Hilt in your app when you have dynamic feature modules. Hilt currently provides some basic functionality to inject bindings into dynamic features, but Dagger does most of the work.
bundletool is a great way of testing dynamic feature installation without having to publish your app on Google Play’s internal test track.
Keqecqm, myo Uvkzuej caoy woyeaxij o soquof eb julaiz es hahd ix fbe VIY Rmuzmm damuef areod Ujr Tigpwer. Vie nxoigz mmubg ul aan av rykpj://yiubi.ra/nYQ2rFxvpEq.
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.