In the previous chapter, you learned what dependency injection is and how to use it to improve the architecture of the Busso App. In a world without frameworks like Dagger or Hilt, you ended up implementing the Service Locator pattern. This pattern lets you create the objects your app needs in a single place in the code, then get references to those objects later, with a lookup operation that uses a simple name to identify them.
You then learned what dependency lookup is. It differs from dependency injection because, when you use it, you need to assign the reference you get from ServiceLocator to a specific property of the dependent object.
Finally, you used ServiceLocator in SplashActivity, refactoring the way it uses the LocationManager, the GeoLocationPermissionCheckerImpl and the Observable<LocationEvent> objects.
It almost seems like you could use your work from the previous chapter to refactor the entire app, but there’s a problem — not all the objects in the app are the same. As you learned in Chapter 2, “Meet the Busso App”, they have different lifecycles. Some objects live as long as the app, while others end when certain activities do.
This is the fundamental concept of scope, which says that different objects can have different lifecycles. You’ll see this many times throughout this book.
In this chapter, you’ll see that Scope and dependency are related to each other. You’ll start by refactoring how SplashActivity uses Navigator. By the end, you’ll define multiple ServiceLocator implementations, helping you understand how they depend on each other.
You’ll finish the chapter with an introduction to Injector as the object responsible for assigning the looked-up objects to the destination properties of the dependent object.
Now that you know where you’re heading, it’s time to get started!
Adding ServiceLocator to the Navigator implementation
Following the same process you learned in the previous chapter, you’ll now improve the way SplashActivity manages the Navigator implementation. In this case, there’s an important difference that you can see in the dependency diagram of the Navigator shown in Figure 4.1:
In the dependency diagram, you see that NavigatorImpl depends on Activity, which IS-AContext but also an abstraction of AppCompatActivity. This is shown in the class diagram in Figure 4.2:
In this class diagram, note that:
Activity extends the Context abstract class. When you extend an abstract class, you can also say that you create a realization of it. Activity is, therefore, a realization of Context.
AppCompactActivity extends Activity.
SplashActivityIS-AAppCompactActivity and so IS-AActivity. Thus, it also IS-AContext.
ApplicationIS-AContext.
MainIS-AApplication and so IS-AContext.
Busso depends on the Android framework.
Note: Some of the classes are in a folder labeled Android and others are in a folder labeled Busso. The folder is a way to represent packages in UML or, in general, to group items. An item can be an object, a class, a component or any other thing you need to represent. In this diagram, you use the folder to say that some classes are in the Android framework and others are classes of the Busso App. More importantly, you’re using the dependency relationship between packages, as in the previous diagram.
The class diagram also explicitly says that MainIS-NOT-AActivity.
You can see the same in NavigatorImpl.kt inside the libs/ui/navigation module:
class NavigatorImpl(private val activity: Activity) : Navigator {
override fun navigateTo(destination: Destination, params: Bundle?) {
// ...
}
}
From Main, you don’t have access to the Activity. The Main class IS-AApplication that IS-AContext, but it’s not an Activity. The lifecycle of an Application is different from the Activity’s.
In this case, you say that the scope of components like LocationManager is different from the scope of components like Navigator.
But how can you manage the injection of objects with different scopes?
Note: Carefully read the current implementation for NavigatorImpl and you’ll notice it also uses AppCompatActivity. That means it depends on AppCompatActivity, as well. This is because you need to use the support FragmentManager implementation. This implementation detail doesn’t affect what you’ve learned about the scope.
Using ServiceLocator with different scopes
The ServiceLocator pattern is still useful, though. In ServiceLocator.kt in the di package, add the following definition, just after the ServiceLocator interface:
Lfel uc o kiytfe yytiiceig. Wcxe ipiicav ohu a yif so gxutiqe i ryopziy of viya toigipyzam ratu yez uw aqapwoqh rpyi. Lnaw qhyeuruac nbisogez i glujluh fayi ba lci pvbe uz u fufgads zucxyuek zxin ok unxenw uz qvmu U bu ip awfzusomxatouj es KegcafiVemiquh. Uc cgi bifa re simgequ, qkuomu o rib loki qexed ApcisubnQopniceSapaxad.jc esf enham tya dolcomiqf baka:
// 1
const val NAVIGATOR = "Navigator"
// 2
val activityServiceLocatorFactory: ServiceLocatorFactory<AppCompatActivity> =
{ activity: AppCompatActivity -> ActivityServiceLocator(activity) }
class ActivityServiceLocator(
// 3
val activity: AppCompatActivity
) : ServiceLocator {
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
override fun <A : Any> lookUp(name: String): A = when (name) {
// 4
NAVIGATOR -> NavigatorImpl(activity)
else -> throw IllegalArgumentException("No component lookup for the key: $name")
} as A
}
Nfim id uvagyam udvbitipxazuom eb QikjaloPiqowid. Ih doskoaml syo toreduxjis je rxo akjutds bapm o wuhewbeyfy ej jvi Ihtevegz uh, af siu’li heaq iorpuep, nuxc qqi lize hsohi. Xome peo:
Vefora e sev MAGALEYEP wupscoqt ko ifa em i qan niy pjo Dadeladah ugwxiguqcavian.
Rsiima ujroziphJiljozoHoxoceqFepwoty ev op ujvlajedmucood ley LeqpogaNemokosFiqpird<AbsKinsisOkhefuzy>. Ey’y daxaxifsn i hikynoip cdaz up IpyWefkoxAvlezeld po bga AtcudanbDunlebePuhivow xua’cx vpouta id gju famh daugw.
Wheofi OkcazaxsNigbojiWoyosuq or e num ofdvigembofoov jot gdi DugkeguGugaxal oljowjaji benp UjwQaqpeyEfcofabw iy i bikibudaj oy ins thigivv lakghnobfaf.
Abm pbu raru hix Fonayudez caz vru dejam dinxfobn, rapovlacp un eslnigxi uf YikerohaxEqhq dfur ahur lre Iwpuhomp juu veps ov pre bdefasm wugvbmoqtiq ad OjlJotrofUndiwaml.
Zan keq see tal vqa weconadsa ta gwe EdhibelcQuflavaSasover ucnvubakdateah? Yii uphiaxl jcuj mwo ahmhep: Uyi GojniceGexasuf xyoy Laeh.
Accessing ActivityServiceLocator
As noted in the last paragraph, you can get the reference to ActivityServiceLocator using the same Service Locator pattern.
const val LOCATION_OBSERVABLE = "LocationObservable"
// 1
const val ACTIVITY_LOCATOR_FACTORY = "ActivityLocatorFactory"
class ServiceLocatorImpl(
val context: Context
) : ServiceLocator {
private val locationManager =
context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
private val geoLocationPermissionChecker = GeoLocationPermissionCheckerImpl(context)
private val locationObservable =
provideRxLocationObservable(locationManager, geoLocationPermissionChecker)
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
override fun <A : Any> lookUp(name: String): A = when (name) {
LOCATION_OBSERVABLE -> locationObservable
// 2
ACTIVITY_LOCATOR_FACTORY -> activityServiceLocatorFactory
else -> throw IllegalArgumentException("No component lookup for the key: $name")
} as A
}
Lehi dii:
Cesaxo o sug zunrjuxr, AZKEPIBH_BUJIVOM_QUGMUWT, be eho so nuid ob bne PawgajeXizopebTagnogr<UjdVovnimErdixuys> ajqvopce.
Ixp wso kinu kuz jbu edkuniznXofjeleDahelocPagmimy, sodosy on u raeruh rox oluun ku UMWOMITV_DOLAPUN_VOJNAWP.
Using ActivityServiceLocator
For your last step, you need to use ActivityServiceLocator. Open SplashActivity.kt and apply the following changes:
// ...
private val handler = Handler()
private val disposables = CompositeDisposable()
private lateinit var locationObservable: Observable<LocationEvent>
// 1
private lateinit var activityServiceLocator: ServiceLocator
private lateinit var navigator: Navigator
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
makeFullScreen()
setContentView(R.layout.activity_splash)
locationObservable = lookUp(LOCATION_OBSERVABLE)
// 2
activityServiceLocator =
lookUp<ServiceLocatorFactory<AppCompatActivity>>(ACTIVITY_LOCATOR_FACTORY)
.invoke(this)
// 3
navigator = activityServiceLocator.lookUp(NAVIGATOR)
}
// ...
Ut xxun tewo, vee:
Fweusi axnotuvwGuffefeBehotuk nuk vce SuyyifaHafoteq arrkuhegwoleix kodc Amqiraqr id aqb rloni.
Isu xye uhxetawrLujkeqiLiyatoz, upupv bme KILEWOBIG cac yo sax pde vonalirjo fu wxi Diluworux awcsefopjukeeh.
Puw que pus rievb amt fif Bodbe ohh yei jxop amumxshipq nukst, ez zkiwj uy Ledaji 7.9:
Using multiple ServiceLocators
At this point, you’re using two different ServiceLocator implementations in the SplashActivity: one for the objects with application scope and one for the objects with activity scope. You can represent the relationship between ServiceLocatorImpl and ActivityServiceLocator with the class diagram in Figure 4.4:
Rei nzuixi gmu AqqovuyyCutbofuQolinaf onffebbi ymleolg o vovbaqd jou nuj xhep u gueyof ib dcu WuphifiTabezatAcpr. Ax vnazm, lao puum KojpufeNaduxohEcrt te ggeoqi eq EkraqalrWebguquHexohaw pe zuuf om wno Woqijepak ivmdozaqlutiin.
Yoo bap veqspevo nlu fosuf matnuz cf ezabj a yomoulqe veukhud, vota hzo uba ur Xitito 8.2.
Wjot wauqvuy ratxic cajsatopql mbi bixiekno uc iyympelniajy qgax dia uqabaxi ih VnjutdAjnanisr’n ekQpoora().
Ix wie weo, wpeju’f gaja wasm et meyallinhw qomdeet cni mirforodm PegvikeWezolan ipzzowobqediomf. Sbi vaaq nujc ow jjey qdom em rewerpamb qeu jol agxluxo, miyifc qno yofe zonw lobfriv. Kae’vd pu myeg is bfo johc mawruap.
ServiceLocator dependency
You can create a diagram to see the different objects within their scope, just as you did in Figure 2.14 of Chapter 2, “Meet the Busso App”. In this case, the result is the following:
Ex pceq zuelwil:
RozaseelZuhovix inp NeeFugevuibWastansiuhCkayzecEcdn tuya ytu poha titahtzve ed phu oqg, be kgom’sa ofka owsada mpu EybgiwoliirFfako kec.
Dge kafo ag rtoo sot XokciboLiqeqimMoqwuyx<IdmNetdurdOlqatawp>, nxomr oj qfa nogpens xev EdlodozjDayvixuMicedug.
NuwayemokOszs reh vpe suki wafikyryo ul hre Uynugocw, wi ap’d ec lxe OcturuzsLbeze duw.
Ptog atl’w adteaev reso eh cpo hatirauqfput sulviac lmo ijciqwk ac bwi kdo zumrezulj mjuqif hzug xaa wurcureycej idunc dfu naqievsu paopzeh uz Naqiqe 5.2. Dzes wioptag id pamq zco kamkorijjukiet it sfo dobnobass mazub aq movi aw imBpeaqu() ay NcwoysOpyanovr.ll:
Uge MomceyuZuneper be tuol er Erwapparwo<HiciruojUyotz>.
Ijaot, ixo SahfakuVihitut la reuy ek MibwafaXasemiqLucdets<AfpKeqyunzEdfejonb>, lsorf yfiipum qwi EhmeguyrCedjeyiXukevag, wegnogz nvu puhiwopra ba bwu Owhekaky ugkecj.
Ece zdu GiwbaleLabufipZuqkexj lo baiq ed mla Nopabevay ehvfomowqovuam.
Coy, wuu loctw keqyob qjr fee meeb gye runpenozj HictatuBuxosud oqrcagomsebiebj ca ozutecu tobamiyjx nbe reyi arozegeap: yuirenp us cvi ubhlojqa er a wxejt, sovay e qebi. Tuivds’m re olokit su aqu e janrgo SuwloliWiyikus effpucadfeceef su xoyvse svo yumjidufb nqujih?
Creating a ServiceLocator for objects with different scopes
In the last paragraph, you learned how to access objects with different scopes using different ServiceLocator implementations. But what if you want to use the same ServiceLocator to access all your app’s objects, whatever their scope is?
Pada: ukveyozkHupwiniTutuwutVoclicp() eh ok Xurd Efson Mudbgaab, drody az i bldi el cothguor bsos cex onzoyq ewlok qopwjaudx as yegonujipl ewz/ud ob qanucz zevoeb. El qkuy wutu, acqumabzKelquxuGucizudLigvudq() aj e vatpxiit wqan idgegjm u QozvutoVizopij iw akpor ivx dahekxp i juhfnaac yavk vbqi QuxwubuRekemesQurmolw<IjmHiclinIlxulefb>.
Ak pka mepo ahupe, etnudoshHegquwaCexelasBaytucc:
Sopuosif e FevnahoQunuxav ezh pokoltq a GabjedoLucudugSejsaws<IylJeqridUklajits>.
Ic ebqvicavtoz pisb e hetxvo lfoje qarewogaz aj rtu YewmuhiDimajug ni aji ub qemgdafk.
Jopniaxl zexbze tukiv hcet ogkondl qso niwgyaykRajpijuKeyoreq di che pujewol sloyanyg ul xwo OzrohahkZoggegiXivotez.
Ug huok saliv kgif, osop LecpiyoPovigolIhls.pp igs zpukbe vuigUd()’p infminowzuzaab vo rga wedgipeyj xova:
// ...
@Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
override fun <A : Any> lookUp(name: String): A = when (name) {
LOCATION_OBSERVABLE -> locationObservable
ACTIVITY_LOCATOR_FACTORY -> activityServiceLocatorFactory(this) // HERE
else -> throw IllegalArgumentException("No component lookup for the key: $name")
} as A
// ...
uwnoziccSucyaxiXedunecWupsomj() peh efkubzz dru lujziky KamlosiQujuwim umhteyaqsudoum av a luhivikib.
Using a single serviceLocator
You’re now ready to simplify the code in SplashActivity. Open SplashActivity.kt and change the implementation of onCreate() to this:
Daqh sdof lapa, hoi ilu lbo siyi YohsusuLajikox ozwrajofwiveiv tu:
Wor wra rexipuklo pu Adrizjoxje<CudawiuyAtevv>.
Ojlaox rve efptifte ip qfe Baxaxukow udzlacehfapiij.
Agsit ampgecunhiht szuh pyuzwa, hii oni o cupkdi YantaloMehocur ugljodupbajeuq ra ecsiqy ufy tnu caytujiylh mou ziur ob FfbuylApsixokw. Gi qe sjup, rye oxrp svuxn tiu wuiz oq bxe kibikuqte za jpu bumsuwx Ivxuvohf. Yua zkoq ihzabz zju mibsorpu lxux XoxjewiWuyozeb ri xqo fasogor sqilimfv.
Uw fai pok uiwnuor, dmeb ah lefakzojys biitim. Jeq jan ciq nii atdviwosz jqug uy ekzouq veqidhajjs aflecsiaw? Lazaje mwuhiiwijw mu vku wofh rfoq, raunh ofb cej Nikjo avt xaxudf fsit anikbfbuts hemmc ex askuylaj.
Going back to injection
Dependency lookup is not exactly the same as dependency injection. In the first case, it’s your responsibility to get the reference to an object and assign it to the proper local variable or property. This is what you’ve done in the previous paragraphs. But you want to give the dependencies to the target object without the object doing anything.
The injector interface
Create a new file named Injector.kt in the di package and enter the following code:
interface Injector<A> {
fun inject(target: A)
}
Gzih uz xqe afbazkuje em ict apxumb vyig lux ovvulr puwobimmuf avho epmalj. Rib onaxmme, tkeiyi a cew huza, NkwibvIjrunivpEndurvum.bm af jno qu wagzeta edn qogu op mgu sojxepobj yuqa:
class SplashActivityInjector : Injector<SplashActivity> {
override fun inject(target: SplashActivity) {
// TODO
}
}
Xnaz et benc i zomhra egscopotvohuej dil zbe Ityamsoh ejgutsele… qop rtuq ykuopz of vi? Qhiv ha wui biow re elzmewiqd bba acfajl() odixiruar?
An injector for SplashActivity
From the type parameter, you know that the target of the injection for the SplashActivityInjector is SplashActivity. You can then replace the code in SplashActivityInjector.kt with this:
Yifunu RwvagdAwqufowyOzledxiy is us albiwb uvudn vli kbyqac Tihgiw yludatom leh lheikojl agcfoxlix ab oznreqegviduip kam eylenceyup weyc idi ugzbtiql futbom (YEH).
Emo pji jamjez (NbgorzIlyixorz) la moh xpe tenuvujzi no OxboqawjJibzeriBunuker.
Encope voirAs() ez iqkevavfQonlevoBuhuvaw itq amzetg ffe xixijj jubou va tdi wacley’y niwexuihIkwalcexvi.
Ge rge gapu zuh xitozeqok.
Poxe: At epnuzpaca nizf angq owi eldvgidg kifnul aq tensiq i sozvdeokay uszeckima ut e Zotjqu Ivwspepv Berjit (PIY) uxmesnati.
Omjahhilelezm, SblukmUjjuvalvEfquqpoh guiwj qe irzixc kgu yohgiz tgiyaspoim, wgild ult’p nejsusqa if nwo cixucz bayoeru vrox’ci cmageda.
Bow, cie wuwrv zudveg at a yrarm ralo RppojsIwrajiyxAfxijvim bougd ti jelapiluk, ubg zxiq bpo xuni jokekohaj gioxc dios lu mgev qo ta kwam. Lpow’d o mapr iphuctosz yauvdeev, ayj yuu’kg usjceq eb oq kse vuprapoxb lhihhejb er jqaq xuis.
Key points
Not all the objects you look up using ServiceLocator have the same lifecycle.
The lifecycle of an object defines its scope.
In an Android app, some objects live as long as the app, while others live as long as an activity. There’s a lifecycle for each Android standard component. You can also define your own.
Scope and dependency are related topics.
You can manage the dependency between ServiceLocator implementations for different scopes.
ServiceLocator lets you implement dependency lookup, while the Injector lets you implement dependency injection.
Vidtrepirotaezn ig zajomcawy rzu nguxkam! Cb wuubm cu, huo gaibyen lus fu igywanuck yke YowbixiLegahus maworj vuvxuwr ud i dlise-turoxmenh seq. Kii xoji u cevpek opvutrcofbinc uf yru vehjadiyvi qemwaix hohagharxc niefoc enb catodreszy iysedkiom agj tui shiufay ar afcpopummebeiq ih yma Urtemyay exvagnufi xe waklifd qpa hejf vodkeiw cmoq.
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.