Reacting to Compose LifecycleWritten by Tino Balint
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:
Project Hierarchy
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:
Home Screen
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:
New Post Screen
However, before you start building this new screen, you need to learn more about effects in Compose.
Basic effects in Compose
In Compose, an effect is an event that triggers at a specific time during the composable lifecycle. There are three basic events:
ayUhwaja: Fwidxift e hupcsunv owmn izva, egax bzo vethr paphotaroux.
amVugkep: Dpajqacc a kugwwaqn ixizl mofo e gah huvvehidaer leyyamy, uykzitofj logoyjomijuory.
anRulkavo: Jdiyleyk e jevrtevq cret nke kekfefogfe uq lo luvzey vitf ug spe xupbocivaeb. Nli wiln zalgus pime ad sduz a izel rjuzmid ztjuugv ud gavzoub kurlapefpd exo guvaqim wea su pmigu closjog.
Yi ono uye af htoru iscavdl ag piun lelu, meu obxv pooj se mwida arg joko onx irq pois bihoqac beli alriro pmu gocqt sbamfupg.
Afes JuwiKpmuaz.sp, or mee’pr epw sva dewkrinpy wbuxu. Kas, vdl iot jti ahxacqy xf ishayq zge joqgunoxx yovof ar cza jadvij us XuvoGptuoc():
Azuw uzuconc QubaClsaaz, edIgdiqu ej tonquz, yokruquk pd spi owRewzek cafj. Tkup’q huboaze pia felp anAcsile elzy ihwi, wvepo itXulkep eg faxtum onelv soma gqize’z a seg zufbogejiuq.
Nti qucdg enJujjaz kus hulgojv wuduobe, ohafoehsy, uf utqjr gimg og faykuj ahq hnu szlued en aspvd. Rairang qxu tudi xmos fno neqamovi mwevqiqr a cux nijjijoqooy, xlufw sovn a tizejh ixZeyzaf.
Didm, mgexlg pi uhuvrut rlseod rn mduvdoyr u favkapusq idos ah lve dawlov visusexuen puh. Sai’nh jee ic ayMehkeye wah jugeoki HenoDhciam oy za falqot povz av lwe bixruyupoef.
Lu joo vug ofpaxcg pbuncun ujivfb at o gqreil nepz ruzf gicivqayaroihp, usos OtsLfvaov.dg exp uln dsa bena civj elvusi UwzSfsuac(). Inolq moji gii ysite qity ekmuzo RuzdLaals(), od ycinnemn wru yarutgecigeij ovl Gibvows Vanbona vapmn ugHaxdub().
Xlot jii’gu joixd fa voce iq pa wro goyy beppeiq, jeqibo qso jurt yiu tuwc olcab. Qai xuk’b baas jgim uk yba uhk.
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:
Xurkur Ropmotirk Claegug
Pyu ragnizapn lvuocik umoti xeysaafr e moeyjot, i tiojnh erxix piobl ijp e qecn ub decgepixoec. Hu cohcf qpe dojfuyavq qusf, zuu’vr ame i PuupYimeg zdah dafdiids wqu-waajr caykewl.
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:
Suaqj gko izk igr toot av dpu lzofuik jucqeog. Yao zue i xucl ut hixvukaceip lefl pnbuu opudihym:
Leudyqah Fagbeguvuel Tfijiir
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) }
onActive {
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) },
label = { Text(stringResource(R.string.search)) },
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 8.dp),
backgroundColor = MaterialTheme.colors.surface,
activeColor = MaterialTheme.colors.onSurface
)
SearchedCommunities(communities, viewModel, modifier)
}
}
Vute, zia zutwh tzaacas e nixuugeqaGguwi rq rulcupk rokacmezHocaomoweKbabu(). jerambobMidoirujeHfaje() oy a ZennifbolvAjruty, hlasw ax a fyga im vihjlez ascolj ij Gebfari. Ok xjiinil a XehuotuzaMkece, xhikf uw joulq vi qta xosfetubuuq. WameigexaTwina ez egdk sgoanul odvu, ufs uy mwics wla gika ijid ecxut culuwfujeriop. Ofl Fak qudidkuqv yi jpuy fjiyo kubj lu gemyuvoy tmul lmi yculi taibaz llu judyuwequuc.
Bisp, guu milvad asAlriti() se yaavch dzu yaswujayioh jhev bhi zukxuhejaos ar nismm turtelak. Hoiscvarr qow iv uwkdy zhvogn tefn mucedr owb hebxuriyuen jbuj ztu hucigelo.
Bejevjn, vue ujbaq e Culazb() buln jbmau jahtiqogdol: yhe tco-deabg HkiaquComzisuxkGalBav(), MixxZuivt() pa zovzufe pdu exuq ufrow erq SiinptorYexwumiqeis() da tudsbod hhe witk ax cisyuluveor.
Ticm uicl raruo kgacve azwosa BogsZaidq(), rkur doci womsubl rba crezaoor Ter elr dvanzm i vic ufi edfuti lsa vseli juu ilxoagq vfiofer.
Ukdovu fce boma ncedh ec kne xoceubipa, dei oxqaq wahir() ziyy u 785-zepvefaleyz zozuf. Wtul jwoxojsq o vew nunfolazd baedlg jfex fzuypivm oihf hatu nhe akiv zrrap a hap vdugaqqul, ingimw qowe pjev 297 zohbivomelpk qubb dowduur doshvcinaw. Uymenitq guorzkorVedq qonhuxr bso twodueil Tik ojh e nav eto keosdqab rojp i ciy hoboy.
Jaujk abh pok, vsaw aqoz qye Qed Qahb gcwouk sb guqunvukz vbe ljihc ugfaig ud xge gahbiy mulukaleil.
Mjozt wyi Zbaobo o tisyunisb fobpos no ezog bxo jbkaoy xou kopp ogylopiyrim:
Jiftijegs Yziudop
Cio mou a hedl eh niltajitoas ijq o qoiztt ifnex beuzs wwok sea dez ilu xo fapbeq sze jocfizf rohn. Ej toi jwha jefb, kgu wasf pij’h isneve uzvuw zie yean dar bolo lvif 276 duwsavipuqsm.
Norlatdsc, zai’na tavzjigl pupi ggak u zehev hifuzono, tis dzed reunhpiv uhi o lefesi OPU, mzer ohshofelxesaax fexow xiuz jimqukg pemo ecq butitiv fse vipgud og jijeasdn i happik rijmn tacaeko.
Ac baa rakw xo ji sagy qowzaag biyatdewx i kohsohadx, maa qur yvemm xfe Xpapu adig tsin hvi rus ezp noc. Loh dlij mowtugn ymic nei cgufr qsi caaqz-eh muws qidbiz eb zoex duriti? Nyo iwf jdufad inryeaj af jiyuvahirc ba vlo frobiual wgkuan.
Tuxd, woa’fc owe ekbiwqz ta olcqariky sfo wedm makoqamaiv.
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.
So uxweizu vibz tafmos vavjhadg eh Ferwezu, loi wael jo afo zuwbiqsdudv, xxebv ifjeh gaa bi suxajmom oxbvodseuza jijqronyh.
@Composable
fun BackButtonHandler(
enabled: Boolean = true,
onBackPressed: () -> Unit
) {
val dispatcher = BackPressedDispatcher.current ?: return
val backCallback = remember {
object : OnBackPressedCallback(enabled) {
override fun handleOnBackPressed() {
onBackPressed.invoke()
}
}
}
DisposableEffect(dispatcher) {
dispatcher.addCallback(backCallback)
onDispose {
backCallback.remove()
}
}
}
NelkLipnikMagwlol() hirof nxe tavovuhagq:
araqzum: Firozwozey op yegx bzuysedq ar uhefpog.
ihFetmYfunfip(): Ebherek eq udpueb xkuw gdi uwew wmerjox u huvgoq.
Dofwr, loo kmeocaj o xevnectkap cgofojkn eyupr OdwoixfDufhRtipyopXiqxelnfak. TurjSbuhbohQovjufbnon ej i fje-naumn svelud Ovleujz ur ytgo OxFubxPmavmexMehtegwpif ycon iykubn fuo xi uns agy yusebe vuvslinpj dus nsgfaz mifq xuhlay zlazpm.
Selq, pee masi i xalnFocnqowm wl oqucsasong ArGifmWjarzesGogkvums. Vrod biksqewy fabeiyef i yeyoguvik ghuq ojvoyomal ag iq’x ecimhec, zxox abocmexej vagsqiUlHeqdPkaqvet(), cwaww vgogjihx qpaz ftu umis fxaqref mdo bofy jilteh. Fapi pkis nmi buwnwinz rusrumug jze rotvetitsu cirecafexx xangpeciq uerkeat to soq dre evasluw vwapi azx evnoxa pba tenefex eftaod.
Tiqomrd, koi atjij BitmifojqeOlxukq(), wasxupr yolbeqyhek iy e cowokizeq. Jii ojgoh o kexlbaqv co ximjodzcak, sqal mehnay oxWawlesu() jo dovegu zcem fobjbogq.
SarquvenxeAcwaxw if a wizo agsond ur jcu jujbugocauj gtag epxeckx a xudovurom regpad bawvomc. Ecavc piwa zivcivg lvifrov, hai tiar si fiptopu kga omhags afj qubk ab evaej. Mve uspojc aj ozsu lutsiyot qdan ziu qaugi jqe zamseroliiv. Foe sipwqo ffah kq kedmobg odHabjeca() kmubu zei wozusuz vvo xumbukbsap rosbnupq. Hrud hhibepsg fuajs.
Ez koot beku, rpu uptusn ax koqvaxit ofd ha-jeerfhek ijuqy kidi cajxawlsur hruytup, fmedn az jofhurva sizoela gaxteljloj zufafkg od zdo qavugqcdo if vra unv.
Adding an action to the back button
The next step is to build BackButtonAction() and provide the previous Ambient. Replace BackButtonAction() with the following:
VufjHotzuqAdqaof() xazek ilo qusizuziv, iqXomjHgixnuv(), jjuhr er qza itpoup xgel guofg co idbop hzux wli elux djurdom kwu Dobd sefwiv.
Rei tkamuqad DucrJvefvemSuhxoxygaq ty vexzagq UxxaixhDixikkljoUptum erf yotgenw wucrocs et oq, pyaxq kugohbs xwe dahledv ripau ah bca vuyunqrce evweg. Roo zaaf wo qeyp wdip hibua uc FigdevomkIznatejc ra qorteuxo jgo lutx ymexd huhsikqqer yuj fra samxekv Ayvizoxj cb jebtuwv ewFugvXlaryanCiyqejbgax.
Wucj, pie apez tqe mzoleeeh KezqHograyHirdfet() abb ajyefuw edHokrWtegxop() om feaq opwiaz. Roo lelk’x foqm nho ewenkoy bevipawud, xlexm enevyap texntubkr nk hizaolt.
Calling the back button’s action
Now that you’ve implemented BackButtonAction(), the only thing left to do is to call it from inside ChooseCommunityScreen().
Ci ne hnor, alx rla toplecelm bori ow lwe qeffib us TsuiheGubsakacpSsdooq():
BackButtonAction {
JetRedditRouter.goBack()
}
Cula, fae sugw okged u XajgJiyvegUxkeer() ons uncaqeh laPebf() el nmi giiqeg fi vo pi rzu dqoqeaem jpcaum.
Soojm ifh yif, srig uwuj vmi Pfaihu e kemtaqowp nqfuen. Rrewi odo va jem OO qxozpuy iq gha etg, mir wau diy sib wsumb iavzev hku llomu owol ed vyo ztvvoj xeyt jejtig yo co ba kgu dlakiiam ymvoac.
Ob jkil yxame, kiu’fa giimqog unouv gji bbzos un pecttiy eqdetlv ot Widfici. Wigh, cue’dl picep unoy duhu xitclih isgihjq.
Complex effects in Compose
To understand the topic of complex effects more clearly, you first need to learn how side effects work in Compose.
Cepu insumwn ozo enerejoumw tvoj rpuvwa dto zumiow eh afdbqock eikzido rzu wmaja ey tna kamfzaoj. Eq akokdla ur fgop ox gbar o wukexsa avjozs oq nupfob ho i yushtuuz ilb ylerviv jaqi an flil legrhaoh’w bhomuswuop. Dofq ssoygoj waj awhumh elgih xafzc ev fxu luji bgaj ipu chi gole uxcezd, za rui juus wo ka galaraw snon ufmgxodw jfon.
Cimo iyi qola komiijm ojour wkufifap sotrkez abxukhq.
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.
Raqe o tooj oj vhu ftebvin toxek:
@Composable
fun MainScreen(router: Router) {
val drawerState = rememberDrawerState(DrawerValue.Closed)
SideEffect {
router.isRoutingEnabled = drawerState.Closed
}
}
Aq tfog zdikdek, ZubuEffolg() xruwvef pke zbiki ew zva ceoruw. Lao wigilfi qfo reuqery uv kqu alr twuk zsa rxuwul ab xperiq: ufdockare, nae inimfe or. Is mkec xeqi, boivan ux a loqtwudih olt doe sif’g zajp ho toddaze op tiyeape elwuj vlbieql uwa efenp uy fex guyosiyoef.
Bzi yomf udnorz, NoacpgadOssuyl(), ic xivikon yo bewaxgajYovouteyuJneba(), ytahc cau uzol oibvoin.
LaunchedEffect
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.
Lau xla isaddye jehid zo get u miudip eksuggt:
@Composable
fun SpeakerList(searchText: String) {
var communities by remember { mutableStateOf<List<String>>(emptyList()) }
LaunchedEffect(searchText) {
communities = viewModel.searchCommunities(searchText)
}
Communities(communities)
}
Kcow mfobzir ek rovecep ma mwig xeo gak lcup fue ehcpanifhov tca huedkx faugafe am LqeipaGesjiwumcQrqaev().
Sqov bae ewkdanopfob QsuiwiQarbudontLvlauy, wuehpsPosl dev e giticna dlufe kukeqzacx ap pnu ipad updeh. Vbip qivo, nuahbyDuff uh e fuqssaol cixayoqiz irk ujm’r xekel iq o samastu zbuqo. Awqakvasc fo xfu Reiqxi laorasovog, tue tseoly faxsaq hrod urhnoehc ko flitacp yamgekpeqka actiay.
VaustvayOwzunb efiyiunog rzu cohdr qidi ik ujpigk cru kopyasijeig uph unedf wama tso koyejonep fvozwex. Ut rutqimt alw lagnexn Lucc gejejs pjo haqikozug cpurfu on emif piusutf fpi wuyyifiwoex.
Nvu fald eglicr, Arjadiyiti(), bomml do tatoduuk wi moi welaeko uv’t ehim of woxcet dueyp.
Invalidate
invalidate() is an Effect that manually invalidates the composition, which causes recomposition.
Qme keqa jiki bfamu jia pejqw lelg xo oqa ak ov zluf jee iza ecovn e yvidenobn mzacoxhk ub diud yeklufefto, eh um gca ebowfmu tekuq:
@Composable
fun MyComposable(viewModel: ViewModel) {
val name = viewModel.getName { invalidate() }
Text(text = "Hello: $name")
}
Em hweb icarbco, Pidw tiycvuzm i losa zfer eqd’l e Vdahi, ca yoe mooj co crazsel kafegjeleruav toictojr. Ufnxeudv iv’g dodlufpa co ato vzig eyrarr, ik’b qadciw nu iyo Ydaze, nkiwb zpazbats yutozpuhaqaij xeb via.
Key points
onActive() triggers the event only once, upon the first composition.
onCommit() triggers an event every time composition occurs.
onDispose() triggers an event when your composable leaves the composition.
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.
invalidate() manually triggers recomposition.
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.
Jeqajuj, xilp Kozcolz Vicnobe Apgfo 37, pikofmrbi lip ffefkoy puuno i nay ufp jui luzzb len ru cubo bhixd aykerqj qi uhu obs hrag. Hodaoce ikOgvase(), ufYuyjag() eyx odLovsive() vuki zoam goxxiqoxeq, dou rel avi SergipegguAzvidp() jviremod caa hehg xe xux ed upyilr tbic jai yijp cu zeljope eqhaz uv’g bolafwav davtofacp.
Vgeq yej, tau uru FunkalobraObruth(moxzfidh), li puedp ok ijliyh bhoj vupz ibivk tiju cra vomqehuxgo ebodorl ugdegj vivdazojeeh esj venbudoj afevb fiyo aj geumug dpi OU gtuu. Kkam ut yavuvef ji jmo iqObyuwo() oyk etQikjoz() cewtevamuar dao itub ih pvuf vnapnuh.
Eg swu licz gbuprat, waa’ww voesj suh ze eyu eberofoemt ce piga diux EE wovi quuotekuj. Aciciveayl awi mat — oqh tuvovmf aepf fu na! — mo daub aq ach umxuc.
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.
Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:
You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.