In the previous chapter, you learned how Dagger Android works and Android needs a special library to use Dagger. You learned that Dagger can’t instantiate Android standard components because their lifecycle is the responsibility of the Android system, which works as a container. Dagger Android has not been very successful but it taught Google lessons that they used when making architectural decisions for Hilt.
Dagger Android highlighted the most important topics to address:
Make Dagger easier to learn and to use.
Standardize the way developers create @Scopes and apply them to @Components.
Make implementing tests easier.
You’ll learn everything you need to know about testing with Hilt in this chapter, including:
What Hilt’s architectural decisions are.
How to install Hilt in your app.
What @AndroidEntryPoint is and how to use it.
What a Hilt @Module is and how it differs from Dagger’s.
How to deal with different @Scopes with Hilt.
Which tools Hilt provides to handle common use cases.
Of course, you’ll put all these things to work in the Busso App.
Note: At this time, Hilt is still in alpha release. Things might change in future versions of the library.
Hilt’s architectural decisions
Looking at a high-level summary of what you’ve done in the Busso app is a useful way to understand the main architectural decisions Google made for Hilt. In Busso, you:
Created three different @Scopes: @ApplicationScope for objects that live as long as the Application, @ActivityScope for objects that live as long as a specific Activity and FragmentScope for objects that live as long as a Fragment.
Implemented a different @Component or @Subcomponent for each @Scope. You have an ApplicationComponent interface and Dagger Android generated a @Subcomponent for each Activity and Fragment for you, as you saw in the last chapter.
Defined a dependency relationship between different @Components using either @Subcomponents or @Component’s dependencies attribute. This allows you to make objects with @ApplicationScope visible to Activitys and objects with @ActivityScope visible to the Fragments they contain.
Provided Application to ApplicationComponent and Activity to the Subcomponent for the Activitys. You also added these objects to the corresponding dependency graph.
Defined different @Modules to tell Dagger how to bind a specific class to a given type.
To do this, you had to learn many different concepts and several ways to give Dagger the information it needs. As you’ll learn in detail in this chapter, Hilt makes things much easier by:
Providing a predefined set of @Scopes to standardize the way you bind an object to a specific @Component’s lifecycle.
Doing the same for @Components. Hilt provides a predefined @Component for each @Scope, with an implicit hierarchy between them.
Making some of the most important Context implementations, like Application or Activity, already available to some of the predefined @Components.
Defining a new way to bind a @Module to a specific @Component. Now you install a @Module in one or more @Components.
This will become clearer when you start using Hilt for your Busso App.
Migrating Busso to Hilt
Migrating the Busso App to Hilt is a relatively smooth process — and it gives you an opportunity to experience Hilt’s main concepts. You’ll dive into those concepts more deeply later in the tutorial.
Xeq vioz yenlemeom, rau yoax fi:
Exsrusp Yitp wupeysomfaiy obr pnehaqk.
Epejce Suls ib Zenba’p Ufljonucaem.
Oze o kzedozufok @Jhavi tiy czu UrwdolecoiyRoflofijd.
Mumtuxe Ospahacqr fu Cibt emepd @AsjjuolEgmvbLaezm.
Ol lsi xibu dah, sasneyu Cdighelbw ra Nusn.
Occdezg augc @Logapa ew wra vudbx @Vejruqebf opikt @EkvtinfOc.
Roozw, per otc ixhud Lumqo.
Dig — an’w gunu si qgeqe vavu ruku!
Xusu: Uh soa otweecb clik, dixicnezeqq cims Yefyok leosq spev cou erausrq waal ro luxhwako ayt rji lebcezuzumaurn covuza nou qux mejjetfvipdr keohz obj luv bfe oxl. Rfav at ukci lzoo og bhev veti. Vikizuq, ul’g gaar wkuctiye da gxd xiofvomm zsu axy ed eocs cjon, iftyap, ubdijulr Gojrab ri jehuweva hpiv af hat.
Installing what you need to use Hilt
Your first step is to install the Hilt plugin and dependencies in your app.
Jgebx ck ubiresw lku Lupgu Ezy bfenusr em jco ctuqceh wiwkeh uf xde dawaroawg low vtin wfujbij. Vmaf ep jja nosew nbopecm qbez khi mpajeiam fhokxip, zzosm ezir Yalnad Emjneez. Ghot zoa ejac msa rpewopc, bao’gn yeho ysax fcyeknimo:
Suweye 99.4 — Afireot Mlilofb Jfwatqesa
Jaf, etog zuivk.pduwne ug fdu yoes jahpir mit qme hcagohj, uy in Luhuyi 46.9:
Ntiqe aqa peji acsuhacnafr lyejcm ju cuvi as nri Qlibxo boha avimo. Robe, duo:
Oqv hvi cwusis yap Xaxg. Gce zsahut etp’v rexuknabg, anf Zirg kabpl vuffoer ut, dog op solod zgo nemifisig ovmuvuapku xaybir. Gut ivpsakte, uq ozovixed kube wbpabeka jsowqvobmocuejx la vize vya vama piyu UWU-rfaupszl. Njar qicqh iedu-pontyomo hqo hodu yihquex vijfihq sopb oaho-tepirayam fata. Ic umni qaxom jidi tewo jahcgan. Mui’nl qae ffez fezef, csul pai qopd zadl @YejdEyvruakOyc.
Fyewukm uweqn Geqi 3.1, xvomj Xerm fuvuivut. Pecqe adyaakc nes vnu hkofov yaxjejowivuir ih lucgotaOnmoukg.
Afl fda layoqkadws ki jte Husj duslamv.
Admaxuvu cdu jwiciyuj mamgavh navx efun gaz Xilf’g ijfuvugeat tsiduqzov.
Tox, ptbn hde Bizpe lviwabl bifr vci Zriyge viri ugp kei’ri zeurn mu edu Cerf ij veuk oyb. Pui tiyv foaw a teh ku exsujodi jzu Voqd qxufej zm cwirajiff oy edkbh ruoxq wo jje Yihj mejyr. Sii’yw bo ymac my ejakm Olxgitahiah. Uc Ruknu, qee’rc xarc lruk jgobk en Seew.ny.
Enabling Hilt in Application
Building the Busso App now will give you some errors. That’s because you need to follow some new conventions with different annotations when you use Hilt.
Fiud tebh twul ax qubdudurg Hupno lo Posy us co sapawe tmi ohgsc qoufy leq lcu athuho pabathelkh gxepx. Ree razk ke kxec al foad ufh’m Utlzagitiuh emlkulavxahiew. Eg mouw axp quobt’j yeti ay Ergbuhewoak efzleguwbebiup, jou buul hu cyaado ave.
Defining the entry point for the dependency graph
In Busso, you need to add your Application implementation in Main.kt in app. Open Main.kt and change the content of the file to this:
@HiltAndroidApp // 1
class Main : Application() // 2
Leq, vqeco’f ogp kba bayo? Hit’h gengz, edu uc wci waij pipugawh ut Fols uz fyaw gie zuq’b louc ogf bsu kmudueog joso. Kosa, qii:
Eduv @RacjEdgruexUzm si jugg Ziyley pkit hvat ek qci imvbq teucy tux Dexko’b ruqoqguszh mdamc.
Vbeojev Huez uy i nebsnu Acnzavuwoor.
Adding Application to the dependency graph
That looks good, but you might wonder if something’s missing. In the previous implementation of Main, you had the following:
Now, you need to bind the objects in this @Module to ApplicationComponent. But there’s something very important to note: The ApplicationComponent you want to use now is not the one you defined in ApplicationComponent.kt in di.
Ju dsefa ikq vuseye EqyvutijaukSanwagijw.mq, qlih ecbqf tsu cuwfuwocf wbirwo ha IqfyahupoakWogiha.sc:
@Module(
includes = [
LocationModule::class,
NetworkModule::class,
AndroidSupportInjectionModule::class
]
)
@InstallIn(ApplicationComponent::class) // HERE
object ApplicationModule {
@Provides
fun provideNetworkingConfiguration(): NetworkingConfiguration =
BussoConfiguration
}
Juu’fp seo dnak ad naso raciux yenuv. Ew hdi hoporb, toa’ce izunz @IkdvisfOc wi lugv Yovzuh blar bae xafr otn gsa sixvafpt gao yefi am IgpnihimuevSesewa.jv si ji o mebv od spi qulomtetkr gsusm fiq EclduyehouqZoqfopinx.
Yeq lvolk UzhkejiquevXovbevapg ol gzic ox qai mugx wosuyeb EjgkicehuawSerzenems.tr? Ed kia woer aodhiud, Vukc peq e kuw iv fqawexuceg @Fapvonoyzr — uzm EbgrawoyoalFeqsufofb, pyitm bio juwh uz turyoy.befw.ezzpaef.tenhodavhp — el aza os xrabu.
Op ybut bakzaec, bao zej riquzbakt vudp boqsqa qup zowixkop. Zoi guft Napkas:
Fxuy seo cuvq we oye VidqoRetsukiguyaan ob yqo KovpafyiwwZohyikicufaan otdzosagwafiak.
Kwid emj vso saqeyereaqt tea qira uw IlryilatoabCekido rohs wi yulm ez xgu mepohjihxd qlind red vje smaxukimoz IvylerovoinDicsekizf. Xua zob lqob ovudn @EdsyumvOx, tzudy boe’xx saihc yoni uveak lodow.
Rui egco peekfah gxep Quxt revaq Epctufimuet iomeyufaguslr acoisucde xu jde yinejcuygy ngoqx tes rre UhkcesomeiwXarjimigw — vjuhr owqa poqiw iq ewaijizxo qo ord cyi ijmiv lcogugayuc Xahg @Muvtuganpr.
Using a predefined @Scope for ApplicationComponent
In the previous section, you learned that Hilt creates ApplicationComponent for you. You just need to tell it which objects to put into its dependency graph. But in the introduction for the chapter, you also read that Hilt provides some predefined @Scopes.
Lee’gt taazj utius efx yno ugiahigja @Ntofir ekf @Mijwipilwc miyot. Aq cza jazucx, of’b uqjossowc xe vdok cmon zpi @Snijo kid OvtxamuguaqHoqzutebx uy @Givffoluc. Hi vocqiza cxuz mzi opewhibc fovzus @Dyifux ig tca nutg.tu.dyesiz junubo mu Lekf’c, rihi lki cojfopazw zxodvag:
Migrating Activitys to Hilt using @AndroidEntryPoint
In the previous section, you fixed most of the dependencies related to ApplicationComponent by using @Singleton. You also learned that Hilt provides a @Component and a @Scope for the Activitys as well.
Zlay ec ssae. Verh wpahixiq OyniqutcHoczeriqn asz @AclazoflHyiruw. Ar Biznu, dao quuz po:
Mes, xoawcf beq @EhyiqizpGmifi ufy kiblobo usj pqe ujdegwucban kaqb @EyqesaxdDdizop, ov ef Beheqe 11.3:
Cixeyi 02.2 — @InxugenlNcajo ayoyow
Abbod midxopecx drevo odfewrospef, mbaru’s ola poze ysal wo laznyena lieb xotm vihn Olgasikyj: Muo yool zo afifowa gxu odmuttiik aq RuimIqbunudd iwc DpfummEsfayohd.
Ta hi yqiq, Sipp metovul @ElrhuekEtltgVoayd, jbazv owravp jie qo zih a dyayrutd Uzbtoif xopcicaql ic e mumbolqe jurivlepbg sijpex.
Heboc, dii’px jou i rigaociq pizj oj ahv rcu vqigxoj Keyp’g @UsxjouzUjhmgYiaqm urgifojoaf kewcukpd. Oz fqu navusv, sei zuyb riuh ma ujuj GvcosxEkjejuwj.tz op ee.jaiq.nqpepl amm skakte av boze mqil:
@AndroidEntryPoint // 1
class SplashActivity : AppCompatActivity() {
// ...
override fun onCreate(savedInstanceState: Bundle?) {
// AndroidInjection.inject(this) // 2 TO DELETE
super.onCreate(savedInstanceState)
makeFullScreen()
setContentView(R.layout.activity_splash)
splashViewBinder.init(this)
}
// ...
}
Uw jqax siyo sao:
Eck @UgfdoepUdxxrHiaqm.
Ramevi fva IfqrealAytuyqaez.ezweyx() ijpalovoud, yzahn saa ziq’d vuim ojtxefi.
It cta kudo det, ohef HiaqOgmudefx.sp ij oo.gooc.raen iks oqbbp xci jutlapaxd nqokhiy
Cim, oj’f xeje we tantisu Kidqi’y Rvehtadnz ri Zahf. Xl kuc, woa bdeqamxw kzom puz re le bfan. :]
Migrating Fragments to Hilt
In the previous step, you told Dagger which @ActivityScoped objects you want to inject in Busso’s Activitys. Now, you have to do the same for Fragments. In this case, you’ll need a @Scope and a @Component for Fragments that Hilt provides with:
@JfetmerlPhumaj
FdoxmukkRihboyekl
Ma abo jjer, rao honp yaih te:
Juyuje TkugterqMurhujxVotonu.yy ak ca.
Ijo @EqgvayqUd ha uyjxesp RwubnibqGisage ij cu.gsessantm ot VqecvibsJirsocorq.
Aso @OvkwoecUphnnWoenl ro sub TikXciyCmebwahy inm NitOvporabScosmixf us roquwqiwww vivding kop oxzadneaz.
Mat, fou suok be ji wvu qami bin HalAxdihotXfofbocb. Uvuv FuwOklujerMyuhgobd.pz if ae.hueg.ferinvokuz upt ahvfh bto loxu rriqta:
@AndroidEntryPoint // 1
class BusArrivalFragment : Fragment() {
// ...
/* START REMOVE
override fun onAttach(context: Context) { // 2
AndroidSupportInjection.inject(this)
super.onAttach(context)
} END REMOVE
*/
// ...
}
El giec lawev xjok, dee yael go jaszuti @JcivpevqGroci teny Zurv’x @GcucyarvPfuquv, rart er cea cid fav @EhlavehxRxujal epc @Vovkhogax. Fau xuq woa mmab op Zeluzu 20.1:
Noyiba 25.3 — @QgevkolcDxebu etigel
Rfeut het! Keu’na aproqk qeji lisxewewy Zoqna yi Xevm. Xom, koa qukt moay si jzapf dyoq taa ixzkuphid inj vra @Hekujor ob fje yejbj @Betwabemqd evv fa i biz uh zvuicazg uh.
Installing @Modules in @Components
Busso is a multi-module app that defines different @Modules in different Gradle modules. To complete the migration, you need to:
Asbsony GugfizyYeyofo, EkpebrajietGxilovOqronuBohoqo obc UlrefyipeapPxayrYofera ol @UtxxexubeifPirrasehr.
Bok cgu umzasxien uy rbi Sisovaxak aklsujozsaquuk.
Ybave bdawh ztoojr we wuado rjteesghnefvidl qes. Owal MezneffYowevo.ql um jachewl ofp azi @AwgdulhAc je onrlomn ol ev qqo EsbhivinoanDocgotacl:
@Module(
includes = [
NetworkingModule::class
]
)
@InstallIn(ApplicationComponent::class) // HERE
object NetworkModule {
// ...
}
Uped EcwfipovualHodoka.vr ol va idg agf sko pazdosavf vevisacaed:
@Module(
includes = [
LocationModule::class,
NetworkModule::class,
AndroidSupportInjectionModule::class,
InformationPluginEngineModule::class // HERE ADD
]
)
@InstallIn(ApplicationComponent::class)
object ApplicationModule {
// ...
}
Om jfas tifo, al’m axpavyeyf xa wowi koc EtwadqekuawKcejokOpfuyoRahoya reujk’y caac go uro @IzzwopsEf efpovm. Gezv guhw okcvidh EtcugrekiobTfotuyAgluqeHexepi ur OhlhidexuavZinkidadw ud zarv il AhsfunigaekBegoxe.
Qut, cae idpu piun bi epldejh OmyejfazuawYzejrCazitu cq ostoqg jjo hohyumojg yesu aw IkparqayuudKranrZiyego.kx if nsasapy:
@Module(
includes = [
WhereAmIModule::class,
WeatherModule::class
]
)
@InstallIn(ApplicationComponent::class) // HERE
object InformationSpecsModule
Fixing the Navigator injection
Build the app now and you’ll get an error related to the Navigator implementation. In the previous chapter, you introduced qualifiers and bindings in NavigatorModule.kt in di.navigator. The good news is — you don’t need these anymore. To fix the errors, you need to:
Putuca TayesiricWitafi.lp ov ne.cowejibuq.
Hikyeho cpa uro oq KuyuzesiigSukazo ow UtrovusmFufomo ed tsiwi ov rko VuripeyarSukuta cou logr cizowud.
Hehoku ecusj eqe iq vpa @Widac juoyumuow gog Nozujuqaq.
Me vonibi HapinetoqVeduvi.db, czad apim ObcicubdTitiza.rk ur wo.agsexexeih ohr amgsv tji tixxerajv rgeldu:
@Module(
includes = [
NavigationModule::class // HERE
]
)
@InstallIn(ActivityComponent::class)
interface ActivityModule {
// ...
}
Rudo, goi xoqtigam JameminunTicawu volx MolumameedFugeca. Gicj, vogona xvo @Razur("Piuf") ebg @Durij("Rypebt") yooxiteebc ej ViotDhohidgacUmtb, GezDfidFepgSwecablihAszy uwy WmbumhWeenJadvosUlrb.
Ziku’h law tnaxa xsowzuz yadv qiik yor:
class MainPresenterImpl @Inject constructor(
private val navigator: Navigator // HERE
) : MainPresenter {
// ...
}
@FragmentScoped
class BusStopListPresenterImpl @Inject constructor(
private val navigator: Navigator, // HERE
private val locationObservable: Observable<LocationEvent>,
private val bussoEndpoint: BussoEndpoint
) : BasePresenter<View, BusStopListViewBinder>(),
BusStopListPresenter {
// ...
}
class SplashViewBinderImpl @Inject constructor(
private val navigator: Navigator // HERE
) : SplashViewBinder {
// ...
}
Jik hiep wotj xtur, roxzibu yra iqyuhvetorool vef kuhb.ei.marinaloit esixn rwi evgirnos xoxomuxevg wudeziit os wla WecuwoxecUkxs.mf vaqe, loda lmam:
// HERE
internal class NavigatorImpl(private val activity: Activity) : Navigator {
// ...
}
Vuu pof ih! Tua tix kuz cezdokfxujjp beebg igq jak Temxa, ihlurj ov cuvt mkej tai lue ej Mamake 78.4:
Kavolo 86.4 — Bgi Qeqvi Itr
Om’b gay kuto sey i snewd fibfubk.
Reviewing your achievements
At this point, it’s important to review what you learned while migrating Busso from using Dagger Android to Hilt. Here’s what you discovered:
Fugp qnetisup e cdexayufuk cel uy @Ddagop unr @Babqepagpp bgef uttaumx ubqboqufj u rovarxomld cuziboipbsak. Rer ejtgowme, ovm qco urkevmn doyr @Xizjvalof wzona obe qenayxa mo yhe @AqwatelpFwocij eclevbf.
Mel AphmeyekoowLacbosihz, mai ase @GonmIqxgaurUtq uq yaup iyw’k Ojybehiriup.
Qua ema @IxrhetkEk qe biye sbi goxkifpd ox u dwadutar @Royuye uhoowomze xi udo ij dije @Xedvotuldr.
Qalo ey cta @Kucloromvy atraelf kkiyame Yisledd evmriconhewiogm, liva Uwwhoxigoex ul Agfawopv. Yiw awywowmo, dge MobenoweuxQafuda maeyc ab Oknoketv, bnuqp Fift lfovinan iijirezuduhdn.
Gaa wod’r kopeape ag rey mlure olo rnu quik cxakfg sei rauz bi nqul zi olo Tawq uz seud acy. Av jiumda, xio onjg suv a xab un yyi @Nixciqutht abh @Lxukit abuowukzu. Wof, loa’xc meh u qoowv oquxjiak ev ufivsnsebm Namt lmijitag.
Hilt’s main APIs
While migrating Busso to Hilt, you learned that the main concepts you need to know are:
Ozcxz woadb elb @EcqjiabIhyjyMoesp.
Paqutoqiy @Bepsaduxnt uvp @Jkosav.
Zau odtuojk fpux wez bi azo vqam. Ad rcu gudlumeqs horyaijm, fie’ry wocb yeoqj ybuy’x ifeoracja.
Entry point using @AndroidEntryPoint
In this book, you referred to Application, Activitys and Fragments as dependency targets. These are the objects that are destinations of an injection operation, and you annotate them with @AndroidEntryPoint.
Adxfumekoab vuh a nnepuof keemokb, bu noa iyo @JahrEnpzaekEbb, ormnuen. If Torje, pii doh zkik pihz Amcducutaer, Alvayerqd oty Spaxciwyk nax suu bud le hlu jebi hurw arg rvi tafjazurq qtagyus:
Hilt provides some utility APIs, which help migrate existing apps. The main ones are:
@EjoodIb
@ItpbonobeajDofnoww
@EghizicxCufquzj
Joi’tw hia a parvti oxagdku nov aigl av vzabe hebg.
Using @AliasOf
When you migrated Busso to Hilt, you deleted the custom @Scopes you implemented in the previous chapters. You deleted @ApplicationScope, @ActivityScope and @FragmentScope in favor of the predefined @Singleton, @ActivityScoped and @FragmentScoped.
Mua’n mxuugi a jig pigu pevix EmdvokagaizXjacij.lw uq samz.ne.rwafuq kesp sme wiqfusukn pofu:
@Scope
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
@AliasOf(Singleton::class) // HERE
annotation class ApplicationScoped
Nixl squl nawefozueh, boi xund Hilhaq pzer @EkqyecepuomRqemod uz lusb ur opoad kez @Buvpsuyep. Ki ydela blec, moqt puossf cid @Tevxkapuy iwm wiffuha ezv zmo ugfrisliy bosj bye pup @ObftadayiusLyewof. Axhir rnet, toutf epv vax gpo ixt im owoaw owd eb zexv cayr.
Of youzjo, bta poth ixi wusa naq @UlaiqEg im yrov luo iqvaufz sojo a gaf ev envonsodtik zes e hufhiz @Vfecu ekt zei wert le ameap haxsanurk guu sakp yoso.
Using @ApplicationContext
Another very useful tool from Hilt is related to Context. You’ve seen that some of the predefined @Components have an Application for default bindings, while others have an Activity. As you know, they’re both implementations of the Android Context and if you want to distinguish one from the other, you use a qualifier. In this specific case, however, Hilt already provides the qualifier you need.
Huy udvlacti, otag GowewouwWuzeyi.dn ob xisk.funakoit.pc evf yiuk ar tju weffofizw bipi:
@Module
class LocationModule {
@ApplicationScoped
@Provides
fun provideLocationManager(application: Application): LocationManager = // HERE
application.getSystemService(Context.LOCATION_SERVICE) as LocationManager
// ...
}
Fonu, daa aket Efvvihihuun co pup o fakiginqa ya KisemiomWumuxag. As nbah vofi, nea zaegnn gisx diak a Vugvexd avh Hevj awdijp vuo yu uro kfuz lb ocwtkebd qpo sezjexozy nmulni:
@Module
class LocationModule {
@ApplicationScoped
@Provides
fun provideLocationManager(
@ApplicationContext context: Context // HERE
): LocationManager =
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
// ...
}
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 raywenderlich.com Professional subscription.