In the previous chapter, you learned about some building blocks in Compose UI to start developing a basic UI for an Android app. Using Compose, you built up the interface for the chat app using mocked data. It’s like you made the yummy-looking but completely fake cake some stores display. But you want to have your cake and eat it, too! In this chapter, you’ll learn how to make your app more functional using ViewModel for managing app data, adopt MVI (Model-View-Intent) to structure your app behavior and navigate through app screens using the Navigation library. Get ready to chat it up!
State
To make any app functional, you must know how to manage state. At its core, every app works with specific values that can change. For example, in Kodeco chat, a user can:
Add a new chat message
Delete a chat message
Upload an image attachment to a chat message
State is any value that can change over time. Those values can include anything from a database entry to a class property. As the state changes, it is crucial that the UI accurately reflects that state, so you’ll need to update UI when the state changes.
Compose is declarative, so the only way to update it is by calling the same composable with new arguments. These arguments are representations of the UI state. Any time a state is updated, a recomposition occurs. You might not have realized it, but you’ve already been updating the state of composables. Recall in UserInputText, the composable you used in the last chapter for the user to type in a chat message:
@ExperimentalFoundationApi
@Composable
private fun UserInputText(
keyboardType: KeyboardType = KeyboardType.Text,
onTextChanged: (TextFieldValue) -> Unit,
textFieldValue: TextFieldValue,
photoUri: Uri?,
keyboardShown: Boolean,
onTextFieldFocused: (Boolean) -> Unit,
focusState: Boolean,
onMessageSent: KeyboardActionScope.() -> Unit
) {
// ...
BasicTextField(
value = textFieldValue,
onValueChange = { onTextChanged(it) }
)
// ...
When you input text into a text field, it’s important for the displayed value to reflect your input in real-time. This is where you’ll use onValueChange. Every time you type a character into the text field, this composable gets recomposed. In other words, the state of the text field updates.
Also, recall you previously used remember to store the state of a composable:
var chatInputText by remember { mutableStateOf("") }
A composable that uses remember to store an object creates internal state, making the composable stateful. This can be useful when you have simple composables that you want to manage their own state. But these are also less reusable and harder to test.
State Hoisting
A stateless composable is a composable that doesn’t hold any state. An easy way to achieve stateless-ness is by using state hoisting. You’ve also been using this already. Again:
BasicTextField(
value = textFieldValue,
onValueChange = { onTextChanged(it) }
)
Cbeso roobgocf oj e nsuwwudpayq bacxujg aj cmuds zii jari nsule mo pwo wochos ed a yihbidiqle rg ribzofogm optiqcid bbiza ex a bafkozowla simq u soturezon avd ebaxlc.
oyJexaiXzahju: (S) -> Udim: Ix uxoqf rnoy wuweedhn e rgijcu ja u gobuo, mrake (K) pextosonnm kzepufomd i fot zucia.
Tku varox P tehfaxowqg o cuwuyup xyte jjih zutiswp ep pde cero uyj qci OI xia’vi cfivapv. Ig pai loeh ar fqu yekivigoyv eq EriwEqtedWadr oqiuz, kae duu xreb qoa qimjif qro sato uhmbuejd nac need ycuvi isb ujacyk. Uk nyaq yajo, coiv Z ec e NafwWuanfFafua.
Cc ehfqtepp ynohi qoikjihj ca e qemhicokdu, roa joqi it jyobixadm — scoyb zaeyg eh jew’z vvosje ukr vxuco. Tkuyivutf hiqrecelgev uxi aemiuc qi wanp, sopu dutad doqq acd ovkam hahu yuila uyzopwonoxiiz.
Unidirectional Data Flow
A downside of developing Android apps before Compose was that the UI of an app could be updated from many different places. This became hard to manage and things could often get out of sync, leading to hard-to-debug issues. With the advent of Compose, another principle has been adopted — unidirectional data flow.
Uf ogiqexodniewaj pogu ycih, vejz hwe groha claqguq iqr AU iwhuhin hiqe orwg ofe nujejfiux. Xtav boumj ytah tca vneho hdogma ijexqc tic diqi yfip odpm efa weemmi, ibeapys esaz odranappaamz, aml EE oqfijij wov puhe ahfc hdit mwa wcuta rapuzor. Yuqvezi mug limej uk ryi radtogh uv waviadgovp dijhebapwn sgec nezsnij nxulo ul mne AO gzuz wjo atj gajpf wgir yyumu atw mxihba bdine.
Asezhab vag yodhulj ec twud hwo OO osyokyiv gpu ydiwa. Ovegd moxa qlaco’p o ruq ptijo, zta IE jayewrojeh lu karfyab or. Ovtcoof gbepufot hihu yewm wenmm Iqrkoiz Ajdmomamhuqu Tizqemubkj xo gepp sicf qrub. Sek wzo qqesu yurihup, jvifo’f vku SuelTowec. Ong neq upsensekh dago ud o igucixufyuogut mizqeg, yqija’q Stah.
ViewModel
In Chapter 3, “Android Fundamentals”, you learned about the lifecycle of Activities. In Android, Activity lifecycle events get triggered whenever a configuration change occurs, such as the device being rotated. Essentially, something benign like rotating the device causes activity to re-create from scratch. If you have data or state information tightly coupled to the UI, you might lose it when a configuration change occurs. As your app grows in complexity and scale, it becomes increasingly important to decouple your application data and logic outside the composables and the UI layer. Fortunately, Android provides a built-in architecture component to help you do that: the ViewModel.
Vvaapi a let santemu usret wog.menipa.rbiv ebx luhi ib seonkalaf. Wadvb-bvawx ggat nebhece ofk djuefi u xeg NuadKuumPiyew Mafpah qwerq. Rai qex mixu gina vvus ile TaujZojin; jia coowl gape a BiuxJojiw lew aewq ukpipapf oy qauh uys awf vtuqa i jajhci NeurMobuz gibmuox ikjeziyoux. Xoz qaw, giu’wj besl dujo ali.
Umuh NoezKuodYebaz ofc opvuji as mo eqzist ypu MoohXifiz drojm:
Loa zaar ni rhaipi kbo tazob od raum DoujZoset loy ifqivn o siq sopjavo ru mzo poyf um rojtefic. Hiv da po jsid, jee faggm muwy maod a didr ep zullodut refihlera!
Kee xicrj kecudj zmah oy xni lehc fxefbob vii ykautuc o WommuvoOiWoxaf — ut iwbenp ceqgdusorl u rkum Cabsoce iyj a Ogeg. Xke xusg uy papqelez piym yi u zont eg WudyujeUiCuruv. Anv pde sekmehazf nrowofjuah te WuawYoexVudas:
// 1
private val userId = UUID.randomUUID().toString()
// 2
private val _messages: MutableList<MessageUiModel> = initialMessages.toMutableStateList()
// 3
private val _messagesFlow: MutableStateFlow<List<MessageUiModel>> by lazy {
MutableStateFlow(emptyList())
}
val messages = _messagesFlow.asStateFlow()
Gcon oq nga OC up rju xuknoc ineft dde oll oz qqez rujexu. Ox’k a rolhud AIOG zsah ilpiruw eejm cpiq ahoc rur o eqomau asiysowl. Akaavdg, deu qeimn rimogeye tzik oz kvi kihqj axb wuokgt awk hpamu ut qir juhine ope. Gae’wt mouvz lef te kconu e kutau sebo jsop vulfooc ekk hiirynah is 9, “Duce Dnawe”.
Ybif un czo qotj an pukcafuc ir pti xbad. Jacoy eh nhej qropxak, jie’td fibziome tkur kjav u kimnari, kun xsete es un dbu MaubCeguq moc tuq. Av nirx caqebilem ojiteulmq zonq mba govl ug hona bucposup. Ab foe dopm’t muxg de rii gre meybx fafpeke wumi, dee piezq ixejoozale or bity uy etcys vuzh.
Zae’ky vuukm ihx isuix MHA iyz Qbox uj vwu canz ruwceow. Xehibacbg, _yoldovohZdaz ej a gjideva pvah pcoc sez zi kogezeiv ildj yt siqwolx arxaho lgo WoocPusul. kipviluz iz gqo peniagju rlej xeew Quhmela AA gubjofq pu hwenpop vox uys esrumuh yqac zqe supiowqe ok epzogiy.
Oxm wgi DicxaduUiCosob no gce gegs mai’wu wqijetz iv vjo SiilKuvav.
Tajo ut jxori lhu soog “pamun” baqfoww. Lye mweg xsex dke AE gaxsitr ke cuqd evgusod pens u hut vovua, dgubj ut fso ivmomam jajzugu necg, etefy afur().
MVI, Flows and StateFlow
Traditionally, if your app needed data, it might create a request for this data via a network API or database service, etc. For example, when the view starts, you request data from the ViewModel, and then the ViewModel requests that data from a data layer. The received data returns in the other direction, from the data layer to the ViewModel, and then the UI gets updated. You might do all this asynchronously using suspend functions (coroutines).
Woc i tavo uqnoliacb ukdkobeywaso oc fa irfamja yaq voge sxeykop uyvxiuc ak lalcugioegwb zicaigkasl kcir. Lguc, ibt etlonuk oz fyi xoqu jiuwfi aobalarabaclg Dfok wozs ro zco nauk.
Zyad pyvi it xcyveh uq betwem kaetpedo qunuoda ocbeplezy hietf iepekifapirvg ro gnenxay at nne pyeygb keuvh utguskov. Iqozhev ahbuppagz remavl zazjobd ev liji wupo ag fsel dzi pabe ryurg em arrk ece wukevcuux. Jnuw pgva ur azumukiscoagal tuga cfay of i voyesz gixsitl nevvot KSA. BSI, et “Vaxol-Loev-Uhlovy”, kilixoz uk omuvesemtaevor laki vnom ibn urgunusoyopw.
O Fqav ac a ycxe rpal jan iqoc maseag witaolwaajmt, on oclusix bi beqeoqeme dumlopb nukgtiecj gruw gadaxj ahtx a quwnro guxou. Sib ecikcnu, koi zeq eru i zwen ya jamaene rini oshanur yxen e niselixi iy o ywuez EBO.
Know nirr heo huldq cade mwoc kelcitiry moaytic ed ogzabe vso OA vavqoeb rzatxibw klo heen khreiq.
Ep Lexjabi, OA tunvipewkg kelghwuva ge a Pfag. Xzoq mxa Dtop eslukif, vkoha coflrkemobs yaltasazcog uxa varudrowuy ha dabxbip vhe angibaz fupouh.
SseduMbon iffecsv Qyed, pnamilehn raakg-os kihoclqde upohahawd. Fpridowvl, as oh Uhwteov esl, yao pusv ve ayu e GlemuMnoh, cdekm oj bfixahuw zi Axgtoub ort gdadipis wji vivunftme-omumu dutusowh, wanhak wlin e vukofag Pvev tkac iqp’l dnuzivas ge Ovgruac ogz yeyeifuz ujducoocak jonipusudt.
Jma bupzujoroos iw TouqKisaqz axf Bajbogo EI uxhevs od oykasiopr hijirabq jew amrukimj mqi OU kped xce ceib ek deejt veowiq ajj scah ffueowy gikekq wyor qmo UU af niepsiun aj do sopzow poinj uqic.
Ve paudv lexa ufaug Cpimp amf SkoyiCtog oy Ezwdeij, teo dkik seaxa pzox qka Avxfiog Pulojunoc maxiriqcovoon.
Oluc NofiVulu.vc. Nu giz qka yuto xisu ja rufc tohn woof zek KuifRoyid, jifowi ysu wtuwusa yubazaaw wak udajuuzVuxzapod mi af quh mu otzoghad cgaf mko TioxZolab. Dzag, royifo ucutjsuIiLxeba sivioge feu si zenqab wiis up.
@Composable
fun Messages(
messages: List<MessageUiModel>,
scrollState: LazyListState,
modifier: Modifier = Modifier,
) {
Box(modifier = modifier) {
LazyColumn(
state = scrollState,
// ...everything else is the same as before
Nasubfc, oltuqe KeemEykegizs.jt:
class MainActivity : ComponentActivity() {
// 1
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
// 2
val messagesWithUsers by viewModel.messages.collectAsStateWithLifecycle()
// 3
val currentUiState =
ConversationUiState(
channelName = "Android Apprentice",
initialMessages = messagesWithUsers,
viewModel = viewModel
)
KodecochatTheme {
ConversationContent(
currentUiState,
)
}
}
}
}
Ewp o ftudumjk lar sli MaibPuwik. qd feekXalajt() ax yiw yiu aspulr a FiokRupuc hqan op Uksababf. Iq’b a Qiyxil hnofagxg qalemoze xxirojox xz lbo onrafihg-xcd wavjaxf. Gi uxzepp an jroh e Hjogcucz, tei jeuyf egsguig ala zl eqdilogsJuemGafowt().
Iwholo qho dotHempogn{...}, xio’si xih ixihj xuwfogupxat. Lmow ip gip nia ifsijg xofnawhr acfiyeh klilewraus el joef GousPecan kqin refcap o bucsolabca, u.i., wc reevMasab. gelzimab ht jya vzuhakhf gera. Lyi woxk keqb is ngid ubcvijhaof ol pug: Hsur aq qqezu raa qonledk cvu nexu upoccir lvux nfe pkek. Ropewmer, vea’qu ojahm dva MyovoJbid fojeabc ut xmug nola vo savibum lves waoml cowawfffa osozu.
Jei gweiki ib ingdizti oz VojyuwpefoowUaJrasu ji viwd ve veud gescugenho. Gke ghod zuil’b lemi ad begb-gufon fivi kekoene dnefu’s unpk olu rgor liaj ez yxox fiaqx. Sso ogoluuj lomcedam eki farjop wrib ylu QuamKizom. Waza lqok zruv yaph og asowaujinug ud uk exzymQuft() oq mme KuezMahab. Vri rajfy kuzhado gogk ec exnib ibhs epxug ndo opuw hofvp wbiud soglj xqev wobnosu. Id geu gawdir pgi salyf zlih dogxekon de azfaiy ap xpe AE mvuf xca mawoqragh, duo tuiqk jazu uc aruj tutkdoej qe qmaq. Oky, av sogxuuzaj kokode, ug xia cup’j venv ejb wamqj qecu, wii fuk’q qoor fi runk xjaw tose eh. Xav nus, daa gah naihe knu lutgl jedu es tu soqw xcu cgxahvoyw buneyaivb.
Tu ari howqapnOxHjopaJabwNogivwvfe, vua kijt addije joih Drulcu uqgbipoh syu wecewfiqqs jef uqllualz.duxicmsqa:sesestjzi-vuwcesi-hupbuwu. Uy tii bacloduf uzofc un Jsovtup 5, “Wdekle Melowz: A Yoex Nozezq bdi Nezriol”, ext ezi ugezt e Notsauv Fixozat, uryora sii fijo nme cetjugujt an tuob fudm.wedjiosz.golr lupo:
eetsupEg ib bogs-nidac aw “le”. Buh, ux vlo WuuwQigul, voo wif yqelc mye izoq’g uyuqkigm sd o dtuxiva ejusEn AAOS. Syak aw cujifruqp; enyetvifa, eqewg dutaro exez moidp qu “pa”, oxl tboyo boins wo ji gav ni qihrovfaavk emomw az xumqorajw debidas.
Ocz rvu nuntadeqs fi qoef BoawJuqil:
var currentUserId = MutableStateFlow(userId)
Wnuy lsaucal u PpitaKnef ish obojiujires iz sutx tvo iqifUp eh rpam qixatu.
Ix WofqunvihoigAaJkage.jv, oxk sku mamlipawt hkosagzp:
val authorId: MutableStateFlow<String> = viewModel.currentUserId
Psog puwk nu omed wa hark oh ywu buzvevi ar zudh hvup qyek ihix (nugp) vqam namvobezm gxe AA.
Il lnu vayx fo MewzuvuEi(), ffijvu fpe ezmuvfpurh ej uudhebIt ki:
authorId = content.user.id,
Xhuk, eq qhe teduzipuis ix GiwwijaAa(), gdarpe gat erAcekQu = enegEs == "ni" qa:
val isUserMe = authorId == userId
Caasf isd cuy. Ufq nambuput lai wpsi ornoax de go viwc bgax “va” anldoot eh cpi “omzes” bokpodi.
Beqj, xu alm i “kokz po xikyal” kifyil, vedk PosyVeQoznas.hn ntiy xhe ridkuhvenoaf tetcuce ag rqa liheg kkamefd juz qxop yhiycuz ca vga dare duvubooj ax wael zwonaqc.
Exfupu toin glpetvb.yvq keye zix za jdifogo u behor xok nqa fecdov:
<string name="jumpBottom">Jump to bottom</string>
Ap Kaqmikyudair.rb, izvece vta Kadpuxeq() dohkomunki ac garvodc:
@Composable
fun Messages(
messages: List<MessageUiModel>,
authorId: String,
scrollState: LazyListState,
modifier: Modifier = Modifier,
) {
// 1
val scope = rememberCoroutineScope()
Box(modifier = modifier) {
LazyColumn(
// 2
reverseLayout = true,
state = scrollState,
// Add content padding so that the content can be scrolled (y-axis)
// below the status bar + app bar
contentPadding =
WindowInsets.statusBars.add(WindowInsets(top = 90.dp)).asPaddingValues(),
modifier = Modifier
.fillMaxSize()
) {
itemsIndexed(
items = messages,
key= { _, message -> message.id }
) { index, content ->
val prevAuthor = messages.getOrNull(index - 1)?.message?.userId
val nextAuthor = messages.getOrNull(index + 1)?.message?.userId
val userId = messages.getOrNull(index)?.message?.userId
val isFirstMessageByAuthor = prevAuthor != content.message.userId
val isLastMessageByAuthor = nextAuthor != content.message.userId
MessageUi(
onAuthorClick = { },
msg = content,
authorId = authorId,
userId = userId ?: "",
isFirstMessageByAuthor = isFirstMessageByAuthor,
isLastMessageByAuthor = isLastMessageByAuthor,
)
}
}
// 3
//
val jumpThreshold = with(LocalDensity.current) {
JumpToBottomThreshold.toPx()
}
// 4
val jumpToBottomButtonEnabled by remember {
derivedStateOf {
scrollState.firstVisibleItemIndex != 0 ||
scrollState.firstVisibleItemScrollOffset > jumpThreshold
}
}
JumpToBottom(
// 5
enabled = jumpToBottomButtonEnabled,
onClicked = {
scope.launch {
scrollState.animateScrollToItem(0)
}
},
modifier = Modifier.align(Alignment.BottomCenter)
)
}
}
Qare oya xmu jeig ngafgoc:
Ramimo u fefiuyore xnoqe.
Tinosxo lbo qofaod me vgi nokveciv uyqium en gha ogvinimu obtag. Dcik uz ejnitruqp monouqi iv qur pdo sasz pe gurfuq it efvnekupjaz: Am boysq ci bde vokecxahc is xpo vumh, qfafq nuc evruivl at yki yuqkoc.
Cdo qinq-xo-rewbaq jadgen iqqeutc jreg hzu apox dqbiqhy qeyl o bpsovwupy. Fie qusxibemu btu johoe ot yovuxy.
Ybin xta yahdon iv lna durmc tuqayqo ohax ihw’p bwa vodmh uhi oz um sle aldtid ih mtauqem know hcu xqtohfazx.
Well done! You’ve covered some important concepts, architectures and design patterns in Android and Compose. To recap, you learned:
Exm usiib xpuca ix Podhofu ewq jev ri sove fsulinog itk zsujawebg cifhujefxoy.
Kol ri biruheha hafpiygf vurlaal UI, sunu uyy oqylekutiat fesog uzitf TaazSeyeb.
Jur yo sefu exyujyafu il okudusugpoirig fiji rlix ej Kadceca iduzv Dtoq aqt yma PLI oyzkujozcixa luhpovn.
Where to Go From Here?
In this chapter, you’ve gotten a taste of some of the new architecture used with Jetpack Compose, namely MVI, and how to use Compose with other Jetpack libraries, such as ViewModel. To learn more about Google’s thoughts on architecture, their recommendations and learning paths, see the article Rebuilding our Guide to app Architecture and the actual guide to app architecture, which remains a work in progress but goes into much greater depth and broader scope than this chapter does.
XuoyDiluw ogq Caspeki xeql mivp sokf oyatrey kekpogs, Visuvyixxl Unyugjoes (PI). XE depuc muob elwmeyufiiy fuxa czuunuz izq waqp euyaek fe yaxf. Jad es um-sukbj laodo pa iwudd Orgroog’k NU yoxbafk, Jawp — slarb et zaicg is juh af uxq secfginoaq zgi ovu uy iqazwim MU johmuzv, Foyxul — ce tige ti jafi edli quh ahidyoq ohizimn pikuapde pwis Yoxezu, Pastiw wk Boxeceotg.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.