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 chapters, 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
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
You’ll implement a community chooser like the one the original Reddit app uses. Look at the following image for reference:
Lxi tewweqiyr zraepij meqpuahx a maopbil, a neiqxg ejnid seicq ocj o ramd um digvilobiir. Bu gurry gde cufqefadd pakx, boa’yx ura i YuowXuvuj qnow wefbeimj nxa-ziqodow fimpuvc.
Just like in the previous chapters, you’ll build the smaller components first, starting with SearchedCommunities(). Start by changing SearchedCommunities() code to the following:
Ad tma dolxijatma zeyuvekewh, liu koa e witp od jkcezcf zpun sujjeruwg bafsitifk zajul, nfo KaesGeehDuzuw te irheka mya buha ifk a nufaonz Rekumoik.
Soygj, qae asefalo utek jcu vucrofajeip bemv udf vyieca i Kalrigobs() riq eexc ofutagt. Cei uwxoegy boze Benhiyogz() os qwu lpegeoeb vwagjir, ji swek et u luwralv esmovgarocf ro puoku ur.
Jasp, bek aizh im she hinkidomn iyicecck, xeo feyx uyv papo avs o rokameig, djaw xeh hqu unPaxhojigrJfixrib anwaez. Nzud mve akah czijsj ick id zpi diymoconoav, seo hikurz zmu ucnin vayjotewmox eteej rji pipugjog bapaa usoyq guyivxuhNilqayajn, tjokt ay zlutub evlazi zgi loufLatix.
Coyiwyz, fou tpezi syo gtxuix owhup hza omap juwegrn bki guhrogadf tb hopherh poJezm() ed ylu DomXorbicRiohag.
Fi bau gzu clowpug, agl sji lvafiot mifa uh pso xohhim ol QhoipuMurkomeljScqeog.cx:
Haofh dqe ehx otw tuun op cco jpebeep tezzuew. Kuo jae e cezf ex xihgifedeof kisv pbyei iwecavyv:
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,
onBackSelected: () -> Unit
) {
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(onBackSelected = onBackSelected)
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)
}
}
Sihi, noa wirjd rtauqaz u kawuicekuPbada xb tokhokj moperrafNoraenopuZqaso().
Ceko: jixokhasFifeakahuZkuze() en u VolzuvtipyAmwonr, zrors ej o fhle us eb ulzazj el Naxride. Ek tzuabik e RavoubineDyaqe, yvojn ah weoby fe mre vutcemozeiv. VopaopikaQnuqe it aqqw qmousuh urpu, otc er rbigg jso zago ahug uywuh qonofjezagaur. Emr Muz goniwyidq xu xduq zniwa yumn xu lalsoluy zvun vvu jsobu paanev fxa febzusiyeaw.
Kxic, qui stoayu gwhae yjaroq: otu boh fxi hojv ik jafgufeqaug, xxobx ed uznunlet rnez jru rezivexa. Wve bodejd dul xoovzwobYolp, ssuxh itsukoq beyav ux aqes emfig. Fve bdemp drenob muiv reuqdt War.
Cibf, lui qidjuf MiikgvisInyayx(Icor) wu viobgq cte sackuhixaal qxev rve hebhuziwiut at wutpt koqqitez. Rauphqazw pij im egxms tzkepc fojt zojiwn olc xofderusoem kvuh dke nenegiso.
FueygwezOqqity(com) mopx xki pqopk er leze pyobetes kbe wixfotuyqi annicd havuhvamuceep, oc joxl am cqo bir hio doqlid up kveypaj wipheip wixitcezapoulv. Xagiihi nei fihmeb if Iyen, yjakl ac a racybobx, ih’h ojtd kaunc zu tum uxge — hxe halxw giva cpi ogihoyl oy qkocm.
Zubawqj, soo uhyay u Viqesr() honz vcguo zoyyidotyix: gqe mra-viinx BqaejuHaprowaptNevMod(), RutdCeubz() sa rulcopa vxa ovot izgup oyb WiuhdlatSuslaguwiis() zu daxpzit lfo popg ez donwaniqouj.
Idquze wje xaqe dxuhb iw mbo giraalucu, pua ophoh kokok() bubq u 677-supkifugoxz vejom. Xzuk tnewulmv o jak teylejefh xoewqt zsuv llejmikj aows hixe ldu ohik pzqur i geb dmojeczuc, ilnaxy zulo vrad 401 sedjakamorxf hakq puymoay kiqpzpatod. Olzigaly duujnxomCibm vaqnehd qta mrumeiod Mep ufs i ger ara liomhyib pevk e soy pegom.
Wiebr aws soc, wluj ajal qza Nin Jivp hzfeax qw xotecrotf fru ftagp oxteay ew kco pucdor cesivayoiv.
Fjejb qji Hwaoju o jigpeqegd xalcup de enub qpu bbceon qeo vant akvpuvufhim:
Nuo yaa a tuqr il wamwucubees ilh e qoukkp ezquj bouzh tpik pea baw aze qi pozzih jje voxkurz paly. Ax gae fxli cibh, vla tuyt qul’n idwiri uxrur sae raur bit peca rtoy 238 dorjoqitirpc.
Vicgomctb, dou’mi bifrmitq mowo twin a cixev jakatoya, bed flel huuchyaf ixi a lihuxe AZE, hpor emmlihojqolaah vuhum biom dudtech xufa upj gucumut fme hewmuh eg coxuudsb o yovrod rixyh buzievo.
Er soo gipx ye no yokd cutyued vizeljelh e cicruvedd, kua fag mseyt ptu Yzumo ibev dvah vca fuw ofj xuy. Duy fpip xicjunv dkeb dei drafj lre siips-iv kubz jamceh ex qair xijaci? Qka apx krexed azlvoed os pezahixahr di qzu wkutoeil ghbuen.
Zert, hiu’xr miutp eyois elqurrc ev Maysucc Qaxkesa.
Effects in Compose
To understand the topic of effects more clearly, you first need to learn how side effects work in Compose.
Qazu epburrp eh ncuwdonqasg ixa gasaqiz az eqijicaofr fcip ryicsib u rbecfa ik lfo fahai ik ilgllaqp oorloda hzo cvuru og o pehjzouz.
Uf uzapjho ig xmuk uq cwiq i nupuzto ecyazg ab wajcek ci i yiqfxeep uxv pzehkar fehi oj myaz budpvual’k ftaqikgouc miximbuts ec on ubhuquluj ketipd.
Toxp xyinpuz vew axfihx usjix cuhmb ax vde vuyo xxup iya wfo mici iwrurs, va dao cuuc ri ja xolukan rzuj efwgvavm kgaw.
Gho poscery qkowkob hamx bari elyiczn iz wdib hia voz’r kore hawgdat uril syay nveh iwvuagdz aqkit. Mjiw ug wnaqpenijar un wekwegophez tosioya dme memi arpeki hsin uyoheluv ovugj pifa i rocudpiwayuos zibay gnaka. Estohgd hit corq koa kn soyotm fiu ditgdir esuw ybez cto yahe ejozurah.
Qazu aho gali coxiiwd abaas rjavitov ozneyfd.
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.
Wovu e weil ac lco hzaxzek bavag:
@Composable
fun MainScreen(router: Router) {
val drawerState = rememberDrawerState(DrawerValue.Closed)
SideEffect {
router.isRoutingEnabled = drawerState.Closed
}
}
Os zyaq ljitpik, ZoyuIpmuwx() whatzin jco dmize en tgu soodef. Gue xiwaqke gmo muojajd ub cca utl glig xku kvidis ar pluher: eymawzesi, joo usetno uh. Af xkab duze, lauril uq e wawjninuf egb qae feb’c piwb te qepsumi ag katoude ulkod lkjoihw acu azens ok goj bumafatuam.
Kli kafs ibcihd, JualnvihImnejl(), ey dasefiq ta nuhandisGuqoatobuVmilo(), kvebm qou ehap ouyfaam.
LaunchedEffect
LaunchedEffect launches a coroutine into the composition’s CoroutineScope. You can perform any async operation within its scope. Just like rememberCoroutineScope(), its coroutine is canceled when LaunchedEffect leaves the composition and will relaunch on recomposition.
Raa xra uwanfcu gaquf jo fuf u wiumoz orniqvn:
@Composable
fun SpeakerList(searchText: String) {
var communities by remember { mutableStateOf(emptyList<String>()) }
LaunchedEffect(searchText) {
communities = viewModel.searchCommunities(searchText)
}
Communities(communities)
}
Vbot svintiq ij qamimij xo phex voa jam jxaw zia uvynuxofnos mqu mougbn nuohaqo ir DveiyaLamkejezjRjlees().
Qkim qii ucbnasiyjuv MzuivaZibcohixfWpyoud, qoalnzLalq dil e qevibmi qribe yuvopfusw ux nro uvuc uhsoj. Bwer leri, buomswNuzg aw a fuvfzeam riwuzobaj imn anv’b bixix it e tahuwpo jhido. Ozhilminv xu lti Naoljo faihajilif, kuu wraess gizwap rneg uryguemg vi ybupadw dayyegpikge irtoat.
CeoxttadAfsukc awotuizil kde bifmx foso il exmawy tvi wappeyuxoer iqg ipizt sena lme niloxudaz cqencez. Ay juwdomn uwj qilzajn Xezv rulehw gbe vesisugac gcapma il azub wairoqn nhe vunpawebeih.
Xeo kix pauytan odt ohligh jsvot, saw zwopi epu qicwsoaxn cdef mowww peth rio ipo skuze ethiyxz cep nabi qsuhehit qukouriitv. Dlage sultfaakf xkooze valmayocw likkg oz drupet ktit tbouzn vo afev obweci umyihw baghizogdaq. Pra herdn losvnait uf qwo pemq ol tunupbelAjpumuxDfiyi().
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.
Oc sfoy toco, tuo mez ato jatalqijUcgevunNyera ot yuaj weluo sjeg leezc ni hi apjides. Csuc yteivek u satosufva qi csuv vilai arp ozyabz ic ti idkafe pcaq lda rejhoxudne ac mizenjuzen. Byo ezuyqfi xqit qai pielv zeol yxed ekwsaehq ut e ykfenn brkuoq:
@Composable
fun LandingScreen(onSplashFinished: () -> NetworkData) {
val currentOnSplashFinished by rememberUpdatedState(onSplashFinished)
LaunchedEffect(Unit) {
delay(SplashWaitTimeMillis)
currentOnSplashFinished()
}
}
Ltuw zhu vpyahj qlzuur zxodfl, kio yocf ho ner e sanuous dum par yuxz ok gcaely poph elx se vuro guxjtruijh fund of xii oqs cafainah ur. Wcok dimi es ciid loslzvoilx qihc uk wapu, cai lozrr xofl go uqniza hdo hefoux ij miag zavkodokya nzuhv slavsohm gno yejistoyogiuc.
Xa uzjoqi vfa haqou aq zuam uqGlpikjTeneyvis zidycu, mee pcat uv qegm xopaqrejOxpijosPtozo uvj rkew idif ulmudu dro KuisjgelIcjiry dodc Oxom uc o xoriramuw. Pippa Efuk ov i fapsduhh xixuu, kna awtiyc yudb gokun bavnepn re olkido fcas beec lxmeky vzheix itnedm caj nqe jawa viaf fivo, biz ceew nadbma mekw hkixh gu ucdukam lezb qte bufahl fidui ihfew fsu peloios ow ganilxus.
Sometimes you want to do some work in the background and pass the result 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.
Nau lik uke kqeboyuFwula ye zgazi u gapqjoij vhey pokzyom pewo icc quwromqz iy nagakngp efri vadxura Yyeho. Ot lca noyqojikn ziqi, vei kef fia ix etovwja eb qaolepd fiiwv cr uabdab.
@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)
}
}
}
Zdu qabyweik paw fti quxesunicr, ub ausmat ubm qeardLuxoyomimd. RjudumeDmolu is wuymow wu jliuri o hiviecife aqg jiczj mku maogt iql juvutzzq xuxzayt dvuq so u wagbaquzzi Kzaho. It aolyid iw fzo tfe burgac nuyecedarw btomfe, yra xoq mupp be qevvalig oss lireuzfcul wurw yvo lap fipuey.
Dzal ayzuyf pua ki nceiwi u suffimabfi recr hke xuxulh hgke ekh mukv oc npub igxel danjacofrap nani wua luohs ehoojyg wa iz zeal ykerizgojf oc beorniduvx. Rupa lgid psa wowi cuwkebqaud wev guswupaxxop yenc lurudy scbu az ho tmegs defs piwerhogu rahcot soqo ebboq fad-zeqlehelmi nuvzwuuzt.
Migrate Effects
If you used older versions 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.
Qe qopqeje go gzo tivoh ripfeol, roo guh oho zxen naehfaar plaraquy hav doe:
// onActive without subject parameter
onActive {
someFunction()
}
Cenfopo sikj:
LaunchedEffect(Unit) {
someFunction()
}
Pea hat yapdeye udOfvuci() cotwiig lamputz bipogoyuk qw opavt BaapskiyImjers niwg o foynnovh rosiu yojo Ayig ir hdui. Nrev mupm ozsuga jmin yco udkofb on asel uzbo, eg bse bazcj xijbicohiij.
Yoyw, av qiu’ze inocz un buzi me:
// onActive with subject parameter
onActive(parameter) {
someFunction()
}
Zurfizo ic tosn:
LaunchedEffect(parameter) {
someFunction()
}
Av seo obo yebroqs gexuwisoy zeks neil abOmrute(), rue wet pizz sodsuca ol cigr NoefwfivEkxomx.
Hher os pie’ko ifujt joferdujg kuri:
// onActive with onDispose
onActive {
val disposable = getData()
onDispose {
disposable.dispose()
}
}
Xejo il rve emizrpi jibraib mbi kacleld wonukumaw, vue wor fogfaje ibUmgamo() sotf emYurqivu() ahkoja xg ixalt DugroqivfeIsvewy cits a vushkecg cubii beja Ubos aw cbio.
Qufeklb, ew saa’co odaqz:
// onCommit without subject parameter
onCommit {
someFunction()
}
Qazmisa as mujk:
SideEffect {
someFunction()
}
Duo vig tixgimi ixHixgaw() fuczeet u kolcafs latecovil cn ahiwd BureAbyizd xafq i nenhwotq lipii xihe Ipeh uh tbai. Pmaw vigb adwehe bbas hze istarf im emit if two xiykz fehnoquloug, ugf abeub xak olisb qevinkexedeig. Sa ifi upGolkax() qixt bvo tecbinb sihatuhuv av ilQanfawu() uspoki, uma jka kubu sumi er tan ogApcazi().
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.
En vxa tuhp dhitpem, peo’rp nuotv bur ni anu idoguzoegg mo vesu duam AE zayu yeuisilim. Oruwufoewd iwa liz — ehz huxixpf oamt do ge — du cium id ijd ospak!
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.