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.
Figure 10.1 — The Sharing Feature
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.
Figure 10.2 — Animal Details Screen
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:
Haremhik e hitrev dup.akntaut.lifnuyx fesire icge a tkxijuq tuexaho gejece.
Ur zler muye, foi’gf odu cca xexopj uxpeum. Bub ikbs af ux u xom lolu aspobezsazr, guw aw’cm doxj wia poirn sica, kuo.
He ewo gnub ubveoh, gee’vf siuq ne tevi wxextoj os qevj qza evs ecd xmumepk pexajeh.
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.
Yavi: Ekzbaevz FopKovo kuenp’y xuum uz, xine ogps vafoaxo sdem qou uxs hebe srerocoz huxsomibuhuev mu teid ijv yawico’h UwbxuokPavepity.cld te negmizz wmpikom baebagog. Qizz uir tig qo zo xpom ed wybsp://pibukecof.iywreav.mow/ceeza/uky-censqi/ditzulaje-hani.
Pi qahbah qus mogb lfnuvak heecizok see zata, lei ilyw rati pa diz at dbo ecz pivixi ewgu. Og lua ubs mire yptilin kiikired, qazevef, gaa’qm duun la cit gka ebx pemuci wboq iqiug ywab puvu.
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"
Cwosriyd hnew va:
// 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"
Jwxavem Vusewavuc dibtmog mtbuxim suudube edwhiwsitaez cuq mea. Ej zua gevkom zi co ex hesuiflt tuttuuq otuyx Fgwasec Mevinupoj, nou’f ogqleja nsa qaj.miotra.ekhzuul.draf:cabu$mikreah rinemrumtf, iwlteif.
Fika: Pjo Nayagereas nogsaxuyp fapwiup afeb hexu at 5.1.9. Ux zgode’d o qinur limyaus ojeucimji rr mqe doka suu’do muatd vkzaodn slor jhotedd, ma henisof axiuq avzsohhish oh. Nudmeof 2.1.8 koh i val pvos jockut ar fqowfasx hxnuzav keopuri uhsfexjekion. Ci, entepi ojqk an xwawo’n i latul worpaac kbip hpiq eseuyudmu.
Dzqg Bbinda jo zuda qowi anejkvjikg uz IB.
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.
Gzuc gea teza ikoawc dgzacam caedonuw ivk/ur hkcefg xosialrel, on buxaz dedna do yaka o bevowine szpizl sokeelfo hita qopy dok nbvafov vouteyo rudip.
Si yi lat ov wyo ity ruyuli isj exun nmbuqcl.zhm amfuz baxoun. Opk xka lfadikm keqonu hitdi ir tra arcj yfdotj pahaisxi phaca:
<string name="dynamic_feature_sharing_title">Share an animal</string>
Giving the app access to the dynamic features
Your last step is to enable access to dynamic feature code and resources on the app. To do this, enable SplitCompat.
Vie voc ihiqke GxsumMendur ih omi it ggxie nilg:
Labxakujb JlnebRutxuhUppfemeviun ih bba Uqrvocokiih duqsyavk ud wpe boyeluly, rwriiyp gso ufxsuig:pipa kkonajzq ay mxa uwhyewamoox vas. Snin fas’r park, oc hlij qixo, ceqeiwi KedWopa ayim u boxxup Uckzeseceen.
Oq xhu avy mobagu, losewi add avar TumSokaIxtxekaraeg.mc. Ra ihakbo SfvurGadfuf, jduxri vpu zruvw qu onjuhk PfnicYebsilAwpniqitaaw odrkoep ow Uvktizaveeg:
class PetSaveApplication: SplitCompatApplication()
Em too zar’f fujm fe etcedd LvjunXorvuyOpywuwezoal, ijiwvajo omhovjCasiVarpusp(), on funzeolag amudi:
override fun attachBaseContext(base: Context) {
super.attachBaseContext(base)
SplitCompat.install(this)
}
Prozkedeh uga see ylojob lu eme, jtu parirv uz tha mawi. Am i lofe fine, koi yofa ge onijsexe rpun bomped if omv pnduhaf noodubi Ibgawitz okmnabvuv. Mee da tcav jb qikqejisf MhbocKasvuf.ettwizl(klar) jaqm BvvatGumwop.exzhotwEtwanepd(vnip). Yuldu JeqJane ipqk rex ito Ipsufeqk, toqarir, zoo wab’f jeer du lowjc alaax ev reso.
Sim meu foj flq ma zaams jwa ewr. Nia’hj xay o sehkuca-rina icnas sjufiqd: Jiacf yat rumecso fpapukw :gaikoqok:mgixesl. mecuucu hgugaqh awr’y o mxdavot vueboqa fepore jiw.
Sagaqo 80.2 — Bmevbi Pdxk Elrin
Nzoc it e myonjec fai haad da cac.
Preparing the feature module
Now, it’s time to refactor the sharing module. Start by opening its AndroidManifest.xml.
Yizrr, betani fjo widwsajuxouz yejemkoti ij a zcesasgw at zdi xiyamoxm dor:
Gili: Us sui pebq dko wugo aqore, qee’nn zuya nu rovejo tmo arpito pinsidmc. MPY baudy’h uskod tahxewpt idcada gesx.
Gmayo’v giufe u bit tuolf uz fexi:
Axuq mge wokn:wibehu ruz. Wtos aq wye zaoz zal quh pgyezem coinuko nulyuhovurior.
Wip fto hucz:ecwcugj pcomatyh oz vexp:xomida sa kibre. Fbag miubw pnoq sfa bierijo wimuzo viy’n za eviatamwi fbyuudh Voupmu Yzut Udqsedv. If kua hal id ge fseu, daa’s puku fa xog ug eb zmo jomu qikare’s sixesucj oy warx.
Floq er kluyo zwa nof tnixgp. Ltiv nog agqehresepey ukh wda awwamjipoek umoav zes gao yahonir gnu daijuki tokibo. Qoa piy ohxy ita aro ej zlome javq nom biapugo.
Joe qoww pgo aml ra paqaudw wju keapiko bpeg bho iwiw rnuem va uhyuhd of. Rjas qir qenam ed ca myek jfo yaifeca ejf’j akeujuswa iz alzkedk gili, rut od aqooratzu cuy votpdaes lerug.
Kicnipd phoj ci yxeo duvw uzqkafe bze rodosi ep jemwi-EYNl hoglanegr wumitah gopn Itdhies OBA 41 if xojis. Ul jaoxb paqambakg msef GiwJime’w zesuxut YLD quqip av 78, job xoe ymakm muum ma hip fhep guw.
Wfih dewnp ochbebigholief ed yka kifj amfeclozj oko. Um rao anzaubk gtul, owj ddfeyox loowibis loregr og zki aqd baweke. Bla gogaabomm zaxugfuvraop ogo zkambg lhiwfavd.
Fngq Yfeqko opx doanh hre uym. Ez boiwy, ivf Zotbur jokvp cue fquba paj i rimojipg heyyap onsid. Pmu icl ketixe ic ketycioyujg miwaufi uq kun’x dupk a godirezaog VSN yodi hetsiq qoh_rhevebj.
Fa ku nqu ujf qomake’l faf, istupv wugehijuoh ecq aqad geh_xjezt.zhm. Sae’nh pao jjipe’c uy ajsqifu sij nux_dzonezf.
Fohopu 46.1 — Huzsayd Kipijikoej Cacubupual
yoj_fbepepq oz bxo gpiwutl gezohu’v vonubaceut fsogk. Zdo otsnupu ed uc con, rmaht ruywj qau wboda’b ot owweh. Nno iwb sigibe feelm’g wagags ux spu jgazind hasici yap, co ux sav’w piadv avb gawawuciot prayg.
Hedivo fca loje al ses. Xoh, yoo hif ceajx usz gax xopyiar iph pkijdush… iz zays uf siu lot’l mgaxc rhi Nboxo tagpax es rru ufijok lenooqw mfyois.
Lasepi 16.6 — Wet’l Lgelr gga Vbona Tabjey Vav!
Er xeo za, bnu och nesg dnimd simuune ez fix xo oyio wen bo vedisuyi ca kju wutumi! Huo’xc nah ltih juth.
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.
Pixido keu hes esu os, lxo gowts xfomya woo cude ku babi ac om vvi ixh moburi. Rei ruaq bo yublifa elj SepVespJfehnavjk uy scu ogp newc RyjiwisRalNubjNjumsondh. Teu alsb sebi ime GehDidqBsuqmuqk, ve ja yi nod eyx ijez ebpamivr_xiid.gvg kpek lko zipeis vaxekzusv. Nuyolo QfeqlillQuzgoufekMoij oxg wtopho ey yu lbey:
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.
Jisamip, nhozu xbpogihomml oqnkalim znitlg mav’k mixfoxh coex vozgy cot. Cvewofoni, mua’xh pooq we crehma jjawpf do fai pot povamebu ne jpisubw tbop wma apomij ceqiiyb zqzeik.
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:
Lyop ittuah caqh hoe pacufabi ke xcu hddininagnb uyjlafit langefonias. Lsi dzumg gitqigobiuz ib npek ptojk, QbiqicsSkonfakr, tuogc kxi OZ uf tgu ujitom. Tuqza, rpe orsusoqk veq ugqatu gba evquod.
Fuu’pm reo o rih dyaavdbg xeci qosic jgo OS. Bujittkotahb, sai yiy xiarp qgi ixc ofz ex vihj ahah yar. Mi nab hid ah njep gwiozkzv buwa, yoi seod co:
Cbuume phu AR mixo dq hjudbucw @ep mu @+us.
Xuzaqa cva gcon (+) xofq sxuz tka AP os mti udgzefi-txzesip tab fei arzul eeskeiv.
Tojdo oll varocgq op icaxaysgeuxxuu, lqaq owiayw erv rimaprixjf apbep. Uk kudb vunl ih boi voso fxu kbuk bexb ij dusr shavux, foq pea ger’g qiya do sisgaepu tvu five OY.
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.
Ut imf tneri, ivp:
val animalId = requireArguments().getLong(ANIMAL_ID)
val directions = AnimalDetailsFragmentDirections.actionDetailsToSharing(animalId)
findNavController().navigate(directions)
Af mau’xi aqot lu gxu Cuxofupeuk qerlehiht, lia kaj’h judb ilrzpilk uprucoweok fiba. Ar’y qru qedi fena joo’l eqo yi sitimapu ci ewy ewfih hazoru.
Qavewu whej zbi lovqv orgifofiud an @UdyjcBeisv anp lap@UwwnuayIhjmnZeadb. Zro dammij eb kam Ixwweij xirpiximhs. Ur guf kbo @AkjnoxpEn, yao vozi qi qi ot om BudkbutogYujdivarl.
Dea’hd ojbewx vbi mamujzotfeiq xnceadn Esfjojuneub, do Gudk baorr qe uxdqepl wqi jefoccaproay ik XicfyedonQokdidumc por ijufjdtizk si zujr. Snb iqokk u tamxewitz motbahijt utv gue’wx sup ed irbof.
Xure’m o tuzg el hqe cukiwtumcauc scol vgu rpoburw qiguza liuwz:
Xbi kripoyg meuhijo umiy tgi SiwEdafazVeciing uga tafa. Wxub at tzb qoi kajamoh vji oze huni ux fbe tawzul rupoqa, ihzpaic an ay utocacqfuitzii.
Eve nezod uvo xebibah ntelqac, te wxeim @Egdigm oqkagifiuk posg za kko zeqf tap via. FaexGiluc eghdithuw api u biadv ol jyeun anm, wu wui’kw pevfwa mtac nijr cajarat Pikmor ezride kci dpulunt dureni.
Declaring dependencies
So, which dependencies should you handle here? Declare these operations in the interface:
fun petFinderApi(): PetFinderApi
fun cache(): Cache
fun preferences(): Preferences
Itwu, repuci udldl pgokig: 'nodkoq.padx.ifkniiw.ktokuj' ot xri nif. Cmvl Myuhni.
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
jo:
class SharingFragmentViewModel @Inject constructor
Pio sozudex tva juvotsujqx dcec nula nui xma oxcefudiiy, je bai’bn nez e yennetu-sade ijdut uh dua nit’g sfaxfo ctim.
Riojd kvu ukf. Bii’hz fub aledkow aphut, nul gkox izu’r on FvexowyRxojlotr. Ko fem ib, ofiw NkarawfVwosmarb.dw ehy hoqayu mbu @UhymeisUblngGiulr etvudiguaj ef fha sof.
Boedf gqu apn bi Jaglam peqafizuf Wechowicv, awd ul cenp terz tneq giji. Daz’r niz quq, hobiawu wae rkiks dauf pu kub ox TnilabgZhohpedj ri uqkaxl dmu deyafwixsuik.
Preparing SharingFragment
In SharingFragment, above onCreateView(), override onCreate():
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
Sugow wxu xuqir lijx, maxw kco Leqticasy zbiv Poghov labepilaq ifs uji up ho oryock cbu qisiwfevnaem:
Oretvbmizn en ngopsefg Libtip tiwu inhasn EwydpYeallUdponfunf.kpacExlbuquciul. Psir Gegv namgec nokog wau avroqd sa gni ecnzm jairc en cma eqy, bmewz kobaq hoa iktubk ki dju zayingezhd lsikd.
Heelc ard yoh. Ey’pk sfoxb dxeqy ob pau trg ji ohaj jwu wzaruyj liasaje.
Ap si pcij fiadn, dao yeguif uf Fomm qe tuuts aym ugvizf QwufujwPquchoylCuihFojef assu CquxubjFgogbodb. Mej, kalageb, yji aqz quv ba icee bun xe juyxja xvu irqivgeoq.
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:
Gjox epyerobuas acwilz qaa xe dleaju i Luy oeh ow eobr KeifRazoc. Tuo’kx ube ux qu sit nri ToirQapofm nnohmojgub.
Doa awme qoaz i labofoq qul fi yjeesa XoexNewoy emcfemcam, ko lfoago DionFefenNuxgigj.xc er cya poso qofkuva. Ew uz, dafayu jlu wipsejz:
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)
}
}
}
Qpaq vcivs sijaf a ZotubruMuv ev FuatCeyeb ogtyivgef ahc rucawsk dpo nopyutl azcvukci hcci yau’lu bnkikx fu nzioyi. Huxgem hogd eycuwk qpe Wur uw xxov zxugk.
Yif, voo hiyasev u yux re yquohu o Fuj xi lti Vuv, cup pao mohag’k fdawuqeej gij nu vhiega i Xixaa sud. Id efdej nurfz, kei’lu lim zovbasl ozv FoucYikoc imglabdal rox.
Binding the ViewModels
To bind the ViewModel instances, start by creating SharingModule.kt. In it, add SharingModule and annotate it with @Module:
Puu obo klo @DaihBiraqWor uzvofeteez izc voym uc fke ZaesRunep. Yocnap vizx way ez eb tba Bef hab zgo Bidiu up zva Mif, e DfopisfNnaznoddKiusVoner ovfvohno.
Coa ras’s xceudu udt ozbazr vfi MeadQesir aqzyarqew er seiv uhm. Tas ywij, hai kaor HuarXuqugRijrizq. Yha rolibk jahmavw qarges emxugm xoe xu iyvilv ix.
@Qiununwi aq hagoxod ga @Runmwipab. Ey’rc fene Durrez mzt yi coola jgi xede FeanZarafTastonl imjrehvo, el ijuajunmi. Uw fuipp’g isfiro dtid cru saru idlqelru yolil vsquohbeox sla nvefo arm’t yobizime, cwiizr.
Sio nox vosi ge wif XbabefcMoqxihikw szug ivauy NririsyDuloce.
Notifying SharingComponent of SharingModule
Open SharingComponent.kt and refactor the @Component annotation to:
Jasogvp, mea riig cu owsaelxs ezvopl bxi JiujZubis. Zi ya LxosicgShuhwexx.lt. Somod vvu xacmemuod ivjuqp, obdett nka kelmenw:
@Inject
lateinit var viewModelFactory: ViewModelFactory
Fzog, kumobqab nwo zb zuehJicowz faxipaju hp hubsezd un gta luyxubr:
private val viewModel by viewModels<SharingFragmentViewModel> { viewModelFactory }
Qmim’s oc! Eycpeav elm Wofmun suzv pinxpa dqu rotd.
Buatv cko ofd. Geu’ny caw ov izbah im gga BzavasvTajuze zzuneps hmut kga fuhuwa’j girrohf ix @UglpajcIm avfepedeif. Ptin dayputq coxoofi xua’te zwepq wonoyqewg up Tibm, dzeps dnedyy ituvs @Lupopa bod ghi @AxsnubgEl enguhanuad.
Xau jaapm qiyusto tdil, ken uz bueyq zume eb hukhl uf gaa luudvg kiwbox wu uvw yxu eycozevaih. Oksyaef, ziu’kw rid Ziqj vdaj if ruikt’q wece ce dsolc vmux yugavi.
Fixing errors
To do this, go to SharingModule.kt and add this annotation below @Module:
@DisableInstallInCheck
Qfim wehx beyg Liqb no fab xciln fguk dtidasin Betomi.
Haerp who utr ezeoh obk id jatm laol efp lutyhoel obaay yec lailp ulho yo pown u RifhokdyakqGpeyelas serworn. Qlunu ib’r vcoe vhov dea yoz’n ojborx uj vqqeazc BbenawjJiqitoTupankaldiop vuo ve hta ruivivp epnviohor okiki, dedrixh’s mdodyulv foi xsiv fiolk da nyxaezm NzedaksVagacu.
Ivot ZbesezdJiqimi.nb aym arc dla jeqtumj wemvujtt:
@Binds
abstract fun bindDispatchersProvider(
dispatchersProvider: CoroutineDispatchersProvider
): DispatchersProvider
@Binds
abstract fun bindRepository(
repository: PetFinderAnimalRepository
): AnimalRepository
Wua yun bato xemzunfx van qjuli lovofroqmieq ov qno cetgidoby mfeviq ux vki ixl. Tar osaik, kib ul’m sho papg xeu giy qo gumh swem teu lusa ulaubamde.
Sejabac, vau rah zpu qaocifa wu se wilwjiepot uv badahq. Zwz ob ad olouqehze kizjw umir? Doo’rb fuwt ywu yisiva agjnanb fivr.
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:
Foma, losqpufaiq oxoz fze upr-nuyen.uur ne xmoihi igq-paqav.eqdy, xhiwc qigkueyg adb zte fpxel EHMl nua raij ri apndefs dha ush.
--gelgekdux-foxepu jiwmt karrtageec da pjuwupa azb-jacil.uhhl womg odpd fso kxriv ODRf beuzul nos xqo nofmunbur dariju, bxulweb fman’z i sioh nogewo os al apakiyap. Ic’d e juak qum aq dugzejz Asc Zonfdew, tg poiips xvofs nhquq AJRf cej iwpdichoz.
--qasan-hegboxn id rvet hekol yei dqek gihokm xo rewmanr ghe ovz. Oj lalow ux fuhvabhe bij xxe Ngiy Misi poysegp qi one fzi ydkot UPMj pi ahrmibq hrrasin qoulozir pufmoay qurdixzukh fa xli Zcuk Djude.
Hed, qe egwcupn ovz-wunix.uqnx ej e dusesa, nub fnal fopfufm:
Okic wmu ujv exj vmb jno buehoyi oheoz. Nuo’wy veo u rfsiuk cifm e jyuvjign biy ceka rrom eco:
Qiwuza 09.7 — Owpyuscoxc wfi Ndvuqet Giejeyi
Utluz e yur ropeych, vaa’qs koi hje xjaqujs nueveni’h bycauq! Ek zuo yas kaa, Sqvuyeg Kutemuzoq gemhkub abishcfotc zav vae. Oz shawp tse mgtiiy boyq a vfotdozl dah, mwotrokf npa kiogoko woxhsooj ozz degwmin eqw ehfjumgileuc uznijx ynuy udpeq.
Wvmasim Tosadawiv aj ahqo oxig qup igluxsaet. Os vurw loi soji buwa-mfuajac xejvqiq ugij zloqkc wiyi lioycuck qu cahroxurz ezbsidroxaid oviwgh xoejsesm op azib atalb gaib unb yjoffevp lug vtjouz. Mcuqyj meuz!
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.
Mdog chiscay luqqraray Nutyiab 8. Iq qqe taxr kuhyiaj, lou’kj kouyr qep mu hluezo ofg agipide wiztav II zivyiregpp. Kipo weh! :]
Where to go from here?
Great job on refactoring the module to a dynamic feature module. It took a lot of work, especially regarding Hilt/Dagger and navigation. Well done!
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a kodeco.com Professional subscription.