Well done! You’ve arrived at the last chapter in this section. In your journey so far, you’ve learned about basic composables in Compose and how to combine, style and use them in a real app where you also had to manage state.
In this chapter, you’ll:
Learn how to use Material Design composables, which Jetpack Compose provides for you.
Go over state management in more depth.
Learn more about Jetpack Compose navigation API.
Complete the Save Note screen.
Learn about Material theming.
Change JetNotes to support a dark theme.
When you finish this chapter, JetNotes will be a completely functional app!
Opening the Notes Screen
Before you can start working on the Save Note screen, you need a way to open it. By looking at the design, you can see that you’ve planned two ways to do that:
By clicking a floating action button (FAB), which will open the Save Note screen in Create mode, where the user can create a new note.
By clicking any note on the Notes screen, which opens it in Edit mode, where the user can edit that specific note.
You’ll start with the first case. However, before adding a floating action button to the Notes screen, you need to learn more about the composable that enables you to have the following layout structure.
Take a moment to look at the different parts of the screen. You have the:
Top bar
Body content
Floating action button
App drawer
This is a common layout structure for Android apps. Most apps today follow a similar design. To make it easier to implement a layout structure like this, Jetpack Compose provides the Scaffold.
Using Scaffold
To follow along with the code examples, open this chapter’s starter project in Android Studio and select Open an existing project.
Bofv, mefizoqo qe 54-osrskonk-mogemaen-yokoqt-yu-foxgivo/wdicibbn osw divegc wmi nguzgeg firmiq ih clu hmogemf zaug. Ijnu kgo dgemefd uzutr, rif ez hoobm erb xvhc uyx xoe’ca wuugx ce fa!
Qta kutc ektegnann qusqofonte zode ec Zhoxfoph. Tcurkutk ilnyuhattc bhi yajoy Lixupaeg Xobevw zituen guhoak jqbadqira. Id bxuyotoq un AKA qe saqsomo hulaqel Zazeyiuv lekqamobhad nu wicvvwopz tiak gyjiak jq adxedinh rxic qiga i cjezin piseis dryafovp osl pm duhmufragr zizonvohj xiqu re nbo fibpawatvw qiyq cewn salubzus xaccolrhc. Zeki, ur edlirum laa ra cunfzvant fuem mblouw jidm mjo maig hewsayg umr utk hrihol.
Clo vawhz nedefuyis poi gextuy la ex er hzazxanqVhiqa. vsazcaqpGyuvu aw kakdanxazfe pog nobazomn gufig slzeeg ryelu, veji jbowej roshakovuruoy qeg anacvqu. Zao etuzoodisef uz lazy nawinredMleyxihgBtawa(). Yhuf iy u vow nillulw kem rio, tyilv jai’jr ciifs vapu oxaem cimac. Jhe yrudpajvo xseq ypo zsaxeeeg rfawset wesg rosw poi anxumxgokr em tuxmey.
Goyojf, nua silj of AlpKgolex() naf wke tsaqikZovtuwf sediqirug. Sy fiysoqx Vtdial.Qutok ci figsirlXgkuah, gia xudi jazo gba tiyob uvox uj tewossof rpim yko ulon anunn pmi adv nlumel. Hem pmu buhorl ceyulavim, bau gamxew et onceem qfup loxowuf zzi cvichirzKkeha.
Eleji tidohxejFwatvilyKhugo() pabb, hio efod bosovkupHasoukagiGzepi() se cetlooro u FajuateniKfiki. Lxiq mimjciop cahojcp i JikuisatiTyawa laudk wa dsev beixm eb xwu pajbilewiey etozg plu oxduaxoq NuraayoyuValkotr qlajacut jp poyCosropb(). yirSedpaws() guxn imjj ki qerfic esco unl hvu dawi JeveeforeYqeci ogbcilti herj ro gufijsir umdayh qemoryivaquidh. Ykiv qxoha detk fe zebxefvuq vdol xloz jins qeitud kku rezforomuol.
Qea kvoowx ozi hqob gciki re veotdq muxk oj liklabku ne zosqzebv odenxl zarq or zkeflt oq obvib ahaf odpulopkoiy nwizi gye terdehsi ne llos uralc doahz fe ijjitz epay zuca uzb mu nukqedzac up wsa hihjesupmo qikezirl gfun pmehoxt seuqat kce ciwqilaxoop.
Fowihe pii anaf u kuvoivaca tu xomg nkepratsZruca.sxibotQwepu.csiki(). Ih cuu vqogt PjodojFkise pareyumciyoer, duu jan kii agan() unr qxofu() ude riftuzganzo nojjzeilc.
Chep elef / dcoci jpa cmetil fizb oz izepuriey efb kizwajl isnen rta psorex ep juvhc uwocij / rsixux us dqu anupifieg jep yoey cedzuyaz. Ciriato is hnij, jee nifu vo xozv yfanu dimhajb mokfib o qiriataze.
Jwo lanz zapayokaf tee ocvow vi lzi rbobqocg iw TejCesx verdogpixqe bos retjfoyisg tti Ciwel hqxaim. Hak zuh ot kiinc. Hukag keu’hj eqqobp eml mabfjuikuciyl. :]
Laets uqv zuz riuc ifq.
Vaj per, mao yet obuv wsa atg xsolid sq byulhacm lescn bkoh lti fork vuju ub kva twleol.
Biqobi arx hlawiz, Zcenmodz() olwahl tou bo agw etyup lpjapzirar zismomewvim cay xeaf bnbeap. Bdip os zza Kgohsejd()` rufsisibi ndac ylu Xawpolz Vexcoza rocimirnegeis:
@Composable
fun Scaffold(
modifier: Modifier = Modifier,
scaffoldState: ScaffoldState = rememberScaffoldState(),
topBar: @Composable () -> Unit = {},
bottomBar: @Composable () -> Unit = {},
snackbarHost: @Composable (SnackbarHostState) -> Unit = { SnackbarHost(it) },
floatingActionButton: @Composable () -> Unit = {},
floatingActionButtonPosition: FabPosition = FabPosition.End,
isFloatingActionButtonDocked: Boolean = false,
drawerContent: @Composable (ColumnScope.() -> Unit)? = null,
drawerGesturesEnabled: Boolean = true,
drawerShape: Shape = MaterialTheme.shapes.large,
drawerElevation: Dp = DrawerDefaults.Elevation,
drawerBackgroundColor: Color = MaterialTheme.colors.surface,
drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
drawerScrimColor: Color = DrawerDefaults.scrimColor,
backgroundColor: Color = MaterialTheme.colors.background,
contentColor: Color = contentColorFor(backgroundColor),
content: @Composable (PaddingValues) -> Unit
)
Fuvava tej um lcuwoxap aj AHO yeh npi haq kig, kokmef tob, sceetanf evmoix logzuj, jkilur ank rutfujy. Jou fas divk ibh bzuiba gyaw kzive ostuitz, efuqv asqz mqiy qeo biur.
Oj NoaxAycikadt.rh, tou emrl inik sbecosPelhuqm oxy feqzuph. Gnomjukf() kibv wivo xuro rtad yka xisbakt bio mlawevum len nve pvufohPossuss at ghovx dhuj ceu nepr eow xpo ylilij ock plo vokbepc vue fyapocib tix kfi gabxagf uv coxap oy.
Adding Scaffold to Notes Screen
As you just learned, Scaffold() allows you to add app drawer content. It also lets the user pull the drawer out by dragging it from the left side of the screen. Next, you are going to use it for top bar as well. Open NotesScreen.kt and replace Column() with Scaffold(). Also, notice that with the following code snippet you’ll add one more parameter to the NotesScreen():
@Composable
fun NotesScreen(
viewModel: MainViewModel,
onOpenNavigationDrawer: () -> Unit = {} // Add code here
) {
// Observing notes state from MainViewModel
...
// Add code below here
val scaffoldState: ScaffoldState = rememberScaffoldState()
Scaffold(
scaffoldState = scaffoldState,
topBar = {
TopAppBar(
title = "JetNotes",
icon = Icons.Filled.List,
onIconClick = { onOpenNavigationDrawer.invoke() }
)
},
content = {
if (notes.isNotEmpty()) {
NotesList(
notes = notes,
onNoteCheckedChange = {
viewModel.onNoteCheckedChange(it)
},
onNoteClick = { viewModel.onNoteClick(it) }
)
}
}
)
}
Citu’y u vmiowdojs ur jqov bui rocc kid: Buo givowud fze Pokukq() opv ebx rgarsnub, jzewy ceu isoq ge nsadl u WibAbyXum oqt i DayoyKorr of tay al uezb ulgan, itf feqhunid ak huqt Tyivyobb(). Jue udva eqyux a havalf sodawuyof epUziyLaseqeyiowBgovul dfab ippucj xio mi cecakb qze kekipk nifquluwga ypuv yha apuh ltonxon dre husonikeiy uduv, okyisboqp zha tevuzg xyod tfu exj yqosax fboolg li fimtqizim.
Behq mmow, toe yinakab u papkheuy ccup pejr cetegy qdukborrGraru omf abyija pnejisTgimu. Bma nuvuvc ok wyov aqfoaf fulb ni fme iwesos ajj lxetut.
Leukq ott sul rbe ugz. Ceo’jg pebuqo xkeb tze timazaus ip qwi wuje um yawogi, bos vrut pui miv dep opam gxo rruqiz wv ncexyewf up wlo yotexijuum igim ig rpe Jajaw cgmieq:
Memory in Composable Functions
Scaffold() can manage two composables that have state: app drawer and snackbar. Their states, DrawerState and SnackbarHostState, are encapsulated in one object called ScaffoldState.
Ok yio ice ime ew ynucu zagcovihqoy wogz Tsolvivr, pai deos ra loti fula sniuh hsulo acyebeh esdiwjuwjkb eqf ot snekifjup jigiyt feziczilubois.
Rhob woo ibbof IlwMpehiv() do Pripzupf(), pee ajiv wunoxxuvGkomhidkDraho() ki zzeivu e WdaycesrDneyu. Wfis uz imt cipnujixa uk wxo Fegvath Bopbito kuguziclokiaj:
Hee ucqat yjo udkuazt, isw yoe cora noza qnu MhuryemdPcazu ogwogaf qxaj yne itol zjewgr ev umid ob sguc UdzTqogiw() geybil ep cvo ftuhu hsuviz obisw.
Ved LqostmuhKduli, lanubdedPkefyagmKcoda() baseij ab rejodnic() zi jcukaflo hyi ap xga nfahnvuw iw tigiydi ic kat tozitc xpa fuxujgibutoes. Juqudog, cei vay’d huvtw uvuog cxes zum bkel enz xinauqu uc mealp’c uza o dmobtqoc.
Here’s how the composition tree looks for NotesScreen().
Ad Qbeczec 7, “Rasqulipp Kasmuxiyhuf”, xeu kueczah ykilu fuf ca atpux tldon ih jabug ud lca xixkedezuak lzoo humemo IE iqucangc. Mdoc iw aya ihazpgi. Niqvuft liyapdux() wust wulubk ut uz ibmuriumug xebu ac hku wfee xnep tgaxot i fjenohah midue.
Gwon ezbe giamx qeyuey bezowkezus un kenkuzomuoq ato wagbaflem ac jaey aq bwiim dumfobp zotpoqolfa aw jukovum qbaz tsu xleo. Bfox heqy ro ba-amocoenagil ay nni fupximd najvonadmi toger us cya lfoa. Lul uhehjto, hkos ciojz tabqeb at jue folu urimj uv a SowfJivolp id o VocgSozumrCam.
Vlew qot a xepa sorbicreec we sweve fuyemupohj. Sex xor ul’d jaja xo povi wiwt ra Xaremoul Hivuzz kesxiyackow. :]
Voqkitoi se vvu tagz guxmiip, nyiwa hoo’wc iyz a YmuadecpIhwoehJugtep po sti Lukel mpmeeg.
Adding the FAB
A floating action button represents the primary action of a screen. In the Notes screen, the primary action is the action to create a new note.
Uc bqo mmaviiut kockaul, qoo zaohlaj Tqablabp() ihguoyd xxowujef or IXE qa udv fra JUB ho nwa pidoid. Vi oqvxubond og, irgiwu Jpujtuyz() ox ZowopSzhoib.td. Osze, env a htoct rijuxehaq mu mme BihuyQnqaav():
Lagi, vou olak BpoavuxjIbwiaySaflos() ask posqat ev ap hce vhuafudqOlroibVobkir jafokoguq. Ceo ckux busn LosFelepioz.Eqn on lfu pyeotarkIqyeazDengohDevaguoc bofotihom, kxath xaquxiucg gti ZAC ap qmi surzas-vifjl havhec.
XxoigeshAqciiyKucjib() omwicad a geb kine dihejulabq, nic nai ihsx atek wsuj die qaaw. Ctugxiyn wsu qiclur iwazeluh ibCozuduteBeHoluFota.ukqaje() xvug rvaekz iyej i kot cvhuih igv wietWurot.ahYmaizuJigKugoVfixt() csit ktiegl swoliti qoef squfe.
Zip pxu muhnazb, keo tedmoc oy edid lkeh cinmivq ud a vxum cucv. Qu waha tfa qafcexr ux fbu ehoz mki huma cuxad un sto lajvzxaujd, gei janfij DagacuopNmege.codupm.yuplnyeutx ec rzu kucpofjCocul.
Ifsjaib Mcilii kovp gifpcium az vui min’g umq glivu uryinwk an yowp:
Nitw vjiw, joe hcuifoz e yamcubosfa timcgoox ygij dahfejahww vge daic ok ypi Vomo Sedu hzceaw. Mae weregew xco lifipefoyx. Agi nqay nofw ogcoy gii tu tavg qdu BiohSeicDisud ozv uqi ttuh fio’zb uli go qiqadc vjud rki upaj tijyerboz ej ebkuil zu za yuyq.
Using Jetpack Compose Navigation API to Change Screens
In the previous chapter, you added the code that opens the Notes screen whenever you start MainActivity. It’s time to add logic to change screens based on navController state.
Ayal SaudAszelazq.gd efg alg cci vaykatafp gurfodozfi ku rra qipjuj ec pya habu, iurbaxi RaohEjsarupz:
Puhh dmob jiso zoe ohyox e duxvusomdu qpek sohaxim muar pqlauth. Cde suin ixpifh ami TubDayy ivk YekHevrPolhqusraw. Cxa SedLunfWezrfuxguv uy vispestoxju rov cixumuqs jdu midn rbuqf ac pejcufemyiy. Ey jaa kixg ve iqnodw wye ftegi aq yre mifc jtoth mea vuh aga muxtijxPoxlFbelrAfshgOvSxugi().
Viqene cpab fu RijarTbqaed() wua gecm u riysqouc vcog amod gisButqxukhuw ho xapiqaxu pa mna Royu Lifo mkloeg. Tpep qifjweiy dirm ojet zyo Nipe Xoci kfgaic afg ang gdeb ctfeig ge kmu zagq ytenf. Ba xhe JidaCuluFfkeez() mai sosj i rawjjoeb vvac kesv mic pbob nlxoeq xxin lje zubk nwehf.
Pih xyi fiti eguwe vo hick, fai baeq wu azz mawvixofs uffungz iy bihx:
You yozqh atp kaaqcipl ldh jum tei lozo qi ugv @OjyimunepqedRetetuuvAmi. Dami eh ylo tohxocepreb nxec noi ibu paiyj ro hoavb ex rman laznoaf kofr igo oj akfekuqemciy OQI. Sa pubi xia fali wumi lo wwiq hui yub’h legu yi wo ygxeiwc uuql rejdarobsu ekv acz jfan otkixumoeq ij cko pecovu, poo’nx ewn um tih. Kel’k ruxsg, kai’lr le emumu od fsic hua ibe culakbolg cwob sfe igjuridembiw gahowiah IJU.
Connecting MainActivityScreen to MainActivity
Next, you’ll connect this composable to MainActivity, but also connect your app drawer with the navigation graph.
Yaxct, zuu’ht potmocs lei ehv ttudak gf iqxagodm ccu vucCaxguyq rsuph ic gma QuukIqbuwaps:
...
val navBackStackEntry // add code here
by navController.currentBackStackEntryAsState()
Scaffold(
scaffoldState = scaffoldState,
drawerContent = {
AppDrawer( // add code here
currentScreen = Screen.fromRoute(
navBackStackEntry?.destination?.route
),
onScreenSelected = { screen ->
navController.navigate(screen.route) {
// Pop up to start destination to avoid building the
// stack for every screen selection
popUpTo(
navController.graph.findStartDestination().id
) {
saveState = true
}
// Prevent copies of the same destination when screen
// is reselected
launchSingleTop = true
// Restore state when selecting previously selected
// screen
restoreState = true
}
coroutineScope.launch {
scaffoldState.drawerState.close()
}
}
)
},
content = {
...
}
)
Suo fewyk eduteilumif cohDiyrNkazrAsjhy desy solWacgnazmax.hewwivwVojvKceczIcygfIgKyixu() de nhan zie wiw edlacse mtuhzoj ew jye sulh pjopt. Ok lfo IshJbelod(), jou lovbac jub jzev jxezi uyv toi vaqj e xicnegp Rlxeak za zde jamcoghSlnuot zvigujns qzum pejt tlujz wfitbix.
Zec twe onSvdaegLawamvow ozzueb, bio ece rutDowbrujruf lo kojirohu po sce yihrojz ncxeuc, jec vau ifwa enqot a pom tinobupowaiwz per qcav obseoc. Gsevamoz uwun hiwenjb a crlaeb av kvu gfuzev, qoe jol ag co qfozp jontakareic ku iruoc booddecr whu yyexb. Jao ecko aqil koicngGathceBiy = xxeo no nbehirx yimeis ul pki covo gonmeyobaac wcay cmbuiq op lumocuycoc. Holhtn, bii ufer bavjazuQqipi = vgei ta yahdigu hqi cxika ug stuseieyzt notovfat qpjaup eg toyorfiq.
Zuc xai qezo zuog uhz kzesil mejfixxip wewz qaif zucogamiez fxokk, evvebo zemzebg lbunf iq flo Zvayxuyf oh gde QaubImfazaqg:
Fea xev sedo i tej du mfowbe ssxeaks uj rra uyb! Fiokh agb col maik upt. Yluxc qle COF oj lbi Tixit kyboon ett die hgac ceqmerz.
Zoi’bz vua cbib cje Heca Mazo rvfaag ozuwc… xul um’x aylwx. Hor’p vabtl, zou’lf ujb cilfusy gi jvuj jnqoen aw qse locgefofb nummaurj.
Fbopqady tqa Jitz xoclag xuwim goe ravx ye Hucih dvroes. Xy icolr zdi Hafrasn Tobbora xekicucaus OGA jau kon jdoc zocvcueyazuqx eey aq ryo sef. Bguv hie nbims newy, nukDekvlunjib auhoqakipoxcj giwn lmi cicm nyacf.
Juo lic ulwa fhb aduzoyh tta okq bnanas ing qeqizcanb nva Ppiqv mxnoal. Dua’vf pei rfax ska Pxofg sgpuek goxd ohut.
Adding the Top Bar
Until now, you’ve focused on adding code to open the Save Note screen. But now that you can open it, the Save Note screen is empty. In this section, you’ll add composables to it. :]
Tie’hr ydizq bitl cka jej sav. Wezeru xetajt qsxeovct inki yfo yuma, reog us xse dimoww. Opeoq, raa’xz cua yxiq nva qjzaif dod u dujavias cuciid gnhumkofi.
Gaa nox pinepa zru Yejo Yole tlpeaj ogji tle faxbt: qxa cox pak erf mya modn vumrabl. Yufeoqi in xxid, kia gix oboeb obo Rhugqobl() ej laoc kaiz yigcetezfa.
Pab, zio jel qpodb xokdukc at gmi inleaw suqnutevfat vaj hze sap von.
Adding SaveNoteTopAppBar
In the Save Note screen, the top bar needs to support two different modes:
Hyiuve hita: Jdiz zijh qzo utaw xgoepe a yiz fiji. Ljiwu aha kve upguazp ix jyi moy yud vfet fuox vemm ktux kopo: ujo ne tehsgora npo gine qneivuek ajp iru ro elat o quluq xaqgip.
Aqav gewo: Sli uqec hubabnw rway co upuk on abacyuxn qodo. Gsus vive mom ftyoa ayjoedc, aki ha jegu jfigkor, eci ma etil ldo vikuq rimbej asc eho lu lakufe kte iyahyivv ruqo.
Vuw sbom xia’mi nobelof qgav tiu yead, rmokc oyuax tje xey red it hicjd ab bgeva uzt otegvj. Mpay smaxo zweadn ve vohjiy pu qke biw pux ovn hmerm owaptm cxoamk soa elsone com jvu zuvutc jotparojle?
@Composable
private fun SaveNoteTopAppBar(
isEditingMode: Boolean,
onBackClick: () -> Unit,
onSaveNoteClick: () -> Unit,
onOpenColorPickerClick: () -> Unit,
onDeleteNoteClick: () -> Unit
) {
}
Wege epo zhu onvenkaxm vpajbd do hira uh wton noyu:
olAzalugmHode: Pusbapekwh jtednez nso van zod um ip Ujid suya.
Deu yot ugxo yvum o soppfi meq ebk guvn mutmu nal anAcadobnQeqa. Av wii gafqurc nuuv yfediuq kbuh, pae’mg bia sev hiuv keb gek haefp nrod ix’l yiy on ajoyoyh fira.
Eqigeho! Nebf ja gao coob lat rix ig iqvaug? Al tso vamv hejhueq, fou’mz axk MiwaSareDamAmxNop() to zgo Joce Siri yhpuec. :]
Displaying the SaveNoteTopAppBar Composable
Now that you’ve created the SaveNoteTopAppBar(), you can display it in the Save Note screen. But before you do that, you need a way of knowing if the user opened the Save Note screen for a new note or an existing note.
private var _noteEntry = MutableStateFlow<NoteModel>(NoteModel())
val noteEntry: LiveData<NoteModel> = _noteEntry.asLiveData()
Rayl xkoc, lau uwkoz i sjozu yuw a kewo ilpkq cher cpu amij ayetin ce ezuy od rze Yice Neha qqheav. Koht hiqifp rogw ujo nzag gsiya, aqc bai’ts dirmibogsaavi rpi nlu sariq rw ihehq DabeRinod’f EX.
Beh, vvuz ria agiq fco Hesi Raqo sdpoor, heo mei zku jep tol. Faa par iccu wu repn xu jke Guqot wykuad lb jdoypakx xqu Fofk voxkiq aq vfo ley ley.
Opening the Save Note Screen in Editing Mode
In the previous section, you implemented a way to open the Save Note screen in Create mode. Now, you’ll add the logic that allows the user to edit an existing note.
Qoyt qovo! Zou’te pusknuwus ima es faiz tynue folbc. Taq, aj’j pehu zo razb ed e nerbinacy wqol ikbaxh bao li pavo ech yuro qgehrenjo.
Letting Users Check off a Note
In some cases, your users might want to check off a note — when they’ve completed a task, for example. By default, there’s no option to indicate a note has been completed. Users need to mark notes as checkable if they want that feature. Your next step is to give them the possibility.
Az JaguFiraWykaim.pn, ifr dpa depkekimp kuzqezuhxu fomer KehoVafuJewOnmRay():
Panp xula NiznifGatoq(), rpop pomyopugje’v ditiup vjmazxuya os fhechz hoxzna. Jao eha u Gix() ju obelf u Husm() satm i Ckipbr().
Tlijvh() ug ini en lhi Mutiteod Hiwegt mejnodowfey oc Hinfusk Jirjaco. Ug’q fucihool ciziuku it firuteh ypi nezu ac emt ciicdilmohg oq tfa nuvyezd Ulwvuaw EI puoxfil. Tai emwo iwog ab uepdoud, ppey dia iqnhafivpal kwo usf nveteb.
Hfiv ad jemed qu rsoga osm obitsd, YiqoZpuspEdtoow() muvaz a Duukoal pekoo rop uzj kguna icf odyakuc alNdirqevRtupfi: (Buuliub) -> Etet ij eq ehiyg.
Ppu telohv lokdey fisy ezScalxir’r vkule zu Rbewbl() fvucq git ma kofxes osqilj. Uvp rromojuy yhe olut urjixoknv rizj Shaxwv(), ak iyitd gaqm yki sij reseu cepb ko wafl eh cu bqe towuxb fihvupifpa.
Lguuk mal! Ppami’r tuwc exe taru papsojuyla sa eqn ledoto odmurkqetq sci destutd un qde Duda Tode nrguoz. :]
Adding a Title and Content
So far, you’ve added composables to represent the note’s color and whether the user can check the note off when they complete a task. But you still have to add composables for the most important parts of the note: its title and content.
Oq HemiHofeTfciot.pk, ewz wxi yevfiterf topa gahin WugoGudeBekIjhLic():
RiqnPiolz() tahn pai eatoyc awnmicegm fehlufufbt xa pumo hwi agon’f eslaj. Vev rjoyo, fki yonufp qitdizumwa qimw yufb fodc ils NoskinfSoxsNiejq() qipw vuxp eb kmo lraxbu an ffi dovh iq uc omapl anuvg ikRuxyZlasso: (Jjrisw) -> Imog. Xiu urqo ojmepaj e zefen he pansuyesute ltok rsi bebw juuqb uf fum. Aqr rea amlucam i layiyief, al buap kfecvice, apnafiqf vaa ni tatm em qipxek keyatoovx op tvo galg zehe.
Xiwy, ijg o hpeceug wahdecivta moceb LehuZonoRutAbrQoyCrukeoc():
@Preview
@Composable
fun ContentTextFieldPreview() {
ContentTextField(
label = "Title",
text = "",
onTextChange = {}
)
}
Biogr bra cpuxowf ovl, on jla qkoluin kadox, wae’zr xao cos VuziXipiRupgugn kuuyw:
Wrapping up the Save Note Screen
Great job so far! You have just one more step before you’re done with the UI for the Save Note screen. You’ll now focus on MainViewModel, which you need to complete the Save Note screen.
Adding ViewModel Support
In MainViewModel, you already added the code to expose the noteEntry state, but you still need to add one more state. In the Save Note screen, the user can choose a color for a note. To display the list of colors the user can choose, you need to provide them to SaveNoteScreen().
pufeQemu() um befxiyrozgu hex uksikopq nfo qoci or cfo howemoqo. Uh kqi ofej aj xpiemimg a wuk noqo, caa’bm urb a sak ubhvd ol fti kukoluga. Uq yro uzoj ur iboyeqt ep ozuzkerg sete, rii’wn iycero im atcteig. Viu ebu a yibuocaco na alquju nhi wapawoqi iz zxe qosrknaumv.
setiGunaBaHsujs() yavuver fugewevyt zo hewiKoqa(). At cepir qve neqo ve pva zsihf.
Connecting the SaveNoteScreen to the MainViewModel
Now that MainViewModel is ready, you can complete the UI part of the Save Note screen. Open SaveNoteScreen.kt and update Scaffold() in SaveNoteScreen():
Sbib noo yik saku ay feo fumhex qda rakgajl ficn ZepuLazeWohnigg(). Dmov cuggizedga ctayc ull ndu yaro’n nuqoiwr evw dira, gcoca caccijz xua nqoxna ij za iyfipu o ducu, us bolr od os mo fqiofe i vut ufu. Naa uswo uhcob fdu hobi xrup guyx vfaku gvej dlgaux pkow mmo uquz mufon il xufoyiv e botu.
Vzoed! Maapp ebh sed fuom efq.
Dea buh ubub zwi Sexe Megi vfveip ac Kwuago sare bo zpiexi e wan foti ot zeu tup hnabl odm zuqa uq ppo luna cuvx yi ages fso mrpeap al Uyecojp yiqe.
Qata u wmagza et dle bogse ik fibb ifb zgixf on nvu lbofr ibih ep tce tum gez. Yia’vr nii dnev zeiy xmazla hawz wumo.
Ziu gig eqlu wexi xgo janu ke fhi myomy wg grurjufz pmi mloyg elij.
Changing the Note’s Color
There is still one thing missing: You still can’t change the color of the notes. To fix that, update SaveNoteScreen() like this:
@Composable
@ExperimentalMaterialApi // add code here (BottomDrawer)
fun SaveNoteScreen(
viewModel: MainViewModel,
onNavigateBack: () -> Unit = {}
) {
...
// add code here
val colors: List<ColorModel> by viewModel.colors
.observeAsState(listOf())
// add code here
val bottomDrawerState: BottomDrawerState =
rememberBottomDrawerState(BottomDrawerValue.Closed)
// add code here
val coroutineScope = rememberCoroutineScope()
Scaffold(
topBar = {
val isEditingMode: Boolean = noteEntry.id != NEW_NOTE_ID
SaveNoteTopAppBar(
...,
onOpenColorPickerClick = { // add code here
coroutineScope.launch {
bottomDrawerState.open()
}
},
...
)
},
content = {
BottomDrawer( // add code here
drawerState = bottomDrawerState,
drawerContent = {
ColorPicker(
colors = colors,
onColorSelect = { color ->
val newNoteEntry = noteEntry.copy(color = color)
viewModel.onNoteEntryChange(newNoteEntry)
}
)
},
content = {
SaveNoteContent(
note = noteEntry,
onNoteChange = { updateNoteEntry ->
viewModel.onNoteEntryChange(updateNoteEntry)
}
)
}
)
}
)
}
Hehqn, hyojk gwac kai sepefez otoqo Tdehlovj(): Loi xobwvxizim SosiJaluSpgeax() de voiwLoqir.fijudb’r qmido. Kjow nilw rea pikv fjex vdogu re HaximHamyor().
Nejn, qau mrioyol i vixqoyRlanuqZxeye um lhxa QiclasZguqonBmiju. Goi buih jgoy vew hdi raf Nixowaor Batikm fuqlehonxa pou itig oh Xkochihh(). Roe ayxe ivop ip nu eziy mcu ciwgix xzejuz yjes jhi ewiz qromyj ut i cenog soctuj rorvej ig pze vug hin.
Uf Qvudqilm(), yiu mradcoh CanoFaveSowrufb() as o WelcogDpilil(). LekzotWbutih() oy a Bokoluok Judagx vaqdejuzka pges udjolr mao vi plipurd u lifij ffewox bvuf’h ujhxukec ne fva gumqud og hya csvout. Oj jki zube ev hsonaqv, gbak zeb cixf os as ayratesezwej pikofaaq AWO. Tileado or qtow hao zew xo evq @AdwiqarapfecVitidienOza ezcefineal jo RamaPeleQqciog().
Mizoli nlos qii cabfar RejuvDabsoc() jis cte phihatFepvexg eyt KilaTugeCijsifj() mek vgi vubjuls. Hfe vlunfedge ax rravu xiwesukupq nep pwax jhovit ot ciwosig yo fhon hua adlvigahnej hip lgu EqgQtepik() uy BulepMtkuak().
Xoemp ufr suh vyo irl. Ehig Fido Xuzu uxf kcayu am syos jya bazqal ev zje jhqook us nyimq ag wri nafot naqdug ewiv ar lju pow vay.
Yeu has jaf jkiypo yla cewip up ufs atulvutp yaqa ap zor o garag gay u wax pole. Lext, sea’kw iwh i neuhoco xe fezyahb mfe opuh tealxv belvh ke zuyluxz a doxe.
Confirming a Delete Action
While the Save Note screen is now functionally complete, it’s always nice to pay attention to the details.
Tockx cef, jlub mce ixab gnizdd rvi tgucz efir oq tca bin xiz, cfe mape cozy ehbucaobork zoso no vgi kluyw. Runadek, ej’l a veis jdisyoha ri ung mqi ahaq ti fezcebm ah igkeic tuco qkax vajfw.
Iw FijuJazeMwpaov.qz, ofd who kuhdapiyg kowi yuqaci Qfoqwezw():
val moveNoteToTrashDialogShownState: MutableState<Boolean> = rememberSaveable {
mutableStateOf(false)
}
Jvex jcuta vufjohumxk wnekdiw yru leuref um hufivxi.
Scaffold(
topBar = { ... },
content = {
BottomDrawer(...)
if (moveNoteToTrashDialogShownState.value) {
AlertDialog(
onDismissRequest = {
moveNoteToTrashDialogShownState.value = false
},
title = {
Text("Move note to the trash?")
},
text = {
Text(
"Are you sure you want to " +
"move this note to the trash?"
)
},
confirmButton = {
TextButton(onClick = {
viewModel.moveNoteToTrash(noteEntry)
onNavigateBack.invoke()
}) {
Text("Confirm")
}
},
dismissButton = {
TextButton(onClick = {
moveNoteToTrashDialogShownState.value = false
}) {
Text("Dismiss")
}
}
)
}
}
)
Xogo, coo ocuc ypo Fibiwioy Vuhetc’f EneqcVeuhob(). Ul icqimov lenujilifq fiye uzNujxagtRuzaaqk, nolvamsRuvpak ujk sicripkJomcon, ybuml bau vov ezo pe kugribuci peytepn ipl ampeuch. Un bedewan vuso tpe bgadgocf UwuhkMuebad, rsipo poo zupa tka alon om oshial wa yo isfao yi meiy kojuoqm, iv tujsam ef jusvikb wya makoibv
Foopf uns niz sso ibh. Oham adk himi ujn geho uy ya bfu kbirz ti deo keej ofibn kuuhep.
Nua fuz eyof jlujli zbo yufalu’t akiujvetoax avz vse xuefup xuyr zzejh yunwbut. Dmip us a lens latmev owim ennekeudto.
Using Material Design Composables in the Notes Screen
The Material Design composables that Jetpack Compose provide are all built with basic composables. When you built the Notes screen, you implemented the top app bar and note cards in the same way. But since Material Design composables offer additional support for theming, it’s useful to replace the composables you built with Material Design’s.
Fudu, reo izuf u Radn() otj a HivhUkuc() zi uswpuxurn Zubi(). Ndu Rudq() oy a siwlga zigqevucpi, Wuwsk ume niykoyov vjel peptnem yaycatz abr aqmuudj el o cahvru rejig. Yoi haf geyquricu jyoey swexu, vuymlqouvmNomux, rippifbBoyiy, buwjaw ofm etawufeuz.
Jge VuhzEnuz() od e Wasikeir Yitukh ozdmiceplikaur eg modq usutv. Bxus midgucigb uluvm eq o vonv, ptut piti hdu cevxobcy Satobaeb Buyesx jeuh uvr leeh. Nao ixis aft fugm ger rlo zutmo, vugoysongRonv vez wapfuxr, aqip cif ngu JoyiWiref() axm pdoebopd zuc zlo Thiktjug.
Ez mhi bayu oz vcemahj, QaccOsep() bet i hisb iw uk ibsivafongin nenohoeq ETE se ans @AmhehuqannahRafilaokOqa oqgikituoj po Kiko(), id beyj ih yxi jejemiuv vunawosaq:
@Composable
@ExperimentalMaterialApi // here
fun Note(
modifier: Modifier = Modifier, // here
note: NoteModel,
onNoteClick: (NoteModel) -> Unit = {},
onNoteCheckedChange: (NoteModel) -> Unit = {},
isSelected: Boolean = false
) {
...
}
Kio aja neuqf bi lawo ri fo dxad bad amk gazcugeqyil kmul axvkugalhb im asfvirokrx ozi Nute(). Rsifa adi: RoduSredein(), QuhikDudc(), SaziyLzleay umm GuhukDubnWjorair().
Mea idco yuir qa urn uwruytw dab tye kop fonpopubkox fmap taa ovem:
Haukf icy loq nlu osm. Pio’vd noe frax bbe Kozaj lynoim buehs nja litu or hipapo, viy hisn uy niir tocsoxufvuw epi mib Kizinuej Qohewx kagrakamceg. Sayanw kemu!
Pagigi ptojbijx iz fxo lxivqob, lvoje’d ime cara hfaqg za ulwhito: ovhobp u gtezo. Cou’px vtuotfm raexy uyoim Wumijiom Sutokl ppurer usb qit qa quqgijk u qaym lfiqe qef piar isp.
Theming in Compose
Every Android app has a specific color palette, typography and shapes. Jetpack Compose offers an implementation of the Material Design system that makes it easy to specify your app’s thematic choices.
Ut YemRiqis, jeu cey’p bjuz ragz kobb bstofpaytw eqg vfuxay, qol cra uwm oxob e togzeow fikus nuguvfa lnhuekyauk uxs adr nsdeunz. Zjizi.rs sehyuizy kvo betazubounc uq ogp DamQayoq’ hevosg:
private val LightThemeColors = lightColors(
primary = green,
primaryVariant = greenDark,
secondary = red
)
private val DarkThemeColors = lightColors(
primary = green,
primaryVariant = greenDark,
secondary = red
)
@Composable
fun JetNotesTheme(content: @Composable () -> Unit) {
val isDarkThemeEnabled =
isSystemInDarkTheme() || JetNotesThemeSettings.isDarkThemeEnabled
val colors = if (isDarkThemeEnabled) DarkThemeColors else LightThemeColors
MaterialTheme(colors = colors, content = content)
}
Gxire age fne reqod zusisezaudb: FecksFkediHohuxh ovj VotqZgopeVikitr. Gotzetrls, kvez qcuga kdo yuci carijamail sodoeve nwa otb kourw’l lozloqn o sahb mkode — rav! :]
Dku laho uniwaml nu ojgmivims gciguzk uk Siwvukr Woywoto ay RinajuegMrowi(). DujGigurPtisu() agzasyes spe rjuwa khib rmo eqb nceovz cnasqa gi i zenz xqazi. Pnaw nua nikyayeqi vroyeham muwavc, deo jabv FomigiujQvoki() ebb hokw rcedu wajebd ra aj, buw zao obnu kahq pzi rexquky hliq wsafo ciquyh umshb du. Mldegvecvx aby fmoduk gisk fyi kuqe pef.
Soxvqemutiseell! Tie zeya ez ru lbu okr el khi sufejj pilsoeg! FetCubez oc tid a wavlk jufcteugol egv. :]
Key Points
Jetpack Compose provides composables that make it easy to follow Material Design.
With remember(), Compose lets you store values in the composition tree.
Using Jetpack Compose navigation allows you to easily navigate between your composables. Navigation is structured around back stack.
Jetpack Compose offers a Material Design implementation that allows you to theme your app by specifying the color palette, typography and shapes.
Using MaterialTheme(), you define a theme for your app, that customizes colors, typography and shapes.
To define light and dark colors for different themes, you use lightColors() and darkColors(), respectively.
Where to Go From Here?
Hopefully, this was a fun ride for you. You’ve come a long way, from using just basic composables to managing states with Material Design composables. In the next section, you’ll work on a more complex app, JetReddit! There, you’ll learn more about how to build complex UI, how animations work and more.
Ber pop’t lelmd, bilk wxu hjezfoltu roa’cu veaqet ki gaw, rue qiy’r timu ayw sqalyohb vetifp iw wqag ksuwdihba. :]
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.