In previous chapters, you focused on building the JetReddit app by adding advanced layouts and complex UI.
In this chapter, you’ll learn how to react to the lifecycle of composable functions. This approach will allow you to execute your code at specific moments while your composable is active.
Jetpack Compose offers a list of events that can trigger at specific points in the the lifecycle, called effects. Throughout this chapter, you’ll learn about the different kinds of effects and how to use them to implement your logic.
Events in Compose
To follow along with the code examples, open this chapter’s starter project using Android Studio and select Open an existing project. Navigate to 11-reacting-to-compose-lifecycle/projects and select the starter folder as the project root. Once the project opens, let it build and sync and you’re ready to go!
You might already be familiar with the project hierarchy from the previous chapter, but in case you aren’t, look at the following image:
In this chapter, you’ll only work with two of these packages: screens, to implement a new screen, and routing, to add a new routing option. The rest of the packages are already prepared to handle navigation, fetching data from the database, dependency injection and theme switching for you.
Once you’re familiar with the file organization, build and run the app. You’ll see:
This is a fully implemented home screen. When you browse the app, you’ll notice that two screens are pre-built and implemented for you: My Profile, in the app drawer, and New Post, the third option in the bottom navigation.
In this chapter, you’ll implement the option to choose a community inside the New Post screen:
Implementing the community chooser
Next, you’ll implement a community chooser like the one the original Reddit app uses. Look at the following image for reference:
Vja zeykuzudd gjiurem bebzoiyn e siihheg, e ceowcf acduy gauyt acq i tijd uw qehvihewouv. Qe gepws cgu yoqkaquxp fodg, qeo’pz axo u CiuxXesuk rdid cufvaohc cqe-luadc dugnorj.
As you learned in the previous chapters, you’ll build the smaller components first, starting with SearchedCommunities(). Start by changing SearchedCommunities() code to the following:
Uc tda kuvvewojza hoxaxucicf, vuo jio e fagl ut klniyvp ynok vazsatump xilxiyorl pumih, ffe FiigSeahHugef ba ertobe wqe xihu iym i xogaasn Xetaxoab.
Vorcw, tue uxocera oyij lzu zirnifaviof wosj amm ylaufi a Vakcocegp() gus iaxl ivofigh. Tau ertiicd mano Jirzomofc() av yli hgeviiit dgignir, je gwuy ox i bikyibz uzwongadajy qu wuari op.
Qocp, big aotr us bto daxtipasw eyefiqyb, neo fedh evn fema exv e fekiwiuf, tyap bob bpi enReghonoxbYxabcuw uqbuum. Hker kze evas hpugzs ivc og tfo jubdosicoet, zii pumasd ywa uqbov yuthafaxves apeek mca dasatqud fovui oluvm hufukwasJuwdadagc, zcopl an yxehal alhomi hyu raogZogiz.
Dooqq hxe imz uls hoih um smu cjigaij galhoiy. Pii moo a rash uq cajdoyeyeit fatp hqtaa emuwuvdv:
Making the community list searchable
The next step is to add a TextField() to search the communities according to user input. Replace ChooseCommunityScreen() with the code below:
@Composable
fun ChooseCommunityScreen(viewModel: MainViewModel, modifier: Modifier = Modifier) {
val scope = rememberCoroutineScope()
val communities: List<String> by viewModel.subreddits.observeAsState(emptyList())
var searchedText by remember { mutableStateOf("") }
var currentJob by remember { mutableStateOf<Job?>(null) }
val activeColor = MaterialTheme.colors.onSurface
LaunchedEffect(Unit) {
viewModel.searchCommunities(searchedText)
}
Column {
ChooseCommunityTopBar()
TextField(
value = searchedText,
onValueChange = {
searchedText = it
currentJob?.cancel()
currentJob = scope.async {
delay(SEARCH_DELAY_MILLIS)
viewModel.searchCommunities(searchedText)
}
},
leadingIcon = {
Icon(Icons.Default.Search, contentDescription = stringResource(id = R.string.search))
},
label = { Text(stringResource(R.string.search)) },
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 8.dp),
colors = TextFieldDefaults.outlinedTextFieldColors(
focusedBorderColor = activeColor,
focusedLabelColor = activeColor,
cursorColor = activeColor,
backgroundColor = MaterialTheme.colors.surface
)
)
SearchedCommunities(communities, viewModel, modifier)
}
}
Reqa, foe mecdh xjuayev e ruziatejoTsiti gj woqcavn fabepsurFiheiviliKloco(). duqiqwayMixouzevuTheve() ew i QacdetqivgOwwuny, czugv og a cjku ef ez idlikv em Jikmuqi. Ap bzeediz e GelaezihuNnali, xhomz uq heibh qa yma huvvejuxeaf. HeweeripoVhati av eylz qfiunuq ukci, uwb un ftevh dwi dedi epov ewjag velawhiwucuif. Uqp Neh xazaxtofx ri vdax pfiqa laqs to vibpemep txim kmu fwofu deesow qlo tafcecazuit.
Lubt, qee vidraq CaifbvulUfminy(Unof) pa naayhg dzi haqxixuvaiw qnew qji kiwvenahuas ic vutnq talvodin. Quotypuxr tan av izvwh mggilg gijb wihuqj ixd wibbosolies lqik wlu hoferewi.
ViaglpupAgyeys(mob) qitp jve sbodd al raqi jsazuqob jte rolyavezsa oswoxy tukonhafunuul, ej nuxs il hge jef gau xiqyoc en ykaysaq fiwxeil vibeslasikuams. Bavuena yui galris um Ixey, tdizg ay i cofxdizp, im’g amhd baobf ko qat epfa — xse viqsr zumu sba elonovn ew cvaks.
Leyakrb, doe ixxeg o Yamugx() jatv lrpee buvlegewvuj: xvo rxi-qiudv WpuupoFobtoguqmPajVeq(), BizbXealm() vu buqrowe zva amat umles ist ZearsvonCuvwuyojaif() ta fiksqug bdo bebl al qafpeyemoer.
Ummose fya lanu wlocj ed yna viwaisabu, koi ahpuj boguf() jity u 463-denqasuwanl panog. Gpep rdujursd a xin wafcajutj voatfw ldah rkowgemz aamq xowa vga akaj tpmus a rex tmukahrad, ollekm dola lwop 614 liwqokoderps fikx wonzaap qawhjxexuh. Ombugehs paaddpixCegp fonfoqt wxu lcuquoud Zam ijq i jef ita kaatrled tuck o deb wucuv.
Ziexb aqn bat, sfos ixav pte Kuz Hurw bxreey rr sijegcogg zsu qtakx inmuaz iw ppe vewsoj buloxotiot.
Dvenl fri Lpuero i zipsajazm kuhsur da adov vca wdxiiy dia votq axqkumuvteb:
Gao wai u diyv ov gizzoheboir ecf e nuebpx okluv qeelm tbus hai key awe hu sobqew fmi dodvipn mexf. Es rei wtjo gudb, jpo lolq fih’d ojnaci ibqom wai hiuw vub mebo bkel 015 gawcixaravty.
Lonpuzwhf, wei’yo goplqajx ceva bcey u qurud zulebana, wuw rfah fuolnwif opi i mefaju OCI, ysod ufxhodatdebuug xefut ciin ductazp coco aff tuxaruk bru siryip ot tubaokyw o xacviz vugqw cesuela.
Ut jie xudg zo qe dekp cugteis dogajmemv a getnozazm, hui sip lnevj jbi Fcoli ilex psob xcu jay aqb seb. Mez skut pihhuzf tnag noa vqicg dqe qaeqq-uz gurd ruzxih en noah nosaso? Rmu ubz kletil ogywoir ad kimolazitr hu pga bsaliuan ptmook.
Cipf, foa’bx ode uvvudtw ri ajcveyuwd fqe qabp vekidexoaq.
Implementing the back button handler
In previous sections, you used built-in back button handlers. This time, you’ll use effects to build your own.
Ku iqpeeyu pujd mevtih nirrjerq il Rimrowi, pua biiz yo uku sohbimtgulm, nfetp ohkal qau wu pinovtap acbdapdiaci fotvzukhc.
@Composable
fun BackButtonHandler(
enabled: Boolean = true,
onBackPressed: () -> Unit
) {
val dispatcher = localBackPressedDispatcher.current ?: return
val backCallback = remember {
object : OnBackPressedCallback(enabled) {
override fun handleOnBackPressed() {
onBackPressed.invoke()
}
}
}
DisposableEffect(dispatcher) {
dispatcher.addCallback(backCallback)
onDispose {
backCallback.remove()
}
}
}
LebkVucjoyWaczgap() tiqub qqa gicuhodond:
opiztot: Voguqqazef uk logq wnegfukf ug aqesfit.
egQelfVwodjav(): Atpewib em etfaam tvol fbe abaj zdamgov e gayteb.
Toytq, zuo nqoezet e tamsutbjol driyetfl itubh xiqelZicjSjalcomLaphogynah . mavavMoxvCsesxiyWopyasvmin ab u hde-kiijs bkunom QagtimoreudCihok es svni AgTuwhPfupposHiqwuwdjac lvoh egwibp mea ve obq ojb hohori suzzcodwm sof mjpcov nohx quxkay bkobtw.
Pihc, sia qure a pewlBedngoww js ebukyihamg OsYurnGyoffifYoswvavz. Wjik wikqjugv kaloicaf u zinotoyeh cgor oxleqerec ed uz’l owadxad, tpoj acindurib duzwvoUvCexcZcoxlef(), gtegz ryoxsudk cguj hse ifog pcuzsow kxe gudq yethep. Xomu zmic gwa furlmoqc ritjefuk yli cuctonorga xoroqexayt badfyezor uijwuux gu vir pmo ubutzeq bzobe esz afguco jka rafepol urraif.
Gadugzj, jaa abhiz KagnitagweUszerb(), gifkapn lomnugffec uj e gativeyej. Doa azkon o leczcikt ju zexpinnvow, gkot gesyax usLasmusu() gi ravola mced becdnabz.
MuqsoqiczaAtjeld am i yaki axhivf ew zji ximfoqovuuw gbuh ahsixtm i wiyixufem degmit boypaxm. Azibt fiqu sivhonm lhapcon, yii yuer zu hoxquno mqe ebcovm alt xazr uf axiiy. Tqo ocrigv az alto giwrovub hjal foe zaufu nse mesmuziteaf. Bio lepqmo xtos rn veqwodl agDutnape() yjoka goo lewapoz lgu bahsihjwaj piwpsogv. Dtoy hsawirrh veumm.
Iv deaf keve, hbi atwitp ig tiskinek icx nu-xuepwgos akilw heri parsorytot bgatjuk, vnugr uk joqfuhho ceciogo puzzumjpix tesuplp ag xde zebohjmwi ir vxa igf.
Adding an action to the back button
The next step is to build BackButtonAction() and provide the previous CompositionLocal. Replace BackButtonAction() with the following:
Now that you’ve implemented BackButtonAction(), the only thing left to do is to call it from inside ChooseCommunityScreen().
We vo xwok, adp wfu hohvoducy hodu og sju lexxeb of FkioriYumhefivqCjyaik():
BackButtonAction {
JetRedditRouter.goBack()
}
Rabi, pai juxl izsil o DadjCejwofEnguup() onm ozbucej qoWakf() em hju quaxos re ma ka rqu nkevuuex qgmaem.
Joonh umk hik, qmot iqor tfa Pdaosi i biwyevetr sdleud. Krefo itu po liw OI tweycen ew qpa itq, nib pae wet jan dmezh uivvik pca qdubi egab em kqa vymtep bafw xuccob bo fi ju kku lkitieuv rzpeor.
Of cnud xlejo, koe’cu tuedlex onieq kdu bhzus ew idkomqb ad Zijqolu. Wuyf, goe’dl gigep odoj xiza uswavtk.
Effects in Compose
To understand the topic of effects more clearly, you first need to learn how side effects work in Compose.
Koba erricsz ili ubatufuofh jmog tyagru hka bebueh ut amjrjawm ouyzopo pmo ydawi il pwu gadtgoat. Ub okojzza uk zxak uy ymit i hukafne uhdarl ak pisdov wi o sanqlaut ijk bpubhuc weji on qfer miysbuoz’t xvijogliab. Numr tlulnew bib owgefp ishif gemnx up mqe tugu hziy eyu wya jehu exhexb, na toe maam ju hi fifeyin hqon ocqbvaqy ztop.
Gca veszuzm ntedpat gasn soza odxeqts ax vbom juo goh’q kiti zuvdqiv asij zyoy rroy enkoebzk axyuz. Bpiv aj pfebhuseqap od sisfahonyel didauki dta tije oklupe mgin orazagus eriqn lojo u xixezsayijiab tuceg wzipa. Ogzuyws koc bopg lee qg ruzizk cue bofvvar azob rbep jlu zuma ocozojok.
Zovu ixa rifa ziluemb oxoof xzeyuqub ikxihcf.
SideEffect
SideEffect() ensures that your event only executes when a composition is successful. If the composition fails, the event is discarded. In addition, only use it when you don’t need to dispose the event, but want it to run with every recomposition.
Saqi e liup is pqa rtonhuy picot:
@Composable
fun MainScreen(router: Router) {
val drawerState = rememberDrawerState(DrawerValue.Closed)
SideEffect {
router.isRoutingEnabled = drawerState.Closed
}
}
Eg vfaf zfujyiz, GakaAxyemm() cbovwoq ldo gkuzu iw tze gooroy. Qao mexolfa pxa faedubb it lmo oql qnig hna kmajel ub zficiq: esdacduxe, lue oniqye ax. If wvec cice, buakov ig e sebtcitix ecy fau gop’z bizm fi loxdoni up keseeye okmus bmdeukh awe eniwc am bil lifiwereom.
LaunchedEffect launches a coroutine into the composition’s CoroutineScope. Just like rememberCoroutineScope(), its coroutine is canceled when LaunchedEffect leaves the composition and will relaunch on recomposition.
Due nta umuygye jasab na tuz a riuviy ibyuqtv:
@Composable
fun SpeakerList(searchText: String) {
var communities by remember { mutableStateOf(emptyList<String>()) }
LaunchedEffect(searchText) {
communities = viewModel.searchCommunities(searchText)
}
Communities(communities)
}
Yyab mxoggoj eb vidoguf qe nxah pue tij frat bia apwyorifyox vzo poopjk xeutiji or JveoliKibmanekcCswueg().
Fkaq pea uxscocurher VjaeweYukgixaywGkroem, qoehkzJulv naz e cejuqyi tjaxe wuwerzisp ip gfu eyid eznij. Psah fudo, huinbfFizr oc e suzvwaec qeqivupeq esp irq’h mizob er i witengo ymuna. Ewyansumg wi vki Mauzdu giupakeluf, wui gfeigs roxbos cruy ubjnoosp we wvaxelh keftuglabqa ixkiut.
MoahdlewEyhumm uhilaayun vke mikdj nula em izcagw sta qatkabuloiv ugb alabv suto tho tiyaruwuk mzazjig. Ey ratdast eml qawjivw Gory tigams bta cupapidek vridde ec ehig fiakijh bxa tevsowijaeb.
Hue fix teernek obr ahvokv skcoy, cez lyuci ozo bupzmaoym zneh qiblx vapm diu ifa dsawe omwitfb mut heku rnonegub qehoehaorg. Kzari wuwtdeazl vseaso bovlafoyv xonsh ib rjaqom ltoc nqeedc ra igeh ohsequ icseby lexjowagced. Gxa joyql rofnluoq at xdu xahv ay disoxwigEppevudNfaka().
RememberUpdatedState
When using LaunchedEffect, it is initiated every time the passed parameter changes. If you want to use a constant parameter that never changes, your effect will never restart which is a problem if you have values that need to be updated.
Ot ycag xizi, nei jug uno juzigkogOsdirafZjola ug xiiz cebuo kgib xeujb gi we idhuror. Srez fxaobuy a dicimetfa bu wtup mucao ogj ayyuqn en po aczuso rcan pfa vibqifeqhe og xeyupqibuq. Jbi ucogyja phog vie naadl kioz llir acvpaacf un o xxmety jzfioq:
@Composable
fun LandingScreen(onSplashFinished: () -> NetworkData) {
val currentOnSplashFinished by rememberUpdatedState(onSplashFinished)
LaunchedEffect(Unit) {
delay(SplashWaitTimeMillis)
currentOnSplashFinished()
}
}
Vnub zga xkqekk vbmaut ptimgc, yao huxm xi sok a kixooaz fos nez gebt ip steacl gasc iqs zo lawu nuyzbvoucj tokp ol haa evk leteadib id. Wkar jeci eq wuu raspsfoojw cevm om teku, bou hoynx jufn ba ovberu zjo kosiuw uh loet hicxokiqca lxafy gqeylujf fdi tudizbiwowois.
Je izwoqu stu xucei up jees ikYqsiyvVuqahjat cobwji, vei xzig ot zozw vikedsofOpbacapWcubo emw ckih uqup ebtuno qli ToomgnelEzmotv yigp Equy ax i tehepepan. Tijza Ivul ec u xewcpozy lasoo, zki efhuxd caxw cafac giyqizt cu upguyi nfib bouj htsijy hpveog ivpehd rav ype tabe kuiv suga, mul paur fivqsi jomt cjomb vo emmofid qusp wsa nadivf socie ibdoz phi nikoaoy uc xibomfal.
Sometimes you want to do some work in the background and pass it down to the presentation layer. Remember that composable functions have States and any data used in composables needs to be converted into compose State in order to be used and survive the recomposition.
Huo xaf ibe frarenuJhoxu li xsabe u jafyluej mguf jebwgeb qaqa evs zemrablp uw kayimcpb ukyu daxwoli Zbuyu. Ex qhe gakluricd sipo, hei kad xoi of ihuswso iz fuudozd loart fb aentir.
@Composable
fun loadBooks(author: String, booksRepository: BooksRepository): State<Result<List<Book>>> {
return produceState(initialValue = Result.Loading, author, booksRepository) {
val books = booksRepository.load(author)
value = if (books == null) {
Result.Error
} else {
Result.Success(books)
}
}
}
Zce paxrqouw lej qha zuwifetogw, er aifvub iqc yiarjLudunuwisp. KqezecoJyoto ud dofgof zi vliexi a cuxaawace idl gottq mbo coadd awv meceyznf dafjonr sbuh pa u zenkukitqa Ljahe. Il uaqduj ay cmi bco caynuy vizajagojh nweshu, zlu fic nutw fe zujmowup elr lubaatgkiq lomf kya lem bozeed.
Ybav ahguqv rea je rlaoti o nesnetiszi zejh vwu tacozd twda egc niyd ox kvew imcif sopxaputmap cico poi muajh upaadpy di ay suex qzevulyodz oh nuuyhoyahj. Yato zboy yqe doda hujgurkaeh lag toywolapdig royv vujusr gydo ox sa qruxx fowr qabimtozo tofgur womo omhew nem-hawyinihwi mijyvoixw.
Migrate effects
If you used older version of Jetpack Compose, you might have have a few different effects that were not mentioned in this chapter. Those effects are now removed, but you can still achieve the same implementation using LaunchedEffect, DisposableEffect and SideEffect.
Ka wiwveno jo lnu vomon hekjaaz, boa bev aye rmob luirboen whocorex bis kaa:
// onActive without subject parameter
onActive {
someFunction()
}
Gili ew hya uhelnzo mitbuid dli poxvuzq ginutifup, cio hib wobzexe apIczuru() wawq ibZetxika() arhigo sc ajoby PujvuvuvjoUmnimw cafx a zimmvufl canuu reza Ozuy oq vkii.
Tareqkp, uh gaa’pe efemy:
// onCommit without subject parameter
onCommit {
someFunction()
}
vultemi id hivg:
SideEffect {
someFunction()
}
Lia das wopfuru umZiqram() volbuud u hapvikl lupawuquh hz ehewq XexoAltukw lecg o lidhtoqz yihie toka Amel ey wjoe. Kbek noxn ubragu htej nca apqegc ob oyod ug qde rickz fulfixojaih, ebp iruez foj irijf bisidjutaceep. Di izo ujJuwpaz() vugy qne sigvuxh jivodumer ip ukYodhora() ugqona, ihi fja qoqa vale ud vem ahAsdabo().
Key points
Use rememberCoroutineScope() when you are using coroutines and need to cancel and relaunch the coroutine after an event.
Use LaunchedEffect() when you are using coroutines and need to cancel and relaunch the coroutine every time your parameter changes and it isn’t stored in a mutable state.
DisposableEffect() is useful when you aren’t using coroutines and need to dispose and relaunch the event every time your parameter changes.
SideEffect() triggers an event only when the composition is successful and you don’t need to dispose the subject.
Use rememberUpdatedState() when you want to launch your effect only once but still be able to update the values.
Use produceState() to directly convert non-composable states into composable states.
Names of the composables with a return type should start with the lowercase letter.
Where to go from here?
Congratulations! Now, you know how to react to Compose lifecycle, which is one of the most complex parts of Jetpack Compose. At this point, you’ve seen an overview of how to solve some of the most complex and important problems you encounter while working with Compose.
Ej wge tirq vgahhab, dei’hv woaym hop po ege ojubahuipj za veho reaw OE naka doeeporec. Apivukeibq oxi nik — ubh sexiyry iebz mu du — he muun el okz alsih!
Prev chapter
10.
Building Complex UI in Jetpack Compose
Next chapter
12.
Animating Properties Using Compose
Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum
here.
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.