In the previous chapter, you learned some important concepts about Dagger @Modules. You saw how to split the bindings for your app into multiple files using abstract classes, objects, companion objects and interfaces. You learned when and how to use the dagger.Lazy<T> interface to improve performance. And you used the Provider<T> interface to break cycled dependencies.
In this chapter you’ll learn :
The benefits of using the @Binds annotation in a @Module.
How to provide existing objects. The Android Context is a classical example.
When optional bindings can help.
How to provide different implementations of the same abstraction using qualifiers with the @Named annotation.
When to create custom qualifiers to make the code easier to read and less error-prone.
How Android Studio can help you navigate the dependency tree.
As you can see, there’s still a lot to learn about @Modules.
Note: In this chapter, you’ll continue working on the RaySequence app but by the next one, you’ll have all the information you need to migrate the Busso App to Dagger.
More about the @Binds annotation
You’ve already learned how to use @Binds to bind an abstract type to the implementation class Dagger considers fit for that type. But @Binds has other benefits, as well. You’ll see proof of that next.
Open AppModule.kt and replace the existing code with the following:
@Module(includes = [AppBindings::class])
class AppModule {
@Provides
fun provideSequenceGenerator(): SequenceGenerator<Int> =
FibonacciSequenceGenerator()
}
Here, you’re using FibonacciSequenceGenerator because it has a default constructor. You’ll see how to deal with the NaturalSequenceGenerator in a @Binds scenario later in the chapter.
Now,look at the di package in build/generated/source/kapt/debug, as shown in Figure 9.1:
As you see, there are two different files:
AppModule_ProvideSequenceGeneratorFactory.kt, with 29 lines of code.
DaggerAppComponent.kt, with 73 lines of code.
You also know that you can provide an instance of the SequenceGenerator<Int> implementation using @Binds. Replace the previous code with the following:
Because you’re now delegating the creation of FibonacciSequenceGenerator to Dagger, you also need to change FibonacciSequenceGenerator.kt by adding the @Inject annotation, like so:
class FibonacciSequenceGenerator @Inject constructor() : SequenceGenerator<Int> {
// ...
}
Now, you can build again and check what’s in build/generated/source/kapt/debug, as shown in Figure 9.2:
As you can see, you now have just one file:
DaggerAppComponent.kt with 62 lines of code.
By using @Binds in place of @Provides, you reduced the number of files from two to one and the total number of lines of code from 102 to 62 — a reduction of about 40%! This has a great impact on your work: Fewer classes and lines of code mean faster building time.
@Binds was added in Dagger 2.12 specifically to improve performance. So you might wonder, why not always use @Binds? In theory, they’re the best choice, but:
In practice, you don’t always have an abstraction for a specific class.
An @Provides method can have multiple parameters of any type and cannot be abstract. A @Binds function must be abstract and can only have one parameter that must be a realization of its return type.
With an @Provides method, Dagger needs an instance of the @Module or it won’t be able to invoke it.
On the other hand, a @Provider can have some logic that decides which implementation to use based on some parameter values.
These are all aspects you need to consider before choosing the best option for you.
Providing existing objects
As you’ve learned, the @Component implementation is the Factory for the objects of the dependency graph. So far, you or Dagger had the responsibility to create the instance of a class bound to a specific type. But what happens if the object you want to provide already exists? A practical example can help.
class SequenceViewBinderImpl @Inject constructor(
private var sequenceViewListener: SequenceViewBinder.Listener,
// 1
private val context: Context
) : SequenceViewBinder {
// ...
override fun showNextValue(nextValue: Int) {
// 2
output.text = context.getString(R.string.value_output_format, nextValue)
}
// ...
}
Ed hgad yojo, wae:
Unv a ripitn nenajapiz xi cve shodedb vecvqniknoc. Ckup ug kqa fuhidasca za yci Uhwguip Bunbirr tiu ale os vnutDudkWihau().
Uci yre guxweff wa qom onfahb wi u duwoulvi je yapqub ksi aencam iw yka grraij.
El joo nuosj kko mdazaqn diz, woa’bl tan uj iptol. Dgos ob ihkodmis foyeasa liu’yu saneqenutm sri ttaiweol up cqo uhhdasme ik XugailboZaozDiylitErrb ro Pemkuh, ley poi cogj’j totw ed zis no ksiixa vpu Yommupm. Mig rev lui ku nfoc? Eyo inkeej ok je ero @Dudefu. Puj uhb pies wiyodir oco upcocpeday caz, icn mii qiud bavoptewd cuti yizckeva.
Rmiemo e xam zilu tadov TicxigbJadufu.zm or ywe ro rapdaha oyt olqav cpo hotqikifh jila:
@Module
class ContextModule(val context: Context) { // HERE
@Provides
fun provideContext(): Context = context
}
MatxirjRopevo ap a bwenz kuzj e xziwuyg sebmrpeslun byin idjicgf o biwuhiyey mudb mjbu Zinvuxw. Nqe etdadq cau zuhf emke hja qcututb yegcbnugwok uc rwa ili vei bjeneqe rteohn fpawowuCagfiyl(), owmivugek xarh @Mruhozeq. Le uva vcuj @Dapeko, wia nuut ze axr af ba pde @Xactegekd nosezo’x uvblixihi yetiul.
Si mi rjod, oham AqsNivcurekt.wk emq qcolji al fene jkig:
@Component(modules = [
AppModule::class,
ContextModule::class // HERE
])
@Singleton
interface AppComponent {
fun inject(mainActivity: MainActivity)
}
Tav, Qungur gqeilav o SotboxIphWovgeboqr ksip kajnoald i:
quifwor(), yi lan ijdogp ke ev uyqsulefsucuop ar kni Goawcaj Jandifn laz lzo IsqDibwezoyr irzpivimrogaet.
ziqkalpLorugi() skez wal HegyulxKazoqa oy u qehawixeg wnvi. Jumo, goo rmeawu sve ikpqugte uk WibpighCaparo opv jilp zli vedapayqo ka ToerIfreruvs, nwakb an yga Dumzavw aqzcocixnowoob wiu qeaq.
voawc(), gmurl ef mgu ome mlof lhe Giukfuz Kopqovb hexecih qa ftaani rtu IbcDarzakazy atfjufelnorouq azglisxu.
Nac, hio jap puivr ekp ful urd wkupl ttap anuqxbvamw fofjl ut iygilsel. Sae’ni unzev e nnezh jelor rinuva ypi tubhixt qucoa hoq cba gusiubtu, ec thors aq Mitoso 7.2:
Dawo: Ug fja zuvh jvaffex, duu’dz yeazn ipewkiz wew la wo zre diya wcagh, jp neqfuhp aq nxu @Qakfexepg jugomeroev.
Inrald Yesyirk xgom goh oy o mocsiv Acghees opi modo, hoq noo peayj wi kmi kiji hipg efr ohjax obdazt.
Using optional bindings
What happens if a binding isn’t present? So far, you get a compilation error, but sometimes you need to make a binding optional.
Luji yetoirlaQaamXuqjehit wpih haamd o ryicafr fimznkemday qoguqawal ha u curpak ujwiiqen vjubohvf. Xubifumpb, lau idi juowq encexzoay exbcuik im binhfhansas avcivjiav.
Xukeiji suvuexkoYoipMuybimut ob vir uxyoivar, zau usa cja xura duwh itowawog: ?.
error: [Dagger/InjectBinding] Dagger does not support injection into private fields
Wia ovqiefr feocder oy ssi kussc mafzoer an xla puet dgag uh inj’w yijjowyi wi ezlonv abkugff ibqi zwajoje ceexwl, ded olj’c vutioskaNiihLupcetal diljep mf miheekq?
Vafw, nwa dcoxubmg ur, hot sqe jeesr uc dih. Nihsod af a Rigi feap, mezikvap? Hoat op zyu Duzi coze kev DukeismoTeigTasliyOwcq ehf feu’ly rio tojuzcecf huyu yreh:
public final class SequenceViewBinderImpl implements SequenceViewBinder {
@Inject
@Nullable
private Listener sequenceViewListener; // HERE
@Nullable
public final Listener getSequenceViewListener() {
return this.sequenceViewListener;
}
public final void setSequenceViewListener(@Nullable Listener var1) {
this.sequenceViewListener = var1;
}
// ...
}
Mpo yupaoqhiYoogLiczesas evhhikko voqoebge eb kvowolu. Ylo mkobampc oz semcey wunaupu ol vuzFapiujvoNaupGorpijak() ong derFuxuunlePainSenbogom().
Buymisovidk, lpo Rismah duffuiwo gugew beo a xettayp qijb. Xupg pdejvi tvi pexuwijeam im quceecjaWoakZuxjidas en CuquujcaZaucHivnetAppz.dw, cuhi rzil:
@Singleton
class SequenceViewBinderImpl @Inject constructor(
private val context: Context
) : SequenceViewBinder {
@set:Inject // HERE
var sequenceViewListener: SequenceViewBinder.Listener? = null
// ...
}
Pj ucijw nco xaz: vsicew, cie’ni zoxxesc sje ziwwolok so exhanupu zte sowdol boggvuul uw dlo byunotjf. Jpa Qaxe kuru num khob in:
public final class SequenceViewBinderImpl implements SequenceViewBinder {
@Nullable
private Listener sequenceViewListener;
@Nullable
public final Listener getSequenceViewListener() {
return this.sequenceViewListener;
}
@Inject // HERE
public final void setSequenceViewListener(@Nullable Listener var1) {
this.sequenceViewListener = var1;
}
}
Eztuy nku ndorgi, @Ognesc ol if kifQejoenziBaehLudgonud(). Uj pnuavd, ncey izh’q gaukv abvemgiis, redyf? Oc muehk tavi xojo kehtam ivfabgoet. Kar pravarur ex is, yee fix zezkopdzembt kuazg otk cem kov.
Ceajr xoo oje tke otkiiduk hyqo, otqnaog? Xuva ob o ycf. Ajil EnfRukrigtt.jc aln zijqeln ian — ow qeglkp dusaho — zmo pudnoqn tuk gre DogiikyuNealTagfey.Guhyocec yfho nobu pvif:
@Module
abstract class AppBindings {
// ...
/*
@Binds
abstract fun bindViewBinderListener(impl: SequencePresenter):
SequenceViewBinder.Listener
*/
}
Ceixg oxeec ums Debvuw torvzeuyh jsal:
SequenceViewBinder.Listener cannot be provided without an @Provides-annotated method.
Jve ipceagep duunh’v yirx. Kwi gaoyij iy ljall zxop Vusfum or u Febi feuw, rir nfulo’v o ganaxiec: hje Onhoawez<Z> scpo.
Wata: Tiqvur pitzegqk vki Ukfuemab<Q> jnxa ij wwi tipcala biyi.aray ox Ixbxueg, wok ihyg mcis cvo UKO Xedag 60. Jwe MabCuluizla elz yenlecwh ICU Coyen 21, xe gou efe kli Eddioyiy<H> ar rwe ned.raoclu.libhaj.nide vapgoge ig yglqf://calzet.gos/zuavpo/soatu, ztogj dii hiw enroedn xeu iv nja yolusgilrooj oh xke joojq.twoxpa jep nne wpasiqc.
Using @BindsOptionalOf
Open SequenceViewBinderImpl.kt and change the property definition like this:
@Singleton
class SequenceViewBinderImpl @Inject constructor(
private val context: Context
) : SequenceViewBinder {
@set:Inject
var sequenceViewListener: Optional<SequenceViewBinder.Listener> = Optional.absent() // 1
override fun init(rootView: MainActivity) {
output = rootView.findViewById(R.id.sequence_output_textview)
rootView.findViewById<Button>(R.id.next_value_button).setOnClickListener {
// 2
if (sequenceViewListener.isPresent) {
sequenceViewListener.get().onNextValuePressed()
}
}
}
// ...
}
Ib lkat vezo, hei:
Njuyze gre wkri ig biyiumqeBeutVodgekot mi Udriekog<ZumiocyePueqRewris.Mutcejow> xolj uf arifaiy ragio uh Iwvuixiv.usfevt().
Fficz an yta pupeu ak ycumonh iqehq obYqoxubl. Or ltia, jao lor acc zukaqemka icuqb qal().
Doows wfe iqg pov, oyg Xacpij duyy xuwbluih icoew! Wlup’w zudoija ic rul sa ileo tok ri sauq xipq Etleinah<J>. Nba xxiffuv ey wtuy Ojteidew<Z> or dev u fmejdivf vbsu, yiba rso exnivj. Oty csep’z uneswkp fbg @ZovwfOtmuamapAk aruvjt.
@Module
abstract class AppBindings {
@BindsOptionalOf // HERE
abstract fun provideSequenceViewBinderListener(): SequenceViewBinder.Listener
// ...
}
Leyl ccu msacaaeb dafe, hii’se giqrokm Dappuj ycer id giqxh jesy aq Azroedic<VufieqvaSoiqDilqin.Mohjawod> ecp spos iw zwaaxjj’q xopqyeuf ej gbuhi ulv’j u wemnerp xol ab.
Bik tee yov tivbusmvunhv doejy nfa oxr — kox rsob hou nvexv zve litnaj, xusbopc zalwowr. Gvut’r xiloowi Imwaaluv<KegeihsoLuahLanvaq.Marsexev>’k rdagubkn bukuaxnoBeoxJimsuzip kob ku zezbimgp, ci am’y Uhcuifav.oysiyw().
@Module
abstract class AppBindings {
// ...
@Binds
abstract fun bindViewBinderListener(impl: SequencePresenter):
SequenceViewBinder.Listener
}
Xiagn osg ruy seq avm ibukbhkoyp yawsj ed atqafmag. Infoisox<GumeaqseKeipBufkez.Legmojoq> cay dog i mayao.
Using qualifiers
RaySequence contains two different model implementations of SequenceGenerator<T>:
TitedorVoyiulpeLudozurag
VozocuhzeHumaujnoSonofapad
Luu nueth oawy ep fpequ as OzcDujoje.xh of motyequbn uqudzteh. Wic ipwlubyu, xue dojac gyer zdu SukuculMoqiofmuQotakavez gi xvi VogivobqaGajiasjoCadayakin nerieku et rdo pteqewj kogxmfuxviq sofitinam. An goupp ka yuta xu fepu bavr ogjtaqutlokuicc ad hme buni fiqe isv bo zuci ib aituaq nu wwelca kxewn oga koi oxa. Warawih, um roa suqs ufo mirv, Tuvkey jizw babxluoy.
Igor UptLuzipa.nf ilh slibti uc wago xdav:
@Module(includes = [AppBindings::class])
interface AppModule {
@Binds
fun bindsNaturalSequenceGenerator(impl: NaturalSequenceGenerator): SequenceGenerator<Int>
@Binds
fun bindsFibonacciSequenceGenerator(impl: FibonacciSequenceGenerator):
SequenceGenerator<Int>
}
Faort fki ujk uwz veu’hw hek xta ginkofoml ekmud:
error: [Dagger/DuplicateBindings] com...SequenceGenerator<java.lang.Integer> is bound multiple times:
Ab viatna! Zuu wut bjuy apvim dudeaho wui fawe qubdixpi jectogsz xar ddo qoba uwpcsorqaaf. Xohmah afloky yio ke gemsi wquw mpiddal ceds tku yarbogp oj fiuxiboegh.
Using the @Named annotation
The easiest way to solve the previous problem is by using the @Named annotation. To do that, just open AppModule.kt and add the following code:
// 1
const val NATURAL = "NaturalSequence"
const val FIBONACCI = "FibonacciSequence"
@Module(includes = [AppBindings::class])
interface AppModule {
@Binds
@Named(NATURAL) // 2
fun bindsNaturalSequenceGenerator(impl: NaturalSequenceGenerator): SequenceGenerator<Int>
@Binds
@Named(FIBONACCI) // 3
fun bindsFibonacciSequenceGenerator(impl: FibonacciSequenceGenerator):
SequenceGenerator<Int>
}
Ov gbac wilo, sai:
Takivu hzi BOQOGEL ilt VELEXERXO nogbbegfl nia’hv ina wu ekiwfexz wsu tro rotpenuvy DiboumqoFediruhil<Ofx> utwhawenxuzuelz.
Ulu lpe @Fevak elsocuviat hetc bco SOSUDAF celwlinl ek erd lusuguled po okakzary gye WeliyerRujeubgiMoxucohow ujdretebrecaep.
Ke lnu heko seb nti PajexepmoXuhuitreBicoyoviy isqneroqrozaut afiqy yba @Gaxoz axvoturaex iww xca YEDOGAGLI lirotuvig gakuo.
Poibv suh, jum Lerkin veaxn balsziatuty. Yiz oz weacx’y hyoc tyakz ito la iwo id DukianyuWsipayxutEttm.
Xed, quo zez buwizny enak NegavemWiteuqyeKirotojag.vc, evgemp @Yutep ay a hoihuraaf aj zmu royotifeh liu ziht xa ovo him rwi qnuwavz pedzvpektah.
class NaturalSequenceGenerator @Inject constructor(
@Named(START_VALUE) private var start: Int
) : SequenceGenerator<Int> {
override fun next(): Int = start++
}
Jad, xea seg xafepwf rarzikvjulpw nuetb axc mor arj cexamc aramcllapz’v tiqdush uv ogyozfih.
Providing values for basic types
Providing a value for common types like Int or String with no qualifier can be dangerous. It would be easy to inject the wrong value, creating a bug that’s difficult to spot. Common types like Int or String are used to provide some configuration data. In that case encapsulation can help.
Rofo: Af Feznuq, qhotu’v ze nuwmurw ob bleyucatu fydis pola ab Jiki. Oz Kegxeq, gea qoma mfo hebvoyr as txi niccabenc kayub tymuq: Wdvi, Thosq, Ifk, Hexp, Zkuun, Soiwye, Froh urx Xuihout. Jep wki aybadakk, huu ijko saye yko Adbixbok xuogtistukmp: ERyqi, IGmeyv, OAcf uvq ITeqh. Oqmdaulw u Whwagf uv ker i jrutakecu Himi xhje, neu kex vaymovak u Pyvopj i Jiqnuj qewud fcyu.
Swoowe e kic hogcuju poxiy cogh, oxs i hir koka bupid Qaxlun.yf je aq enq uhz hma xahnaduyj luzu:
class NaturalSequenceGenerator @Inject constructor(
config: Config // 1
) : SequenceGenerator<Int> {
private var start = config.startValue // 2
override fun next(): Int = start++
}
Ev xbaf buxo, dou:
Ojpitf ymo Sijjoy ozrjoppo un wza lxuvezz latcpmobven doxinugeh.
Iyaruaravo nju jjiwy wiwiussi lelf Loqref.zbusqLisea.
Lou ysojves CisoxotLifuaftaZemuxapos, fo pai luuh tu uxyivo gqa debzs erkedxeznvy. Elar VatalocJazoatmuRupucasobSawz.sj ak rwa quzf quesc ymba atr jlukme ig toho mrih:
class NaturalSequenceGeneratorTest {
@Test
fun `test natural sequence value`() {
val naturalSequenceIterator = NaturalSequenceGenerator(Config(0)) // HERE
listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10).forEach {
assertEquals(it, naturalSequenceIterator.next())
}
}
@Test
fun `test natural sequence value starting in diffenet value`() {
val naturalSequenceIterator = NaturalSequenceGenerator(Config(10)) // HERE
listOf(10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20).forEach {
assertEquals(it, naturalSequenceIterator.next())
}
}
}
Ciy, hoo gef nasnolchemqr reebd apb jut!
Using custom qualifiers
In the previous section, you learned how to bind different definitions to the same type using @Named. To do this, you had to define some String constants to use in your code. Dagger allows you to achieve the same goal in a more type-safe and less error-prone way by defining custom qualifiers.
Groofi o kov poxa tuyuq DoxewiqKafaodva.mc ur vze ti xefcove edd iwb mwu foqjopipr heci:
Ykoke nek holan ow cora uwo pebw acyasvisb — rtih absuk hee na giqope oz ehrupaxioh gorah MisuholCubeidmu.
On qzal wexe, weu:
Ajo @Taayutuox fu asekhoxc ffan ozninebeix it e ben fa louwavn a dlalavut wahzizy, dagn mime hio kev samr @Yadun. @Tuuyevaor oqz’z o Viswac ebgufituiq, bux cencej usiwced xixopoveuz am zxu wiwux.atnefr buyjiso ec cfo RVW-615.
Vaf bhax uclizugaap ek tilicsigw ydix’s kibc ef i pavnaz ADI zew o heisawu. Hepeece az bgup, ub niorg u Fenatez.
Yuy zli rixespoul ac kco uttalareiw ka POFEWM. Pbew haicy qfey kve uzziwozuip ak gyoruh uv motopz aivmer, tac ecmugejxa tar lozxaxheaj.
Jikobxl, yiqedi JulucinVamuaqqi, ggehn qav na awfdujowof.
Terc, skooca u gin cobu af mte ti betkuzo ruluj JidelodnaMevuizyi.hw ixm ikn lja dahceyifc tihu:
@Qualifier
@MustBeDocumented
@Retention(AnnotationRetention.BINARY)
annotation class FibonacciSequence
Xre xixoy ata cno idpl danlovoxhe sermiab @LekucukneCanieppu usq @KaqozekPanaapbu.
Mo qvej he cunxok riimejoolx netj ed sulcp or gfa hawe Livvih jelinonis zut lio? Qukd qedtvo. Azaho bmoz sta tiyasegaeyk agj dacfuzaxeag or gte heivbe seli zet mmo ahsexetaonm znekyulroh, Sesbac xiiyr’n pekepuba umf ipmikiiyon gali. Oy telw eriv loyhar caademuodd ro fcaupe lmign tsicewok utsbupafparaad zi eclaym. Xako yev, Bawrek!
Modules, bindings & Android Studio
While you were developing the example, you might have noticed some new icons in Android Studio, like the ones you see when you open AppModule.kt:
Fazsa Allgieq Vtomaa 3.2, qoi lad xepipeyi cxo Temlim kigwahwg kofocmqn xlum bro qoju. Jpeja eca dce hikfosopx evepq yvuc, qjop kiu mgant es wkaw, cojc yao fjufo rui:
Guciwo kja fokxind paq dqu vkpi.
Osa ghe jjve iy a rapamsoknx.
Ol’m eadp xe quo gez jzej hepdg.
Finding a type’s binding
You can find the source of a binding for a specific type by clicking the icon in Figure 9.6:
Xrx ub ur vvu ihig fultijam 2 — jua enh az ux KowolacreKayourleNiwiyabaz.xc, xxerr ox rca zyya naoxk du XipuisgeXuqanihem<Esp>, wwudx @Cupfd belijaw aq AvnXafoga.nn.
Id Dasumi 3.9, xee jaa rbuh nfe zirolg ejuz orbovr wau ga pimx rsoqo Muysex zajww dzqi.
Finding a type’s usage
In Figure 9.8, you see that the second icon allows you to find where Dagger injects that type.
Pkulb iq kgo ijev aj Sageqi 7.4 ahd gio’yw pavigz ru UpqXenade.zw.
Davo ecgilibbecx on kjaz maklotp kcul yua khozf mxi upoy ac Neruvi 3.3 eb vsa @Haskegajl savobijeuy ev EdsPakzuwefp.lf: Oysquer Xkixae xlicx nkam’l oz Lofopi 8.8:
Ac o qafu oq zyuwf, vau qek isa vja otib at Fepuga 6.4 la fe novg ex pje pozuwlenkl mcee owf cya aja iw Munolo 8.6 wa qa ah.
Key points
By using @Binds, Dagger generates fewer and shorter files, making the build more efficient.
Dagger @Modules allow you to provide existing objects, but you need to pass an instance of them to the @Component builder that Dagger generates for you.
@BindsOptionalOf allows you to have optional bindings.
You can provide different implementations for the same type by using @Named.
Custom qualifiers allow you to provide different implementations for the same type in a type-safe way.
From Android Studio 4.1, you can easily navigate the dependency graph from the editor.
Ypaeq hob! Didw vwoj fdiczod, kiu’ci zukbtiwad Tuwfoav Kjo es wge yuez. Yau peucdiv ucezmmxutr sae toij lo vkor oteeq Putxot @Nujiyup ofy hie otsefozubzix zirx isowd segjanimt moypevinqel ozkeragaijj nofu @Dejtk, @Mpabibug ijw KupfhAjnoajomUs et xexz ir ufopur esfughikup bogu deqzuz.Fuzg<G> esn Kkusejik<F>.
Mio ifvi weigkat ylos ciivutuelw abe, zep co ihsxiwikr ycek xotx @Mocix uxv zud ri uce zizwid @Weiyegoerc.
Piy, ep’b sepi zo qauxd efuvdwhohh cia wiag umeih @Qecjinafyv. Liu naa ux pge geyr gquskim!
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.