0% au considerat acest document util (0 voturi)
8 vizualizări121 pagini

Internals Jetpack Compose

Cartea 'Internals Jetpack Compose' de Jorge Castillo oferă o explorare detaliată a funcționalităților interne ale Jetpack Compose, un sistem de interfață utilizator pentru Android. Autorul își exprimă recunoștința față de colaboratorii săi și subliniază importanța învățării despre aceste interioare pentru a dezvolta aplicații Android eficiente. Cartea este disponibilă pe Leanpub și se concentrează pe aspecte precum compilatorul, runtime-ul și gestionarea efectelor în Compose.

Tradus de

ScribdTranslations
Drepturi de autor
© All Rights Reserved
Respectăm cu strictețe drepturile privind conținutul. Dacă suspectați că acesta este conținutul dumneavoastră, reclamați-l aici.
Formate disponibile
Descărcați ca PDF, TXT sau citiți online pe Scribd
0% au considerat acest document util (0 voturi)
8 vizualizări121 pagini

Internals Jetpack Compose

Cartea 'Internals Jetpack Compose' de Jorge Castillo oferă o explorare detaliată a funcționalităților interne ale Jetpack Compose, un sistem de interfață utilizator pentru Android. Autorul își exprimă recunoștința față de colaboratorii săi și subliniază importanța învățării despre aceste interioare pentru a dezvolta aplicații Android eficiente. Cartea este disponibilă pe Leanpub și se concentrează pe aspecte precum compilatorul, runtime-ul și gestionarea efectelor în Compose.

Tradus de

ScribdTranslations
Drepturi de autor
© All Rights Reserved
Respectăm cu strictețe drepturile privind conținutul. Dacă suspectați că acesta este conținutul dumneavoastră, reclamați-l aici.
Formate disponibile
Descărcați ca PDF, TXT sau citiți online pe Scribd

Internals Jetpack Compose

Jorge Castillo
Această carte este de vânzare la[Link]

Această versiune a fost publicată pe 28-07-2021

Acesta este unLeanpubcarte. Leanpub împuterniceș te autorii ș i editorii cu Lean Publishing


[Link]ț ie Leaneste actul de a publica un ebook în curs de desfăș urare folosind instrumente uș oare ș i
multe iteraț ii pentru a obț ine feedback de la cititori, pivotaț i până obț ineț i cartea potrivită ș i construiț i tracț iune odată
tu o faci.

© 2021 Jorge Castillo


Tweet-ează această carte!

Te rog să-l ajuț i pe Jorge Castillo răspândind vestea despre această carte peTwitter!

Hashtagul sugerat pentru această carte este#composeinternals.


Află ce spun alț ii despre carte accesând acest link pentru a căuta acest lucru
hashtag on Twitter:
#composeinternals
Această carte este dedicată tuturor oamenilor care m-au ajutat pe parcurs. Vreau să vă mulț umesc lui Manuel
Vivo, Joe Birch, Antonio Leiva, Enrique López-Mañas, Andrei Shikov ș i Leland Richardson pentru
revizuind părț ile acestei cărț i ș i oferind feedback foarte valoros.
Nu vreau să-l uit nici pe Adam Powell. Mulț umesc Adam pentru că ai răspuns de mai bine de un zillion
întrebări despre compilator ș i timpul de execuț ie.
Vreau, de asemenea, să spun un cuvânt pentru toț i dezvoltatorii incredibil de experimentaț i care trec pe la Jetpack
Comportaț i canale atât din Kotlinlang, cât ș i din Android Study Group pentru a discuta despre bibliotecă. Eu
continu să învăț mult de la voi toț i ș i m-aț i ajutat să rezolv multe îndoieli.
Desigur, doresc să mulț umesc în mod special echipei Google Jetpack Compose pentru munca incredibilă
ceea ce fac cu biblioteca, deoarece sunt convins că va fi un schimbător de jocuri. De asemenea pentru
îmbrăț iș ând cartea într-un mod atât de frumos ș i chiar trimiț ând propuneri interesante. Să menț inem entuziasmul
pe o perioadă :)
Mulț umiri speciale ș i salutări lui Andrei Shikov, care a scris unul dintre capitole (probabil cel mai
interesant!). Mulț umesc Andrei pentru efortul atât de mare de a-ț i pregăti conț inutul pentru pre-
eliberare. Apreciez foarte mult ajutorul tău ș i trebuie să spun că am învăț at o mulț ime de la tine.

Finally, I really want to thank my wife, María Isabel, and my beloved baby, Julia, for giving me all
forț a ș i motivaț ia de a înfrunta o astfel de provocare complicată în aceste momente deosebit de dificile
toț i trăim. A avea o familie ca a mea este ca ș i cum ai avea o superputere. Sunt atât de norocos că
te iubesc pe amândoi.
Conț inut

Preludiu. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1. . . . . . . . . . . . .
De ce să citeș ti această carte. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1. . . . . . . . . .
Despre ce nu este această carte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1. . . . . . . . .
Despre ce este această carte. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1. . . . . . . . . .
Păstrează sursele aproape.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2. . . . . . . . . .
Fragmente de cod ș i exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2. . . . . . . . .

Funcț ii compozabile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3. . . . . . . . . .
The nature of Composable functions . . . . . . . . . . . . . . . . . . . . . . . . . 3. . . . . . . .
Proprietăț i ale funcț iilor compozabile. . . . . . . . . . . . . . . . . . . . . . . . . . . 5
. . . . . . . . .
Contextul apelului . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5. . . . . . . . . . .
Idempotent. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6. . . . . . . . . . . .
Fără efecte secundare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 . . . . . . . . . . .
Restartabil10
Execuț ie rapidă. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
Memoizare pe poziț ii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Similarităț i cu funcț iile suspendate14
Funcț iile compuse sunt colorate 15
Tipuri de funcț ii compozabile. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

2. Compilatorul Compose. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
Un plugin pentru compilatorul Kotlin 19
Compuneț i anotaț ii 20
Înregistrarea extensiilor compilatorului 25
Analiză statică. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Versiunea Compilatorului Kotlin. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26
Verificatori statici 26
Verificări de apel27
Verificări de tip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Verificările declaraț iei 29
Suprimarea diagnosticului . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
Verificare versiune de execuț ie 32
Generarea codului 32
IR-ul Kotlin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
CONTENTS

Scăderea33
Inferarea stabilităț ii clasei 34
Activarea literelor live 37
Compune memoizare lambda 38
Injecț ia Composerului 41
Propagarea comparării 42
Parametrii implici. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44
Generarea grupurilor de control al fluxului45
Generarea Klib ș i a decoy-urilor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

3. Runtimul Compose 51
Masa de sloturi ș i lista modificărilor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
Tabelul fantei în profunzime52
Lista modificărilor 55
Compozitorul. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
Întreț inerea Compozitorului56
Modelarea Schimbărilor. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
Optimizarea momentului în care să scrii 59
Grupuri de scriere ș i lectură 59
Amintind valori60
Recompoziț ia domeniilor 60
Efecte secundare în Composer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
Stocarea CompositionLocals62
Stocarea informaț iilor sursă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
Legarea compoziț iilor prin intermediul CompositionContext 62
Accesarea instantaneului actual al stării . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
Navigarea nodurilor 63
Menț inerea sincronicităț ii între cititor ș i scriitor 63
Aplicarea modificărilor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
Performanț ă în timpul construirii arborelui de noduri64
Cum sunt aplicate modificările 66
Ataș area ș i desenarea nodurilor67
Compoziț ie69
Crearea unei compoziț ii69
Procesul iniț ial de compunere 72
Aplicarea modificărilor după compunerea iniț ială. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73
Informaț ii suplimentare despre compoziț ie 74
Recompunătorul 74
Generarea Recompunerii 74
Procesul de recompunere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
Recompoziț ie concurentă . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 79
Stări de recompunere 80
CONTENTS

4. Compoziț ie UI ...................................................................... 81

5. Sistem de instantanee a stării 82

6. Recompoziț ie Inteligentă 83

7. Efecte ș i gestionari de efecte 84


. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
Introducerea efectelor secundare
Efecte secundare în Compose 85
Ce avem nevoie87
Manipulatoare de efecte 87
Efecte nesuspendate. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
Efecte suspendate. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Adaptatoare de biblioteci terț e 93

8. Ciclu de viaț ă Composable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96

9. Cazuri avansate de utilizare a Runtime-ului Compose. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97


Compose runtime vs Compose UI. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Compoziț ia grafica vectorială 98
Construirea imaginii vectoriale a copacului 100
Integrarea compunerii vectoriale în Compose UI . . . . . . . . . . . . . . . . . . . . . . . . . 104
Gestionarea DOM-ului cu Compose . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
Compoziț ie independentă în browser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
Concluzie113
Preludiu
De ce să citeș ti această carte

Jetpack Compose va deveni standardul „de facto” pentru UI pe platforma Android mai devreme decât
mai târziu, ș i chiar dacă multe aplicaț ii vor continua să folosească sistemul View, noile ecrane vor fi codificate folosind Compose
în schimb, va deveni o chestiune inevitabilă de învăț at. Sugestia mea puternică este să dedici puț in
timpul să învăț ăm despre interioarele sale în profunzime, deoarece acest lucru va genera abilităț i puternice pentru a scrie modern ș i
aplicaț ii Android eficiente.
Pe de altă parte, dacă eș ti interesat de alte cazuri de utilizare ale Jetpack Compose, în afară de Android,
vei fi foarte fericit să ș tii că această carte te acoperă ș i pe tine. Jetpack Compose
internals este foarte axat pe detaliile compilatorului ș i ale executării, făcând experienț a generală foarte
agnostic faț ă de platforma ț intă. A avea un background Android nu ar trebui să fie o cerinț ă pentru
citind cartea. Cartea oferă de asemenea un capitol dedicat cazurilor de utilizare diverse pentru Jetpack
Compose, care expune câteva exemple cu adevărat interesante în cod.

Despre ce nu este această carte

Această carte nu încearcă să replici documentaț ia oficială Jetpack Compose, care este destul de bună
deja ș i sursa adevărului pentru orice nou-venit în bibliotecă. Din acest motiv, nu veț i găsi
liste sau cataloage cu toate componentele sau API-urile existente pe care biblioteca le oferă în carte.
Dacă ceea ce căutaț i este să învăț aț i Compose, v-aș recomanda să continuaț i ș i să vă abonaț i la
the“Practical Jetpack Compose” by Joe Birch¹Cartea lui Joe este plină de exemple interesante ș i detaliate.
explicaț ii despre toate cazurile de utilizare relevante ale Jetpack Compose. Aceasta este o referinț ă extrem de valoroasă
carte de avut pe birou dacă eș ti un dezvoltator Android. Cartea este încă în lucru, dar va
va fi lansat mai târziu în acest an.

Despre ce este această carte

Această carte se concentrează foarte mult pe interiorul Jetpack Compose. Ca dezvoltator Android ș i peste
în aceș ti ani, am dezvoltat un sentiment despre cât de incredibil de important poate deveni să înveț i despre inside-uri
a platformei cu care lucrezi în fiecare zi. Asta mă ajută foarte mult să înț eleg ce cod vreau să scriu.
Având acel tip de cunoș tinț e, îmi permite să scriu cod performant care respectă platforma
aș teptări în loc să merg împotriva lor, ș i îmi permite să înț eleg de ce lucrurile funcț ionează aș a
¹[Link]
Preludiu 2

ei o fac. Pentru mine, acesta este probabil unul dintre cele mai mari diferenț e între cei care nu au foarte multă experienț ă ș i
dezvoltatori Android experimentaț i.
De mulț i ani, toț i ne-am scufundat în aspecte inferioare ale platformei, cum ar fi aranjarea ș i desenarea.
transferuri, eficienț a desenării, interioarele sistemului View, stiluri ș i teme, cicluri de viaț ă ș i multe altele
mai mult. Această carte este o oportunitate de a face acelaș i lucru, dar pentru Jetpack Compose, ș i de a-ț i deschide mintea în
condiț iile despre cum să gândeș ti la acest lucru, având în vedere cât de important va deveni în anii ce vor urma. Al meu
Obiectivul personal ca autor al acestei cărț i este să-ț i ofer toate uneltele necesare pentru a face un salt mare în acest domeniu.

Păstrează sursele aproape

Dacă mă întrebi, aș spune că citirea surselor este una dintre cele mai mari abilităț i pe care le putem dobândi ca software.
dezvoltatori, să le scriem noi, de colegii noș tri sau să facă parte din orice biblioteci externe sau
limbi. Recomand cu tărie oricui citeste această carte să păstreze sursele cât mai aproape posibil
în timp ce-l citeș ti ș i explorezi ș i mai departe. Poț i găsi totul î[Link]². Sursele sunt
de asemenea indexat în orice versiuni Android Studio care suportă Compose, aș a că ar trebui să poț i naviga
A avea un proiect de tip playground cu Compose este, de asemenea, de dorit.

Fragmente de cod ș i exemple


Unul dintre lucrurile pe care le învăț ăm în această carte este că Jetpack Compose poate fi folosit nu doar pentru a reprezenta
Arbori UI, dar orice grafuri de apeluri mari cu tipuri de noduri generice. Totuș i, unele dintre fragmentele de cod ș i
exemplele pe care le vei găsi în carte vor fi orientate pe UI pentru o mapare mentală mai uș oară, deoarece asta este ceea ce
cei mai mulț i dezvoltatori sunt obiș nuiț i cu acest lucru la acest moment. Ceea ce se spune, această carte include o lecț ie care pătrunde adânc în
cum să foloseș ti Jetpack Compose pentru cazuri diverse, astfel încât capitolul să conț ină fragmente ș i exemple
care nu sunt neapărat legate de UI-ul Android.

Bine ai venit la Internele Jetpack Compose. Ia-ț i o cafea ș i bucură-te de lectură.


Jorge.
²[Link]
Funcț ii compozabile
Natura funcț iilor compozabile

Probabil cea mai indicată cale de a începe o carte despre internals Jetpack Compose ar fi să învăț ăm
despre funcț iile Composable, având în vedere că acestea sunt blocurile fundamentale de construcț ie ale Jetpack Compose, ș i
structura pe care o vom folosi pentru a scrie arborii noș tri composabili. Spun intenț ionat "arbori" aici, deoarece composabil
funcț iile pot fi înț elese ca noduri într-un arbore mai mare pe care runtime-ul Compose îl va putea
reprezentaț i în memorie. Vom ajunge la acest lucru în detaliu când va veni vremea, dar este bine să începem
dezvoltarea mentalităț ii corecte încă de la început.
Dacă ne concentrăm pe sintaxă simplă, orice funcț ie standard Kotlin poate deveni o funcț ie Composable.
prin comentarea sa ca@Composable:
[Link]

1 @Composable
2 distractivNamePlate(name: String) {

3 // Codul nostru compus


4 }

Prin aceasta, spunem de fapt compilatorului că funcț ia intenț ionează să convertească datele într-un
nod pentru a te înregistra în arborele compozabil. Adică, dacă citim o funcț ie Compozabilă ca@Composable
(Input) -> Unit, Intrarear fi datele ș i rezultatul (Unitate) nu ar fi o valoare returnată
din funcț ie aș a cum ar gândi majoritatea oamenilor, ci o acț iune înregistrată pentru a adăuga elementul la
reprezentarea în memorie a arborelui compozabil.

Observaț i cum revenireaUnitatede la o funcț ie care ia un input, înseamnă că este probabil să consumăm acel lucru.
input într-un fel în cadrul corpului funcț iei.

Acț iunea descrisă se numeș te „emitere” în jargonul Compose. Reprezintă o schimbare programată la
arborele nodurilor. Funcț iile compozabile emit când sunt executate, iar acest lucru se întâmplă în timpul Compozitiei.
Vom ajunge la asta când vom învăț a despre runtime-ul Jetpack Compose în capitolele următoare.
Funcț ii componibile 4

Funcț ia compusă emite imagine

Dar nu toate funcț iile Composable returneazăUnitatetotuș i. Unele dintre ele returnează o valoare, iar aceasta se schimbă
sensul lor. Am putea spune că acestea nu sunt „consumatoare” de input, ci „oferă” o valoare ceva
bazat pe contribuț ia lor. Un exemplu al acestui lucru poate fiaminteș te-ț i.

Aminteș te-ț [Link]

1 Compozabil
2 distrac ț ieNamePlate() {

3 valoarename = remember { generateName() }

4 Text(nume)
5 }

Theaminteș te-ț iFuncț ia compusă permite memorarea rezultatului unei operaț ii ș i, de asemenea, returnarea acestuia.
Cu toate acestea, de fiecare dată când această funcț ie este executată, reprezentarea în memorie a arborelui va fi actualizată cu
informaț iile relevante din apel. Aceasta va include apelul în sine ș i rezultatul acestuia. Scopul final
consecinț a este de a menț ine reprezentarea în memorie a arborelui întotdeauna actualizată cu
structura ș i informaț iile relevante ale graficului nostru de apeluri.

Există alte implicaț ii relevante ale anotării unei funcț ii ca fiind Composable.@Compozabil
annotarea schimbă eficient tipul funcț iei sau expresiei la care este aplicată, ș i
impune unele constrângeri sau proprietăț i asupra acestuia. Aceste proprietăț i sunt foarte relevante pentru Jetpack Compose
deoarece vor debloca capacităț ile bibliotecii.
Runtime-ul Compose se aș teaptă ca funcț iile compozabile să se conformeze proprietăț ilor menț ionate, aș a că
poate prezenta anumite comportamente ș i, prin urmare, poate exploata diferite optimizări de execuț ie, cum ar fi paralel
compoziț ie, ordinea arbitrară a compoziț iei pe baza priorităț ilor, recompunere inteligentă sau poziț ională
memoizare printre altele. Dar te rog, nu te simț i copleș it de toate aceste concepte noi deocamdată,
Vom explora fiecare dintre ele în profunzime la momentul potrivit.

Vorbind în termeni generali, optimizările de rulare sunt posibile doar atunci când un runtime poate avea unele
certitudini despre codul care trebuie să ruleze, astfel încât să poată presupune condiț ii ș i comportamente specifice din
Aceasta deblochează ș ansa de a executa, sau cu alte cuvinte „a consuma” acest cod urmând diferite
strategii de execuț ie sau tehnici de evaluare care profită de certitudinile menț ionate.
Un exemplu al acestor certitudini ar putea fi relaț ia dintre diferitele elemente din cod. Sunt ele
sunt dependente una de cealaltă sau nu? Putem să le rulăm în paralel sau într-o ordine diferită fără a afecta
1. Funcț ii compozabile 5

rezultatul programului? Putem interpreta fiecare bucată atomică de logică ca o unitate complet izolată?

Proprietăț i ale funcț iilor compozabile

Să învăț ăm despre proprietăț ile funcț iilor composabile.

Contextul apelului

Orice funcț ie care este notată ca@Compozabilse traduce de compilatorul Jetpack Compose în
o funcț ie care primeș te implicit o instanț ă a unuiComposercontext ca parametru, ș i că
de asemenea, transmite acea instanț ă copiilor săi Composable. Am putea să ne gândim la ea ca la o injectare implicită
parametru de care timpul de execuț ie ș i dezvoltatorul pot rămâne agnostic.
Arată cam aș a. Să spunem că avem următorul compus pentru a reprezenta un plăcuț ă de nume:
[Link]

1 @Composable
2 distrac ț ieNamePlate(name: String, lastname: String) {

3 Coloană(modificator = [Link]([Link])) {
4 nume
5 Text(text = nume de familie, stil = [Link].subtitle1)
6 }
7 }

Compilatorul va adăuga un implicitCompozabilparametru pentru fiecare apel Composable din arbore, plus
câteva marcaje la începutul ș i sfârș itul fiecărui component. Reț ineț i că următorul cod este simplificat,
dar rezultatul va fi ceva de genul acesta:
Placă de nume după procesarea compilatorului.

1 distra ț ieNamePlate(nume: String, prenume: String, $composer: Composer<*>) {


2 $[Link](123)
3 Coloană(modificator = [Link]([Link]),$composer) {
4 Text(
5 nume
6 $compozitor
7 )
8 Text(
9 nume de familie,
Funcț ii compozabile 6

10 stil = [Link].subtitle1,
11 $composer
12 )
13 }
14 $[Link]()
15 }

Acesta obț ineCompozitorcontext transmis mai jos în arbore, astfel încât va fi întotdeauna disponibil la orice nivel
având în vedere că arborele este conformat doar din funcț ii compozabile. Compilatorul se va asigura de acest lucru
prin impunerea unei reguli foarte stricte: Funcț iile compozabile pot fi apelate doar din alte funcț ii compozabile
funcț ii. Cu alte cuvinte, acesta va fi contextul de apel necesar.
Prin impunerea acestei cerinț e, Jetpack Compose poate asigura că informaț iile necesare pentru ...
runtime este întotdeauna accesibil de către orice subarbore. În secț iunea anterioară am învăț at cum Composable
funcț iile emit schimbări în arbore în loc să genereze UI-ul real. Composable va folosi injectat
Compozitorinstanț ă pentru a emite aceste modificări în timpul compunerii, iar recompoziț iile ulterioare vor depinde
pe modificările emise de execuț iile anterioare ale funcț iei. Aceasta este legătura între
codul de producț ie pe care îl scriem ca dezvoltatori ș i timpul de execuț ie Compose. Această conexiune ne oferă ...
abilitatea de a informa timpul de execuț ie despre forma arborelui astfel încât să poată construi reprezentarea sa în memorie
a lui ș i îndeplineș te magia sa.

Nu te îngrijora prea mult dacă nu înț elegi complet asta încă. Este ceva ce se va desfăș ura treptat
devine mai clar în lecț iile care urmează. Pentru acum, putem păstra o idee despre un DSL declarativ (de exemplu:
Compose UI) pe care îl putem folosi pentru a emite acț iuni de adăugare, eliminare sau înlocuire a nodurilor din/într-o memorie temporară
reprezentarea copacului compozabil. Acea reprezentare poate fi folosită ca referinț ă pentru a materializa
complet UI-uri mai târziu.

Idempotent

O altă proprietate pe care funcț iile compozabile o au este că se aș teaptă să fie idempotente.
referitor la starea programului. Aceasta înseamnă că am putea apela o funcț ie Composable de o mie de ori
furnizând aceleaș i date de intrare, ar genera întotdeauna aceeaș i stare a programului.
Runtime-ul Jetpack Compose se bazează pe această presupunere pentru lucruri precum recompunerea. Această carte are
o capitol dedicat acestuia, dar putem avea o privire de ansamblu pentru a evidenț ia punctul aici.

În Jetpack Compose, recompunerea este acț iunea de a apela din nou funcț iile Composable atunci când
datele de care depind variază, astfel încât să poată emite elemente actualizate. Dacă traducem asta în UI, funcț ia
Funcț ii compuse 7

ar putea emite un nod actualizat sau nou în arborele UI.

Recompoziț ia poate avea loc din diferite motive în Compose, iar o funcț ie ar putea fi recompusă.
de mai multe ori. De aceea este atât de important ca funcț iile Composable să fie idempotente. Altfel
am modifica starea programului ca efect secundar al fiecărei recompuneri.
Recompoziț ia este o sarcină verticală care coboară pe arbore verificând care noduri trebuie să fie
recompoziț ionat. Atunci când ne referim la „recompunere inteligentă”, ne referim la faptul că părț ile
Arborele compozabil care nu depinde de datele variate poate rămâne neschimbat, aș a că nu este
recompoziț ionat. Este un salt mare în termeni de eficienț ă, deoarece înseamnă că acele lambda Composable
nu sunt chemate din nou. Dacă ne gândim de două ori la asta, vom observa că acest lucru este posibil doar pentru că
starea generată de aceste funcț ii este deja stocată ș i disponibilă în memorie ș i poate fi reutilizată aș a cum este
când inputul lor nu a variat. Aceasta este exact o consecinț ă directă a faptului că este idempotent.
Dacă funcț iile noastre Composable nu ar produce aceeaș i stare a programului pentru aceleaș i intrări de fiecare dată,
runtime-ul nu ar putea niciodată să facă acea presupunere ș i să ia scoruri scurte în această privinț ă. Ș i aceasta duce la
ne duce direct la următoarea proprietate.

Fără efecte secundare

S-ar putea să fi auzit termenii „funcț ii pure” sau „puritate” înainte. Acest lucru este foarte legat de asta. Pur
funcț iile sunt funcț ii care nu conț in efecte secundare.

Să înț elegem "efect secundar" ca orice acț iune care scapă din sfera de aplicare a funcț iei pentru a face ceva.
neprevăzut pe partea. În contextul Jetpack Compose, un efect secundar ar putea fi o schimbare în
starea aplicaț iei care se întâmplă în afara domeniului unei funcț ii Composable. În termeni mai generali, lucruri
la fel ca setarea unei variabile globale, actualizarea unui cache de memorie sau efectuarea unei interogări de reț ea ar putea fi de asemenea
efecte secundare considerate. Ce s-ar întâmpla dacă interogarea reț elei eș uează? Sau dacă memoria cache externă
se actualizează între diferitele execuț ii ale funcț iei? Comportamentul funcț iei noastre depinde de
aceste lucruri. Un efect secundar este într-adevăr o sursă de ambiguitate ș i face ca funcț ia să fie non-deterministă.
Efectele secundare conduc adesea la condiț ii de competiț ie în programe.

Aș a cum probabil ai imaginat deja, faptul că o funcț ie Composable este idempotentă implică de asemenea
că funcț ia nu trebuie să genereze efecte secundare necontrolate. Altfel, ar putea produce un rezultat diferit
starea programului la fiecare execuț ie (adică: compunere), făcând-o să nu fie idempotentă. Acest lucru efectiv
face ca ambele proprietăț i să fie cu adevărat legate între ele.

Permiț ând efectele secundare ar implica, de asemenea, că o funcț ie Composable ar putea deveni dependentă de
rezultatul execuț iei unei funcț ii Composable anterioare. Aceasta trebuie evitată cu orice preț , deoarece
runtime-ul se aș teaptă ca funcț iile Composable să poată fi executate în orice ordine ș i chiar în paralel.
Funcț ii compozabile 8

Aceasta permite descărcarea recompoziț iei pe fire diferite ș i valorificarea mai multor nuclee, pentru
exemplu.
[Link]

1 @Composable
2 distrac ț ieMainScreen() {

3 Antet()
4 DetaliiProfil()
5 ListaEveniment()
6 }

TheAntet, DetaliiProfilș iListaEvenimentelorComponentele pot fi executate în orice ordine, aș a că nu putem


presupuneț i că vor fi rezolvate secvenț ial.
În acest sens, nu ar trebui niciodată să încercăm să profitem de o presupusă ordine de compunere pentru a crea
logici bazate pe asta. Un exemplu al acestui lucru ar putea fi actualizarea unui stat dinProfileDetail cu
intenț ia de a declanș a un nou efect asupraListaEvenimentelorîn răspuns. Ce s-ar întâmpla dacă EventList aleargă
înainte sau în acelaș i timp cuProfileDetailUn alt exemplu ar putea fi setarea unei variabile globale
de laAntetș i citind-o de laEventList. Ce ar trebui să facă programul dacă schimbăm ordinea? Orice
relaț ia pe care o putem stabili între Composables prin efectele secundare ale compunerii este probabil greș ită
ș i ar trebui evitat. Dacă trebuie să scriem logică de afaceri, aceasta nu este responsabilitatea unuia sau
funcț ii Composable multiple ș i ar trebui probabil să fie delegate pe un nivel arhitectural diferit.

Compose are capacitatea de a reordona compoziț ia în funcț ie de priorităț i. Un exemplu este atribuirea de priorităț i mai mici.
prioritate pentru componentele care nu sunt pe ecran, în cazul în care folosim Jetpack Compose pentru a reprezenta
UI.

O altă consecinț ă a rulării efectelor secundare direct dintr-o funcț ie Composable este că aceste
ar putea fi apelat de mai multe ori ca un efect secundar al recompunerii. Asta este prea riscant, deoarece ar putea avea potenț ial
compromite integritatea codului nostru ș i a stării aplicaț iei noastre ș i creează condiț ii de concurenț ă. Imaginează-ț i
o funcț ie Composable care trebuie să-ș i completeze starea cu date încărcate de pe reț ea:
[Link]

1 @Composable
2 distrac ț ieEventsFeed(serviciuReț ea: ServiciuReț eaEvenimente) {

3 valoareevents = [Link]()

4
5 LazyColumn {
6 items(events) { event ->
7 numele evenimentului
8 }
Funcț ii compozabile 9

9 }
10 }

Efectul aici va fi activat din nou pentru fiecare recompoziț ie ș i este posibil să ajungem cu multe efecte.
având loc în acelaș i timp fără niciun fel de control sau coordonare între ele, doar pentru că
runtime-ul ar putea necesita recompunerea acestui Composable de mai multe ori într-o perioadă foarte scurtă de timp.

Dar efectele secundare date sunt necesare pentru a scrie programe cu stare, Jetpack Compose oferă mecanisme
pentru a apela în siguranț ă efecte din funcț iile Composable făcându-le conș tiente de ciclul de viaț ă Composable,
aș adar, cineva poate extinde o sarcină pe parcursul recompoziț iilor, de exemplu. Acestea se numesc manipulatoare de efecte, iar noi
le vom acoperi în capitolele viitoare. Deocamdată, le putem înț elege ca un mediu sigur pentru a rula
efectele secundare, aș a că evităm să le apelăm direct în corpul Composable-ului.
Un alt exemplu de o problemă care apare atunci când executăm efecte secundare într-un Composable fără niciun
o formă de control ar putea fi un compozabil care actualizează o variabilă externă ce deț ine o stare. Deoarece
funcț ia poate rula potenț ial din thread-uri diferite, accesarea acelei variabile devenind automat
nu este sigur pentru fire. Iată un exemplu:

[Link]
1 @Composable
2 distrac ț ieBuggyEventFeed(events: List<Event>) {

3 vartotalEvents =0
4
5 Coloană {
6 Text(dacăNu sunt [Link] evenimente $totalEvents
7
8 [Link] { eveniment ->
9 Articol: ${[Link]}
10 totalEvents++ //
11 }
12 }
13 }

Aici,totalEvents se modifică de fiecare dată cândColoanăcomposable devine recombinat. Asta înseamnă


numărul total nu va corespunde probabil ș i este deschis la condiț ii de cursă.

Un lucru interesant de realizat ca o ultimă gândire este cât de "fără efecte secundare" este o cerinț ă.
concentrat pe codul pe care îl scrie utilizatorul. Dar adevărul este că construirea sau actualizarea Compoziț iei este de fapt
un efect secundar al executării graficului de apeluri Composable. Dar acesta este acceptat (ș i necesar)
din punct de vedere structural, deoarece deblochează capacităț ile bibliotecii ș i nu va implica inconsistenț e
în comportamentul programului nostru în vreun fel.
1. Funcț ii compozabile 10

Reporabil
Funcț iile compozabile sunt, de asemenea, aș teptate să fie restartabile, ceea ce este ceva ce probabil ai
am auzit sau citit undeva deja. Pentru a clarifica această proprietate, acest lucru este pur ș i simplu acelaș i lucru pe care-l
au fost descrise în secț iunile anterioare. Funcț iile compozabile pot recombina, ș i prin urmare ele
nu sunt ca funcț iile standard într-un lanț de funcț ii, în sensul că nu vor fi apelate doar o dată.
Recompoziț ia unei funcț ii compozabile înseamnă executarea ei din nou.

Compose oferă funcț iilor Composable capacitatea de a recombina în orice moment ș i doar când
necesar, astfel încât să poată fi selectiv cu privire la care noduri ale graficului de apeluri să le recompozeze, sau, cu alte cuvinte,
repornire. Aceasta este, în esenț ă, o abordare reactivă în care funcț iile noastre pot fi re-executate pe baza
modificări în starea pe care o observe.

Toate funcț iile compozabile sunt repornibile în mod implicit, deoarece aceasta este ceea ce aș teaptă rularea Compose.
Totuș i, runtime-ul oferă o adnotare numităNonRestartableComposablecare poate fi folosit pentru a elimina
această capacitate dintr-o funcț ie Composable, astfel încât compilatorul să nu genereze codul boilerplate necesar
a fost nevoie să se permită funcț iei să se recompozeze sau să fie sărită.

Vă rog să ț ineț i cont că acesta trebuie folosit cu măsură, deoarece ar putea avea sens doar pentru foarte
funcț ii mici care este probabil să fie recompuse (restartate) de o altă funcț ie Composable care
îi numeș te, deoarece ar putea conț ine foarte puț ină logică, deci nu are mult sens pentru ele să se auto
anula. Invalidarea / recompunerea lor va fi, în esenț ă, condusă de părintele/situatioea înconjurătoare
Compozabil.
Vom acoperi acest lucru în detaliu în capitolul 2.

Executare rapidă

Funcț iile compozabile sunt aș teptate să fie rapide, deoarece pot fi apelate de mai multe ori. De aceea
ele sunt concepute pentru a emite modificări ale nodurilor în copacul Composable, în loc să materializeze UI-ul efectiv
imediat. O funcț ie Composable ar putea fi apelată pentru fiecare cadru al unei animaț ii, de exemplu.
Orice calcul costisitor ar trebui să fie delegat corutinelor ș i întotdeauna să fie încapsulat într-una.
a handler-elor de efecte conș tiente de ciclu de viaț ă despre care vom învăț a mai departe în această carte. Realizând efecte secundare
efectele conș tiente de ciclu de viaț ă ș i suspendate sunt modul în care compilatorul Jetpack Compose poate asigura utilizarea lor
corect, ș i în coordonare cu ceea ce aș teaptă runtime-ul Compose.
Putem gândi la funcț iile Composable ș i la arborele funcț iei Composable ca la o metodă rapidă, declarativă,
ș i o abordare uș oară pentru a scrie o descriere a unui program care va fi reț inut în memorie ș i
interpretat / materializat mai târziu.
1. Funcț ii compozabile 11

Memorarea poziț ională

Pentru a înț elege această proprietate, probabil trebuie să învăț ăm mai întâi despre "memoizarea funcț iilor".
memoizarea este capacitatea unei funcț ii de a salva rezultatul său pe baza intrărilor sale, astfel încât să nu fie nevoie
să fie recalculat de fiecare dată când funcț ia este apelată pentru aceleaș i intrări. Aș a cum am explicat mai sus,
acest lucru este posibil doar pentru funcț ii pure (deterministe), deoarece avem certitudinea că acestea vor
întotdeauna returnează acelaș i rezultat pentru aceleaș i intrări, astfel încât putem să-l salvăm ș i să-l reutilizăm.

Memoizarea funcț iilor este o tehnică bine cunoscută în lumea programării funcț ionale, unde
programele sunt definite ca o compunere de funcț ii pure ș i, prin urmare, memorarea rezultatului acestora
funcț iile pot implica un salt mare în performanț ă.

Memorizarea poziț ională se bazează pe această idee, dar cu o diferenț ă cheie. Funcț iile compuse au
constant knowledge abouttheir location on the Composable tree. The runtime will differentiate
apeluri către aceeaș i funcț ie Composable oferindu-le o identitate care este unică în cadrul părintelui.
This identity is generated based on the position of the Composable function call, among other things.
Astfel, timpul de execuț ie poate diferenț ia cele trei apeluri cătreText()Funcț ie compusă aici:

[Link]

1 @Compozabil
2 distrac ț ieMyComposable() {

3 Bună!
4 Bună!
5 Bună!
6 }

Cele trei sunt apeluri către acelaș iTextcompozabil, ș i au aceleaș i intrări (niciuna, în acest
caz). Dar ele sunt realizate din locuri diferite din cadrul părintelui, prin urmare Compoziț ia va obț ine
trei instanț e diferite ale acestuia, fiecare cu o identitate diferită.
Această identitate este păstrată în timpul recompunerilor, astfel încât timpul de execuț ie poate apela la Compoziț ie pentru
verifică dacă un Composable a fost apelat anterior sau nu, sau dacă s-a schimbat.
Uneori, generarea acelei identităț i poate fi dificilă pentru timpul de execuț ie, deoarece se bazează pe poziț ia apelului.
în surse. Există cazuri în care acea poziț ie va fi aceeaș i pentru apeluri multiple la aceeaș i
Compozabil, ș i totuș i reprezintă noduri diferite. Un exemplu sunt listele de Composabile generate din
o buclă:
Funcț ii compozabile 12

[Link]
1 @Composable
2 distrac ț ieTalksScreen(talks: List<Talk>) {

3 Coloană {
4 pentru(vorbe ș teîndiscute)

5 Vorbeș te
6 }
7 }
8 }

Aici,Vorbeș teeste chemat din aceeaș i poziț ie de fiecare dată, dar fiecare discuț ie este aș teptată să fie diferită.
În cazuri ca acesta, runtime-ul Compose se bazează pe ordinea apelurilor pentru a genera ID-ul unic ș i încă
a putea să le diferenț ieze. Acest lucru funcț ionează bine atunci când adaugi un nou element la sfârș itul listei,
deoarece restul apelurilor rămâne în aceeaș i poziț ie în care erau înainte. Dar ce se întâmplă dacă adăugăm elemente la
sus, sau mijlocul? Execuț ia va recombina toateVorbeș tesunt sub acel punct de când s-au schimbat
poziț ia lor, chiar dacă contribuț iile lor nu s-au schimbat. Acest lucru este ineficient ș i ar putea duce la rezultate neaș teptate
probleme.
În aceste cazuri, timpul de execuț ie oferăcheieCompozabil, astfel încât să putem atribui o cheie explicită apelului
manual
[Link]
1 @Compozabil
2 distrac ț ieTalksScreen(talks: List<Talk>) {

3 Coloană {
4 pentru(vorbiîndiscu ț ii) {

5 key([Link]) { Cheie unică


6 Vorbeș te(vorbi)
7 }
8 }
9 }
10 }

În acest fel, putem asocia fiecare apel laVorbeș te()cu un ID de discuț ie, care va fi probabil unic, ș i acest lucru va
permite Compoziț iei să păstreze identitatea tuturor elementelor de pe listă, indiferent de schimbările acestora
poziț ii.
Când spunem că o funcț ie Composable „emite un nod către Compoziț ie”, toate informaț iile
în legătură cu apelul Composable este stocat, inclusiv parametrii lor, apelurile lor Composable interne,
rezultatul apelurilor lor de reamintire ș i orice altă informaț ie relevantă. Acest lucru se face prin intermediul injectat
Composer instanț ă.
Funcț iile Composable ș tiu despre locaț ia lor, orice valoare stocată de acestea va fi stocată.
only in the context delimited by that location. Here is an example for more clarity:
1. Funcț ii compozabile 13

[Link]

1 @Composable
2 distrac ț ieFilteredImage(path: String) {

3 valoarefilters = remember { computeFilters(path) }

4 ImageWithFiltersApplied(filters)
5 }
6
7 @Composable
8 distrac ț ieImageWithFiltersApplied(filters: List<Filter>) {

9 TODO()
10 }

Aici folosimaminteș te-ț ipentru a salva rezultatul unei operaț iuni complexe pentru a pre-calcula filtrele unei imagini
odată ce le calculăm, putem reda imaginea cu filtrele deja calculate.
Salvarea rezultatului precalculării este de dorit. Cheia pentru indexarea valorii cache va fi
based on the call position in the sources, and also the function input, which in this case is the file
cale.

Theaminteș te-ț ifuncț ia este o funcț ie Composable care ș tie cum să citească din tabela de sloturi în ordinea corespunzătoare
pentru a obț ine rezultatul său. Când funcț ia este apelată, va căuta apelul funcț iei în tabel ș i va returna
rezultatul în cache, atunci când este disponibil. Altfel, va calcula ș i va stoca rezultatul înainte de a-l returna
astfel încât să poată fi recuperat mai târziu.

În Jetpack Compose, memoizarea nu este memoizarea tradiț ională „la nivel de aplicaț ie”. Aici,
aminteș te-ț i valorizează memoizarea poziț ională pentru a obț ine valoarea cache-uită din contextul delimitat de
componenț a o numeș te:ImagineFiltratăAsta înseamnă că se duce la tabel ș i caută valoarea
în intervalul de sloturi în care se aș teaptă ca informaț iile pentru acest Composable să fie stocate. Acest lucru face
este mai mult ca un singleton în acel domeniu, deoarece va calcula valoarea numai în timpul iniț ial
compoziț ie, dar pentru fiecare recompoziț ie va recupera valoarea cached. Dar dacă acelaș i Composable
a fost folosit într-o compunere diferită, sau aceeaș i apelare a funcț iei a fost reț inută dintr-o diferită
compozabil, valoarea reț inută ar fi o instanț ă diferită.
Compose se bazează pe conceptul de memorizare pe pozi ț ii, deoarece acesta este ceea ce este inteligent
recompoziț ia se bazează pe. Theaminteș te-ț i Funcț ia compusă foloseș te pur ș i simplu acest lucru în mod explicit
pentru un control mai detaliat.

Această postare de Leland Richardsonechipa Jetpack Compose de la Google explică poziț ional
Funcț ii compozabile 14

memoizarea foarte bine ș i aduce niș te grafice vizuale care ar putea fi foarte utile.
[Link]

Similarităț i cu funcț iile suspendate


Momentul în care am descris cerinț a unui context de apel pentru funcț ii Composable ar putea
au ridicat unele sprâncene, deoarece ar putea aminti de un alt primitiv de limbaj disponibil în Kotlin:
suspendafunctions.

Dacă eș ti familiarizat cu limba, este probabil să ș tii căsuspendafuncț iile au de asemenea o


cerinț a unui context de apel: Ele pot fi apelate doar dintr-un altulsuspendafuncț ie. Kotlin
compilerul impune această regulă, astfel încât să poată înlocui toatesuspendafuncț ii în lanț prin copii noi de
cei care transmit un parametru implicit la fiecare nivel. Sună familiar, nu-i aș a? Singura diferenț ă
în acest caz, parametrul implicit este unContinuare.
Iată un exemplu. Un cod ca următorul:
[Link]
1 suspendadistrac ț iepublishTweet(tweet: Tweet): Post = ...

Este înlocuit de compilatorul Kotlin cu:


PublishTweet after compiler processing.
1 fun publishTweet(tweet: Tweet, callback: Continuation<Post>): Unit

TheContinuareeste adăugat de compilator ș i trimis către toț isuspendaapeluri. Transportă toate


informaț iile de care are nevoie runtime-ul Kotlin pentru a suspendș i continuă executarea de la diferit
puncte de suspendare la voinț ă.
Acest lucru facesuspendaun exemplu foarte bun de cum impunerea unui context de apel poate servi ca un mijloc
pentru transportarea informaț iilor implicite prin arborele de execuț ie. Informaț ii care pot fi folosite mai târziu la
timp de execuț ie pentru a valorifica caracteristici lingvistice mai avansate, ca în acest caz.

În acest sens, am putea înț elege de asemenea@Composableca o caracteristică a limbii.

O întrebare corectă care ar putea apărea în acest moment este de ce echipa Jetpack Compose nu a folositsuspendapentru
atingerea comportamentului dorit. Ei bine, chiar dacă ambele caracteristici sunt într-adevăr similare în modelul lor
implementa, ambele deblochează comportamente complet diferite în limbaj.
TheContinuareinterfaț a este foarte specifică în legătură cu cazul de utilizare pe care îl rezolvă, care este suspendarea
and resuming execution, so it is modeled as a callback interface and Kotlin generates a default
implementare pentru aceasta cu toată maquinaria necesară pentru a efectua salturile, a coordona diferitele
Funcț ii compozabile 15

puncte de suspensie, partajaț i date între ele, ș i aș a mai departe. Cazul de utilizare Compose este diferit, deoarece acesta
obiectivul este de a crea o reprezentare în memorie a unui grafic de apeluri mare care poate fi optimizat la timpul de execuț ie
în moduri diferite.
Ar fi posibil să codifici comportamentele Compose folosind o implementare personalizată pentru
Continuare? Asta este probabil ceva la care doar creatorii Compose pot răspunde la acest
punct, dar singura lor necesitate este de a impune un context de apel, nu foarte mult control al fluxului de cod, ceea ce
este în cele din urmă ceea ce unContinuareeste.

Odată ce în ț elegem asemănările dintre func ț iile Composable ș i cele suspendate, poate fi
interesant să reflectăm puț in asupra ideii de „vopsire funcț ională”.

Funcț iile compozabile sunt colorate

Funcț iile compozabile au capacităț i ș i proprietăț i diferite faț ă de funcț iile standard, aș a cum
am învăț at în această lecț ie. Ele au un tip diferit ș i modelează o preocupare foarte specifică. Aceasta
diferenț ierea poate fi înț elesă ca o formă de "colorare a funcț iei", deoarece cumva reprezintă o
categorie separată de funcț ii.
„Colorarea funcț iilor” este un concept explicat de Bob Nystrom din echipa Dart de la Google într-un
postare pe blog numită "Ce culoare are funcț ia ta?" din 2015. A explicat cum funcț iile async ș i
funcț iile sincrone nu se compun bine între ele, deoarece nu este posibil să apelăm funcț ii asincrone din cele sincrone
cele decât dacă faci ș i ultimul asincron, sau oferi un mecanism de aș teptare astfel încât să poț i apela asincron
funcț iile funcț ionează transparent ș i aș teaptă rezultatul lor. Acesta este motivul pentru care Promisiunile ș iasync/awaitau fost aduș i
la masă de către unele biblioteci ș i limbaje. A fost o încercare de a recupera compunerea.
În Kotlin existăsuspendacare abordează această problemă, dar este încă colorat. Putem doar să numim
suspendafuncț ii dinsuspendafuncț ii, deoarece necesită un context foarte specific pentru a rula. –The
Continuare–.
Indiferent dacă aceste lucruri au rezolvat problema sau nu, acest lucru se întâmplă dintr-un motiv: Noi modelăm
două categorii foarte diferenț iate de funcț ii aici. Două categorii care reprezintă concepte de o very
natură diferită. E ca ș i cum ai vorbi două limbi diferite. Combinarea lor nu va însemna că va ieș i
grozav în cea mai mare parte a timpului. Avem operaț iuni care sunt menite să calculeze un rezultat imediat (sincro),
ș i operaț iuni care se desfăș oară în timp ș i în cele din urmă oferă un rezultat (asincron), care va dura probabil
mai mult timp pentru a rezolva. De aceea, folosim frecvent ultima pentru lucruri care durează ceva timp, cum ar fi cititul
din fiș iere, scriind în baze de date sau interogând servicii de reț ea.
Revenind la cazul funcț iilor Composable, acestea reprezintă funcț ii care pot fi reluate ș i memorabile care
mapaț i datele imutabile la noduri într-un copac, aș a cum am descris mai sus. Ș i timpul de execuț ie Compose depinde
pe acest fapt. Acesta este motivul pentru care compilatorul impune ca funcț iile Composable să fie apelate doar din alte
Funcț ii compozabile. Această restricț ie este folosită pentru a asigura un context de apelare care deblochează ș i conduce
Compoziț ia. Vom explora acest subiect în curând.
1. Composable functions 16

Dar am învăț at că problema de bază a colorării funcț iei provine din faptul că tu
nu poț i compune funcț ii cu culori diferite într-un mod transparent. Este cazul ș i pentru Compose?
Ei bine, de fapt nu este. Funcț iile compozabile nu se aș teaptă să fie apelate din funcț ii necompozabile.
ones, deoarece modul în care Compose este utilizat este de a construi un grafic din funcț ii Composable doar. Ei
sunt unitatea noastră atomică de compoziț ie în acest DSL pe care îl folosim pentru a construi copacul nostru. Funcț ii compozabile
nu sunt consideraț i să scrie logica programelor nici să compună cu funcț ii standard.
S-ar putea să fi observat că mă joc puț in aici. Unul dintre beneficiile funcț iilor Composable
este că poț i declara UI folosind logica, precis. Asta înseamnă că uneori s-ar putea să fie nevoie să apelăm la o
Funcț ie compusă dintr-o funcț ie standard Kotlin. De exemplu:
[Link]

1 @Composable
2 distrac ț ieSpeakerList(speakers: List<Speaker>) {

3 Coloană {
4 [Link] {
5 Speaker(it)
6 }
7 }
8 }
9
10@Compozabil
11distrac ț ieVorbit(Vorbitor: Vorbitor) {
12 Text([Link])
13 }

Aici numimDifuzorCompozabil dinpentru fiecarelambda, iar compilatorul nu


de ce să te plângi. Cum este posibil să amesteci culorile funcț iei în acest mod atunci?

Motivul esteîn linieOperaț iile de colectare sunt toate marcate caîn liniedeci, în esenț ă, integrează
lambdas în apelanț ii lor, făcându-l efectiv ca ș i cum nu ar exista nicio indirectionare. În exemplul de mai sus,
theVorbitApelul compus este inclus în cadrulSpeakerList corpul, ș i acest lucru este permis având în vedere ambele
sunt funcț ii compozabile.
Astfel, prin utilizarea inline în API-uri putem evita problema colorării funcț iilor pentru a scrie logica
a compozabilelor noastre, în timp ce funcț iile Composable în sine rămân în continuare colorate prin impunerea
un context de apel. De asemenea, reț ineț i că tipul de logică aș teptat într-un compus de obicei vine în
forma de logică condiț ională pentru a înlocui Composables în funcț ie de anumite condiț ii (de obicei unele
stat care a variat sau un parametru). Acestea sunt în cea mai mare parte logici simple care pot
de obicei este scris inline sau scos pentruîn liniefuncț ii dacă este necesar.
Odată ce înț elegem că funcț iile Composable au o culoare, putem înț elege ș i cum aceasta
makes them necessarily be a different category of functions, and we are ready to learn how
aceasta permite compilatorului Jetpack Compose să le trateze diferit ș i, prin urmare, să le valorifice
1. Funcț ii compozabile 17

capacităț ile datorită presupunerilor pe care le poate face despre cum se comportă acestea ș i din ce contexte
ei pot fi sunaț i.
Ca o recapitulare a acestei secț iuni, ș i în termeni generali, să ne amintim pur ș i simplu că impunerea constrângerilor este
în esenț ă, ce fac tipurile ș i acest lucru este realizat în cele din urmă pentru a ajuta compilatorul ș i timpul de execuț ie.

Recomand cu siguranț ă citirea acestor două postări despre colorarea funcț iilor, deoarece există multe foarte
perspective valoroase de design al limbajului acolo.

Iată postarea lui Bob Nystrom, ș i acesta este cel de la Roman Elizarov.
Ce culoare are funcț ia ta?
[Link]

Tipuri de funcț ii compozabile

Ceva ce am învăț at deja este că@Compozabilanotarea schimbă eficient


tipul funcț iei la timpul compilării. Mai specific, funcț iile compozabile respectă
@Composable (T) -> Unitatetip de funcț ie, undeTar fi datele de intrare, ș iUnitatereflectă că
funcț ia trebuie să consume inputul ș i să schimbe arborele. Dezvoltatorii pot folosi acel tip pentru
declaraț i lambda-uri Composable aș a cum aț i declara orice lambda standard în Kotlin.

Funcț ii Compozabile [Link]

1 Aceasta poate fi reutilizată din orice arbore Composable


2 valoaretextComposable: @Composable (String) -> Unit = {
3 Text(
4 el
5 style = [Link].subtitle1
6 )
7 }
8
9 @Composable
10distrac ț ieNamePlate(name: String, lastname: String) {
11 Coloană(modificator = [Link] ng([Link])) {
12 Text(
13 nume
14 stil = [Link].h6
15 )
16 textCompozabil(numeDeFamilie)
17 }
18 }
Funcț ii compozabile 18

Funcț iile compozabile pot fi, de asemenea, conforme cu@Composable Scope.() -> Unitatecând avem nevoie de o lambda
cu receptor, care este stilul folosit frecvent pentru a valorifica DSL-ul Composable pentru a putea cuibări
Compozabile. În aceste cazuri, domeniul poartă de obicei informaț ii precum modificatori sau variabile relevante.
la Composable care poate fi citită în cadrul blocului.
[Link]

1 fun inlineCutie
2 ...,
3 content: @Composable BoxScope.() -> Unit
4 ) {
5 // ...
6 Aspect
7 content = { [Link]() },
8 measurePolicy = measurePolicy,
9 modifier = modifier
10 )
11 }

At the same time, we have learned about the restrictions and properties that the Compose compiler
impune funcț iilor Composable. Aceste cerinț e ar putea fi, de asemenea, înț elese ca parte a tipului
definition itself somehow, since at the end of the day types exist to refine data and impose properties
ș i limitările asupra acestora, astfel încât compilatoarele să le poată verifica static. Tipurile oferă informaț ii suplimentare
compilerului, ceea ce face exact adnotarea Composable.
Cerinț ele funcț iilor Composable sunt verificate de compilatorul Compose uneori chiar
în timp ce încă scriem codul. Compilatorul are verificatori de apel, tip ș i declaraț ie în vigoare pentru a
asiguraț i contextul de apel necesar, garantaț i idempotenta, interziceț i efectele secundare necontrolate, ș i
mult mai mult. Aceste verificări rulează în faza de frontend a compilatorului Kotlin, care este faza care
este folosit în mod tradiț ional pentru analiza statică ș i are cel mai rapid ciclu de feedback posibil. Biblioteca îș i propune să
oferi o experienț ă ghidată dezvoltatorilor, condusă de API-urile sale publice concepute conș tient ș i statice
verificări.
2. Compilatorul Compose
Jetpack Compose este compus dintr-o serie de biblioteci, dar ne vom concentra pe trei specifice.
compilatoarele, timpul de execuț ie ș i bibliotecile UI.
Compilerul Compose ș i runtime-ul sunt pilonii Jetpack Compose. UI-ul nu este tehnic vorbind
parte a arhitecturii Compose, deoarece runtime-ul ș i compilatorul sunt concepute pentru a fi generice ș i
consumat de orice biblioteci client care respectă cerinț ele acestora. Ceea ce se spune, trecerea prin aceasta va ajuta
ne ajută să înț elegem cum Compose hrăneș te reprezentarea în memorie a copacului Composable în timpul execuț iei,
ș i cum, în cele din urmă, se materializează elemente reale din acesta.

compune arhitectură

Într-o primă abordare a Compose, cineva s-ar putea simț i un pic confuz în legătură cu ordinea exactă a lucrurilor.
Până în acest moment al cărț ii, ni s-a spus că compilatorul ș i timpul de execuț ie lucrează împreună pentru a
deblocaț i toate funcț iile bibliotecii, dar probabil că rămâne încă puț in prea abstract dacă nu suntem familiarizaț i
cu el deja. Probabil am aprecia explicaț ii mai profunde despre ce acț iuni face compilatorul Compose
ne ia să facem ca codul nostru să respecte cerinț ele de rulare, cum funcț ionează rularea, când iniț ial
compoziț ia ș i reînceperea ulterioară sunt declanș ate, cum reprezentaț ia în memorie a
copacul este hrănit, cum este folosită acea informaț ie pentru recompoziț ii ulterioare… ș i mult mai mult. Înț elegere
conceptelor ca acestea ne pot ajuta să ne dezvoltăm o înț elegere generală despre cum funcț ionează biblioteca ș i ce să aș teptăm
în timp ce codificăm.
Să mergem înainte ș i să începem prin a înț elege compilatorul.

Un plugin de compilator Kotlin

Jetpack Compose se bazează puț in pe meta-programare. În lumea Kotlin ș i a JVM-ului,


Modul obiș nuit de a proceda în acest caz este procesorii de annotare prin kapt. Dar Jetpack Compose este diferit.
Compilerul Compose este de fapt un plugin pentru compilatorul Kotlin. Acest lucru oferă bibliotecii capacitatea de a
încorporaț i munca sa de compilare în cadrul fazelor de compilare Kotlin, obț inând acces la informaț ii mai relevante
informaț ii despre forma codului ș i accelerarea procesului general. Între timp, kapt are nevoie de
pentru a rula înainte de compilare, un plugin de compilator este complet încorporat în procesul de compilare.
2. Compilatorul Compose 20

Pluginurile de compilator sunt un pas mare înainte pentru metaprogramare în limbaj. Multe cunoscute
Procesoarele de anotări de acolo vor fi probabil migrate treptat pentru a deveni plugin-uri de compilator.
Fiind un plugin de compilare Kotlin, oferă, de asemenea, ș ansa de a integra direct verificările Compose cu idea
inspecț ii, astfel încât dezvoltatorul să poată obț ine feedback în timp real în timp ce codifică, fără a fi nevoie să construiască efectiv.
proiectul. Aceasta se datorează faptului că pluginurile de compilator au capacitatea de a raporta avertismente ș i erori în
faza de frontend a compilatorului, iar acea maș inărie este deja integrată cuidee. Îmbunătăț irea
ciclul de feedback este beneficiul final al efectuării analizei statice în faza de frontend a unui Kotlin
compilator.
Un alt avantaj mare al pluginurilor pentru compilatorul Kotlin este că acestea pot modifica sursele după bunul plac (nu doar
adăugaț i un cod nou) prin ajustarea ieș irii IR pentru acele elemente înainte de a fi coborâte la un nivel mai atomic
termeni care pot fi apoi traduşi în primitivele susţinute de platformele ţintă - aminteşte-ţi Kotlin
este multiplatformă–. Vom aprofunda puț in mai mult acest subiect în acest capitol, dar aceasta va oferi Compose
compilatorul capacitatea de a transforma funcț iile Composable pentru a fi acceptate de timpul de execuț ie.

Dacă eș ti deosebit de interesat de metaprogramarea Kotlin, îț i recomand cu căldură să verifici


KSP (Procesarea simbolurilor Kotlin), o bibliotecă pe care Google o propune ca înlocuitor pentru Kapt. KSP
propune un DSL normalizat pentru "scrierea pluginurilor de compilator uș oare" pe care orice biblioteci pot începe
dependând de metaprogramare. Asigură-te că îi acorzi o citire la secț iunea "De ce KSP" din KSP
repozitoriu.
[Link]
[Link]

Compuneț i anotaț ii
Înapoi la ordinea lucrurilor. Primul lucru la care trebuie să ne uităm este cum ne anotăm codul astfel încât
compilatorele pot scana elementele necesare ș i să facă magia lor. Să începem prin a învăț a despre
Compuneț i anotări disponibile.
Even if compiler plugins can do quite more than annotation processors, there are some things that
ambele au în comun. Un exemplu în acest sens este faza lor frontend, utilizată frecvent pentru analiza statică
şi validare. Compose foloseşte bine acest lucru.
Primul lucru care se întâmplă atunci când rulezi un program folosind Jetpack Compose este că sursele trebuie să
fie procesate de bibliotecă. Compilatorul Compose va prelua sursele programului ș i va căuta toate
aparitii ale@Compozabilanotare. Ceea ce se spune, Compose oferă de asemenea alte complementare
anotaț ii menite să deblocheze verificări suplimentare ș i optimizări diverse la runtime sau „scurtături”
în anumite circumstanț e. Toate anotările disponibile sunt furnizate de Compose
biblioteca de execuț ie.

Să începem prin a face o analiză profundă a celor mai relevante note.


2. Compilatorul Compose 21

Toate anotaț iile Jetpack Compose sunt furnizate de runtime-ul Compose, deoarece este atât
compilatorul ș i timpul de execuț ie modulele care fac o utilizare bună a acestora.

@Compozabil
Aceasta a fost deja tratată în detaliu în capitolul 1, aș a că nu consider necesar să mă aprofundeaz din nou în aceasta.
Doar pentru a înregistra, această anotare schimbă efectiv tipul funcț iei annotate pentru a face
este reutilizabil - astfel încât poate fi executat de mai multe ori - ș i reprezintă o mapare de la date la un nod
care este emis arborelui la executare. Acest nod poate fi un nod UI sau un nod de orice altă natură,
în funcț ie de biblioteca pe care o folosim pentru a consuma runtime-ul Compose.
Reț ineț i că runtime-ul Jetpack Compose lucrează cu tipuri generice de noduri care nu sunt legate de niciun
caz de utilizare specific sau semantica. Vom acoperi acest subiect în detaliu spre sfârș itul acestei cărț i, când
înț elegerea noastră asupra Compose este mai diversă ș i mult mai bogată.

@ExperimentalComposeApi
Nimic foarte special. Doar o notare folosită pentru a marca API-urile Compose care sunt încă deschise la modificări.
înainte de a deveni stabile. Asta va implica primirea unor avertismente atunci când folosim acele API-uri din
baza de cod ș i necesitatea ca noi să optăm explicit pentru acele API-uri. O verificare frontend pe compilator
voi avea grijă să activez acele avertismente cât mai curând posibil.

@ComposeCompilerApi
Această anotare este folosită de Compose însuș i pentru a marca unele părț i ale acestuia care sunt destinate să fie accesibile doar
de către compilator, astfel încât să primeș ti erori inline de îndată ce încerci să le foloseș ti în afara acestuia.

@InternalComposeApi
Unele API-uri sunt marcate ca interne în Compose deoarece se aș teaptă să varieze intern, chiar dacă...
suprafaț a API public rămâne neschimbată ș i îngheț ată pentru o versiune stabilă. Această anotare are o
un domeniu mai larg decât limbainterncuvânt cheie, deoarece permite utilizarea între module, ceea ce este un
concept pe care Kotlin nu îl suportă.

@InterziceApelurileCompozabile
Utilizat pentru a preveni apelurile comasabile să aibă loc în interiorul unei funcț ii. Acest lucru poate fi util pentruîn linie
parametrii lambda ai funcț iilor Composable care nu pot avea în siguranț ă apeluri composable în ele.
Un exemplu al acestuia poate fi găsit înComposeNode, parte a Compose UI. Acest Composable emite
un nod UI în compunere. Atunci când este apelat, apelantul trebuie să transmită lambda pentru iniț ializare sau
actualizând Composable-ul odată creat.
2. Compilatorul Compose 22

[Link]

1 @Composablefun inline<T: Orice,reificatE : Applier<*>> ComposeNode(


2 fără inser ț iefactory: () -> T,

3 actualizare: @DisallowComposableCalls Updater<T>.() -> Unit


4 ) {
5 // ...
6 }

Theactualizarelambda este inline ș i este de asemenea marcat ca@InterziceApelurileCompozabilede când rularea


va apela această funcț ie lambda în linie la fiecare emisie, ceea ce înseamnă la fiecare recompunere. Dând noi
apelurile compozabile în acel moment al compunerii nu ar fi fost aș teptate nici susț inute, runtime-ul
se asigură că este interzis.
Aș a cum probabil ai ghicit, aceasta este cel mai probabil o anotare pe care s-ar putea să nu o foloseș ti niciodată în niciun proiect pentru clienț i.

dar ar putea deveni mai relevant dacă foloseș ti Jetpack Compose pentru un caz de utilizare diferit decât
Compose UI. În acest caz, va trebui probabil să scrieț i propria bibliotecă client pentru runtime, ș i
acest lucru va necesita să te conformezi cu restricț iile de execuț ie.

@ReadOnlyComposable

Când este aplicat peste o funcț ie Composable, înseamnă că ș tim că corpul acestei Composable
nu va scrie niciodată în compunere, doar va citi din ea. Acest lucru trebuie să rămână adevărat pentru toate cele imbricate
Apeluri compozabile în corp. Acest lucru permite runtime-ului să evite generarea de cod care nu va fi
necesar dacă Composable poate trăi până la acea presupunere.
Pentru orice Composable care scrie în compoziț ia interioară, compilatorul generează un „grup” care
înfăș oară corpul său, astfel încât întreaga grupă este emisă în timpul execuț iei. Aceste grupuri emise oferă
informaț iile necesare despre Composable pentru compunere, astfel încât să ș tie cum să curăț e orice
datele scrise ulterior când o recompunere trebuie să o suprascrie cu datele unei Composable diferite,
sau cum să mutaț i acele date păstrând identitatea Composable-ului. Există diferite
tipuri de grupuri care pot fi generate: de exemplu: grupuri restartabile, grupuri mobile... etc.
Pentru mai mult context despre ceea ce este exact un „grup”, imaginează-ț i câteva puncte la început ș i sfârș it al unui
intervalul dat al textului selectat. Toate grupurile au o cheie de poziț ie sursă, care este utilizată pentru a stoca grupul,
ș i, prin urmare, ceea ce deblochează memoizarea poziț ională. Acea cheie este, de asemenea, modul în care ș tie diferite identităț i
întredacăsaualtfelramuri ale logicii condiț ionale precum:
2. Compilatorul Compose 23

[Link]
1 dacă(condi ție) {
2 Text("Hello")
3 } altfel{
4 Text("World")
5 }

Acestea sunt ambeleText, dar au o identitate diferită deoarece reprezintă idei diferite pentru apelant.
Grupurile mobile au, de asemenea, o cheie de identitate semantică, astfel încât să se poată reordona în cadrul grupului părinte.

Când compozabilul nostru nu scrie în compoziț ie, generarea acelor grupuri nu oferă
orice valoare, deoarece datele sale nu vor fi înlocuite sau mutate. Această annotare ajută la evitarea
aceasta.
Unele exemple de Componibile doar pentru citire în bibliotecile Compose ar putea fi multe
CompositionLocaldefiniț ii sau utilităț i care se bazează pe acestea, cum ar fi MaterialCulori, Tipografie,
theisSystemInDarkTheme()funcț ia,ContextLocal, orice apeluri pentru a obț ine aplicaț iaresurse
de orice tip –deoarece se bazează peContextLocal–, sauLocalConfiguration. În general, este vorba despre
lucruri care sunt setate doar o singură dată când rulăm programul nostru ș i se aș teaptă să rămână aceleaș i ș i să fie
disponibil pentru a fi citit din Composables pe arbore.

@NonRestartableComposable
Când este aplicat pe o funcț ie sau pe un obtinator de proprietate, practic o face să fie un Composable nereporabil.
care altfel este o proprietate cu care toate funcț iile Composable se conformează în mod implicit.

When added, the compiler does not generate the required boilerplate needed to allow the function
a recompune sau a fi sărit în timpul recompunerii. Vă rugăm să reț ineț i că acest lucru trebuie folosit foarte
cu parcimonie, deoarece s-ar putea să aibă sens doar pentru funcț ii foarte mici care probabil sunt recompuse
(reînceput) de o altă funcț ie Composable care le apelează, deoarece acestea ar putea conț ine foarte puț ină logică
aș a că nu are mult sens pentru ei să se invalideze singuri. Invalidarea/recompoziț ia lor va fi
în esenț ă, determinate de părintele lor/Composable-ul care le înconjoară, cu alte cuvinte.

@StableMarker
Runtime-ul Compose oferă, de asemenea, câteva adnotări pentru a indica stabilitatea unui tip. Acestea sunt
the@StableMarkermeta-annotare, ș i@Immutableș i@Stabil{"annotations. Let’s start with":"anumite note. Să începem cu"}
the@MarkerStabilunu.
@StableMarkereste o meta-anotare care anotează alte anotări precum@Immutableș i@Stabil.
Acest lucru poate suna un pic redundant, dar este destinat reutilizării, astfel încât implicaț iile pe care le are se aplică de asemenea
peste toate notele annotate cu acesta.
@StabilMarkerimplică cerin ț ele următoare legate de stabilitatea datelor pentru în cele din urmă
tip anotat:
2. Compilatorul Compose 24

Rezultatul apelurilor cătreegalva fi întotdeauna la fel pentru aceleaș i două instanț e.


• Compoziț ia este întotdeauna notificată atunci când o proprietate publică de tipul annotat se schimbă.
• Toate proprietăț ile publice ale tipului annotate sunt de asemenea stabile.

Any types annotated with @Imutabilsau@Stabilva trebui, de asemenea, să implice aceste cerinț e, deoarece
ambele anotări sunt marcate ca un@StableMarker, sau cu alte cuvinte, ca indicatori pentru stabilitate.
Observaț i cum acestea sunt promisiuni pe care le facem compilatorului astfel încât atunci când procesează sursele noastre, poate face
unele presupuneri, dar nu sunt validate la timpul de compilare. Asta înseamnă că această anotare ș i
cele explicate mai jos sunt responsabilitatea dezvoltatorului care le foloseș te ș i trebuie utilizate doar atunci când
ai foarte clar că toate cerinț ele lor sunt îndeplinite.

@Immutable
Această adnotare este aplicată asupra unei clase ca o promisiune strictă pentru compilator în legătură cu toate publicele
proprietăț i ș i câmpuri de clasă accesibile rămân neschimbate după crearea lor. Reț ineț i că aceasta este o mai puternică
promisethan limbavaloarekeyword, since valoareasigură doar că proprietatea nu poate fi reasignată
prin intermediul setter-ului, dar acesta poate să indice către o structură de date mutabilă, de exemplu, făcând datele noastre mutabile chiar dacă este

numai arevaloareproprietăț i. Aceasta ar încălca aș teptările runtime-ului Compose. Cu alte cuvinte,


această adnotare este necesară pentru Compose esenț ialmente deoarece limbajul Kotlin nu oferă o
un mecanism (un cuvânt cheie sau ceva similar) pentru a asigura că o anumită structură de date este imutabilă.

Pe baza presupunerii că valoarea citită din tip nu se va schimba niciodată după iniț ializare, valoarea
runtime poate aplica optimizări pentru caracteristicile de recompunere inteligentă ș i săritor de recompunere.

Un exemplu bun de o clasă care ar putea fi marcată în siguranț ă ca@Immutable ar fi unclasă de datewith
valoareproprietăț i doar, unde niciuna dintre ele nu are accesorii personalizate - care în mod normal ar fi calculate
la fiecare apel ș i posibil să returneze rezultate diferite de fiecare dată, făcându-l o API nesigură
pentru a citi din - ș i toate acestea sunt fie tipuri primitive, fie tipuri marcate ca@Immutable.
@Immutableeste de asemenea o@StableMarkeraș a cum s-a explicat mai sus, deci moș teneș te toate implicaț iile de la
Un tip care este considerat imutabil respectă întotdeauna implicaț iile stipulate pentru un@StableMarker,
deoarece valorile sale publice nu se vor schimba niciodată.@Immutableanotarea există pentru a marca tipurile imutabile ca
stabil

@Stabil

Aceasta ar putea fi o promisiune puț in mai uș oară decât@ImmutableAre sensuri diferite în funcț ie de
la ce element de limbaj se aplică.
Când această anotare este aplicată unui tip, înseamnă că tipul este mutabil–(Am folosi @Immutable
altfel) – ș i va avea doar implicaț iile moș tenite de@StableMarkerSimț iț i-vă liber să citiț i
încă o dată pentru a-ț i reîmprospăta memoria.
Când@Stabilannotarea este aplicată unei funcț ii sau unei proprietăț i în schimb, îi spune compilatorului că
Funcț ia va returna întotdeauna acelaș i rezultat pentru aceleaș i intrări (pură). Acest lucru este posibil doar când
2. Compilatorul Compose 25

parametrii funcț iei sunt, de asemenea,@Stabil, @Immutablesau tipuri primitive (acestea sunt considerate
stabil).
Există un exemplu frumos despre cât de relevant este acest lucru pentru timpul de execuț ie în documente: Când toate tipurile sunt transmise ca
parametrii unei funcț ii Composable sunt marcaț i ca fiind stabile, atunci valorile parametrilor sunt comparate
pentru egalitate bazată pe memorarea poziț iei ș i apelul este sărit dacă toate valorile sunt egale cu
apelul anterior.
Un exemplu de un tip care ar putea fi marcat ca@Stable este un obiect ale cărui proprietăț i publice nu
se schimbă, dar nu poate fi considerat imuabil. De exemplu, are un stat privat modificabil sau foloseș te
delegarea proprietăț ii către un obiect MutableState, dar este altfel imutabil în ceea ce priveș te modul în care este folosit
din exterior.
Încă o dată, implicaț iile acestei adnotări sunt folosite de compilator ș i de timpul de execuț ie pentru a face
presupunerile despre modul în care datele noastre vor evolua (sau nu vor evolua) ș i luaț i scurtături acolo unde este necesar. Ș i
încă o dată, această anotare nu ar trebui utilizată decât dacă eș ti complet sigur de implicaț iile sale
împlinit. Altfel, am oferi informaț ii incorecte compilatorului ș i acest lucru ar duce cu uș urinț ă la
la erori de execuț ie. Aceasta este motivul pentru care toate aceste anotări sunt recomandate să fie folosite cu parcimonie.

Un lucru interesant de subliniat este că, chiar dacă ambele@Immutableș i@Stabilannotaț iile sunt
promisiuni diferite cu semnificaț ii diferite, astăzi compilatorul Jetpack Compose tratează ambele
în acelaș i mod: pentru a activa ș i optimiza recompunerea inteligentă ș i a sări peste recompuneri. Totuș i, ambele
există pentru a lăsa uș a deschisă pentru diferite semantici pentru a impune o diferenț iere pe care compilatorul ș i
timpul de execuț ie ar putea dori să profite în viitor.

Registrarea extensiilor compilatorului

Odată ce am aruncat o privire asupra celor mai relevante adnotări disponibile furnizate de runtime,
este timpul să înț elegem cum funcț ionează pluginul de compilare Compose ș i cum foloseș te acestea
note
Primul lucru pe care îl face pluginul compiler Compose este să se înregistreze în compilatorul Kotlin
conductă folosind unComponentRegistrar, care este mecanismul pe care compilatorul Kotlin îl oferă pentru
această chestiune. TheRegistrarComponentelorDeCompoziț ieînregistrează o serie de extensii ale compilatorului pentru diferite
scopuri. Aceste extensii vor fi responsabile de facilitarea utilizării bibliotecii ș i generarea
codul necesar pentru runtime. Toate extensiile înregistrate vor rula împreună cu compilatorul Kotlin.
Compilatorul Compose înregistrează, de asemenea, câteva extensii în funcț ie de steagurile compilatorului activate.
Dezvoltatorii care folosesc Jetpack Compose au ocazia să activeze câteva flaguri specifice ale compilatorului care
permiț ându-le să activeze caracteristici precum literele live, inclusiv informaț iile sursă în cele generate
code astfel încât Android Studio ș i alte unelte să poată inspecta compoziț ia, optimizări pentru a-ș i aminti
funcț ii, suprimarea verificărilor de compatibilitate a versiunilor Kotlin ș i/sau generarea de metode de diversiune în
Transformarea IR.
2. Compilatorul Compose 26

Dacă suntem interesaț i să aprofundăm modul în care extensiile compilatorului sunt înregistrate de către compilator
plugin, sau alte explorări ulterioare, aminte ș te-ț i că putem întotdeauna răsfoi sursele pe
[Link].
[Link]
gazduit/src/principal/java/androidx/compose/compiler/pluginuri/kotlin/[Link]

Analiză statică
Urmând comportamentul standard al unui plugin de compilator mediu, primul lucru care se întâmplă este linting-ul.
Analiza statică se face prin scanarea surselor căutând orice dintre anotările bibliotecii ș i apoi
efectuând unele verificări importante pentru a mă asigura că sunt utilizate corect. Ș i prin corect mă refer la
modul în care rularea se aș teaptă. Aici, avertizările sau erorile relevante sunt raportate prin intermediul trasării contextului,
ce pluginuri de compilator au acces la. Acest lucru se integrează bine cu idea, deoarece este deja pregătit
pentru a afiș a acele avertizări sau erori în linie în timp ce dezvoltatorul încă tastează. Aș a cum s-a menț ionat anterior,
toate aceste validări au loc în faza frontend a compilatorului, ajutând Compose să ofere
cel mai rapid ciclu de feedback posibil pentru dezvoltatori.
Să aruncăm o privire asupra unora dintre cele mai importante verificări statice efectuate.

Versiunea compilatorului Kotlin

Compilatorul Compose necesită o versiune foarte specifică de Kotlin, aș a că verifică dacă Kotlin
versiunea compilatorului folosită se potriveș te cu cea necesară. Aceasta este prima verificare care se face deoarece este o mare
blocant dacă nu este îndeplinit.

Există ș ansa de a ocoli această verificare folosindsuppressKotlinVersionCompatibilityCheck


argumentul compilatorului, dar asta este pe propria noastră răspundere, deoarece atunci devenim capabili să rulăm Compose cu
orice versiune de Kotlin, ceea ce ar putea duce cu uș urinț ă la inconsistenț e importante. Cu atât mai mult dacă ne gândim
despre evoluț ia backend-urilor compilatorului Kotlin în cele mai recente versiuni Kotlin. Acest parametru a fost
probabil adăugat pentru a permite rularea ș i testarea Compose împotriva versiunilor experimentale Kotlin ș i
îmi place.

Controale Statice

Some of the registered extensions come in the form of static checkers that will guide developers while
codificare. Verificările pentru apeluri, tipuri ș i declaraț ii sunt înregistrate ca extensii de Jetpack Compose.
Ei vor asigura utilizarea corectă a bibliotecii ș i sunt evident părtinitori faț ă de problemă.
2. Compilatorul Compose 27

biblioteca doreș te să rezolve. Lucruri precum cerinț ele pentru funcț ii Composable, cum ar fi cele pe care le
învăț ate în capitolul 1 sunt validate aici ș i raportate atunci când sunt încălcate.

În lumea compilatoarelor Kotlin există diferite tipuri de analizoare disponibile, în funcț ie de


elementul pe care vrem să-l verificăm. Există verificatori pentru instanț ierea claselor, tipuri, apeluri de funcț ii, deprecate
apeluri, contracte, capturarea în închidere, apeluri infixate, apeluri de corutine, apeluri de operator ș i multe altele care
permeteț i pluginurilor de compilator să analizeze toate elementele corespunzătoare din sursele de intrare ș i să raporteze
informaț ii, avertismente sau erori acolo unde este necesar.

Având în vedere că toate verificatoarele înregistrate rulează în faza de frontend a compilatorului Kotlin, se aș teaptă să
fii foarte rapid ș i să nu conț ină operaț iuni foarte consumatoare de CPU. Aceasta este o responsabilitate a dezvoltatorului,
so keeping always in mind that these checks will run while the developer types and that we do not
vrem să creăm o experienț ă utilizator janky este cheia. Vrem să implementăm verificatoare uș oare.

Verificări apeluri

Una dintre diferitele tipuri de verificatori înregistraț i de Compose sunt cei folosiț i pentru validarea apelurilor.
Compilatorul Compose are verificări statice pentru a valida apelurile funcț iilor compozabile în
multe contexte diferite, cum ar fi atunci când sunt realizate sub incidenț a@InterziceApelurileCompoziț ionalesau
@CititDoarComponen ț i.
Un verficator de apeluri este o extensie a compilatorului utilizată pentru a efectua o analiză statică asupra tuturor apelurilor din întreaga noastră bază de cod.

aș adar, oferă overificăfuncț ie care este apelată recursiv pentru a vizita toate elementele PSI care sunt
apele considerate în sursele noastre. Sau, cu alte cuvinte: Toate nodurile de pe arborele PSI. Este o implementare
al modelului vizitator.
Unele dintre aceste verificări necesită un context mai larg decât elementul de limbaj actual pe care îl vizitează,
deoarece s-ar putea să aibă nevoie să ș tie lucruri precum de unde este apelat Composable, de exemplu. Aceasta
înseamnă că analizarea unui singur nod PSI nu este suficient. Colectarea unor astfel de informaț ii necesită înregistrarea
bucăț i mai mici de informaț ii din diferite elemente vizitate, ca un fir pentru a construi o poveste ca un
întreg, ș i să efectueze validări mai complexe în treceri ulterioare. Pentru a face acest lucru, compilatorul poate înregistra
acele informaț ii convenabil în contextul urmăririi. Acest lucru permite extinderea domeniului pentru verificări în
locaț ie ș i să poată căuta expresii lambda de închidere, blocuri try / catch sau lucruri similare care
ar putea fi relevant.
Iată un exemplu de apel al compilatorului care înregistrează informaț ii relevante în urmărire ș i, de asemenea
o folose ș te pentru a raporta o eroare atunci când se face un apel Composable într-un context marcat cu
@InterzicereApeluriComposable:
2. Compilatorul Compose 28

[Link]

1 dacă(arg?.type?.hasDisallowComposableCallsAnnotation() == adevărat) {
2 [Link](
3 ComposeWritableSlices.CAPABIL_DE_CAPTURĂ_CU_LAMBDA
4 descriptor// referin ț ă la literalul func ț iei
5 fals
6 )
7 [Link](
8 ComposeErrors.CAPTURED_COMPOSABLE_INVOCATION.on(
9 raportaPe
10 arg
11 [Link]
12 )
13 )
14 returna
15 }

Contextul ș i, prin urmare, urmăritorul de context sunt disponibili din fiecare apel laverificăfuncț ie, ș i
într-adevăr, este aceeaș i urmă pe care o putem folosi ș i pentru a raporta erori, avertismente sau mesaje de informaț ii. Noi
putem înț elege urma ca o structură mutabilă pe care o putem umple cu informaț ii relevante de care să ne putem folosi
în cadrul analizei generale.

Alte verificări sunt mai simple ș i necesită doar informaț iile disponibile despre elementul curent vizitat,
asa ca ei îș i îndeplinesc acț iunea ș i se întorc. La fiecareverificăapel, pluginul va potrivi tipul de element
pentru nodul curent, ș i în funcț ie de acesta, pur ș i simplu efectuează o verificare ș i returnează –dacă totul este
corectează–, raportează o eroare –dacă este necesar–, înregistrează informaț ii relevante pentru context, sau recursionează
din nou la părintele acestui nod pentru a continua să viziteze mai multe noduri ș i să adune mai multe informaț ii. Diferit
verificările pentru diferite annotaț ii sunt efectuate pe parcurs.
Un lucru pe care compilatorul Compose îl verifică este că Composable-urile nu sunt apelate din locuri nepermise.
locuri, ca din interiorul uneitry/catchbloc (care nu este suportat), dintr-o funcț ie neannotată
ca Composable de asemenea, sau din lambda-uri annotate cu@InterziceApelurileCompozabile–Aminteș te-ț i că
anotarea a fost folosită pentru a evita anotările compozabile în cadrul lambda-urilor inline.

Pentru fiecare apel compus, compilatorul vizitează arborele PSI verificând apelurile sale, apelurile apelatorilor săi,
apelanț ii ș i aș a mai departe, pentru a confirma că toate cerinț ele pentru această apelare sunt îndeplinite. Toate scenariile sunt luate în considerare.
în considerare, deoarece părinț ii pot fi expresii lambda, funcț ii, proprietăț i, accesori de proprietate,
blocuri try/catch, clase, fiș iere ș i altele.
2. Compilerul Compose 29

PSI modelează structura limbii pentru fazele compilatorului frontend, prin urmare trebuie să păstrăm
ț ine cont că modul său de a înț elege codul este complet sintactic ș i static.

Este de asemenea important ca aceste verificări să aibă locîn liniefuncț iile în considerare, deoarece trebuie să fie posibil
pentru a apela o funcț ie Composable dintr-o lambda inline atâta timp cât apelanț ii lambda-ului inline
sunt de asemenea Componibile. Compilatorul verifică faptul că orice funcț ii lambda inline care apelează funcț ii Componibile
sunt de asemenea închise de o funcț ie Composable la un anumit nivel în apelul stivă.
O altă verificare efectuată este cea care detectează posibilele anotări Composable lipsă
unde ar fi necesar sau aș teptat, astfel încât să poată solicita convenabil dezvoltatorului să le adauge.
De exemplu: Dacă o funcț ie Composable este apelată într-o lambda, compilatorul va sugera prieteneș te să de asemenea
adaugă adnotarea Composable la acel lambda. – Există verificări de analiză statică pentru a ghida dezvoltatorul
în timp ce scrie cod, aș a că nu este vorba doar despre interzicere, uneori pot deduce ș i sugera ce este
necesar sau spune-ne cum să ne îmbunătăț im codul.
Există, de asemenea, verificări statice pentru apeluri pentru func ț iile Composable annotate ca
@CuloareNumaiCitireAcestea pot apela doar alte Composable-uri doar pentru citire, altfel noi
ar constitui o încălcare a contractului pentru optimizare, unde un component compus doar în citire poate citi doar
din compunere, nu scrie niciodată în ea. Ț inând cont de aceasta, trebuie să fie îndeplinit la toate nivelurile de adâncime în cadrul
Compozabil, modelul vizitatorului va fi util.
O altă verificare pe care o putem găsi este cea de a interzice utilizarea referinț elor funcț iilor composabile, deoarece
acest lucru nu este susț inut de Jetpack Compose în acest moment.

Verificări de tip

Uneori, annotăm tipurile ca fiind Composable, nu doar funcț ii. Pentru aceasta, compilatorul Compose
are un control legat de inferenț a tipului, astfel încât să poată raporta atunci când un tip este annotat cu@Componibil
a fost aș teptat, dar în schimb a fost găsit un tip neannotat. Similar cu verificarea apelurilor de funcț ii
menț ionat mai sus. Eroarea va imprima tipurile inferate ș i aș teptate împreună cu anotările lor
pentru a face diferenț a mai evidentă.

Verificări de declaraț ie

Verificările pentru locurile de apel ș i tipuri sunt necesare, dar nu sunt suficiente. Site-urile de declaraț ie sunt de asemenea parte a oricărei
Compune baza de cod. Lucruri precum proprietăț i, accesori de proprietate, declaraț ii de funcț ii sau funcț ii.
parametrii trebuie analizaț i.
Proprietăț ile, getter-ii de proprietate ș i funcț iile pot fi suprascrise, chiar ș i atunci când sunt annotate ca
Compozabil. Compilatorul Composer are o verificare în loc pentru a se asigura că orice suprascriere a oricăror
aceste KtElements este, de asemenea, annotat ca Composable pentru a menț ine coerenț a.
2. Compilatorul Compose 30

O altă verificare a declaraț iei disponibilă este cea care asigură că funcț iile Composable nu suntsuspenda,
deoarece acest lucru nu este acceptat. Aș a cum s-a explicat în capitolul 1, suspendarea are un înț eles diferit decât
@Compozabil, ș i chiar dacă ambele ar putea fi înț elese ca primitive ale limbajului într-un fel, ele sunt
concepute pentru a reprezenta lucruri complet diferite. Ambele concepte nu sunt susț inute împreună începând cu
astăzi.
Lucruri precum Composableprincipalfunc ț ii sau câmpuri de suport pe proprietă ț ile Composable sunt de asemenea
interzis prin verificări de declaraț ie.

Suprimarea diagnosticării
Pluginurile compilatorului pot înregistra suprimatoare de diagnostice ca extensii, astfel încât să poată practic să reducă la tăcere
diagnostic pentru anumite circumstanț e specifice - de exemplu: erori notificate prin verificări statice - . Acest lucru este obiș nuit
când pluginurile compilatorului generează sau susț in cod pe care compilatorul Kotlin nu l-ar accepta în mod normal,
astfel încât să poată o ocoli verificările corespunzătoare ș i să funcț ioneze.

Composează un registru aSuprimatorDiagnosticCompozitorpentru a ocoli unele restricț ii lingvistice care ar putea


altfel va eș ua compilarea, astfel încât să poată dezvălui unele cazuri de utilizare specifice.

Una dintre aceste restricț ii se aplică lambda-urilor inline annotate cu "anotaț ii non-sursă" pe apel.
site-uri. Aceasta este o anotare cu retenț ie BINARYsauTIMP DE EXECUTAREAceste anotări supravieț uiesc până la
ieș iri binare, nu caSURSAanotări. Lambda-urile inline date sunt practic inserate în interiorul lor
apelanț ii la timpul de compilare, nu vor fi stocaț i nicăieri, aș a că nu va fi nimic de
nu mai poate fi annotat în acel moment. De aceea, Kotlin interzice acest lucru ș i raportează următoarea eroare:
„Expresia lambda aici este un argument încorporat, astfel că această adnotare nu poate fi stocată nicăieri.”
Iată un exemplu pe o bucată de cod care ar declanș a eroarea folosind Kotlin simplu:
[Link]

1 @Target([Link])
2 anotare clasaFunAnn
3
4 fun în liniemyFun(a: Int, f: (Int) -> String): String = f(a)
5
6 distrac ț iemain()
{
7 myFun(1) @FunAnn { [Link]() }// Apelul de adnotare a site-ului
8 }

Compilatoarele Compose suprimă această verificare doar pentru cazurile în care anotarea utilizată este
@Compozabil, aș a că putem scrie cod precum următorul:
2. Compilatorul Compose 31

[Link]

1 @Compozabil
2 func ț ie inlineMyComposable(@StringRes nameResId: Int, resolver: (Int) -> String) {
3 valoarenume = rezolvator(numeResId)

4 Text(name)
5 }
6
7 @Compozabil
8 distrac ț ieEcran() {

9 MyComposable(nameResId = [Link].app_name) @Composable {


10 [Link](it)
11 }
12 }

Aceasta permite să annotăm parametrii noș tri lambda ca@Compozabilpe site-uri de apel, aș a că nu neapărat
trebuie să o facem pe declaraț ia funcț iei. Acest lucru permite funcț iei să aibă un contract mai flexibil.
O altă restricț ie lingvistică care este ocolită cu ajutorul supresorului este legată de permiterea denumirii
argumente în locuri pe care compilatorul Kotlin nu le-ar accepta, dar doar în cazul în care funcț ia la care aparț ine
la este annotat ca@Compozabil.
Un exemplu sunt tipurile de funcț ii. Kotlin nu permite argumente numite pentru acestea, dar Compose
îi permite dacă funcț ia este anotată ca Composable:
[Link]

1 interfaceFileReaderScope{
2 distractivonFileOpen(): Unit

3 distractivonFileClosed(): Unitate

4 distrac ț ieonLineRead(line: String): Unit

5 }
6
7 objectScopeFileReaderScope {
8 suprascrie func ț iaonFileOpen() = TODO()

9 suprascrie te rogonFileClosed() = TODO()


10 suprascrie func ț iaonLineRead(line: String) = TODO()

11 }
12
13@Compozabil
14distrac ț ieFileReader(cale: String, conț inut: @Composable FileReaderScope.(cale: String) ->
15Unit)
16 Coloană {
17 //...
18 [Link](cale = cale)
2. Compilatorul Compose 32

19 }
20 }

Dacă eliminăm@Composableanotare vom obț ine o eroare ca:


Argumentele numite nu sunt permise pentru tipurile de funcț ii.
Aceeaș i cerinț ă este suprimată în alte cazuri, cum ar fi membrii claselor aș teptate. Amintiț i-vă de Jetpack
Compose îș i propune să fie multiplatformă, aș a că runtime-ul ar trebui să accepte cu siguranț ăexpect funcț ii ș i
proprietăț i semnalizate ca Composable.

Verificare versiune runtime

Avem deja toț i verificatorii statici ș i supresorul de diagnostic instalat. Putem trece la
lucruri mai interesante. Primul lucru care se întâmplă imediat înainte de generarea codului este o verificare pentru
Compuneț i versiunea de runtime utilizată. Compilatorul Compose necesită o versiune minimă a runtime-ului
aș adar, are un mecanism de verificare pentru a se asigura că timpul de execuț ie nu este învechit. Este capabil să detecteze atât atunci când
lipsesc timpul de execuț ie ș i când este depăș it.
O singură versiune a compilatorului Compose poate suporta multiple versiuni de runtime atâta timp cât acestea sunt mai mari.
decât cel minim acceptat.
This is the second version check in place. There is one for the Kotlin compiler version, and then this
altul pentru runtime-ul Jetpack Compose.

Generarea de cod

În sfârș it, a venit momentul ca Compilatorul să treacă la faza de generare a codului. Asta este altceva
procesoarele de adnotare ș i pluginurile pentru compilator au în comun, deoarece ambele sunt utilizate frecvent pentru
sintetizaț i un cod convenabil pe care bibliotecile noastre de runtime îl vor consuma.

IR-ul Kotlin
Aș a cum s-a explicat anterior, pluginurile compilatorului au capacitatea de a modifica sursele, nu doar de a genera
cod nou, având în vedere că au acces la reprezentarea intermediară a limbii (IR) înainte de
acesta produce codul suprem pentru platforma/ele ț intă. Asta înseamnă că pluginul compilatorului poate să se strecoare în
ș i înlocuiț i parametrii, adăugaț i alț ii noi, remodelaț i structura codului înainte de a-l „comite”. Aceasta
are în etapele de backend ale compilatorului Kotlin. Ș i, după cum probabil aț i ghicit, acesta este ceea ce
Compose se ocupă de „injectarea” parametrului suplimentar implicit,Compozitor, la fiecare apel Composable.
Pluginurile de compilator au capacitatea de a genera cod în formate diferite. Dacă ne-am concentra doar pe
JVM ne-am putea gândi să generăm bytecode compatibil cu Java, dar urmând cele mai recente planuri ș i
2. Compilatorul Compose 33

refactorizările din echipa Kotlin pentru stabilizarea tuturor backend-urilor IR ș i normalizarea acestora într-o
unul singur pentru toate platformele, are mult mai mult sens să generăm IR. Amintiț i-vă că IR există
ca o reprezentare a elementelor de limbaj care rămân agnostice faț ă de platforma ț intă–un
„reprezentare intermediară”–. Asta înseamnă că generarea IR va putea face Jetpack Compose
cod generat multiplatformă.
Pluginul de compilare Compose generează IR prin înregistrarea unei implementări a
ExtensiaGeneraț ieiIr, care este o extensie furnizată de compilatorul Kotlin IR comun
backend.

Dacă vrei să înveț i Kotlin IR în profunzime, îț i recomand cu tărie să verificiaceaste seriide Brian Norman
acoperă subiectul IR Kotlin ș i crearea pluginurilor pentru compilator, în general. M-a ajutat să învăț foarte multe
de lucruri interesante. Învăț area în profunzime a IR este necesar în afara domeniului de aplicare al acestei cărț i.

[Link]

Scădere

Termenul „încărcare” se referă la traducerea pe care compilatoarele o pot face de la un nivel mai înalt sau mai avansat
conceptele de programare la o combinaț ie de niveluri inferioare, mai atomice. Asta este destul de comun.
în Kotlin, unde avem o reprezentare intermediară (IR) a limbajului care este capabilă să exprime
concepte destul de avansate care trebuie traduse în atomice de nivel inferior înainte de a se transforma
le transformă în cod byte JVM, Javascript, IR-ul LLVM sau orice platformă vizăm. Compilatorul Kotlin
are o fază de scădere pentru această problemă. Scăderea poate fi, de asemenea, înț eleasă ca o formă de normalizare.

The Compose compiler needs to lower some of the concepts the library supports, so they are
normalizat într-o reprezentare pe care timpul de execuț ie o poate înț elege. Procesul de scădere este efectiv
code generation phase for the Compose compiler plugin. This is where it visits all the elements from
arată arborele IR ș i ajustează IR-ul după voinț ă pentru cei care sunt necesari pe baza nevoilor de rulare.

Iată un rezumat scurt al câtorva exemple semnificative de lucruri care se întâmplă în timpul scăderii
faza pe care o acoperim în această secț iune:

• Inferarea stabilităț ii clasei ș i adăugarea metadatelor necesare pentru a o înț elege la runtime.
• Transformarea expresiilor literale live astfel încât acestea să acceseze o instanț ă de stare mutabilă în schimb.
possible for the runtime to reflect changes in the source code without recompiling (live literals
caracteristică).
• Injectarea implicituluiComposerparametru pe toate funcț iile Composable ș i redirecț ionarea acestuia către toate
Apeluri compozabile.
2. Compilatoarele Compose 34

• Împachetarea funcț iilor Composable pentru lucruri precum:


–Generarea diferitelor tipuri de grupuri pentru fluxul de control (grupuri înlocuibile, grupuri mobile
grupuri…)
–Implementarea suportului pentru argumente implicite, astfel încât acestea să poată fi executate în cadrul
grup generat al funcț iei -în loc să ne bazăm pe suportul implicit pentru parametrii din Kotlin-.
Învăț aț i funcț ia să sară peste recompuneri.
–Propagarea informaț iilor relevante cu privire la schimbările de stare în josul arborelui, astfel încât acestea să poată

recomposite automat atunci când se schimbă.

Să învăț ăm toate tipurile de reducere aplicate de pluginul de compilare Jetpack Compose.

Inferarea stabilităț ii clasei

Recompoziț ia inteligentă înseamnă să sări peste recompoziț ia Composabilelor atunci când intrările lor nu au
schimbate ș i aceste intrări sunt considerate stabile. Stabilitatea este un concept foarte relevant în acest
sens, deoarece înseamnă că runtime-ul Compose poate citi ș i compara în siguranț ă acele intrări pentru a le sări peste
recompoziț ie atunci când este necesar. Scopul final al stabilităț ii este de a ajuta timpul de execuț ie.

Urmând această linie de gândire, să recapitulăm proprietăț ile pe care un tip stabil trebuie să le îndeplinească:

• Apeluri cătreegalpentru două instanț e întotdeauna returnează acelaș i rezultat pentru cele două instanț e.
Asta înseamnă că comparaț ia este coerentă, astfel încât timpul de execuț ie se poate baza pe ea.
Compoziț ia este întotdeauna notificată atunci când o proprietate publică de acest tip se schimbă. Altfel, noi
ar putea duce la desincronizare între inputul nostru Composable ș i cel mai recent
stabilitează că se reflectă atunci când sunt materializate. Pentru a ne asigura că acest lucru nu se întâmplă, recompunerea este întotdeauna
activat pentru cazuri ca acesta. Recompunerea inteligentă nu se poate baza pe acest input.
Toate proprietăț ile publice au tipuri primitive sau tipuri care sunt considerate stabile.

Toate tipurile primitive sunt considerate stabile în mod implicit, dar ș iȘ ir, ș i toate tipurile de funcț ii. Aceasta
este pentru că sunt imuabile prin definiț ie. Deoarece tipurile imuabile nu se schimbă, ele nu au nevoie de
a notifica compoziț ia nici ea.
De asemenea, am învăț at că există tipuri care nu sunt imutabile, dar pot fi considerate stabile de către Compose
–ele pot fi annotate cu @StabilUn exemplu al acestui lucru esteStareMutabilă, deoarece Compose este
notificat de fiecare dată când se schimbă, prin urmare este sigur să te bazezi pe el pentru recompunerea inteligentă.

Pentru tipurile personalizate pe care le creăm în codul nostru, putem determina dacă acestea respectă proprietăț ile
enumerate mai sus ș i marchează-le manual ca fiind stabile folosind @Imutabilsau@Stabil{"annotations":"anotaț ii"}
convenabil. Dar a te baza pe dezvoltatori pentru a menț ine acel contract îndeplinit poate fi destul de riscant ș i greu.
a menț ine în timp. Ar fi de dorit să se inferă automat stabilitatea clasei în schimb.
Compose face asta. Algoritmul pentru a deduce stabilitatea este într-o continuă evoluț ie, dar merge împreună cu
linii de vizitare a fiecărei clase ș i sintetizarea unei anotaț ii numite@StabilityInferredpentru aceasta. De asemenea
2. Compilatorul Compose 35

adaugă un sinteticstatic final int $stable valoare care codifică informaț iile relevante despre stabilitate pentru
clasa. Această valoare va ajuta compilatorul să genereze echipamente suplimentare în etapele ulterioare pentru a determina
stabilitatea clasei la timpul de execuț ie, astfel încât Compose să poată determina dacă funcț iile noastre Composable care
depinde de această clasă dacă trebuie să recompună sau nu.

Adevărul este că nu sunt literalmente fiecare clasă, ci doar fiecare clasă eligibilă. Adică fiecare clasă publică.
nu este un enum, o intrare enum, o interfaț ă, o anotare, un obiect anonim, unaș teptaț i
element, o clasă interioară, un companion sauîn linie. De asemenea, nu este deja marcat ca stabil cu
anotările menț ionate mai sus, aș a cum probabil ai ghicit. Aș adar, în general, sunt doar clase, clase de date, ș i
ceea ce nu a fost annotat ca fiind stabil deja. Are sens, având în vedere că aceasta este ceea ce vom folosi
pentru a modela intrările funcț iilor noastre Composable.

Pentru a deduce stabilitatea unei clase, Compose ia în considerare diferite aspecte. O clasă va fi...
considerat stabil atâta timp cât toț i parametrii săi de constructor sunt, de asemenea, stabili. Clase precumclasa Foo, sau
clasa Foo(val value: Int)vor fi inferate ca fiind stabile, deoarece nu au parametri sau parametri stabili
doar. Apoi lucruri precumclasa Foo(var valoare: Int)va fi considerat instabil imediat.
Dar lucruri precum parametrii de tip generic ai clasei ar putea afecta de asemenea stabilitatea clasei, de exemplu:

[Link]

1 clasaFoo<T>(valoarevalue: T)

În acest caz,Teste folosit pentru unul dintre parametrii clasei ș i, prin urmare, stabilitateaFoose va baza pe
stabilitatea tipului trecut pentruT. Dar având în vedereTnu este un tip reificat, va fi necunoscut până la
runtime. Prin urmare, trebuie să existe o anumită mecanică pentru a determina stabilitatea unei clase la runtime,
după ce tipul a fost trecut pentruTeste cunoscut. Pentru a rezolva acest lucru, compilatorul Compose calculează ș i pune o
masca de biț i înStabilitateInferatăanotare care indică faptul că se calculează stabilitatea acestuia
clasa la runtime ar trebui să depindă de stabilitatea parametrului de tip corespunzător.
Dar a avea tipuri generice nu înseamnă neapărat instabilitate. Compilatorul ș tie că, de exemplu,
cod ca:clasa Foo<T>(val a: Int, b: T) { val c: Int = [Link]() }este stabil de când hashCode
se aș teaptă să returneze întotdeauna acelaș i rezultat pentru aceeaș i instanț ă. Este parte din contract.
For classes that are composed of other classes, like clasă Foo(val bar: Bar, val bazz: Bazz),
stabilitatea este inferată ca fiind combinaț ia stabilităț ii tuturor argumentelor. Aceasta este rezolvată
recursiv.
Lucruri precum starea internă mutabilă fac, de asemenea, o clasă instabilă. Un exemplu în acest sens ar putea fi
următorul
2. Compilatorul Compose 36

[Link]

1 classCounter{
2 variabilă privatăcount: Int =0
3 distrac ț iegetCount(): Int = count

4 distrac ț ieincrement() { count++ }

5 }

Acest stat se schimbă în timp chiar dacă este mutat intern de către casă însăș i. Asta înseamnă că timpul de execuț ie
nu pot să mă bazez cu adevărat pe faptul că va fi constant.

În general, compilatorul Compose consideră un tip stabil numai atunci când poate demonstra acest lucru. De exemplu: o interfaț ă este
presupus a fi instabil, deoarece Compose nu ș tie cum va fi implementat.
[Link]

1 @Compozabil
2 distrac ț ieListaMeaDeArticole(items: List<T>) {

3 // ...
4 }

În acest exemplu obț inem unListaca un argument, care poate fi implementat în moduri mutabile –vezi:
Listă de arrayNu este sigur pentru compilator să presupună că vom folosi doar implementări imuabile.
ș i a deduce că nu este atât de uș or, aș a că va presupune că este instabil.
Un alt exemplu sunt tipurile cu proprietăț i publice mutate ale căror implementări ar putea fi imutabile.
Acestea sunt, de asemenea, considerate instabile din punct de vedere implicit, deoarece compilatorul nu este capabil să deducă atât de mult.

Aceasta este o mică înș elăciune, deoarece de multe ori acele lucruri ar putea fi implementate pentru a fi imutabile ș i pentru
ce este demn pentru runtime-ul Compose care ar trebui să fie suficient. Din acest motiv, dacă un model pe care noi
sunt folosite ca input pentru funcț iile noastre Composable, este considerat instabil de către Compose, putem totuș i
marcaț i-l ca@Stabilexplicit dacă avem mai multe informaț ii în mâinile noastre ș i acestea sunt sub controlul nostru. A
documentele oficiale oferă acest exemplu, care este destul de semnificativ:

[Link]

1 // Marcarea tipului ca stabil pentru a favoriza omisiunile ș i recompunerile inteligente.


2 @Stabil
3 interfaceUiState<T : Result<T>> {
4 valvalue: T?
5 valoareexception: Throwable?

6
7 valoarehasError:Boolean
8 prinde() = excep ț ie != null
9 }
2. Compilatorul Compose 37

Există mai multe cazuri acoperite de algoritmul de inferenț ă a stabilităț ii claselor. Pentru toate cazurile acoperite de
această funcț ie îț i sugerez cu siguranț ă să ocitiț i teste de bibliotecă³ pentruTransformareStabilitateClasă.

Ț ine minte că modul în care stabilitatea este dedusă de compilator poate varia ș i se poate îmbunătăț i.
În timp. Punctul bun este că va fi întotdeauna transparent pentru utilizatorii bibliotecii.

Activarea literelor live


Una dintre opț iunile pe care le putem transmite compilatorului este cea a literalelor active. De-a lungul timpului au existat două
implementările acestei caracteristici, astfel încât să puteț i activa fie una, fie cealaltă folosindliteralsLive
(v1) sauliveLiteralsEnabled (v2) steaguri.
Live literals is this feature where the Compose tools are able to reflect changes live in the preview
fără a fi nevoie de recompilare. Ceea ce face compilatorul compose este să înlocuiască acele expresii
de versiuni noi ale acestora care îș i citesc valorile dintr-oStareVariabilăîn schimb. Acest lucru permite timpul de execuț ie
să fiț i notificat despre schimbări instantaneu, fără a fi nevoie să recompilaț i proiectul. Deoarece biblioteca
kdocs expun:
Această transformare este destinată îmbunătăț irii experienț ei dezvoltatorului ș i nu ar trebui niciodată activată într-o
versiunea de lansare deoarece va încetini semnificativ codul conș tient de performanț ă
Compilatorul Compose va genera ID-uri unice pentru fiecare expresie constantă singulară din baza noastră de cod,
apoi transformă toate acele constante în getter-e de proprietate care citesc dintr-un anumit StareMutableaceea
este păstrat într-o clasă singleton generată pe fiș ier. La momentul execuț iei, există API-uri pentru a obț ine valoarea pentru
acele constante folosind cheia generată.
Iată un exemplu extras din biblioteca kdocs. Să spunem că începem cu acest Composable:
[Link]

1 @Composable
2 distrac ț ieFoo() {

3 print("Salut Lume")
4 }

Se va transforma în următoarele.

³[Link]
tests/src/test/java/androidx/compose/compiler/plugins/kotlin/[Link]
2. Compilatorul Compose 38

[Link]
1 // fi ș ier: [Link]
2 @Composable
3 distrac ț ieFoo() {

4 print(LiveLiterals$FooKt.`getString$arg-0$call-print$distracț ie-Foo`())
5 }
6
7 literaleliveobiect$FooKt {
8 varString$arg-0$call-print$distracț ie-Foo`: String ="Hello World"
9 varStat$String$arg-0$apel-print$distracț ie-Foo`: MutableState<String>? =null
10 distrac ț iegetString$arg-0$call-print$fun-Foo`(): Sir {

11
12 valoarefield = aceastaString$arg-0$apel-print$distracț ie-Foo`
13
14 valoarestate= dacă(câmp == null) {
15 valoaretmp = liveLiteral(

16 String$arg-0$apel-imprimare$fun-Foo
17 [Link]$arg-0$call-print$distracț ie-Mâncare
18 )
19 acesta.`String$arg-0$apel-imprimare$distracț ie-Foo`= tmp
20 tmp
21 } altfelcâmp
22
23 [Link]
24 }
25 }

Putem vedea cum constantul este înlocuit de un getter care citeș te dinStareMutabilăț inut în
singleton-ul generat pentru fiș ierul corespunzător.

Compoziț ie de memoizare lambda

Această etapă generează IR convenabil pentru a învăț a runtime-ul cum să optimizeze execuț ia lambda-urilor transmise
la funcț ii Composable. Asta este:

• Lamba-uri non-compozabile: Compilatorul generează IR pentru memorarea acestora prin înfăș urarea fiecăruia
unul dintre ei într-un aminteș te-ț i apel. Gândeș te-te la un callback pe care îl transmitem unei funcț ii Composable, pentru
exemplu.
•Lambda-uri compozabile: Compilatorul generează IR pentru a le învălui ș i a adăuga informaț iile relevante
pentru a învăț a runtime-ul cum să stocheze ș i să citească expresia din/d către Compoziț ie. Un exemplu
Aceasta poate fi conț inutul lambda-urilor compuse pe care le transmitem nodurilor noastre UI Compose atunci când apelăm
ei.
2. Compilatorul Compose 39

Lambdas non-compozabile
Această acț iune optimizează apelurile lambda transmise funcț iilor Composable, astfel încât să poată fi reutilizate. Kotlin
optimizează deja lambda-urile atunci când nu capturează nici o valoare, modelându-le ca singletoni, aș a că
există o singură instanț ă reutilizabilă pentru întregul program. Această optimizare nu este posibilă atunci când
lambdas capturează valori, deoarece aceste valori ar putea varia per apel, făcându-le unice, ș i prin urmare o
o instanț ă diferită per lambda va fi necesară. Compose este mai inteligent pentru acest caz specific. Să explorăm
acest lucru folosind un exemplu:

[Link]
1 @Composable
2 distrac ț ieNamePlate(name: String, onClick: () -> Unit) {

3 // ...
4 // onClick()
5 // ...
6 }

Aici,onClickeste un lambda standard Kotlin care este trecut la o funcț ie Composable. Dacă lambda pe care o
trece la acesta din site-ul de apel captează orice valori, Compose are abilitatea de a învăț a runtime-ul cum să
memorează-l. Asta înseamnă, în esenț ă, a-l învârti într-un apel laaminteș te-ț iAcest lucru se face prin generatul
IR. Această apelare va reț ine expresia lambda pe baza valorilor pe care le capturează, atâta timp cât acestea
valorile sunt stabile. Acest lucru permite timpului de execuț ie să reutilizeze lambda-urile care există deja în loc să creeze
nouă, atâta timp cât valorile pe care le capturează se potrivesc (inclusiv parametrii de intrare).

Motivul pentru care se cere ca valorile capturate să fie stabile este că acestea vor fi folosite ca argumente de condiț ie
pentruaminteș te-ț iapelează, aș a că trebuie să fie de încredere pentru comparaț ie.

Reț ineț i că lambdas-urile memorizate nu pot fi inline, deoarece altfel nu ar fi nimic de reț inut.
după ce sunt inline pe apelanț ii lor la timpul compilării. O altă cerinț ă este ca încadrarea
Funcț iile compozabile nu pot returna valori, deoareceaminteș te-ț i este permis doar în funcț ii care
emit din motive de design.

Această optimizare are sens doar pentru lambdas care capturează. Dacă nu, valoarea implicită a lui Kotlin
optimizarea –reprezentându-le ca singletoni– este suficientă.
După cum s-a explicat mai sus, memoizarea se face pe baza valorilor capturate de lambda. Când se generează
IR-ul pentru expresia, compilatorul va adăuga un apel laaminteș te-ț icu tipul de returnare corespunzător
tipul expresiei memorate, atunci va adăuga argumentul de tip generic la apel -
aminteș te<T>...– pentru a se potrivi cu tipul de returnare al expresiei. Imediat după, va adăuga toate valorile capturate.
prin lambda ca argumente de condiț ie -aduceti aminte<T>(arg1, arg2...) – astfel încât să poată fi folosite pentru
comparaț ie, ș i în cele din urmă, lambda pentru expresia –aminte ș te<T>(arg1, arg2..., expresie)
astfel încât să poată funcț iona ca un lambda care urmăreș te–.
2. Compilatorul Compose 40

Folosind valorile capturate ca argumente de condiț ie va asigura că acestea sunt folosite ca chei pentru
amintind rezultatul expresiei, astfel încât acesta va fi invalidat ori de câte ori acestea variază.
Reamintirea automată a lambda-urilor transmise funcț iilor Composable deblochează reutilizarea când
recompoziț ia are loc.

Lambdas compozabile
Compilatorul Compose este, de asemenea, capabil să împacheteze toate lambda-urile Composable pentru a învăț a timpul de execuț ie cum să stocheze

ș i le recuperează din/în Composition la runtime. Aceasta este o altă formă de memoizare lambda.
Iată un exemplu de lambda Composable care va fi încapsulată:
[Link]

1 @Compozabil
2 distrac ț ieContainer(conț inut: @Composable () -> Unit) {

3 // ...
4 // content()
5 // ...
6 }

Pentru a face împachetarea, IR-ul expresiei lambda este ajustat astfel încât să apeleze o fabrică compusă.
funcț ie cu anumiț i parametri mai întâi:composableLambda(...).
Primul parametru adăugat va fi curentul$composer, a ș a că este redirec ț ionat a ș a cum era de a ș teptat.
composableLambda($compozitor, ...).

Apoi va adăugacheieparametru, ob ț inut dintr-o combina ț ie de cod de hashdin


numele complet calificat al lambda-ului Composable ș i offsetul de început al expresiei, care este
în esenț ă, unde se află în fiș ier, pentru a te asigura că cheia este unică - memoizare poziț ională -.
composableLambda($composer, $key, ...).

În continuare, un parametru booleanshouldBeTracked este adăugat. Acest parametru determină dacă


această apelare lambda Composable trebuie urmărită sau nu. Când lambdas nu au capturi, Kotlin
le transformă în instanț e singleton, deoarece ele nu se vor schimba niciodată. Asta înseamnă că ele nu
trebuie să fie urmărit de [Link]($composer, $key, $shouldBeTracked, ...).
Un parametru opț ional despre aritatea expresiei poate fi de asemenea adăugat, necesar doar atunci când aceasta are
mai mult de 22 de parametrii (număr magic).composableLambda($composer, $key, $shouldBeTracked,
_$arity, ...).

În cele din urmă, adaugă expresia lambda însăș i ca ultimul parametru al wrapper-ului (blocul, ca un
lambda de sfârș it).composableLambda($composer, $key, $shouldBeTracked, $arity, expression).
Scopul funcț iei Composable numită form din corpul înfăș urat este simplu:
Adăugând un grup înlocuibil la compunere pentru a stoca expresia lambda folosind
2. Compilatorul Compose 41

cheie generată. Aceasta va fi folosită pentru a găsi lambda Composable mai târziu. Aici învăț ăm
modul de execuț ie cum să stochezi ș i să recuperezi expresia Composable mai târziu.
Pe lângă această înfăș urare, Compose poate de asemenea să optimizeze lambda-urile Composable care nu capturează
valori, la fel cum o face Kotlin: Reprezentându-le folosind singletons. Pentru aceasta, generează un
„ComposableSingletons” sinteticeobiect internper fiș ier unde au fost găsite lambda-uri comasabile.
Acest obiect va reț ine (memoiza) referinț e statice la acele lambda-uri Composable ș i va include de asemenea
getter-uri pentru aceș tia astfel încât să poată fi recuperaț i mai târziu.

Injectarea Composer-ului
Aceasta este etapa în care compilatorul Compose înlocuieș te toate funcț iile Composable cu versiuni noi de
cu un extraCompozitorparametru sintetic adăugat. Acest parametru este, de asemenea, transmis fiecărei
Apel compus în cod pentru a asigura că este întotdeauna disponibil în orice punct al arborelui. Asta include de asemenea
apeluri la lambda compozabilă.
Aceasta necesită, de asemenea, un anumit tip de remodelare a tipurilor, deoarece tipul funcț iei variază atunci când compilatorul
pluginul adaugă parametrii suplimentari la acesta.

Injecț ie de compozitor

Aceasta face efectiv caCompozitordisponibil pentru orice subarbore, oferind toate informaț iile necesare
pentru a materializa arborele Composable ș i a-l menț ine actualizat.

Iată un exemplu de acesta.


2. Compilatorul Compose 42

[Link]

1 chiuNamePlate(nume: String, prenume: String, $compozitor: Composer) {


2 $[Link](123)
3 Column(modifier = [Link]([Link]),$composer) {
4 Text(
5 nume
6 $compozitor
7 )
8 Text(
9 nume de familie
10 stil = [Link].subtitle1,
11 $composer
12 )
13 }
14 $[Link]()
15 }

Lambdas inline care nu sunt Composable nu sunt transformate intenț ionat, deoarece vor dispărea.
la timpul compilării atunci când sunt integrate pe apelanț ii lor. De asemenea,aș teptafuncț iile nu sunt transformate
nici. Aceste funcț ii sunt rezolvate laactualcele referitoare la rezolvarea tipurilor, ceea ce înseamnă că este cel din urmă
cele care ar fi transformate în orice caz.

Propagarea comparativă

Am învăț at cum compilatorul injectează$composerparametru suplimentar ș i îl transmite tuturor


apeluri compozabile. Există câteva informaț ii suplimentare de metadate care sunt, de asemenea, generate ș i adăugate la fiecare
Compozabil. Unul dintre ele este$schimbatparametru. Acest parametru este folosit pentru a aduce indicii despre
dacă parametrii de intrare ai Composable-ului curent ar fi putut să se schimbe de la precedentul
compoziț ie. Aceasta permite sări peste recompuneri.
[Link]

1 @Composable
2 distractivHeader(text: String,$composer: Composer<*>,$changed: Int)

Acest parametru este sintetizat ca o combinaț ie de biț i care reprezintă această condiț ie pentru fiecare oneof.
parametrii de intrare ai funcț iei – Există un singur$schimbatparametru care codifică această condiț ie pentru fiecare
nparametrii de intrare (10 sau cam aș a), care sunt limitaț i de cantitatea de biț i folosiț i. Dacă compunerea se întâmplă să
au mai multe parametri, se adaugă 2 sau mai multe steaguri –. Motivul pentru utilizarea biț ilor este că procesoarele sunt foarte
bun la procesarea bitilor prin design.
Portarea acestei informaț ii permite anumite optimizări pentru timpul de execuț ie:
2. Compilerul Compose 43

Poate sări pesteegalcomparatii pentru a verifica daca un parametru de intrare s-a schimbat fata de ultima sa valoare
valoare stocată – din compoziț iile anterioare –. Acest lucru se întâmplă în cazurile în care parametrul de intrare
este cunoscut ca fiind static.$schimbatbitmask oferă aceste informaț ii. Să presupunem că acest parametru
este unȘ irliteral ca în fragmentul de mai sus, o constantă sau ceva similar. Biturile pe acest subiect
flagul va spune timpul de execuț ie că valoarea este cunoscută la timpul compilării, prin urmare nu se va schimba niciodată
la timpul de execuț ie, iar prin urmare, timpul de execuț ie poate evita să-l compare vreodată.

De asemenea, există ș ansa de a ș ti deja că parametrul s-a schimbat de la ultima dată.


compoziț ie. Când timpul de execuț ie ș tie deja acest lucru dinainte, nu are rost să compari
nici noi nu ș tim, dar ș tim că s-a întâmplat. În acest caz, reconstrucț ia va fi declanș ată cu siguranț ă, aș a că
nu putem stoca în siguranț ă valoarea în tabelul sloturilor, putem pur ș i simplu să o ignorăm.
• Pentru orice alte cazuri, statul este considerat „nesigur”, aș a că timpul de execuț ie poate continua.
compară-l - folosindegal– ș i stocaț i-l în tabelul de sloturi, astfel încât să putem găsi întotdeauna cele mai recente.
rezultatul mai târziu. Valoarea bitului pentru acest caz este0,
care este cazul implicit. Când 0a fost trecut pentru
the$changed parametrul prin care spunem runtime-ului să facă toată munca (să nu ia scurtături).

Iată un exemplu de cum arată corpul unei funcț ii compozabile după injectare$changed param
ș i logica necesară pentru a o gestiona:
[Link]

1 @Componibil
2 distractivHeader(text: String,$composer: Composer<*>,$changed: Int) {

3 variabilă$dirty =$changed

4 dacă($changed ș i0b0110 ===0) {


5 $dirty =$dirty ordacă($[Link](text))0b0010altfel0b0100
6 }
7 dacă(%murdar ș i 0b1011 xor 0b1010 !== 0 || !$[Link])
8 f(text)// execută corpul
9 } altfel{
10 $[Link]()
11 }
12 }

Există o mică dans acolo, dar încercând să rămânem agnostici faț ă de detaliile de nivel inferior, putem vedea cum
o variabilă locală$murdareste utilizat. Această variabilă stochează dacă parametrul s-a schimbat sau nu, ș i că
este determinat atât de$changed parametru bitmask ș i, în caz că este nevoie, valoarea anterioară
stocat în tabela de sloturi. Dacă valoarea este considerată „murdară” (s-a schimbat), corpul funcț iei este apelat
(recompoziț ionat). În caz contrar, Composable-ul va sări peste recompoziț ie.

Recompoziț ia dată poate avea loc de multe ori, purtând informaț ii despre cum este starea de intrare.
evoluează poate salva destul de mult timp de calcul.
La fel cum ob ț inem Composable-ul nostru $schimbatparametru transmis de apelant, acesta
Composable are, de asemenea, responsabilitatea de a transmite orice informaț ie pe care o are despre orice parametru.
2. Compilatorul Compose 44

a fost transmis pe copac. Acest lucru se numeș te „propagarea comparaț iei”. Acestea sunt informaț iile pe care le avem disponibile
în corp –durante compunere–, aș a că, dacă ș tim deja că un input s-a schimbat, este static, sau
oricum, putem transmite acea informaț ie către$schimbatparametru al oricărui Composable copil care
se întâmplă să reia acel parametru.

Dacă vrei să aprofundezi mai mult acest subiect, existăniș te videoclipuri frumoaseexplicarea fundaț iilor
de aceasta, plus inferenț a de stabilitate de Leland Richardson pe care ai putea dori să o urmăreș ti.
[Link]

Parametrii implici
O altă informaț ie suplimentară de metadate care este adăugată fiecărei funcț ii Composable la timpul compilării este/sunt
the$defaultparametru/i.
Suportul pentru argumentele implicite în Kotlin nu este utilizabil pentru argumentele funcț iilor Composable, deoarece
Funcț iile compozabile au nevoie să execute expresiile implicite pentru argumentele lor în interiorul
domeniul (grupul generat) al funcț iei. Pentru a face acest lucru, Compose oferă o implementare alternativă
al mecanismului de rezolvare a argumentelor implicite.

Compose reprezintă argumentele implicite folosind un$defaultparametru bitmask care mapează fiecare
indexul parametrului la un bit pe masca. Cam ca ceea ce s-a făcut pentru$schimbatparametru/i. Acolo
este de asemenea unul$defaultparam fiecareninput parameters with default values. This bitmask provides
informaț ii despre dacă parametrii au o valoare furnizată la locul apelului sau nu, pentru a determina
dacă trebuie folosită expresia implicită.
Am extras acest exemplu din documentele bibliotecii care arată foarte clar cum o funcț ie Composable
arată înainte ș i după$defaultbitmask este injectat, plus codul pentru a-l citi ș i a folosi valoarea implicită
valoarea parametrului dacă este necesar.

[Link]

1 // Before compiler (sources)


2 @Composable distrac ț ieA(x: Int =0) {
3 f(x)
4 }
5
6 // După compilator
7 @Compozabildistrac ț ieA(x: Int,$changed: Int,$default: Int) {
8 // ...
9 valoarex = dacă($default ș i 0b1 != 0) 0altcevax

10 f(x)
2. Compilatorul Compose 45

11 // ...
12 }

Încă o dată există un dans al bitilor, dar comparaț ia verifică pur ș i simplu$default mască de biț i la
implicit0sau păstrează valoarea trecută pentrux.

Generarea grupului de control al fluxului

Compilatorul Compose de asemenea introduce agroupon corpul fiecărei funcț ii Composable. Există
diferite tipuri de grupuri care pot fi generate în funcț ie de structurile de control ale fluxului găsite
în interiorul corpului:

• Grupuri înlocuibile.
Grupuri mobile.
• Restartable groups.

Funcț iile compuse ajung să emită grupuri în timpul execuț iei, iar acele grupuri învăluie toate informaț iile relevante
informaț ii despre starea actuală a apelului Composable. Aceasta permite Compoziț iei să ș tie
how to cleanup any written data when the group needs to be replaced (replaceable groups), move the
datele în jurul păstrării identităț ii Composable tot timpul (grupuri mutabile), sau repornire
funcț ia în timpul recompunerii (grupuri restartabile).
La sfârș itul zilei, timpul de execuț ie trebuie să ș tie cum să gestioneze fluxul de control pe baza
informaț ii pe care Compoziț ia le-a stocat în memorie.
Grupele poartă, de asemenea, informaț ii despre poziț ia apelului în surse. Ele învăluie o perioadă de
text în surse ș i să aibă o cheie generată folosind poziț ia apelului ca unul dintre factorii săi. Aceasta
permite stocarea grupului ș i deblochează memoizarea poziț ională.

Grupuri înlocuibile

Cu câteva secț iuni în urmă, am explicat că corpul unei lambda Composable este împachetat automat.
prin inserarea unui apel la o fabrică de funcț ii Composable care primeș te informaț ii precum$composer,
the generated $key, ș i expresia lambda Composable efectivă, printre altele.
Aș a arată funcț ia fabrică în cod:
CONTENTS

...................................................................... 81

82

83

84
Introducerea efectelor secundare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
Efecte secundare în Compose 85
Ce avem nevoie87
Manipulatoare de efecte 87
Efecte nesuspendate. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
Efecte suspendate. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 91
Adaptatoare de biblioteci terț e 93

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Compose runtime vs Compose UI. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
Compoziț ia grafica vectorială 98
Construirea imaginii vectoriale a copacului 100
Integrarea compunerii vectoriale în Compose UI . . . . . . . . . . . . . . . . . . . . . . . . . 104
Gestionarea DOM-ului cu Compose . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
Compoziț ie independentă în browser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
Concluzie113
2. Compilatorul Compose 47

12 Wat(%composer,0)
13 %[Link]()
14 }

Apelul Composable va emite, de asemenea, un grup substituibil care va fi stocat în Compoziț ie.
Grupele sunt ca un copac. Fiecare grup poate conț ine un număr nelimitat de grupuri [Link]
de asemenea, un Composable, compilatorul va insera ș i un grup pentru acesta.
Cu câteva secț iuni în urmă, am folosit următorul exemplu pentru a arăta cum identitatea poate fi păstrată de asemenea.
bazat pe poziț ia unui apel Composable, astfel încât runtime-ul să poată înț elege aceste două apeluri Textca
diferit
[Link]
1 dacă(condi ț ie) {
2 Bună
3 } altfel{
4 Lume
5 }

O funcț ie Composable care face o logică condiț ională ca aceasta va emite de asemenea un grup înlocuibil.
astfel că stochează un grup care poate fi înlocuit ulterior atunci când condiț ia se schimbă.

Grupuri mobile
Acestea sunt grupuri care pot fi reordonate fără a pierde identitatea. Acestea sunt necesare doar pentru corp.
decheieapeluri în acest moment. Recapitulând puț in un exemplu pe care l-am folosit în capitolul anterior:
[Link]
1 @Compozabil
2 distractivTalksScreen(talks: List<Talk>) {

3 Coloană {
4 pentru(vorbe ș teîndiscu ț ii) {

5 key([Link]) { // Cheie unică


6 Vorbeș te(vorbi)
7 }
8 }
9 }
10 }

Împachetându-neVorbeș teîncheiecomposable asigură că i se oferă o identitate unică fără excepț ie.


Când învăluim Composable-urile cucheie, un grup mobil este generat. Acesta va ajuta la reorganizarea oricărei
al apelurilor fără riscul de a pierde identitatea articolelor.
Here is an example of how a Composable using cheieeste transformat:
2. Compilatorul Compose 48

[Link]

1 // Înainte de compilator (surse)


2 @Compozabil
3 distrac ț ieTest(valoare: Int) {

4 key(value) {
5 Wrapper {
6 Frunză("Valoare ${'$'}valoare")
7 }
8 }
9 }
10
11// După
12@Compozabil
13distrac ț ieTest(valoare: Int, %compozitor: Composer?, %schimbat: Int) {
14 // ...
15 %[Link](<> , valoare)
16 Wrapper(composableLambda(%composer, <>,adevărat) { %composer: Composer?, %changed: In\
17t
18 Frunza("Valoare %value", %composer,0)
19 }, %compozitor,0b0110)
20 %[Link]()
21 // ...
22 }

Grupuri reîncepătoare

Grupurile restartabile sunt probabil cele mai interesante. Acestea sunt inserate doar pentru restartabile.
Funcț ii compozabile. Ele înveleș te de asemenea apelul corespunzător Composable, dar pe deasupra, ele
extindesfârș itapelează puț in astfel încât să returneze o valoare nullable. Această valoare va finull only when
corpul apelului Composable nu citeș te stări care ar putea varia, prin urmare recompunerea
nu va fi niciodată necesar. În acest sens, nu este nevoie să învăț ăm runtime-ul cum să recompună acest lucru.
Compozabil. Altfel, dacă returnează o valoare non-null, compilatorul generează un lambda care învaț ă
the runtime how to “restart” (re-execute) the Composable and therefore update the Composition.
Aș a arată în cod:
2. Compilatorul Compose 49

[Link]
1 // Înainte de compilator (sursă)
2 @ComposabledistractivA(x: Int) {
3 f(x)
4 }
5
6 // După compilator
7 @Composable
8 distrac ț ieA(x: Int,$composer: Composer<*>,$changed: Int) {

9 $[Link]()
10 // ...
11 f(x)
12 $[Link]()?.updateScope { următor ->
13 A(x, următor,$modificat sau 0b1)
14 }
15 }

See how the update scope for recomposition contains a new call to the same Composable.
Acesta este tipul de grup generat pentru toate funcț iile Composable care citesc dintr-o stare.
Before wrapping this section I want to add some extra reasoning applied by the Compiler to
blocuri executabile găsite pentru a genera diferitele tipuri de grupuri. Acest lucru a fost extras din
documentele oficiale:

• Dacă un bloc se execută exact 1 dată întotdeauna, nu sunt necesare grupuri.


• Dacă un set de blocuri este astfel încât exact unul dintre ele este executat odată (de exemplu, rezultatul
blocuri ale unei instrucț iuni if, sau o clauză when), atunci introducem un grup înlocuibil în jurul fiecăruia.
bloc. Avem logică condiț ională.
• O grupă mobilă este necesară doar pentru conț inutul lambda alcheieapeluri.

Generarea de Klib ș i decoy

Există suport specific [Link](multiplatform) ș i Kotlin/JS adăugat la compilatorul Compose. Acesta


a fost necesar din cauza modului în care IR pentru dependenț e este deserializat în JS, deoarece nu va putea să se potrivească
semnăturile tipurilor odată ce IR-ul este transformat -aminteș te-ț i că Compose adaugă parametri sintetici suplimentari
declaraț ii de funcț ii Composable ș i apeluri de funcț ii Composable–.
În această privinț ă, Compose evită înlocuirea IR pentru funcț ii în Kotlin/JS – aș a cum face pentru JVM – ș i
creează copii în schimb. Va păstra declaraț ia funcț iei originale pentru a avea o conexiune
între fiecare funcț ie din metadata Kotlin ș i IR-ul său, iar toate referinț ele din cod pot fi în continuare rezolvate
foarte bine, iar apoi IR-ul pentru copie va fi ajustat de Compose, după cum este necesar. Pentru a diferenț ia ambele
la rulare a$composable un sufix va fi adăugat la nume.
2. Compilatorul Compose 50

[Link]

1 // Înainte de compilator (surse)


2 @Compozabil
3 distrac ț ieCounter() {

4 ...
5 }
6
7 // Transformat
8 @Decoy(...)
9 distrac ț ieContor() {// semnătura este păstrată la fel

10 illegalDecoyCallException("Counter")
11 }
12
13@ImplementareDecoy(...)
14distrac ț ieCounter$composable(// semnătura a fost schimbată
15 $composer: Composer,
16 $changed: Int
17) {
18 ...cod transformat...
19 }

Dacă vrei să aprofundezi această susț inere [Link] ș iKotlin/JSÎț i recomand să citeș tiacesta
postare grozavă de Andrei Shikov⁴.
⁴[Link]
3. Runtime-ul Compose
Nu cu mult timp în urmăAm postat un rezumat despre cum funcț ionează arhitectura Compose intern.⁵, acoperind
comunicarea între UI, compilator ș i runtime.

Compune un tweet despre arhitectură

Acest fir de Twitter poate funcț iona grozav ca o introducere pentru acest capitol, deoarece a oferit o perspectivă de ansamblu.
peste unele dintre cele mai importante puncte pe care trebuie să le înț elegem. Acest capitol este axat pe
pe runtime-ul Jetpack Compose, dar va întări harta noastră mentală despre cum diferitele
părț ile Compose comunică ș i colaborează. Dacă te simț i curios, ț i-aș recomanda să citeș ti
Fir Twitter înainte de a merge mai departe.

Acest fir a explicat cum funcț iile Composable emit modificări în Compoziț ie, aș a că Compo-
situaț ia poate fi actualizată cu toate informaț iile relevante, ș i cum se întâmplă acest lucru prin intermediul actual
injectat$composerinstanț ă datorită compilatorului (vezi capitolul 2). Apelul pentru a obț ine curentul
Compozitorinstanț a ș i Compoziț ia în sine fac parte din timpul de execuț ie Jetpack Compose.

Firul a rămas intenț ionat puț in deasupra suprafeț ei, deoarece Twitter probabil nu este cel mai bun loc
a explora în profunzime un subiect ca acesta. Această carte este o ocazie foarte bună pentru a face asta.

Până acum, am făcut referire la starea menț inută în memorie de runtime ca „Compoziț ia”.
Acesta este un concept intenț ionat superficial. Să începem prin a învăț a despre structurile de date utilizate pentru
stochează ș i actualizează starea Compoziț iei.
⁵[Link]
3. Rularea Compose 52

Tabelul de sloturi ș i lista schimbărilor


Am observat o oarecare confuzie în jurul diferenț ei dintre aceste două structuri de date,
probabil datorită lipsei curente de literatură despre internele Compose. Până astăzi, îl consider
necesar să clarificăm acest lucru mai întâi.

Tabelul slot este o structură optimizată în memorie pe care timpul de execuț ie o foloseș te pentru a stoca starea curentă
al Compoziț iei. Este umplut cu date în timpul compunerii iniț iale ș i se actualizează cu fiecare
recompoziț ie. Ne putem gândi la ea ca la o urmă a tuturor apelurilor funcț iilor Composable, inclusiv a lor
locaț ie în surse, parametrii, valori memorate,Compoziț ieLocalăs, ș i altele. Tot ce
s-a întâmplat în timpul compunerii este acolo. Toate aceste informaț ii sunt folosite ulterior de către Compozitorla
produce următoarea listă de modificări, deoarece orice modificări aduse copacului vor depinde întotdeauna de situaț ia actuală
starea Compoziț iei.
În timp ce tabela sloturilor înregistrează starea, lista de modificări este ceea ce face modificările efective la nod.
arbore. Poate fi înț eles ca un fiș ier patch care, odată aplicat, actualizează arborele. Toate modificările la
schimbările trebuie să fie înregistrate ș i apoi aplicate. Aplicarea modificărilor pe listă este o responsabilitate a
theAplicant, care este o abstracț ie de care runtime-ul se bazează pentru a materializa în cele din urmă copacul. Noi
voi reveni la toate acestea în detaliu mai târziu.
În cele din urmă,Recompozitorcoordonează totul, decidând când ș i pe ce fir să recompozeze, ș i
când ș i pe ce fir să aplici modificările. De asemenea, mai multe despre acest lucru mai târziu.

Masa de sloturi în profunzime

Să învăț ăm cum este starea Compoziț iei stocată. Tabelul de sloturi este o structură de date optimizată
pentru acces rapid liniar. Se bazează pe ideea unui „buffer de gol”, foarte comun în editorii de text. Acesta stochează
datele în două array-uri liniare din acest motiv. Unul dintre aceste array-uri păstrează informaț iile despre
grupurile disponibile în Compoziț ie, celălalt stochează sloturile care aparț in fiecărui grup.
[Link]
1 vargroups = IntArray(0)
2 set protected
3
4 varslots = Array<Any?>(0) { nul}
5 setare privată

În capitolul 2 am învăț at cum compilatorul înveleș te corpurile funcț iilor Composable pentru a le face să emită
grupuri în schimb. Aceste grupuri vor oferi identitate Composable-ului odată ce este stocat în memorie
(cheie unică), astfel încât să poată fi identificată mai târziu. Grupurile învăluie toate informaț iile relevante pentru
Apelul compus ș i copiii săi, ș i furnizaț i informaț ii despre cum să trataț i Composable-ul (ca
un grup). Grupurile pot avea un tip diferit în funcț ie de modelele de flux de control găsite în interiorul
3. RUNTIME-ul Compose 53

Corp compus: Grupuri reutilizabile, grupuri mutabile, grupuri înlocuibile, grupuri reutilizabile…

Aria grupurilor foloseș teIntvaloarea deoarece va stoca doar „câmpuri de grup”, care reprezintă meta-date
for the groups. Parent and child groups are stored in the form of group fields. Given it’s a linear
în structura de date, câmpurile grupului unui grup părinte vor veni întotdeauna primele, iar câmpurile grupului de toate
copiii săi vor urma. Aceasta este o modalitate liniară de a modela un arbore de grup, ș i favorizează o scanare liniară a
copiii. Accesul aleator este scump, cu excepț ia cazului în care se face printr-un ancoraj de [Link] ca
puncț i care există din acest motiv.
Pe de altă parte, matricea slots stochează datele relevante pentru fiecare dintre aceste grupuri. Stochează
valori de orice tip ( Oricare?), deoarece este destinat să stocheze orice tip de informaț ie. Aici este unde se află efectiv
Datele de compunere sunt stocate. Fiecare grup stocat îngrupuriarray descrie cum să găseș ti ș i să interpretezi
îș i alocă sloturile însloturiarray, deoarece un grup este întotdeauna legat de o gamă de sloturi.

Masa de slot se bazează pe un interval pentru a citi ș i a scrie. Gândiț i-vă la ea ca la un interval de poziț ii din masă.
Această fereastră se miș că ș i determină de unde sunt citite datele ș i unde sunt scrise în array-uri când
vine timpul. Spaț iul are un pointer pentru a indica de unde să înceapă scrierea ș i poate muta începutul său ș i
poziț ii finale, astfel încât datele din tabel pot fi de asemenea suprascrise.

Imagine masă slot

Imaginaț i-vă o logică condiț ională ca aceasta:


3. Timpul de execuț ie Compose 54

[Link]

1 @Compozabil
2 @NonRestartableComposable
3 distrac ț ieTextConditionat() {

4 dacă(a) {
5 Text(a)
6 } altfel{
7 Text(b)
8 }
9 }

Având în vedere că acest Composable este marcat ca non-restartabil, va fi inserat un grup înlocuibil (în loc)
al unui restartabil). Grupul va stoca date în tabel pentru „copilul” activ curent. Aceasta va fi
Text(a), în caz căaesteadevăratCând
condiț ia se schimbă, intervalul se va muta înapoi în poziț ia de început
grupului, ș i va începe să scrie de acolo, suprascriind toate aceste sloturi cu datele pentruText(b).
Pentru a citi din ș i a scrie în tabel, avemCititor de sloturiș iSlotWriterTabela de sloturi poate
au mai mulț i cititori activi, dar un singur scriitor activ. După fiecare operaț iune de citire sau scriere,
cititorul sau scriitorul corespunzător se închide. Oricât de mulț i cititori pot fi deschiș i, dar tabela poate
poate fi citit doar în timp ce nu este scris, pentru siguranț ă. TheTabelSlotrămâne invalid până la
scriitorul activ este închis, deoarece va modifica grupurile ș i sloturile direct ș i asta ar putea duce la
condiț ii de competiț ie dacă încercăm să citim din ea în acelaș i timp.
Un cititor lucrează ca vizitator. Acesta ț ine evidenț a grupului curent care este citit din array-ul grupurilor, acesta
poziț iile de început ș i de sfârș it, părintele său (depozitat imediat înainte), slotul curent din grupul fiind
citeș te, numărul de sloturi pe care grupul le are... etc. Cititorul poate repoziț iona, sări peste grupuri, să citească valoarea
din slotul curent, citeș te valori din indecș i specifici ș i alte lucruri asemănătoare. Cu alte cuvinte,
este folosit pentru a citi informaț ii despre grupuri ș i sloturile lor din array-uri.
Scriitorul, pe de altă parte, este folosit pentru a scrie grupuri ș i sloturi în tablouri. Aș a cum s-a explicat mai sus,
poate scrie date de orice tip –Orice?- la masă. TheScriitorSlotse bazează pe lacunele menț ionate mai sus
pentru grupuri ș i sloturi, astfel că le foloseș te pentru a determina unde să scrie (poziț iile) în cadrul arrays-urilor.

Gândeș te-te la un interval ca la un segment glisant ș i redimensionabil de poziț ii pentru un array liniar. Scriitorul păstrează
urmăreș te poziț iile de început ș i de sfârș it, precum ș i lungimea fiecărei lacune. Poate muta lacuna actualizând
poziț iile sale de început ș i de sfârș it.

Scriitorul poate adăuga, înlocui, muta ș i elimina grupuri ș i locuri. Gândeș te-te la adăugarea unei noi
Nod compus în arbore, sau Composable sub logici condiț ionale care ar putea necesita înlocuirea
când condiț ia se schimbă, de exemplu.
Scriitorul poate sări peste grupuri ș i sloturi, avansa cu o anumită cantitate de poziț ii, căuta o poziț ie
determinat de unAncoră, ș i multe alte operaț iuni similare.
Păstrează evidenț a unei liste deAncoraindicând spre indici specifici pentru acces rapid prin tabel.
Poziț ia fiecărei grupe - numită ș i index de grup - în tabel este de asemenea urmărită printr-unAncoră.
3. Rularea Compose 55

Ancorăeste actualizat atunci când grupurile sunt mutate, înlocuite, inserate sau eliminate înainte de poziț ia care
Ancorăindică către.
Tabelul slot funcț ionează de asemenea ca un iterator al grupurilor de compunere, astfel încât poate oferi informaț ii despre
le instrumentelor astfel încât acestea să fie capabile să inspecteze ș i să prezinte detalii despre Compoziț ie.

Acum este timpul să aflăm despre lista de schimbări.

Pentru mai multe detalii despre tabela cu sloturi, recomand să citiț iaceastă postare de Leland Richardsondin
Echipa Jetpack Compose.
[Link]

Lista modificărilor
We have learned about the slot table, how it allows the runtime to keep track of the current state of
Compoziț ia. Corect, dar care este exact rolul listei de modificări atunci? Când este produsă?
ce modelează? când sunt aplicate aceste schimbări ș i din ce motiv? Încă avem destul de
few things to clarify. This section will be adding another piece to the puzzle. Let’s try to put things
în ordinea.
De fiecare dată când are loc o compunere (sau recompunere), funcț iile Composable din sursele noastre
sunt executate ș i emit. "Emiterea", am folosit acel cuvânt de multe ori deja. Emiterea înseamnă
creând modificări amânate pentru a actualiza tabela slot ș i, în cele din urmă, ș i arborele materializat. Acele
Schimbările sunt stocate ca o listă. Generarea acestei noi liste de schimbări se bazează pe ceea ce este deja stocat.
în tabela de sloturi. Aminteș te-ț i: Orice modificări în arbore trebuie să depindă de starea curentă a
Compoziț ie.

Un exemplu al acestui lucru poate fi mutarea unui nod. Imaginează-ț i reorganizarea Composable-urilor unei liste. Trebuie să
verifică unde a fost plasat acel nod înainte în tabel, elimină toate acele sloturi ș i scrie-le din nou
dar începând de la o nouă poziț ie.

Cu alte cuvinte, de fiecare dată când un Composable emite, se uită la tabelul de sloturi, creând un amânat.
schimbă în funcț ie de nevoi ș i informaț iile actuale disponibile, ș i adaugă-le într-o listă cu
toate schimbările. Mai târziu, când Compoziț ia este terminată, va fi momentul pentru materializare, iar aceș tia
schimbările înregistrate se vor executa efectiv. Atunci când acestea actualizează efectiv tabelul sloturilor cu
cele mai proaspete informaț ii disponibile despre Compoziț ie. Acest proces este ceea ce face ca emisia
Procesează foarte repede: Pur ș i simplu creează o acț iune amânată care va aș tepta să fie executată.
În urma acestei observaț ii, putem vedea cum lista de schimbări este ceea ce face în cele din urmă modificările în tabel. Aș a-i?
după aceea, va notifica Aplicatorul să actualizeze copacul de noduri materializate.
3. Runtimul Compose 56

Aș a cum am spus mai sus,Recomposerorchestraează acest proces ș i decide ce fir să compună sau
recomandă pe, ș i ce fir să foloseș ti pentru a aplica modificările din listă. Acesta din urmă va fi, de asemenea
contextul implicit folosit deEfect lansata rula efecte.
Ș i cu asta, avem o viziune mai clară asupra modului în care modificările sunt înregistrate, amânate ș i, în cele din urmă
executat, ș i de asemenea cum toată starea este stocată în tabelele de slot. Acum este un moment bun să învăț ăm despre
theCompozitor.

Composerul

Injectat$compozitoreste ceea ce leagă funcț iile Composable pe care le scriem de Compose


timp de execuț ie.

Hrănirea Compozitorului

Să explorăm cum sunt adăugate nodurile în reprezentarea în memorie a arborelui. Putem folosiAspect
Compozabil pentru a direcț iona [Link] instalarea tuturor componentelor UI furnizată de
Compoziț ie UI. Aș a arată în cod:
[Link]

1 @Suppress("ComposableLambdaParameterPosition")
2 @Compozabilfun inlineAspect
3 content: @Composable () -> Unit,
4 modifier: Modifier = Modifier,
5 measurePolicy: MeasurePolicy
6 ) {
7 valoaredensity = [Link]

8 vallayoutDirection = [Link]
9 ReusableComposeNode<ComposeUiNode, Applier<Any>>(
10 factory = [Link],
11 update = {
12 set(measurePolicy, [Link])
13 set(densitate, [Link])
14 set(layoutDirection, [Link])
15 },
16 skippableUpdate = materializerOf(modifier),
17 content = content
18 )
19 }
3. Runtime-ul Compose 57

AranjarefolosiriReusableComposeNodea emite unNodDeLayoutîn compoziț ie. Dar chiar dacă aceasta ar putea
pare că creează ș i adaugă nodul imediat, ceea ce face cu adevărat este să înveț e timpul de execuț ie
cum să creezi, să iniț ializezi ș i să inserezi nodul la locaț ia curentă în Compoziț ie când
timpul vine. Iată codul:
[Link]

1 @Compozabil
2 fun inline<T,reificatE : Applier<*>> ReusableComposeNode(
3 fără liniefactory: () -> T,
4 actualizare: @DisallowComposableCalls Updater<T>.() -> Unitate,
5 fără liniile de bazăskippableUpdate:@Composable SkippableUpdater<T>.() -> Unit,
6 content: @Composable () -> Unit
7 ) {
8 // ...
9 [Link]()
10 // ...
11 [Link](factory)
12 // ...
13 Updater<T>(composerulCurent).actualizează() // ini ț ializare
14 // ...
15 [Link](0x7ab4aae9)
16 conț inut()
17 [Link]()
18 [Link]()
19 }

Omit câteva păr ț i care nu sunt (încă) relevante, dar observă cum delegă totul către
currentComposer instan ț ă. De asemenea, putem observa cum folose ș te ocazia de a începe un lucru înlocuibil.
grup pentru a înfăș ura conț inutul acestui Composable atunci când este stocat. Orice copii emise în interior
thecon ț inutlambda va fi stocat practic ca copii ai acestui grup (ș i prin urmare ș i
Compozabil) în Compoziț ie.
Aceeaș i operaț iune de emitere se face pentru orice alte funcț ii Composable. Veziaminteș te-ț ide exemplu:
[Link]

1 @Compozabil
2 fun ț ie inlineT> remember(calcul: @DisallowComposableCalls () -> T): T =
3 [Link](invalid = fals, calculatie)

Theaminte ș te-ț i Func ț ia compusă folose ș te currentComposer a salva (a re ț ine) valoarea


returnat de lambda furnizat în compoziț ie. Ainvalid parametrul forț ează o actualizare
pentru valoarea indiferent de faptul că a fost stocată anterior. Thecachefuncț ia este codificată astfel:
3. Rularea Compose 58

1 @ComposeCompilerApi
2 fun în linie<T> [Link](invalid: Boolean, block: () -> T): T {
3 întoarcererememberedValue().let {

4 dacă(invalid || it === [Link]) {


5 valoarevalue = block()

6 updateRememberedValue(value)
7 value
8 } altfeleste
9 } caT
10 }

Mai întâi, caută valoarea în Compoziț ie (tabelul sloturilor). Dacă nu este găsită, va emite modificări.
pentru a programa o actualizare pentru valoare (sau, cu alte cuvinte, înregistrare). În caz contrar, va returna valoarea
ca atare.

Modelarea Schimbărilor
Aș a cum s-a explicat în secț iunea anterioară, toate operaț iunile de emiț ie delegate cătrecurrentComposer
sunt modelate intern caSchimbarecare sunt adăugate la o listă. ASchimbăeste o funcț ie amânată cu acces
la curentApllicantș iSlotWriteraminteș te-ț i că există un singur scriitor activ deodată). Să
uita-te la el în cod:
[Link]

1 interntipalias Schimbare = (
2 applier: Applier<*>,
3 slots: SlotWriter,
4 rememberManager: RememberManager
5 ) -> Unitate

Aceste schimbări sunt adăugate la listă (înregistrate). Acț iunea de "emisie" în esenț ă înseamnă a crea
acesteaChanges, care sunt lambda amânate pentru a adăuga, elimina, înlocui sau muta noduri din
tabela de sloturi, ș i prin urmare notificăAplicant(astfel încât aceste schimbări să poată fi materializate).
Din acest motiv, ori de câte ori vorbim despre "emisia de modificări", am putea folosi ș i cuvintele "înregistrare"
schimbări
După compunere, odată ce toate apelurile funcț iilor Composable sunt complete ș i toate modificările sunt înregistrate,
toate acestea vor fi aplicate într-un lot de către aplicator.

Compoziț ia în sine este modelată cuCompoziț ieclasa. O păstrăm deoparte pentru moment,
since we will learn about the composition process in detail in the sections to come later in this
3. Rularea Compose 59

capitol. Să învăț ăm câteva detalii suplimentare despre Composer mai întâi.

Optimizarea momentului în care să scrii

Aș a cum am învăț at mai sus, inserarea de noduri noi este delegată Composer-ului. Asta înseamnă că întotdeauna
ș tie când este deja implicat în procesul deinserarenoi noduri în compoziț ie.
Când aceasta este situaț ia, Composerul poate accelera procesul ș i poate începe să scrie în tabela sloturilor imediat
departe când schimbările sunt emise, în loc să le înregistreze (adăugându-le la lista care trebuie interpretată
mai târziu). În alt caz, aceste modificări sunt înregistrate ș i amânate, deoarece nu este momentul să fie făcute
încă.

Grupuri de scriere ș i lectură

Odată ce Compoziț ia este finalizată,[Link]()este în sfârș it chemat să materializeze copacul,


ș i modificările sunt scrise în tabela de sloturi. Composerul poate scrie diferite tipuri de informaț ii:
date, noduri sau grupuri. Ceea ce se spune, toate acestea sunt în cele din urmă stocate sub formă de grupuri pentru simplicitate.
Ei au pur ș i simplu câmpuri de grup diferite pentru diferenț iere.
Compozitorul poate "începe" ș i "termina" orice grup. Acest lucru are semnificaț ii diferite în funcț ie de
acț iuni întreprinse. Dacă este scriere, va reprezenta "grup creat" ș i "grup eliminat" din
masa de sloturi. Dacă citeș te, atunciCititor de sloturiva fi solicitat să îș i mute indicatoarele de citire în interior ș i în afara
grup pentru a începe sau a încheia citirea din acesta.

Nodurile de pe copacul Composable (în cele din urmă grupurile din tabel) nu sunt doar inserate, ci pot fi de asemenea
eliminat sau mutat. Eliminarea unui grup înseamnă eliminarea acestuia ș i a tuturor sloturilor corespunzătoare din
tabel. Pentru a face asta,Compozitorcere să reprogramezeCititor de sloturiîn consecinț ă ș i faceț i-l să sară peste
grup (deoarece nu mai există), ș i operaț ii de înregistrare pentru a elimina toate nodurile sale din aplicator.
Orice acț iuni de modificare trebuie planificate (înregistrate) ș i aplicate ulterior ca un lot, aș a cum s-a explicat.
deasupra, în principal pentru a se asigura că toate au sens împreună. Composerul va renunț a de asemenea la orice în aș teptare
invalidări pentru grupul eliminat, deoarece acestea nu se vor întâmpla niciodată.

Nu toate grupurile sunt refolosibile, înlocuibile, mobile sau reutilizabile. Printre alte lucruri care sunt de asemenea
salvate ca grupuri, putem găsi blocul de ambalare implicit. Acest bloc înconjoară valorile reț inute
pentru apelurile Composable necesare pentru a produce parametrii impliciti: de exemplu:model: Model = aminte ș te-ț i {
DefaultModel() }Aceasta este de asemenea stocată ca un grup foarte specific.

Când Compozitorul vrea să înceapă un grup, următoarele lucruri se întâmplă:


3. R runtime Compose 60

• Dacă compozitorul este în proces deinserarevalorile, va continua să scrie în slot


masă în timp ce este la asta, deoarece nu există niciun motiv să aș tepț i.
În alte cazuri, dacă există operaț iuni în aș teptare, va înregistra acele modificări pentru a fi aplicate când
aplicarea modificărilor. Aici, Composer-ul va încerca să reutilizeze grupul în cazul în care acesta există deja (în
masa).
• Când grupul este deja stocat, dar într-o poziț ie diferită (a fost mutat), o operaț iune
a muta toate sloturile pentru grup este înregistrat.
• În cazul în care grupul este nou (nu se găseș te în tabel), va trece îninseraremod, care va
scrie grupul ș i toț i copiii săi într-un intermediarinsertTable (altulTabelSlot) până la
grupul este complet. Aceasta va programa grupurile care vor fi inserate în tabela finală.
• În final, dacă Composer-ul nu inserează ș i nu există operaț iuni de scriere în aș teptare, va încerca
pentru a începe să citesc grupul.

Reutilizarea grupurilor este comună. Uneori nu este necesar să creăm un nou nod, ci putem să-l reutilizăm.
în caz că există deja. (VeziReusableComposeNodedeasupra). Aceasta va emite (înregistra) operaț iunea pentru
navigate to the node by the Aplicator, dar va sări peste operaț iile de creare ș i iniț ializare a acestuia.
Când o proprietate a unui nod necesită o actualizare, acea acț iune este, de asemenea, înregistrată ca oSchimbare.

Amintind valori

Am învăț at cum Composerul are abilitatea de aaminteș te-ț ivalori în Compoziț ie (scrie-le)
la tabela de sloturi), ș i poate actualiza aceste valori mai târziu. Comparaț ia pentru a verifica dacă s-au schimbat
din ultima compunere este făcută corect atunci cândaminteș te-ț ise numeș te, dar acț iunea de actualizare este înregistrată ca un
Schimbăcu excepț ia cazului în care compozitorul este deja în curs de inserare.

Când valoarea de actualizat este unObserverMemorează, atunci Composerul va înregistra de asemenea un implicit
Schimbarepentru a urmări acț iunea de amintire în Compunere. Acest lucru va fi necesar mai târziu când toate acele
valorile reamintite trebuie uitate.

Recomponerea domeniilor

Ceva ce se întâmplă ș i prin Composer sunt domeniile de recompunere, care permit inteligent
recompunere. Acestea sunt legate direct de grupurile de restart. Fiecare dată când un grup de restart este creat,
compunătorul creează unRecompoziț ieScoppentru aceasta ș i îl stabileș te ca pecurrentRecomposeScope pentru
Compoziț ie.

ARecomanda ț i cadrumodelează o regiune a Compoziț iei care poate fi recompusă independent


de restul Compozi ț iei. Poate fi folosit pentru a invalida manual ș i a declan ș a
recompozi ț ia unui Composable. O invalidare este solicitată prin intermediul composer-ului, cum ar fi:
[Link]ăRecompune().invalidate().
Pentru recompunere, Compunerul va poziț iona
tabelul de sloturi la locaț ia de început a acestui grup ș i apoi apelează blocul de recompunere trecut la
3. Runtima Compose 61

lambda. Acesta va invoca din nou funcț ia Composable, care va emite încă una
timp, ș i prin urmare să ceară Compozitorului să suprascrie datele sale existente în tabel.
Compozitorul menț ine unStivădintre toate domeniile de recompunere care au fost invalide. Înseamnă
ele sunt în aș teptare să fie recompuse, sau cu alte cuvinte, trebuie să fie declanș ate în următoarea recompunere.
ThecurrentRecomposeScope de fapt este obț inut aruncând o privire în aceastăStivă.
Asta spus, RecomposeScopenu sunt întotdeauna activate. Asta se întâmplă doar când Compose găseș te citire
operaț iuni dinStatinstantanee în cadrul Composable. În acest caz, Composer-ul marchează
Recompoziț ia domeniuluicautilizat, ceea ce face apelul „end” inserat la sfârș itul Composablenot
nu mai returna null ș i, prin urmare, activează lambda de recompunere care urmează (vezi mai jos, după
the? caracter).
Recompoziț [Link]

1 // După ce compilatorul inserează codul de bază


2 @Composable
3 distrac ț ieA(x: Int, $composer: Composer<*>, $changed: Int) {

4 $[Link]()
5 // ...
6 f(x)
7 $[Link]()?.updateScope { next ->
8 A(x, următor,$schimbat sau 0b1)
9 }
10 }

Compozitorul poate recompozi toate grupurile copil invalide ale grupului părinte curent când
recompunerea este necesară, sau pur ș i simplu face ca cititorul să sară grupul până la sfârș it când nu este.
secț iunea de propagare a comparaț iei din capitolul 2).

Efecte secundare în Composer

Compozitorul este, de asemenea, capabil să înregistrezeEfecte secundare. AEfect secundarîntotdeauna fuge după compoziț ie.
Ele sunt înregistrate ca o funcț ie de apelat atunci când modificările corespunzătoare arborelui au fost deja aplicate.
Ele reprezintă efecte care se întâmplă pe lateral, astfel încât acest tip de efect este complet agnostic de
ciclul de viaț ă Composable. Nu vom finaliza lucrurile ca anularea automată atunci când părăsim
Compoziț ie, nici efectele de retry asupra recompunerii. Acesta este motivul pentru care acest tip de efect nu este
stocate în tabela de sloturi ș i, prin urmare, pur ș i simplu eliminate dacă compunerea eș uează. Vom învăț a despre acest lucru
ș i scopul său în capitolul despre gestionarea efectelor. Totuș i, este interesant de observat cum sunt
înregistrat prin intermediul Composer-ului.
3. Runtimul Compose 62

Stocarea CompositionLocals
Compozitorul oferă de asemenea mijloace pentru a înregistraCompositionLocals ș i obț ine valorile sale având o cheie.
[Link] se bazează pe acesta. Un Furnizor ș i valorile sale sunt de asemenea stocate ca un grup în
masa cu sloturi, toate împreună.

Stocarea informaț iilor sursă


De asemenea, Composerul stochează informaț ii sursă sub formă deCompozi ț ieDateadunate în timpul
Composition to be leveraged by Compose tools.

Legarea compoziț iilor prin intermediul CompositionContext

Nu există o singură Compoziț ie, ci un arbore de compoziț ii ș i subcompoziț ii. Subcompoziț iile
sunt Compoziț ii create inline cu singura intenț ie de a construi o compoziț ie separată în
contextul celui actual pentru a susț ine invalidarea independentă.
O Subcompunere este conectată la compunerea sa părinte printr-un părinte Context de compunere
referinț ă. Acest context există pentru a lega Compoziț ia ș i subcompoziț iile împreună ca un arbore. Asigură
aceeaCompositionLocals ș i invalidările sunt rezolvate / propagate transparent în josul arborelui ca
dacă ar aparț ine unei singure Compoziț [Link] compuneriide asemenea, este scris în tabela de sloturi
ca un grup.
Crearea subcompunerilor se face de obicei princontextulCompoziț ieiDeReț inere:
[Link]
1 @Composable distrac ț ierememberCompositionContext(): CompositionContext {
2 returna ț [Link]()
3 }

Această funcț ie îș i aminteș te o nouă Compoziț ie la poziț ia curentă din tabela de sloturi sau o returnează în
cazul în care este deja reț inut. Este folosit pentru a crea o Subcompunere din locuri unde sunt separate
Compoziț ia este necesară, precumPictorVectorial([Link] mai devreme în acest
capitolul), unDialog, theSubcomposeLayout, aPopup, sau efectivulAndroidView, care este un wrapper
pentru a integra vizualizările Android în arborii Composable.

Accesarea instantaneului curent al stării

Compozitorul are o referinț ă la snapshot-ul curent, ca într-un snapshot al valorilor returnate de


stări mutable ș i alte obiecte de stare pentru firul de execuț ie curent. Toate obiectele de stare vor avea aceeaș i
valoarea în instantaneu aș a cum aveau când a fost creat instantaneul, cu excepț ia cazului în care sunt modificate explicit
în instantaneu. aceasta va fi extinsă în capitolul despre gestionarea stării.
3. Runtime-ul Compose 63

Navigând prin noduri


Navigarea arborelui de noduri este realizată de aplicator, dar nu direct. Aceasta se face prin înregistrarea tuturor
locaț iile nodurilor pe măsură ce sunt traversate de cititor ș i înregistrându-le într-o noduriCăzute
array. When the node navigation is realized all the downs in the down nodes is played to the applier.
Dacă un 'up' este înregistrat înainte ca 'down'-ul corespunzător să fie realizat, atunci acesta este pur ș i simplu eliminat din
stiva nodurilor jos, ca un scurtcircuit.

Menț inerea sincronizării între cititor ș i scriitor

Aceasta este puț in la un nivel scăzut, dar având în vedere că grupurile pot fi inserate, ș terse sau mutate, locaț ia unui grup
în scriitor ar putea diferi faț ă de locaț ia sa în cititor pentru o perioadă (până când modificările sunt aplicate). Asta
face necesar să menț ii un delta pentru a urmări diferenț a. Acest delta este actualizat cu inserț ii, ș tergeri,
ș i se miș că, ș i reflectă „distanț a nerealizată pe care scriitorul trebuie să o parcurgă pentru a se potrivi cu slotul actual în
cititorul" conform a ceea ce spun documentele.

Aplicarea modificărilor

Aș a cum am menț ionat de multe ori în acest capitol,Aplicatoreste responsabil să facă asta. Actualul
Compozitordelegează această abstracț ie pentru a aplica toate modificările înregistrate după Compoziț ie. Acest
este ceea ce cunoaș tem ca „materializare”. Acest proces execută lista de Schimbări ș i, ca rezultat,
actualizează tabela sloturilor ș i interpretează datele de compoziț ie stocate în ea pentru a obț ine un rezultat eficient.

Runtime-ul este agnostic în ceea ce priveș te modul în care ...Aplicanteste implementat. Se bazează pe un contract public care
se aș teaptă ca bibliotecile client să implementeze. Acesta este motivul pentru careAplicatoreste un punct de integrare cu
platforma, astfel încât va varia în funcț ie de cazul de utilizare. Acest contract arată astfel:

[Link]

1 aplicatorInterfa ț ă<N> {
2 valoareN

3 distrac ț ieonBeginChanges() {}
4 distrac ț ieonEndChanges() {}

5 distrac ț iejos(nod: N)

6 distrac ț iesus()

7 distrac ț ieinseraSusJos(indice: Int, instanț ă: N)

8 distractivinserareSubMediu(index: Int, instanț ă: N)

9 distrac ț ieelimină(index: Int, count: Int)

10 distrac ț iemove(from: Int, to: Int, count: Int)

11 distrac ț ieclara()

12 }
3. Runtima Compose 64

Primul lucru pe care-l vedem este Nparametru de tip în declaraț ia contractului. Acesta este tipul pentru
nodi pe care îi aplicăm. De aceea, compunerea poate funcț iona cu grafuri de apeluri generice sau arbori de noduri.
este întotdeauna agnostic faț ă de tipul de noduri utilizate. AcestAplicatoroferă
operaț iuni pentru a traversa arborele,
insera, elimina sau muta noduri, dar nu îi pasă de tipul acestor noduri sau de modul în care acestea
sunt în cele din urmă inserate. Spoiler: Aceasta va fi delegată nodurilor în sine.
Contractul defineș te, de asemenea, cum se elimină toț i copiii dintr-un anumit interval din nodul curent, sau
mută copiii de la nodul curent pentru a le schimba poziț iile. A clear operaț iunea defineș te cum să
indică spre rădăcină ș i elimină toate nodurile din copac, pregătind atâtAplicantiar rădăcina sa să fie
folosit ca ț intă pentru o nouă compoziț ie în viitor.
TheAplicatortraversează întregul arbore vizitând ș i aplicând toate nodurile. Arborele poate fi parcurs
de sus în jos sau de jos în sus. Întotdeauna păstrează o referinț ă a nodului curent
vizitarea ș i aplicarea modificărilor. Are apeluri pentru a începe ș i a încheia aplicarea modificărilor pe care le Compozitor
va suna înainte ș i după, ș i oferă mijloace pentru a insera de sus în jos sau de jos în sus, ș i pentru a naviga
de sus în jos (navighează la nodul copil al celui curent), sau de jos în sus (navighează la părintele
nodul curent).

Performanț a atunci când se construieș te arborele de noduri

Există o diferenț ă importantă între construirea arborelui de sus în jos sau de jos în sus. Eu voi
extrage acest exemplu specific din documentele oficiale, deoarece este deja destul de semnificativ.

Inserarea de sus în jos

Consideraț i următoarea arbore:

tree1

If we wanted to build this tree top-down, we would first insert BînR, apoi inseraț iAînB, ș i
în cele din urmă introduceț iCînB. Adică:
3. Runtimul Compose 65

copac2

Inserând de la baza către vârf

O construcț ie de tip bottom-up a arborelui începe prin inserareaAș iCînB, apoi inserândBcopac înR.

tree3

Performanț a pentru construirea unui arbore de sus în jos versus de jos în sus poate varia considerabil. Acea decizie
depinde deAplicantimplementarea utilizată ș i de obicei se bazează pe numărul de noduri care trebuie
a fi notificat de fiecare dată când un nou copil este inserat. Imaginează-ț i că graficul pe care dorim să-l reprezentăm
cu Compose necesită informarea tuturor strămoș ilor unui nod de fiecare dată când este inserat. De sus în jos,
fiecare inserț ie ar putea notifica mai multe noduri (părinte, părinte al părintelui său… etc). Acea numărătoare va creș te
exponenț ial cu fiecare nou nivel inserat. Dacă ar fi de jos în sus, ai notifica întotdeauna doar pe
părinte direct, deoarece părintele nu este încă ataș at de copac. Dar acest lucru poate fi inversat dacă
3. Runtime-ul Compose 66

strategia noastră este să notificăm toț i copiii în schimb. Aș adar, depinde întotdeauna de copacul pe care îl reprezentăm ș i
cum trebuie să fie notificate schimbările de sus în jos sau de jos în sus în arbore. Singurul punct cheie aici este să alegi o strategie
sau cealaltă pentru inserț ie, dar niciodată ambele.

Cum sunt aplicate modificările

Aș a cum am descris mai sus, bibliotecile client oferă implementări pentruAplicantinterfaț ă, unul
exemplu de aceasta fiindUiApplier, pentru UI-ul Android. Putem folosi acela ca un exemplu perfect pe
ce înseamnă „aplicarea unui nod” ș i cum acesta generează componente pe care le putem vedea pe ecran pentru acest specific
caz de utilizare.

Dacă ne uităm la implementare, aceasta este foarte îngustă:

[Link]
1 clasa internă UiApplier(
2 root: LayoutNode
3 ) : AbstractApplier<LayoutNode>(root) {
4
5 înlocuie ș te func ț iainseraDeSusJos(index: Int, instanț ă: LayoutNode) {
6 // Ignored.
7 }
8
9 suprascrie distric ț iainsertBottomUp(index: Int, instance: LayoutNode) {
10 [Link](index, instan ț ă)
11 }
12
13 suprascrie func ț iaremove(index:
Int, count: Int) {
14 [Link](index, count)
15 }
16
17 anulează func ț iamută(din:
Int, în: Int, număr: Int) {
18 [Link](din, la, număr)
19 }
20
21 suprascrie func ț iaonClear() {
22 root.îndepărteazăTot()
23 }
24
25 anulează func ț iaonEndChanges() {
26 [Link]()
27 ([Link]? AndroidComposeView)?.clearInvalidObservations()
28 }
29 }
3. Timpul de execuț ie Compose 67

Primul lucru pe care-l vedem este că tipul genericNa fost corectat să fieNodDeLayoutAsta este tipul de
nodul pe care Compose UI l-a ales pentru a reprezenta nodurile UI care vor fi redimensionate.
Următorul lucru pe care îl observăm este cum se extindeAplicantAbstractAceasta este o implementare implicită care stochează
nodurile vizitate într-unStivăDe fiecare dată când un nou nod este vizitat în josul arborelui, acesta va fi adăugat în stivă.
ș i de fiecare dată când vizitatorul se deplasează în sus, va elimina ultimul nod vizitat din partea de sus a stivei. Aceasta
este de obicei comun între aplicanț i, aș a că este probabil o idee bună să-l ai într-o clasă părinte comună.
De asemenea, vedem cuminsereazăSusJoseste
ignorat înUiApplier, deoarece inserț iile vor fi efectuate
bottom up in the case of Android. As we said above, it is important to pick one strategy or the other,
nu ambele. În acest caz, metoda de jos în sus va fi mai potrivită pentru a evita notificările duplicate atunci când un
un copil nou este inserat. Această diferenț ă în termeni de performanț ă a fost explicată anterior.

Metodele de inserare, eliminare sau mutare a unui nod sunt delegate întotdeauna nodului însuș [Link] cum
Compose UI models a UI node, hence it knows everything about the parent node and its children.
Inserarea unui nod înseamnă ataș area acestuia la părintele său nou într-o poziț ie dată (poate avea multiple)
copii). Mutarea acestuia este în esenț ă o reordonare a listei de copii pentru părintele său. În cele din urmă, eliminarea
înseamnă pur ș i simplu să-l îndepărtaț i din listă.

De fiecare dată când termină aplicarea modificărilor, poate apelaonEndChanges()care va delega pe


nodul rădăcină proprietar pentru o acț iune finală necesară.onBeginChanges()se presupune întotdeauna că este apelat
înainte de a aplica modificările, aș a căfinalizeazăModificările()trebuie să fie apelat la final. - În acest moment, orice
observaț iile invalide în aș teptare sunt eliminate. Acestea sunt observaț ii instantanee menite să se automatizeze
reinvocarea layout-ului sau desenului atunci când valorile de la care citesc ș i pe care depind s-au schimbat. Imaginează-ț i
noduri fiind adăugate, inserate, înlocuite sau mutate, ș i cum acest lucru poate afecta lucruri precum măsurarea sau
dispunere.

Ataș area ș i desenarea nodurilor


Odată ce am ajuns aici, putem finalmente răspunde la întrebarea reală: Cum se inserează un nod în arbore (ataș ând
înseamnă că, în cele din urmă, o vedem pe ecran? Răspunsul este: Nodul ș tie cum să deseneze
de la sine.
LayoutNode este tipul de nod ales pentru acest caz specific de utilizare (UI Android). CândUiApplier
implementarea delegă inserț ia către ea, lucrurile se întâmplă în următoarea ordine:

• Verifică dacă condiț iile pentru inserarea nodului sunt îndeplinite - de exemplu: nu are un părinte
deja–.
• Adăugaț i copilul la lista de copii pe care o păstrează ș i actualizaț i lista copiilor sortate. Acesta este un
listă paralelă menț inută pentru sortare rapidă în indexul Z.
• Ataș aț i noul nod la părintele său (Proprietar). Explicat mai jos.
3. Timpul de compunere 68

Proprietarul locuieș te la rădăcina copacului ș i implementează conexiunea la vizualizarea de bază


sistem. Putem să ne gândim la el ca la un strat subț ire de integrare cu Android. De fapt, este implementat de
AndroidComposeView, care este un Vizualizaț ide unul singur. Proprietarul se conectează la vizualizările Android ș i la toate aranjamentele,
desenare, introducere ș i accesibilitate sunt conectate prin ele. ONodLayouttrebuie să fie ataș at la unProprietar
pentru a apărea pe ecran, iar proprietarul său trebuie să fie acelaș i cu proprietarul la care este ataș at părintele
proprietarul face parte din Compose UI. Vom aprofunda acest subiect în capitolul următor.

Ierarhia nodului de layout

Se efectuează unele acț iuni suplimentare atunci când se ataș ează nodul:

• Verifică dacă nu este deja ataș at sau dacă nu încearcă să se ataș eze unui proprietar diferit decât
al unui părinte. (Cerinț a explicată mai sus).
• Actualizaț i semantica părinț ilor, deoarece un nou nod semantic este adăugat la semantica
copac. –Vom învăț a despre asta în capitolele următoare, dar imaginează-ț i un copac paralel menț inut
pentru a descrie componentele noastre ș i a expune unele informaț ii specifice despre ele care vor fi
valorificate de lucruri precum serviciile de accesibilitate sau testele UI–.
• Cere proprietarului să creeze un strat care ș tie cum să desenezeNodLayoutconț inut utilizând
Compoziț ie UIPânzăInterfaț a de compunerePânzăeste o abstractizare care este implementată pentru
Android prin învelirea Android-ului nativePânzăș i delegându-i.
• Având în vedere că proprietarul este implementat deAndroidComposeView–un actualVizualizaț i–, oferă acces
tuturor celorVizualizareprimitive, care sunt folosite pentru invalidare după ce modificările sunt efectuate.
• Solicită o nouă măsurătoare proprietarului ș i părintelui.

Ș i profit! În sfârș it ș tim cum Compose UI materializează un arbore de noduri pentru [Link]
implementarea va delega aceasta către noduri, care ș tiu cum să se deseneze singure înPânză
ș i efectuează invalidările necesare astfel încât modificările să fie reflectate pe ecran.

Am încheiat ciclul, dar poate un pic prea devreme. Până acum am adunat tonuri de informaț ii interesante
detalii ș i avem o imagine mai clară despre modul în care funcț ionează Compoziț ia, dar ce zici despre
Procesul de compunere în sine?
3. Runtimelul Compose 69

Să ne ducem la asta.

Compoziț ie

Am învăț at multe detalii interesante despre Composer în secț iunea anterioară. Ș tim cum
înregistrează modificările pentru a scrie sau a citi din tabela de sloturi, cum sunt emise aceste modificări când
Funcț iile compuse se execută în timpul Compoziț iei, iar modul în care aceste modificări înregistrate sunt aplicate
în cele din urmă. Dar adevărul este că nu am spus un cuvânt (încă) despre cine este responsabil de crearea unei Compoziț ii,
cum, când se desfăș oară, sau ce paș i sunt implicaț i. Compoziț ia este piesa noastră lipsă până acum.
Am spus căCompozitorare o referinț ă laCompoziț ie, dar asta ne-ar putea face să credem că
theCompoziț ieeste creat ș i deț inut deCompozitor, când de fapt este invers.
Când o Compoziț ie este creată, aceasta construieș te un Composer de la sine. Composerul devine accesibil
princurrentComposer machinerie, ș i va fi folosită pentru a crea ș i actualiza arborele gestionat de
compunerea.
Punctul de intrare în runtime-ul Jetpack Compose de către bibliotecile client este împărț it în două părț i diferite:

• Scrierea funcț iilor compuse: Aceasta le va face să emită toate informaț iile relevante, ș i
deci conectează cazul nostru de utilizare cu runtime-ul.
Funcț iile compozabile sunt grozave, dar nu vor fi niciodată executate fără un proces de compunere.
De aceea, este necesar un alt punct de intrare:setContentAceasta este stratul de integrare cu
platforma ț intă, ș i unCompoziț ieeste creat ș i iniț iat aici.

Crearea unei compoziț ii


De exemplu, pentru Android, aceasta poate fi [Link], care returnează un nou
Compoziț ie:

[Link]

1 fun [Link](
2 parent: CompositionContext,
3 content: @Composable () -> Unit
4 Compunere {
5 // ...
6 valoarecomposeView = ...

7 returnadoSetContent(composeView, parent, content)


8 }
9
10fun ț ia privatădoSetContent(
11 owner: AndroidComposeView,
12 parent: CompositionContext,
3. Runtime-ul Compose 70

13 content: @Composable () -> Unit


Compozi ț ie {
15 // ...
16 valoareCompozi ț ie(UiApplier([Link]), părinte)// Aici!
17 valoarewrapped = [Link]([Link].wrapped_composition_tag)

18 ca? WrappedComposition?:WrappedComposition(owner, original).also {


19 [Link]([Link].wrapped_composition_tag, it)
20 }
21 [Link](content)
22 returna ț iînfăș urat
23 }

ACompozi ție Împachetatăeste un decorator care ș tie cum să lege unCompozi ț iela un
AndroidComposeViewastfel îl conectează direct la sistemul de vizualizare Android. Începe controlat
efecte pentru a urmări lucruri precum schimbările vizibilităț ii tastaturii sau accesibilitatea, ș i transmite informaț ii
despre AndroidContextaceasta va fi expusă Compoziț iei caCompositionLocals. (adică:
contextul în sine, configurarea, actualLifecycleOwner, curentulsavedStateRegistryOwner, sau
perspectiva proprietarului, printre altele). Aș a devin toate aceste lucruri implicit disponibile pentru toț i
funcț iile noastre Composable.
Observaț i cum o instanț ă a unuiUiAppliercare începe să indice spre rădăcinăNodLayoutal copacului este trecut
la Compoziț ie. (TheAplicatoreste un vizitator pentru noduri, astfel încât începe să se îndrepte spre cel rădăcină). Acesta este
prima dată vedem în mod explicit cum biblioteca client este cea responsabilă de alegerea implementării
pentruAplicant.
De asemenea, putem vedea [Link](con ț inut)
se nume ș te în cele din urmă.
Compoziț ie#setContenteste ceea ce stabileș te conț inutul Compoziț iei. (Actualizează Compoziț ia
cu toate informaț iile furnizate deconț inut).
Un alt exemplu foarte bun de creare a unei Compoziț ii poate fiVectorPainter, de asemenea parte a
Compune UI ș i folosit pentru a picta vectori pe ecran. Pictorii de vectori creează ș i menț in propriile lor
Compoziț ie:

[Link]

1 @Compozabil
2 fun internăRenderVector(
3 name: String,
4 viewportWidth: Float,
5 viewportHeight: Float,
6 content: @Composable (viewportWidth: Float, viewportHeight: Float) -> Unit
7 ) {
8 // ...
9 valoarecomposition = composeVector(rememberCompositionContext(), content)

10
3. Runtime-ul Compose 71

11 Efect de tip Disposable(compozi ț ie) {


12 onDispose {
13 [Link]()// compunerea trebuie să fie eliminată la final!
14 }
15 }
16 }
17
18fun privatăcomposeVector(
19 parent: CompositionContext,
20 composable: @Composable (viewportWidth: Float, viewportHeight: Float) -> Unit
Compozi ț ie {
22 valoareexisting = composition
23 valoarenext = dacă(există == null || [Link]) {
24 Compozi ț ie(VectorApplier([Link]), părinte)// Aici!
25 } altfel{
26 existent
27 }
28 composition = next
29 [Link] {
30 composable([Link], [Link])
31 }
32 întoarceurmătorul
33 }

Vom explora acest lucru mai departe într-un capitol viitor despre cazuri avansate de utilizare a Jetpack Compose, dar
putem observa aici cum un diferitAplicatorstrategia este aleasă: oVectorAppliercare începe să indice către
noda rădăcină în arborele vectorial, care în acest caz va fi unVNode.
În cele din urmă, un alt exemplu al acestui lucru pe care l-am putea găsi ș i în Compose UI este SubcomposeLayout,
care este unAspectcare îș i menț ine propria Compoziț ie astfel încât să fie capabilă să subcompozeze conț inutul său în timpul
faza de măsurare. Acest lucru poate fi util când avem nevoie de măsura unui părinte pentru compoziț ie
al copiilor săi.
Indiferent de cazul de utilizare, de fiecare dată când se creează o Compoziț ie, uncontext de compunere părintecan
fie trecut (vezi mai sus). Dar reț ine că poate finullContextul părinte (dacă este disponibil) va fi folosit pentru
leagă noua compunere logic de una existentă, astfel încât invalidările ș iCompositionLocals
poate rezolva în cadrul compoziț iilor ca ș i cum ar fi aceeaș i.
Când creezi unCompoziț ieeste de asemenea posibil să se transmită un context de recompunere, care va fi
Context de corutinăutilizat deCerepentru aplicarea modificărilor ș i în cele din urmă materializarea copacului.
Dacă nu este furnizat, se va folosi implicit cel furnizat de Recomposer, care esteEmptyCoroutineContext.
Asta înseamnă că Android va recompozi [Link].
În aceeaș i manieră în care o Compoziț ie este creată, trebuie să fie eliminată – adică:[Link]()când este
nu mai este necesar. Asta este atunci când interfaț a utilizatorului (sau cazuri de utilizare alternative) pentru aceasta sunt eliminate. Am putea
3. Runtime-ul Compose 72

spune că o Compoziț ie este limitată la proprietarul său. Uneori, eliminarea poate fi puț in ascunsă, ca în
caz [Link] (în spatele unui observator de ciclu de viaț ă), dar este întotdeauna acolo.

Procesul iniț ial de compunere


Ori de câte ori se creează o nouă Compozi ț ie, se face un apel [Link](con ț inut)întotdeauna
urmează (vezi cele 2 fragmente anterioare). Acolo este de fapt locul unde Compoziț ia este iniț ial populată
(tabelul de sloturi este umplut cu date relevante).
Această apel este delegat cătrepărinteCompoziț ie pentru a declanș a procesul iniț ial de Compoziț ie (Remem-
cum Compoziț iile ș i Subcompoziț iile sunt legate printr-un părinteContextulCompoziț iei):
Compoziț [Link]
1 override fun setContent(content: @Composable () -> Unit) {
2 // ...
3 [Link] = content
4 [Link](acesta, compus)`this` este Compozi ț ia curentă
5 }

Pentru Subcompoziț ii, părintele va fi o altă Compoziț ie. Pentru Compoziț ia rădăcină, părintele
va fiRecompozitorDar, indiferent de asta, logica pentru realizarea compunerii iniț iale va...
mereu te poț i baza peRecompozitorîn orice caz, deoarece pentru Subcompoziț ii,composeInitial apelează
delegaț ii către părinte din nou ș i din nou până ajunge la rădăcina Compoziț iei.
Deci apelul lapă[Link](compoziț ie, content) poate fi tradus în
, ș i face câteva lucruri importante
[Link](compozi ț ie, con ț inut)
aici pentru a popula compunerea iniț ială:

• Face o fotografie instantanee a valorii curente a tuturor obiectelor de Stat. Aceste valori vor fi izolate de
schimbări potenț iale din alte instantanee. Această instantanee este modificabilă, dar în acelaș i timp este
sigur pentru concurenț ă. Poate fi modificat în siguranț ă fără a afecta alte instantanee de stare existente,
deoarece orice modificări ale oricăror obiecte de stare se vor întâmpla doar pentru acesta ș i se va sincroniza atomic
toate acele modificări cu starea globală partajată într-un pas ulterior.
• Valorile stării din acest instantaneu mutabil pot fi modificate doar din blocul trecut atunci când
apelâ[Link](block: () -> T).
• Când se ia instantaneul,Recompozitorde asemenea, trece observatorii pentru orice citiri sau scrieri la
obiectele de stat menț ionate, astfel încât Compoziț ia să poată fi notificată corespunzător atunci când acestea au loc
loc. Aceasta permiteCompoziț iepentru a marca domeniile de recompoziț ie afectate cafolosit, care
îi va face să revină la normal atunci când va veni vremea.
• Intră în instantaneu – Adică:[Link]ă(bloc)– prin trecerea blocului următor:
[Link](content)Acolo este locul unde are loc Compozi ț ia.
loc. Acț iunea de a intra este ceea ce permiteRecomposerș tii că orice obiecte State citite sau
scris în timpul Compoziț iei va fi urmărit (notificat Compoziț iei).
3. Rularea Compose 73

• Procesul de compunere este delegat Compozitorului. Mai multe informaț ii despre acest pas mai jos în această listă.
Odată ce Compoziț ia este finalizată, orice modificări ale obiectelor State sunt făcute asupra stării curente.
instantaneu doar, aș a că este timpul să propagăm acele modificări în starea globală. Acest lucru se întâmplă prin
[Link]().

Aceasta este ordinea generală a lucrurilor în jurul Compoziț iei iniț iale. Tot ce ț ine de Stat
sistemul snapshot va fi extins cu mult mai multe detalii în capitolul următor despre acest subiect.
Acum, să detaliem procesul efectiv de compunere în sine, delegat cătreCompozitorAș a este cum
lucrurile se întâmplă în termeni duri.

• Compoziț ia nu poate fi începută dacă este deja în derulare. În acest caz, este aruncată o excepț ie ș i
Noua Compoziț ie este anulată. Compoziț ia reentrantă nu este acceptată.
• Dacă există orice invalidări în aș teptare, le va copia în lista de invalidări menț inută
de compozitor pentruRecompoziț ieDomeniuîn aș teptare de a fi invalidat.
• Muta steagulisComposing a fiadevăratdeoarece compunerea urmează să înceapă.
• ApeluristartRoot()pentru a începe Compoziț ia, care va începe grupul principal pentru Compoziț ie
în tabelul slot ș i iniț ializaț i celelalte câmpuri ș i structuri necesare.
• SunăîncepeGruppentru a începe un grup pentruconț inutîn tabela sloturilor.
• Invocăconț inutlambda astfel încât să emită toate schimbările sale.
ApeluriendGroup pentru a încheia grupul din tabela de sloturi.
• ApelurifinalizeazăRădăcina()pentru a încheia compunerea.
Mută steagulisComposing a fifals, deoarece Compunerea este finalizată.
• Ș terge alte structuri care menț in date temporare.

Aplicarea modificărilor după compunerea iniț ială

După compunerea iniț ială,Aplicatoreste notificat să aplice toate modificările înregistrate în timpul
procesul:[Link]()Aceasta se face prin intermediulCompozi ț iede asemenea, ce apeluri
[Link](), trece prin lista modificărilor, executându-le pe toate ș i trecând la
necesarAplicantș iSlotWriterinstanț e pentru fiecare schimbare. În cele din urmă, după ce toate schimbările sunt aplicate, ea
[Link]()Acesta este procesul natural.
După asta, trimite toate înregistrărileObservatorAminteș tes, a ș a că orice clase care implementează
ObservatorDeAmintiricontractul poate fi notificat atunci când intră sau părăseș te Compoziț ia. Lucruri
caEfect lansatsauEfect de Aruncareimplementează-l, astfel încât să poată constrânge efectul la
Ciclul de viaț ă compus în cadrul Composition.
Imediat după, toateEfecte secundaresunt declanș ate în aceeaș i ordine în care au fost înregistrate.
3. Runtimera Compose 74

Informaț ii suplimentare despre Compoziț ie


O compunere este conș tientă de invalidările sale pendente pentru recompoziț ie. De asemenea, ș tie dacă este în prezent
compoziț ia. Această cunoș tinț ă poate fi folosită pentru a aplica invalidări instantaneu (când este), sau pentru a le amâna
în caz contrar. Poate fi folosit ș i de Recomposer pentru a elimina recompoziț iile atunci când este adevărat.

Runtime-ul se bazează pe o variantă a Compoziț iei numităCompoziț ieControlatăcare adaugă câteva


funcț ii suplimentare astfel încât să poată fi controlat din exterior. Astfel, Recomposerul poate orchesta
invalidări ș i recompoziț ii ulterioare. Funcț ii precumcomposeContent saurecomponeresunt buni
exemple de acest lucru. Recomposerul poate declanș a acele acț iuni în compunere atunci când este necesar.

Compoziț ia oferă mijloace de a detecta dacă un set de obiecte este observat de sine însuș i pentru a
aplică recompunerea atunci când acestea variază. De exemplu, acesta este folosit de Recomposer pentru a impune
recompoziț ie într-o compoziț ie de copil atunci când un Compoziț ieLocalăvariază într-o compoziț ie părinte.
Amintiț i-vă că compoziț iile sunt conectate prin părinteContextulCompuneriipentru această chestiune.

Uneori se găseș te o eroare în timpul compunerii, în acest caz poate fi abandonată, ceea ce este destul de
ca resetarea Composer-ului ș i a tuturor referinț elor / stivelor ș i a tot ceea ce este.

Compozitorul presupune că sărăceș te recompunerea atunci când nu inserează ș i nici nu reuseș te, nu există
furnizori invalizi (deoarece aceasta ar necesita recompunere) ș icurrentRecomposeScope nu
recompoziț ie necesară. Un capitol despre recompoziț ia inteligentă va veni, de asemenea.

Recompozitorul

Deja ș tim cum are loc Compoziț ia iniț ială ș i am învăț at de asemenea câteva lucruri despre
RecompozitieScopș i invalidare. Dar ș tim în continuare aproape nimic despre cum funcț ionează Recomposer-ul
de fapt funcț ionează. Cum este creat ș i când începe să ruleze? Cum începe să asculte pentru
invalidările pentru a declanș a automat recompoziț ia? Unele întrebări ar putea apărea.
Recompozitorul controleazăCompoziț ieControlată, ș i declanș ează recompuneri atunci când este necesar
pentru a aplica în cele din urmă actualizările asupra acestuia. De asemenea, determină ce fir să compună sau să recomponă ș i
ce fir să foloseș ti pentru a aplica modificările.
Să învăț ăm cum să creăm un Recomposer ș i să-l facem să aș tepte invalidările.

Generarea Recomposerului
Punctul de intrare în Jetpack Compose prin bibliotecile client este crearea unei Compoziț ii ș i apelarea
setContent peste asta -vezi secț iunea de mai sus: Crearea unei Compoziț ii-. Când creezi Compoziț ia
este necesar să se ofere un părinte pentru acesta. Având în vedere că părintele unei Compoziț ii rădăcină este unRecompozitor, acesta
este de asemenea momentul să-l creati.
3. Runtima Compose 75

Acest punct de intrare este conexiunea între platformă ș i runtime-ul Compose, ș i este cod
furnizat de client. În cazul Android, acesta este Compose UI. Această bibliotecă creează o Compoziț ie
(care creează intern propriul său Composer) ș i un Recomposer de folosit ca părinte.

Notati ca fiecare caz de utilizare potential pentru fiecare platformă este susceptibil să creeze propria sa Compoziț ie pe măsură ce
au învăț at înainte, iar în acelaș i mod, este de asemenea probabil să creeze propriul său Recomposer.

Când vrem să folosim Compose pe AndroidGrup de vizualizareda, noi [Link]


în cele din urmă, ș i după câteva direcț ionări, delegaț ii care creează contextul părinte pentru un Recomposer
factory:

1 distrac ț iefabricaRecomposeruluiFereastrăInterfa ț ă
2
3 distrac ț iecreaRecompozitor(windowRootView: View): Recompozitor
4
5 obiect companion{
6 val LifecycleAware: WindowRecomposerFactory = WindowRecomposerFactory { rootView\
7 ->
8 [Link]()
9 }
10 }
11 }

Această fabrică creează un Recomposer pentru fereastra curentă. Consider că procesul de creare este foarte interesant.
pentru a explora, deoarece oferă multe indicii despre cum Android rezolvă integrarea cu Compose.
Transmiterea unei referinț e la vista rădăcină este necesară pentru a apelacreaRecompozitor, deoarece a fost creat
Recomposer va fi conș tient de ciclul de viaț ă, ceea ce înseamnă că va fi legat deProprietarulCicluluiVieț iiViewTree
la rădăcina ierarhiei View. Aceasta va permite anularea (închiderea) Recomposer-ului când
arborul de vizualizare este detaș at, de exemplu, ceea ce este important pentru a evita scurgerea procesului de recompunere.
–Acest proces este modelat ca o funcț ie suspendată care, altfel, va scurge.–

În Compose UI, toate lucrurile care se întâmplă pe UI sunt coordonate /


distribuit folosindAndroidUiDispatcher, care din acest motiv este asociat cu unCoregraf
instanț ă ș i un handler pentru principalLooperAcest dispatcher efectuează distribuț ia evenimentelor în timpul
handler callback sau etapa cadru de animaț ie a coregrafului, oricare apare prima. De asemenea, are
aMonotonicFrameClock asociat care foloseș tesuspendapentru a coordona redarea cadrului. Acesta este ceea ce
ghidează întreaga experienț ă utilizatorului în Compose, iar lucruri precum animaț iile depind foarte mult de aceasta pentru a atinge un
experienț ă fluidă în sincronizare cu cadrele sistemului.
3. Runtime-ul Compose 76

Primul lucru pe care îl face funcț ia factory este să creeze unCeasMonotonicSuspendabilAcesta este un wrapper
pesteAndroidUiDispatcherceas monoton care adaugă suport pentru suspendarea manuală a dispatch-ului
decuFrameNanosevenimente până când este reluat. Aceasta îl face util pentru cazurile în care cadrele ar trebui să
nu poate fi produs în perioade specifice de timp, cum ar fi atunci când o Fereastră care găzduieș te o interfaț ă utilizator nu mai este

vizibil.
OriceCeasMonotonicFrameeste de asemenea unElementCoroutineContext, ceea ce înseamnă că poate fi combinat
cu alteleContextulCoroutines.
Când instanț iem Recomposer-ul, trebuie să furnizăm un Contextul coroutineila acesta. Acest context este
creat folosind o combinaț ie a contextului actual al firului de execuț ie dinAndroidUiDispatcherș i
ceas de cadru pauzabil tocmai creat.
[Link]

1 valoarecontextCuCeas = contextulThread-uluiCurent + (ceasPauzabil?:EmptyCoroutineContex


2 t)
3 val recomposer = Recomposer(effectCoroutineContext = contextWithClock)

Acest context combinat va fi folosit de Recomposer pentru a crea un internLoc de muncăpentru a asigura că toate
efectele de compunere sau recompunere pot fi anulate atunci când se opreș te Recomposerul. Acest lucru
va fi nevoie când o fereastră Android este distrusă sau neataș ată, de exemplu.
contextul va fi cel folosit pentru aplicarea modificărilor după compunere / recompunere, ș i va
de asemenea, să fie contextul implicit utilizat deEfect lansatpentru a rula efecte. –Asta face ca efectele să înceapă în
acelaș i fir pe care îl folosim pentru a aplica modificări, care în Android este de obicei firul principal. Desigur că noi
poț i să sari întotdeauna de pe firul principal oricând doreș ti în cadrul efectelor noastre.

LaunchedEffect este un handler de efecte care va fi explicat în detaliu în capitolul despre acest subiect.
Toate handler-urile de efecte sunt funcț ii Composable ș i, prin urmare, emit modificări care sunt înregistrate.
Efect lansateste cu adevărat înregistrat ș i scris în tabelul slot când vine momentul, astfel încât este
Compoziț ie conș tientă de ciclul de viaț ă, nu caEfect secundar.

În cele din urmă, se creează o arie de coroutine folosind acelaș i context combinat: adică:val runRecomposeScope
= CoroutineScope(contextCuCeas)Acest domeniu va fi folosit pentru a lansa lucrul de recompoziț ie (un
suspend funcție), care va aș tepta invalidările ș i va declanș a recompuneri în consecinț ă. Să
zăreș te codul ș i discută câteva idei despre acesta.
3. Runtime-ul Compose 77

Fereastră[Link]

1 [Link]ăObservator(
2 obiect: Observer de evenimente ale ciclului de via ț ă{
3 suprascrie func ț iaonStateChanged(lifecycleOwner: LifecycleOwner, event: [Link]\

4 nt) {
5 valoareself = asta

6
7 când(event) {
8 [Link].ON_CREATE ->
9 [Link](start = [Link]) {
10 încearcă{

11 [Link]()
12 } în sfâr ș it{
13 // After completion or cancellation
14 [Link](self)
15 }
16 }
17 [Link].ON_START -> pausableClock?.resume()
18 [Link].ON_STOP -> pausableClock?.pause()
19 [Link].ON_DESTROY -> {
20 [Link]ă()
21 }
22 }
23 }
24 }
25 )

Aici este locul unde se întâmplă lucrurile. Un observator este ataș at la ciclul de viaț ă al arborelui de vizualizare ș i va
utilizaț i ceasul pauzabil pentru a relua ș i a suspenda trimiterea evenimentelor atunci când arborele de vizualizare este pornit ș i oprit,

respectiv. De asemenea, va închide (anula) Recomposerul la distrugere ș i va lansa recompunerea


job pe create.
Jobul de recompoziț ie este început [Link](), care este
suspendafuncț ia menț ionată mai sus care va aș tepta invalidarea oricăror Compozitori asociaț i
(ș i a lorRecomponenț ăDomenius), recompune-le ș i, în cele din urmă, aplică noile modificări la ele
compoziț ie asociată.
Această fabrică este modul în care Compose UI generează un Recomposer conectat la ciclul de viaț ă Android. Funcț ionează
frumos ca un exemplu de cum este creat Recomposerul la punctul de integrare cu platforma,
împreună cu Compoziț ia. Ca o reamintire, aici putem vedea din nou cum a fost creată compoziț ia
când se stabileș te conț inutul pentruViewGroups:
3. Rularea Compose 78

[Link]
1 fun [Link](
2 parent: CompositionContext, // Recomposer este aici!
3 content: @Composable () -> Unit
4 ): Compozi ț ie {
5 // ...
6 valoarecomposeView = ...

7 returnadoSetContent(composeView, parent, content)


8 }
9
10fun privatdoSetContent(
11 owner: AndroidComposeView,
12 parent: CompositionContext,
13 content: @Composable () -> Unit
14): Compozi ț ie {
15 // ...
16 val Compozi ț ie(UiApplier([Link]), părinte)// Aici!
17 valoarewrapped = [Link]([Link].wrapped_composition_tag)

18 ca? WrappedComposition?:WrappedComposition(owner, original).also {


19 [Link]([Link].wrapped_composition_tag, it)
20 }
21 [Link](conț inut)
22 returna ț iînvelit
23 }

Aceeapărinteva fi un Recomposer, care va fi furnizat de către apelantsetContent, că pentru


acest caz de utilizare esteAbstractComposeView.

Procesul de recompoziț ie

[Link]() funcț ia este apelată pentru a începe aș teptarea pentru invalidare


ș i se recompun automat atunci când acestea au loc. Să învăț ăm paș ii diferiț i implicaț i.
Într-o secț iune anterioară, am învăț at cum starea snapshot este modificată în cadrul propriului său snapshot, dar mai târziu
acele modificări trebuie propagate în starea globală [Link]ă()pentru sincronizare. Când apelăm
[Link]()primul lucru pe care îl face este să înregistreze un observator pentru
această propagare a schimbărilor. Când se întâmplă acest lucru, acest observer se trezeș te ș i adaugă toate acele modificări la un
lista invalidărilor snapshot care sunt propagate tuturor compozitorilor cunoscuț i astfel încât să poată înregistra ce
parts of the composition need to be recomposed. In simple terms, this observer is a stepping stone
pentru declanș area recompunerii automate atunci când starea se schimbă.

După înregistrarea observatorului instantaneului, Recomposerul invalidează toate Compoziț iile pentru
presupune că totul s-a schimbat ca punct de plecare. Orice schimbări care se întâmplă înainte de acest moment
3. Rularea Compose 79

nu au fost urmărite, aș a că aceasta este o modalitate de a începe de la zero. Apoi se suspendă până când există muncă
disponibil pentru recompunere. „A avea muncă disponibilă” înseamnă a avea orice instantaneu de stat în aș teptare
invalidări sau orice invalidări de compoziț ie provenind de laRecompozitieDomenius.
Următorul lucru pe care îl face Recomposerul este să folosească ceasul monostatic furnizat la crearea acestuia, ș i
[Link] {}a aș tepta următorul cadru. Restul muncii de la
aceasta se va realiza la acea vreme ș i nu înainte. Intenț ia este de a coagula modificările cadrului.
În interiorul acestui bloc, Recomposer-ul trimite întâi cadrele de ceas monotonic pentru orice potenț ial
aș teptători (ca animaț iile). Acest lucru ar putea genera noi invalidări ca rezultat care trebuie, de asemenea, urmărite
(de exemplu: activarea sau dezactivarea unui Composable condiț ionat atunci când se încheie o animaț ie).

Ș i acum este timpul pentru adevărata acț iune. Recomposerul preia toate invalidările de instantanee în aș teptare,
sau, cu alte cuvinte, toate valorile Statului modificate de la ultima apelare a reconstituirii ș i înregistrează toate acestea
schimbări în compozitor ca recompuneri în aș teptare.
Ar putea exista ș i Compoziț ii invalide - prin [Link]()–, de exemplu atunci când
un stat este scris într-o lambda compusă–. Pentru fiecare dintre acestea, Recomposer-ul efectuează
recompoziț ie (o secț iune despre aceasta mai jos) ș i o adaugă la lista Compoziț iilor cu modificări în aș teptare
a aplica.
Recompoziț ia înseamnă recalcularea tuturorSchimbareEste necesar pentru starea de Compoziț ie (tabelul sloturilor)
ș i copacul materializat Aplicant), aș a cum am învăț at. Am văzut deja cum se face asta
–vezi secț iunea: „Procesul iniț ial de compunere”–. Recompunerea reutilizează tot acel cod, aș a că nu are sens
pe repetarea tuturor paș ilor pe care procesul îi urmează aici.
Apoi, găseș te compuneri potenț iale de urmărire care trebuie să fie compuse din cauza unei modificări de valoare.
printr-o compunere ș i le programează pentru recompunere, de asemenea. Acest lucru se poate întâmpla, de exemplu, dacă o
CompositionLocalschimbări într-un părinte ș i a fost citit într-o compunere a unui copil care era altfel
valid.
În cele din urmă, trece prin toate Compoziț iile cu modificările de aplicat ș i apelează[Link]()
pe ei. După aceea, actualizează starea Recomposer.

Recompoziț ie concurentă

Recompozitorul are capacitatea de a efectua recompoziț ii în paralel, chiar dacă Compose UI face
nu folosi această funcț ie. Orice alte biblioteci de clienț i ar putea să se bazeze pe ea, în funcț ie de nevoile lor.
Recomposerul oferă un contrapartid concurrent pentrurulaRecompoziț iuneȘ iAplicăSchimbărifuncț ie
asta se numeș teexecutaRecompoziț iaConcomitentȘ iAplicăModificările. Aceasta este o altăsuspend funcț ie pentru
aș teptând invalidările instantaneelor de stat ș i declanș ând recompuneri automate ca cele anterioare,
dar cu o singură diferen ț ă, că acesta din urmă va efectua recompunerea celor invalida ț i
Compoziț ii într-oContextul corutineiprovided from the outside:
3. Rularea Compose 80

[Link]

1 suspenddistrac ț ierulaRecompoziț ieConcomitentȘ iAplicăSchimbări(


2 recomposeCoroutineContext: CoroutineContext
3 ) { /* ... */}

Acestasuspendafuncț ia îș i creează propriulCoroutineScopefolosind contextul furnizat ș i îl foloseș te pentru a genera


şi coordonaţi toate lucrările pentru copii create pentru toate recompunerile concurente necesare.

Statele reconstructor

Recombinatorul trece printr-o serie de stări pe parcursul vieț ii sale:


[Link]

1 enum classState{
2 Închidere
3 Oprire
4 Inactiv
5 LucruInactivInAsteptare
6 Inactiv,
7 Lucrări în aș teptare
8 }

Acesta a fost extras direct din kdocs ș i nu are sens să fie reformulat. Iată-te
au ce înseamnă fiecare dintre aceste state:

• ÎnchidereRecomposerul a fost anulat ș i lucrările de curăț are finalizate. Nu mai poate fi folosit.
• ÎnchisRecomposer a fost anulat, dar este încă în mijlocul procesului de curăț are.
Nu mai poate fi folosit.
• InactivRecomposer va ignora invalidările din partea Compozitorilor ș i nu va declanș a recom-
poziț ionaț i corespunză[Link]ăȘ iAplicăModificăriletrebuie să fie apelat pentru a începe ascultarea. Acesta este
starea iniț ială a unui Recomposer după creare.
• Lucru în aș teptare inactivExistă ș ansa ca Recompunătorul să fie inactiv, dar să aibă deja
câteva efecte în aș teptare a unui cadru. Cadrul va fi produs de îndată ce recompozitorul
începe să alerge.
• InactivRecomposer urmăreș te compunerile ș i invalidările instantanee, dar în prezent nu există
de făcut.
• Lucrări în aș teptareRecomposerul a fost notificat cu privire la lucrările în aș teptare ș i deja le desfăș oară sau
aș teptând ocazia să o fac. (Am descris deja ce înseamnă „muncă în aș teptare” pentru
Recompozitor).
4. Compoziț ie UI
De scris.
Sistem de instantanee de stare
De scris.
6. Recompunere Inteligentă
De scris.
7. Efecte ș i gestionari de efecte
Înainte de a trece la gestionarii de efecte, este probabil util să recapitulăm puț in despre ce trebuie avut în vedere.
efect secundar. Aceasta ne va oferi ceva context despre de ce este esenț ial să menț inem efectele secundare sub control în
arborele nostru compus.

Introducerea efectelor secundare

Efectele secundare au fost acoperite în capitolul unu când am învă ț at despre proprietă ț ile Composable.
funcț ii. Am învăț at că efectele secundare fac funcț iile non-deterministe ș i, prin urmare, le fac
este greu pentru dezvoltatori să raț ioneze despre cod.

În esenț ă, un efect secundar este orice lucru care scapă controlului ș i domeniului unei funcț ii. Imaginează-ț i o
o funcț ie care se aș teaptă să adauge două numere:
[Link]

1 distrac ț ieadaugă(a: Int, b: Int) = a + b

Aceasta este, de asemenea, frecvent denumită o funcț ie „pură”, deoarece foloseș te doar intrările sale pentru a calcula un
rezultatul. Acest rezultat nu va varia niciodată pentru aceleaș i valori de intrare, deoarece singurul lucru pe care funcț ia îl face este
adăugându-le. Prin urmare, putem spune că această funcț ie este deterministă ș i putem raț iona uș or despre
asta.
Acum, să luăm în considerare adăugarea unor acț iuni colaterale la aceasta:

[Link]

1 distractivadauga(a:
Int, b: Int) =
2 [Link]ț ine(a, b)?:
3 (a + b).de asemenea { [Link](a, b, it) }
4 }

Introducem un cache pentru calcule pentru a salva timpul de calcul dacă rezultatul a fost deja calculat.
înainte. Acest cache scapă de controlul funcț iei, aș a că nimic nu ne spune dacă valoarea citită din
nu a fost modificat de la ultima execuț ie, de exemplu. Imaginează-ț i că acest cache se actualizează
concomitent dintr-un fir diferit, ș i brusc două apeluri secvenț iale laob ț ine(a, b)pentru acelaș i
intrările returnează două valori diferite:
7. Efecte ș i gestionari de efecte 85

[Link]

1 distrac ț iemain()
{
2 adaugă(1,2)// 3
3 // Un alt fir apelează: [Link](1, 2, res = 4)
4 adăuga(1,2)4
5 }

Funcț ia de adunare returnează o valoare diferită pentru aceleaș i intrări, prin urmare nu mai este deterministă.
La fel, imaginează-ț i că acest cache nu era în memorie, ci se baza pe o bază de date. Am putea
obț ine excepț iile aruncate deprindeș imagazinapeluri în funcț ie de ceva de genul momentan lipsind un
conexiunea la baza de date. Apelurile noastre laadaugapoate eș ua ș i în scenarii neaș teptate.

Ca un rezumat, putem spune că efectele secundare sunt acț iuni neaș teptate care apar pe lângă ceea ce
apelanț ii s-ar aș tepta de la funcț ie ș i care poate altera comportamentul său. Efectele secundare fac dificil pentru
dezvoltatorilor să raț ioneze despre cod ș i, de asemenea, să îndepărteze testabilitatea, deschizând uș a către instabilitate.

Exemple diferite de efecte secundare pot fi scrierea sau citirea dintr-o variabilă globală, accesarea unui
cache de memorie, o bază de date, efectuând o interogare de reț ea, afiș ând ceva pe ecran, citind
dintr-un fiș ier… etc.

Efecte secundare în Compose

Am învăț at cum cădem în aceleaș i probleme atunci când efectele secundare sunt executate în cadrul Composable
funcț ii, deoarece aceasta face ca efectul să scape de controlul ș i constrângerile impuse de
Ciclul de viaț ă compus.
Un lucru pe care l-am învăț at anterior este cum orice Composable poate suferi multiple recompoziț ii.
De aceea, rularea efectelor direct în cadrul unui Composable nu este o idee grozavă. Acest lucru este
ceva ce am menț ionat deja în capitolul 1 când am enumerat proprietăț ile funcț iilor Composable,
unul dintre ele fiind că funcț iile Composable sunt restartabile.
Executarea efectelor în interiorul unei Composable este prea riscantă, deoarece poate compromite integritatea
codul nostru ș i starea aplicaț iei noastre. Permiteț i-mi să revin la un exemplu pe care l-am folosit în capitolul 1: Un Composable
funcț ie care îș i încarcă starea de pe reț ea:
7. Efecte ș i gestionari de efecte 86

[Link]

1 @Compozabil
2 distrac ț ieEventsFeed(serviceReț ea: EventsNetworkService) {

3 valoareevents = [Link]() // efect secundar

4
5 ColoanăÎncetinită {

6 items(events) { event ->


7 Text(text = numele evenimentului)

8 }
9 }
10 }

Efectul de aici va rula la fiecare recompunere, ceea ce, cel mai probabil, nu este ceea ce căutăm.
timpul de execuț ie ar putea necesita recompunerea acestui Composable de mai multe ori într-o perioadă foarte scurtă de timp.
Rezultatul ar fi multe efecte concurrente fără nicio coordonare între ele. Ce ne
probabil că a vrut să ruleze efectul doar o dată la prima compunere, ș i să păstreze acea stare
pentru ciclul de viaț ă complet Composable.
Acum, să ne imaginăm că cazul nostru de utilizare este UI-ul Android, aș a că folosimcompose-uipentru a construi un Composable
arbore. Orice aplicaț ii Android conț in efecte secundare. Iată un exemplu de ceea ce ar putea fi un efect secundar
pentru a menț ine un stat extern actualizat.

[Link]

1 @Composable
2 distrac ț ieMyScreen(drawerTouchHandler: TouchHandler) {

3 valoaredrawerState = rememberDrawerState([Link])

4
5 [Link] = [Link]
6
7 // ...
8 }

This composable describes a screen with a drawer with touch handling support. The drawer state is
iniț ializat caÎnchis, dar s-ar putea schimba înDeschisîn timp. Pentru fiecare compunere ș i recompunere,
composable-ul notificăHandler de atingeredespre starea actuală a sertarului pentru a activa manipularea tactilă
sprijin doar când esteDeschis.
[Link] = [Link] este un efect secundar. Atribuim un callback
referinț ă la un obiect extern ca efect secundar al compoziț iei.
Aș a cum am descris deja, problema de a face lucrurile corect în corpul funcț iei Composable este că
nu avem niciun control asupra momentului în care acest efect se rulează, aș a că va rula la fiecare compunere / recompunere,
ș i nu va fi niciodată eliminat, deschizând uș a pentru posibile scurgeri.
7. Efecte ș i gestionari de efecte 87

Revenind la exemplul unei cereri de reț ea, ce s-ar întâmpla dacă, un compus care a declanș at
o cerere de reț ea ca un efect secundar, părăseș te compunerea înainte de a se finaliza? S-ar putea să preferăm
anulând slujba în acel moment, corect?
Deoarece efectele secundare sunt necesare pentru a scrie programe cu stare, Jetpack Compose oferă mecanisme pentru
rula efecte secundare într-o manieră conș tientă de ciclul de viaț ă, astfel încât cineva să poată extinde o sarcină pe parcursul recompunerilor sau să o obț ină

anulate automat atunci când Composable părăseș te compoziț ia. Aceste mecanisme sunt numite
manipulatoare de efecte.

Ce ne trebuie
Compoziț iile pot fi descărcate pe fire de execuț ie diferite, executate în paralel sau într-o ordine diferită,
printre alte strategii de execuț ie la viteză. Asta este o uș ă pentru diverse optimizări potenț iale.
Echipa de compunere doreș te să păstreze deschis, ș i de aceea nu am dori niciodată să ne desfăș urăm efectele
imediat în timpul compunerii fără niciun fel de control.
În general, avem nevoie de mecanisme pentru a ne asigura că:

• Efectele rulează în pasul corect al ciclului de viaț ă composabil. Nu prea devreme, nu prea târziu. Exact când
composable este pregătit pentru asta.
• Efectele suspendate rulează pe un runtime configurat convenabil (Coroutine ș i convenabil
ContextCoroutine).
• Efectele care captează referinț e au ș ansa de a le elimina atunci când părăsesc compoziț ia.
Efectele suspendate în curs sunt anulate la ieș irea din compunere.
• Efectele care depind de un input care variază în timp sunt anulate / anulate automat
ș i relansat de fiecare dată când se schimbă.

Aceste mecanisme sunt furnizate de Jetpack Compose ș i se numesc manipulatoare de efecte.

Toț i handlerii de efecte partajaț i în această postare sunt disponibili în cea mai recentă1.0.0-beta02.
Amintiț i-vă că Jetpack Compose a îngheț at suprafaț a API-ului public când a intrat în versiune beta, aș a că vor
nu se schimba mai mult înainte de1.0.0 eliberare.

Gestionari de efecte

Înainte de a le descrie, permite-mi să-ț i ofer o privire de ansamblu asupra @Composableciclul de viaț ă, deoarece acesta va fi
relevant de acum înainte.
Orice compozabil intră în compozi ț ie atunci când este materializat pe ecran ș i, în cele din urmă, iese din
compoziț ie atunci când este eliminată din arborele UI. Între aceste două evenimente, efectele ar putea rula. Unele efecte
poate supravieț ui ciclului de viaț ă compozabil, astfel încât să poț i extinde un efect pe parcursul compoziț iilor.

Aceasta este tot ce trebuie să ș tim pentru moment, haideț i să continuăm.

Am putea împărț i handlerii de efecte în două categorii:


7. Efecte ș i gestionari de efecte 88

•Efecte ne suspendate: De exemplu: Rulaț i un efect secundar pentru a iniț ializa un callback atunci când Composable
intră în compoziț ie, o elimină când iese.
•Efecte suspendate: De exemplu: Încarcă date din reț ea pentru a alimenta o stare a interfeț ei utilizatorului.

Efecte non suspendate

Efect de eliminare
Reprezintă un efect secundar al ciclului de viaț ă al compunerii.

• Folosit pentru efecte nesuspendate care necesită a fi eliminate.


• Se activează prima dată (când componenta compusă intră în compunere) ș i apoi de fiecare dată când se schimbă cheile sale.
• NecesităonDispose callback la sfâr ș it. Este eliminat atunci când composabilul părăse ș te
compoziț ie, ș i de asemenea la fiecare recompoziț ie atunci când cheile sale s-au schimbat. În acest caz,
efectul este eliminat ș i relansat.

[Link]

1 @Composable
2 distrac ț iebackPressHandler(onBackPressed: () -> Unitate, activat: Boolean =adevărat) {

3 valoaredispatcher = [Link]

4
5 valoarebackCallback = remember {
6 obiect: OnBackPressedCallback(activat) {
7 decalca functiahandleOnBackPressed() {
8 onBackPressed()
9 }
10 }
11 }
12
13 DisposableEffect(dispatcher) {// aruncare/relaunch dacă dispatcher-ul se schimbă
14 [Link]ăCallback(backCallback)
15 onDispose {
16 [Link]()// evita ț i scurgerile!
17 }
18 }
19 }

Aici avem un handler pentru apăsarea butonului de întoarcere care ata ș ează un callback la un dispatcher ob ț inut dintr-un
Compoziț ieLocală(vechi ambienturi). Vrem să ataș ăm callback-ul când composabilul intră în
7. Efecte ș i gestionari de efecte 89

compoziț ie, ș i de asemenea atunci când dispecerul variază. Pentru a realiza asta, putem transmite dispecerul ca
cheia handler-ului de efect. Asta se va asigura că efectul este eliminat ș i relansat în acel caz.
Callback-ul este de asemenea eliminat atunci când composabilul părăseș te în cele din urmă compoziț ia.

Dacă ai dori să rulezi efectul o singură dată la intrarea în compoziț ie ș i să îl elimini la ieș ire
ai putea trece o constantă ca cheie:Efect de eliminare (DisposableEffect) adevăratsauEfect de eliminare (Unit).
Reț ineț i căEfect de unică folosinț ăîntotdeauna necesită cel puț in o cheie.

Efect secundar

Un alt efect secundar al compoziț iei. Acesta este puț in special, deoarece este ca un „foc pe aceasta
compunere sau uitare”. Dacă compunerea eș uează din orice motiv, aceasta este abandonată.
Dacă eș ti familiarizat puț in cu internele runtime-ului Compose, notează că este un efect neconservat.
în tabelul de sloturi, înseamnă că nu va supravieț ui compoziț iei ș i nu va fi încercat din nou în viitor.
prin compoziț ii sau orice altceva de genul acesta.

• Folosit pentru efecte care nu necesită eliminare.


• Se execută după fiecare compoziț ie / recompunere.
• Actualizări utile pentru publicarea către state externe.

[Link]

1 @Composable
2 distractivMyScreen(drawerTouchHandler: TouchHandler) {

3 valoaredrawerState = rememberDrawerState([Link])

4
5 EfectSecundar {
6 [Link] = [Link]
7 }
8
9 // ...
10 }

Acesta este acelaș i fragment pe care l-am folosit la început. Aici ne pasă de starea curentă a sertarului,
care poate varia în orice moment. În acest sens, trebuie să-l notificăm pentru fiecare compoziț ie în parte
sau recompoziț ie. De asemenea, dacăGestorDeAtingerea fost un singleton care a trăit în timpul aplicaț iei complete
execuț ie deoarece aceasta a fost ecranul nostru principal (întotdeauna vizibil), s-ar putea să nu dorim să eliminăm
referinț ă deloc.
Putem înț elegeEfect secundarca un handler de efecte destinat să publice actualizări către o stare externă
neadministrat de compunereStatsistem pentru a-l menț ine întotdeauna sincronizat.
7. Efecte ș i gestionari de efecte 90

currentRecomposeScope
Acesta este mai mult un efect în sine decât un handler al efectelor, dar este interesant de abordat.

As an Android dev you might be familiar with the Vizualizaresistemanulacorespondent, care


în esenț ă impune un nou sistem de măsurare, layout ș i desenare pe vizualizarea ta. A fost folosit intens
pentru a crea animaț ii bazate pe cadre folosindPânză, de exemplu. Aș adar, la fiecare tic de desen aț i
invalidează vizualizarea ș i, prin urmare, desenează din nou pe baza unui timp scurs.

ThecurrentRecomposeScope este o interfaț ă cu un singur scop:


[Link]

1 interfa ț ăRecomponenteDomeniu{
2 /**
3 * Invalida ț i domeniul corespunzător, solicitând compozitorului să recompună acest sc\
4 ope.
5 */
6 distrac ț ieinvalidare()

7 }

Deci, prin a [Link]() va invalida compoziț ia local


recompoziț ia forț elor.
Poate fi util atunci când foloseș ti o sursă de adevăr care nu este un instantaneu de stare compus.

[Link]

1 interfacePresenter{
2 distrac ț ieloadUser(after: @Composable () -> Unit): Utilizator

3 }
4
5 @Componibil
6 distrac ț ieMyComposable(presenter: Prezentator) {

7 valoareuser = [Link] { [Link]() //


} nu este un Stat\
8 e!
9
10 Utilizatorul încărcat: ${[Link]}
11 }

Aici avem un prezentator ș i invalidăm manual pentru a impune reconfigurarea atunci când există un rezultat,
deoarece nu folosimStatîntr-un fel sau altul. Aceasta este, evident, o situaț ie foarte delicată, aș a că probabil vei prefera
valorificareStatşi o recompoziţie inteligentă a marii majorităţi a timpului.
Deci, în general, Folosiț i cu moderatie! . Folosiț iStatpentru recompunere inteligentă când variază cât mai mult posibil, deoarece

that’ll make sure to get the most out of the Compose runtime.
7. Efecte ș i gestionari de efecte 91

Pentru animaț ii bazate pe cadru, Compose oferă API-uri pentru a suspenda ș i a aș tepta până la următorul
redarea cadrului pe coregraf. Apoi execuț ia continuă ș i poț i actualiza unele
stat în funcț ie de timpul scurs sau cumva folosind recompunerea inteligentă încă o dată. Eu
sugestii de lecturădocumentele oficiale de animaț ie⁶ pentru o mai bună înț elegere.

Efecte suspendate

rememberCoroutineScope
Această apelare creează unCoroutineScopefolosit pentru a crea locuri de muncă care pot fi considerate ca fiind copii ai
compoziț ie.

• Folosit pentru a rula efecte suspendate legate de ciclul de viaț ă al compoziț iei.
• CreeazăCoroutineScopelegat de acest ciclu de viaț ă al compoziț iei.
Domeniul este anulat la părăsirea compoziț iei.
• Acelaș i domeniu este returnat în întreaga compunere, aș a că putem continua să trimitem mai multe sarcini către acesta ș i toate
cele în curs vor fi anulate când vor pleca în cele din urmă.
Util pentru a lansa locuri de muncă ca răspuns la interacț iunile utilizatorului.

• Rulează efectul pe dispatcher-ul de aplicare (De obicei [Link]⁷) când intri.

[Link]

1 @Compozabil
2 distrac ț ieSearchScreen() {

3 valoarescope = rememberCoroutineScope()

4 var currentJob deaminte ș te-ț i { mutableStateOf<Job?>( null) }


5 vararticoledeaminte ș te-ț i { mutableStateOf<List<Item>>(listă goală()) }
6
7 Coloană {
8 Row {
9 Începeț i să tastaț i pentru a căuta
10 onValueChange = { text ->
11 currentJob?.cancel()
12 currentJob = [Link] {
13 întârziere(threshold)
14 items = [Link](query = text)
15 }
16 }
17 )
⁶[Link]
⁷[Link]
compose/ui/platform/[Link]
7. Efecte ș i gestionari de efecte 92

18 }
19 Linie { ListaVerticalăDeObiecte(items) }
20 }
21 }

Aceasta este o limitare pe partea de interfaț ă. Este posibil să fi făcut asta în trecut folosindpostDelayedsau o
ManipulantcuVizualizaresistem. De fiecare dată când un text introdus se schimba, dorim să anulăm orice anterior
joburi în curs, ș i a postat unul nou cu o întârziere, astfel încât întotdeauna impunem o întârziere minimă între
cereri de reț ea potenț iale, de exemplu.

Diferenț a cuLaunchedEffect este astaEfect lansateste folosit pentru definirea domeniului de aplicare a muncilor
iniț iat de compoziț ie, în timp ce rememberCoroutineScope este gândit pentru a delimita sarcinile
iniț iat de o interacț iune a utilizatorului.

Efect Lansat
Aceasta este varianta de suspendare pentru încărcarea stării iniț iale a unui Composable, de îndată ce acesta intră în
compunere.

Rulează efectul la intrarea în compunere.


• Anulează efectul la părăsirea compoziț iei.
• Anulează ș i relansează efectul atunci când tastele se schimbă.
• Util pentru a întinde un loc de muncă pe parcursul recompoziț iilor.

• Rulează efectul pe dispatcher-ul aplicatorului (De obicei [Link]⁸) când se intră.

[Link]
1 @Composable
2 distractivSpeakerList(eventId: String) {

3 vardifuzoaredeţine minte { mutableStateOf<List<Speaker>>(lista goală()) }


4 LaunchedEffect(eventId) { // anulat / relansat când eventId variază
5 speakers = [Link](eventId) // efect suspendat
6 }
7
8 ItemsVerticalList(speakeri)
9 }

Nu am prea multe de spus. Efectul se activează o dată la intrare, apoi încă o dată de fiecare dată când tasta variază.
deoarece efectul nostru depinde de valoarea sa. Acesta va fi anulat când părăseș te compunerea.
Aminteș te-ț i că este de asemenea anulat de fiecare dată când trebuie să fie [Link] Lansatnecesită la
cel puț in o cheie.
⁸[Link]
compose/ui/platform/[Link]
7. Efecte ș i gestionari de efecte 93

produceStare
Aceasta este de fapt un zahăr sintactic construit pe deasupraEfect_Lansat.

• Folosit când al tăuEfect Lansatajunge să hrănească unStat(ce este cea mai mare parte a timpului).
Se bazează peEfect Lansat.

[Link]
1 @Compozabil
2 distrac ț ieCautaEcran(eventId: String) {

3 valuiState = produceState(initialValue = emptyList<Speaker>(), eventId) {


4 [Link](eventId) // efect suspendat
5 }
6
7 ItemsVerticalList([Link])
8 }

Puteț i oferi o valoare implicită pentru stare ș i, de asemenea, una sau mai multe chei.
Singura capcană este căproduceState permite să nu se treacă nicio cheie, iar în acest caz va apela
Efect LansatcuUnitateca cheia, făcând-o să se întindă peste compoziț ii. Ț ine cont de asta
deoarece suprafaț a API-ului nu o face explicită.

Adaptori pentru biblioteci de terț ă parte

We frequently need to consume other data types from third party libraries like Observabil, Flux, or
LiveDataJetpack Compose oferă adaptoare pentru cele mai frecvente tipuri de terț ă parte, aș a că în funcț ie de
în bibliotecă va trebui să obț ii o dependenț ă diferită:
[Link]
1 implementare "[Link]:runtime:$compose_version" // includează Flow \
2 adaptor
3 implementare "[Link]:runtime-livedata:$compose_version"
4 implementare "[Link]:runtime-rxjava2:$compose_version"

Toate acele adaptoare sfârș esc prin a delega către handlerii de efecte. Toate ataș ează un observator folosind
API-urile bibliotecilor de terț ă parte ș i ajung să mapareze fiecare element emis la un ad hocStareMutabilă
care este expus de funcț ia adaptor ca un element imuabilStat.
Câteva exemple pentru bibliotecile diferite de mai jos

LiveData
7. Efecte ș i gestionari de efecte 94

[Link]

1 clasaMeaComposableVMViewModel() {
2 val privat_user = MutableLiveData(User("John"))
3 valoareuser: LiveData<User> = _user

4 //...
5 }
6
7 @Composable
8 distrac ț ieMyComposable() {

9 valoareviewModel = viewModel<MyComposableVM>()

10
11 [Link]()

12
13 Nume utilizator: ${user?.name}
14 }

Aici⁹ este implementarea efectivă aobservaCaStarecare se bazează peEfect de eliminaremanipulator.

RxJava2

[Link]

1 clasaMyComposableVMViewModel() {
2 valoareuser: Observable<ViewState> = [Link]([Link])

3 //...
4 }
5
6 @Compozabil
7 distrac ț ieMyComposable() {

8 valoareviewModel = viewModel<MyComposableVM>()

9
10 valuiState [Link]([Link])
11
12 când(uiState) {
13 [Link] -> TODO("Show loading")
14 [Link] -> TODO("Show Snackbar")
15 is [Link] -> TODO("Show content")
16 }
17 }

⁹[Link]
livedata/[Link]
7. Efecte ș i gestionari de efecte 95

Aici¹⁰ este implementarea pentruabonaț i-vă ca stare(). Aceeaș i poveste Aceeaș i extensie este, de asemenea
disponibil pentruFluxabil.

Fluxurile KotlinX Coroutine

[Link]

1 clasaMyComposableVMViewModel() {
2 valoareutilizator: Flow<ViewState> = flowOf([Link])
3 //...
4 }
5
6 @Composable
7 distractivMyComposable() {

8 valoareviewModel = viewModel<MyComposableVM>()

9
10 valoareuiState [Link]ăCaStare(ViewState.Încărcare)
11
12 când(uiState) {
13 [Link] -> TODO("Show loading")
14 [Link] -> TODO("Show Snackbar")
15 [Link] -> TODO("Show content")
16 }
17 }

Aici¹¹ este implementarea pentrucolecteazăCaStareAcesta este puț in diferit deoareceFluxtrebuie să fie


consumat dintr-un context suspendat. De aceea se bazează peproduceStareaîn schimb, care deleagă
peEfect Lansat.
Deci, aș a cum puteț i vedea, toate aceste adaptoare se bazează pe gestionarii de efecte explicaț i în acest articol, ș i aț i putea
scrie cu uș urinț ă propriul tău urmând acelaș i model, dacă ai o bibliotecă de integrat.
¹⁰[Link]
rxjava2/[Link]
¹¹[Link]
[Link]
8. Ciclu de viaț ă Composable
A fi scris.
9. Utilizarea avansată a runtime-ului Compose

cazuri
Până acum, cartea discuta despre Compose în contextul Android, deoarece acesta este unghiul cel mai
oamenii vin din. Cu toate acestea, aplicaț iile Compose se extind mult dincolo de Android sau utilizator
interfeț e. Acest capitol va trece în revistă unele dintre aceste utilizări avansate cu exemple practice.

Compose runtime vs Compose UI

Înainte de a trece la subiect, este important să trasăm o linie întreInterfaț ă utilizator Compose ș i runtime Compose¹².
Compose UI este noul toolkit UI pentru Android, cu arborele deNodLayouts care mai târziu trage
their content on the canvas. TheCompose runtimeprovides underlying machinery and many
primitivi legati de stare/compoziț ie.
Cu compilatorul Compose primind suport pentru întreaga gamă de platforme Kotlin, acum este
posibil să foloseș ti runtime-ul pentru a gestiona UI-ul sau orice alte ierarhii arbore aproape peste tot (ca
long as it runs Kotlin). Note the “other tree hierarchies” part: almost nothing in Compose runtime
menț ionează direct UI (sau Android). În timp ce runtime-ul a fost cu siguranț ă creat ș i optimizat pentru a susț ine
această caz de utilizare, este suficient de generic pentru a construi structuri de tip arbore de orice fel. De fapt, este foarte similar
în această privinț ă, React JS, a cărui utilizare principală a fost de a crea UI pe web, dar a găsit mult
utilizare mai largă în lucruri precumsintetizatoare
sau redaț i 3D¹³. Cele mai multe dintre rendererele personalizate reutilizează nucleul
functionality from React runtime but provide their own building blocks in place of browser DOM.

Nu este un secret că dezvoltatorii Compose au fost inspiraț i de React în timp ce creau biblioteca. Chiar ș i prima
prototipuriXML direct în Kotlina avut o senzaț ie foarte similară cu abordarea HTML-in-JS pe care o are React.
Fără surprize, Compose poate face majoritatea lucrurilor realizate cu React de-a lungul anilor, dar le rulează
nativ cu Kotlin multiplatform în loc să necesite o maș ină virtuală JavaScript.
Pagina nu poate fi accesată.

Chiar înainte ca versiunea Android a Compose să fie disponibilă oficial, JetBrains deja a început să adopte
Compune pentru Kotlin multiplatform: la momentul scrierii, ei lucrează la o versiune JVM pentru
desktop ș i o versiune JS pentru browsere. Ambele exemple reutilizează diferite părț i ale Compose:
¹²[Link]
¹³[Link]
9. Cazuri avansate de utilizare a runtime-ului Compose 98

• Compose pentru Desktop a reuș it să se apropie foarte mult de sistemul Android, reutilizând întreaga
stratul de redare al Compose UI, datorită wrapper-ilor Skia portate. Sistemul de evenimente a fost de asemenea
extins pentru a susț ine mai bine mouse-ul/tastatura.
• Compose pentru Web a urmat o cale de a se baza pe DOM-ul browserului pentru a afiș a elemente,
reutilizând doar compilatorul ș i timpul de execuț ie. Componentele disponibile sunt definite pe baza HTML/CSS,
rezultând într-un sistem foarte diferit faț ă de Compose UI. Cu toate acestea, runtime-ul ș i compilatorul sunt
utilizat aproape în acelaș i mod, chiar dacă platforma de bază este complet diferită.

Compilator -> Timp de execuț ie -> Web -> Compose UI -> Android/Desktop

Posibilitatea de a folosi Compose pentru lucruri diferite de UI-ul Android a fost ultimul motiv care a condus
eu cătreexperimentând cu acesta¹⁴ în primele zile ale Compose.

Părț ile rămase ale acestui capitol vor trece prin câteva exemple de utilizare a Compose
timp de execuț ie pentru a construi ierarhii personalizate pentru nevoile tale. Primul exemplu de acest fel este din interior
Biblioteca UI Android, unde Compose este utilizat pentru a reda grafica vectorială. După aceea, ne vom schimba către
Kotlin/JS ș i creează o versiune de jucărie a bibliotecii de gestionare a DOM-ului cu Compose.

Compoziț ia graficii vectoriale

Redarea vectorială în Compose este implementată prinPictorabstrac ț ie, similar cu


Desenabilîn sistemul Android clasic:

¹⁴[Link]
9. Cazuri de utilizare avansate pentru Compose Runtime 99

[Link]
1 Imagine(
2 painter = rememberVectorPainter { width, height ->
3 Grup(
4 scaleX =0.75f,
5 scaleY =0.75f
6 ) {
7 valoarepathData = PathData { ... }

8 Path(pathData = pathData)
9 }
10 }
11 )

Funcț iile din interiorpictorVectorMemoriebloc (Grupș iCaleîn special) sunt composables


sunt bine, dar un alt tip. În loc să creezeLayoutNodes ca celelalte componente în Compose
UI, ele creează elemente specifice vectorului. Combinându-le rezultă un arbore vectorial, care este
apoi tras pe pânză.

Imagine: Compoziț ie UI arbore -> Painter -> Arbore vectorial

TheGrupș iCaleexistă într-o compoziț ie diferită faț ă de cea aImagineface. Această compunere
este conț inut înVectorPainterș i permite doar utilizarea elementelor care descriu o imagine vectorială,
9. Cazuri de utilizare avansate ale Compose Runtime 100

în timp ce compozabilele UI obiș nuite sunt interzise.

Verificarea compozitelor vectoriale se face în timpul execuț iei în momentul scrierii, aș a că compilatorul
va sari cu bucurie dacă foloseș tiImaginesauCutieîn interiorulPictorVectorialbloc. Acest lucru face ca scrierea să
aceș ti pictori pot fi nesiguri, dar erau zvonuri că echipa de compilare Compose îmbunătăț eș te
siguranț a la momentul compilării pentru cazuri ca acesta în viitor.

Cele mai multe dintre regulile referitoare la stări, efecte ș i tot ce ț ine de timpul de execuț ie au fost discutate în precedenta
capitolele se transferă de la compoziț ia UI la cea vectorială. De exemplu, API-ul de tranziț ie poate fi
folosit pentru a anima schimbările imaginii vectoriale alături de UI. Verifică demo-urile Compose pentru mai multe
details: [Link]¹⁵ ș [Link]¹⁶.

Construirea imaginii vectoriale a unui copac

Imagina vectorială este creată din elemente mai simple decâtLayoutNode pentru a se adapta mai bine cerinț elor
componente ale graficii vectoriale:

[Link]

1 clasă sigilată VNode{


2 fun abstractDesenează[Link]ă()
3 }
4
5 // nodul rădăcină
6 clasă internă ComponentăVectorVNode() {
7 valoareroot = GroupComponent()

8
9 suprascrie func ț [Link]() {
10 // configurează dimensiunea viewport-ului ș i cachează desenarea

11 }
12 }
13
14componentaCaleInterioarăVNode() {
15 varpathData: List<PathNode>
16 // mai multe proprietă ț i
17
18 suprascrie [Link]() {
19 // desenează calea

¹⁵[Link]
java/androidx/compose/ui/demos/[Link]
¹⁶[Link]
java/androidx/compose/ui/demos/[Link]
9. Cazuri avansate de utilizare a Compose Runtime 101

20 }
21 }
22
23internal classGroupComponentVNode() {
24 valoare privatăchildren = mutableListOf<VNode>()
25 // mai multe proprietă ț i
26
27 func ț ie de [Link]() {
28 // desenează copiii cu transformare
29 }
30 }

The nodes above define a tree structure similar to the one used in classic vector drawable XMLs.
Copacul în sine este construit din două tipuri principale de noduri:
- GroupComponentcare combină copiii ș i le aplică o transformare comună;
- ComponenteCale, un nod frunză (fără copii) care deseneazăpathData.
oferă o modalitate de a desena conț inutul nodurilor ș i al copiilor lor. A
distrac ț ie [Link] ț i()

semnătura acestei funcț ii este aceeaș i ca înPictorinterfaț ă care este integrată cu rădăcina de
această arbore mai târziu.

Acelaș iVectorPainter este folosit pentru a arăta resursele vectoriale drawable XML din clasic
Sistem Android. Parserul XML creează o structură similară care este transformată într-o lanț de
Componibilapeluri, rezultând în aceeaș i implementare pentru tipuri de resurse aparent diferite.

Nodurile copacului de mai sus sunt declarate ca interne, iar singura modalitate de a le crea este prin
corespunzător@Compozabildeclaratii. Aceste funcț ii sunt cele folosite în exemplul cu
rememberVectorPainterla începutul acestei secț iuni.

[Link]

1 @Composable
2 distrac ț ieGrup

3 scaleX: Float = DefaultScaleX,


4 scaleY: Float = DefaultScaleY,
5 ...
6 content: @Composable () -> Unit
7 ) {
8 ComposeNode<GroupComponent, VectorApplier>(
9 factory = { GroupComponent() },
10 update = {
11 set(scaleX) {[Link] = it }
9. Cazuri de utilizare avansate ale Compunerii Runtime 102

12 set(scaleY) { [Link] = it }
13 ...
14 },
15 content = content
16 )
17 }
18
19@Compozabil
20distrac ț ieCale(
21 pathData: List<PathNode>,
22 ...
23) {
24 ComposeNode<PathComponent, VectorApplier>(
25 factory = { PathComponent() },
26 update = {
27 set(pathData) { [Link] = it }
28 ...
29 }
30 )
31 }

ComposeNodeapelurile emit nodul în compunere, creând elemente de arbore. În afara acestora,


@Compozabilfuncț iile nu trebuie să interacț ioneze deloc cu arborele. După inserț ia iniț ială (când
elementul nod este creat), Compose urmăreș te actualizările pentru parametrii definiț i ș i incremental
actualizări legate de proprietăț i.

• fabricaparametrul defineș te cum este creat nodul arborelui. Aici, se apelează doar constructorii
pentru corespondentCalesauGrupcomponente.
• actualizareoferă o modalitate de a actualiza proprietăț ile unei instanț e deja create în mod incremental. În interior
lambda, Compose memorează datele cu ajutoare

(cum ar fifun <T> [Link](valoare: T)saufun <T> [Link](valoare: T)) care reîmprospătează
proprietăț ile nodului copacului doar atunci când valoarea furnizată se schimbă pentru a evita invalidările inutile.

• conț inuteste modul de a adăuga noduri copil la părintele lor. Acest parametru compus este executat
after the update of the node is finished, and all the nodes that are emitted are then parented
la nodul [Link] asemenea are o suprasarcină fărăconț inutparametru, care
poate fi utilizat pentru noduri frunza, de exemplu pentruCale.

Pentru a conecta nodurile copil la părinte, Compose foloseș teAplicant, discutate în capitolele anterioare.
VNodes sunt combinate prinVectorApplier:
9. Cazuri de utilizare avansată a Runtime-ului Compose 103

[Link]
1 classVectorApplier(rădăcină: VNode) : AbstractApplier<VNode>(rădăcină) {
2 suprascrie func ț iainsertTopDown(index: Int, instance: VNode) {

3 [Link]().inseraLa(index, instanta)
4 }
5
6 suprascrie func ț iainsertBottomUp(index: Int, instance: VNode) {
7 // Ignorat deoarece arborele este construit de sus în jos.

8 }
9
10 suprascrie func ț iaelimină(index:
Înt, număr: Înt) {
11 [Link]().remove(index, count)
12 }
13
14 suprascrie ț i func ț iamutare(de
la: Int, la: Int, număr: Int) {
15 [Link]().mutare(de la, la, număr)
16 }
17
18 suprascrie func ț iaonClear()
{
19 [Link]().let { [Link](0, [Link]) }
20 }
21
22 // VectorApplier func ț ionează doar cu [GroupComponent], deoarece nu poate adăuga
23 // copii la [PathComponent] prin design
24 functie [Link](): GroupComponent {
25 întoarce-te când (acesta) {
26 esteGroupComponent ->aceasta
27 altfel-> eroare("Nu se poate introduce doar VNode în Grup")
28 }
29 }
30 }

Cele mai multe dintre metodele dinAplicantinterfaț


a produce frecvent operaț iuni cu lista (inseraț i/mută/elimină).
Pentru a evita reimplementarea lor din nou ș i din nou,Aplicaț ie abstractăoferă chiar comoditate
extensii pentruListaMutableÎn cazul în careVectorApplier, aceste operaț ii pe liste sunt implementate
direct în unComponentăGrup.

Solicitantoferă
două metode de inserare:de sus în josș ide jos în sus, cu un ordonare diferită a asamblării-
bradul de Crăciun

• topDown mai întâi adaugă un nod în arbore ș i apoi adaugă copiii săi, inserându-i unul câte unul;
9. Cazuri avansate de utilizare a runtime-ului Compose 104

• de jos în suscreează nodul, adaugă toț i copiii ș i abia apoi îl inserează în arbore.

Motivul de bază este performanț a: unele medii au costul asociat al adăugării


copiii la copac (gândiț i-vă la reproiectare atunci când adăugaț i o View în sistemul clasic Android). Pentru
în cazul utilizării vectorilor, nu există un astfel de cost de performanț ă, astfel încât nodurile sunt inserate de sus în jos. Vezi

Aplicatordocumentaț iepentru mai multe informaț ii.


[Link]
commonMain/kotlin/androidx/compose/runtime/[Link];l=67

Integrarea compunerii vectoriale în Compose UI


Cu Applier-ul la locul său, compoziț ia vectorială este aproape gata de utilizare. Ultima parte estePictor
integrare.
[Link]
1 classVectorPainterinternal constructor() : Painter() {
2 ...
3
4 1. Apelat în contextul compunerii UI
5 @Compozabil
6 fun internRenderVector(
7 content: @Composable (...) -> Unit
8 ) {
9 // 2. Contextul părinte este capturat cu [rememberCompositionContext]
10 // pentru a propaga valorile sale, de exemplu, CompositionLocals.
11 valoarecomposition
= composeVector(
12 rememberCompositionContext()
13 conț inut
14 )
15
16 Ori de câte ori interfa ț a UI "uită" VectorPainter-ul,
17 // compunerea vectorului este dispusă cu [DisposableEffect] mai jos.
18 Efect De Consumabil(compozi ț ie) {
19 onDispose {
20 [Link]()
21 }
22 }
23 }
24
25 fun privatcomposeVector(
9. Cazuri de utilizare avansate ale Compose Runtime 105

26 parent: CompositionContext,
27 composable: @Composable (...) -> Unit
28 ): Compozi ț ie {
29 ...
30 // Vezi implementarea mai jos
31 }
32 }

Prima parte a integrării este conectarea compunerii UI Compose ș i a compunerii imaginii vectoriale.
tion:

1. VectorDeRedareacceptăconț inutcu descriere compusă a imaginii vectoriale. ThePictor


instanț a este de obicei păstrată la fel între recompuneri (cuaminteș te-ț i), darRenderVector
se poartă apel pe fiecare compoziț ie dacăconț inuts-a schimbat.
2. Crearea compoziț iei necesită întotdeauna un context părinte, iar aici este preluat din UI
compoziț ie cuaminteș teContextCompoziț ieAsigură că ambele sunt conectate la aceeaș i
Recompozitorș i toate valorile interne (de exempluCompoziț ieLocalăs pentru densitate) sunt propagate către
compoziț ia vectorială la fel.
3. Compoziț ia este păstrată prin actualizări, dar ar trebui eliminată oricâ[Link]
părăseș te [Link]ă această curăț are similar cu alte tipuri de subscripț ie
funcț ii în Compose.

În cele din urmă, ultimul pas este să umplem compoziț ia cu conț inut de imagine pentru a crea un arbore de vectori.
nodi, care sunt folosiț i mai târziu pentru a desena imaginea vectorială pe canvas:

[Link]

1 classVectorPainterPainter() {
2 Componenta rădăcină pentru arborele vectorial
3 privat valvector = VectorComponent()
4 1. Compozi ț ie cu elemente vectoriale.
5 variabilă privatăcomposition: Composition? = null

6
7 @Composable
8 func ț ie internăRenderVector(
9 content: @Composable (...) -> Unit
10 ) {
11 ...
12 // Vezi implementarea completă de mai sus
13 }
14
15 fun privatăcomposeVector(
16 parent: CompositionContext,
9. Cazuri avansate de utilizare a Compose Runtime 106

17 composable: @Composable (...) -> Unit


18 Compozi ț ie {
19 // 2. Creează o compunere sau reutilizează una existentă
20 valoarecomposition=
21 dacă([Link] == null || [Link] ț [Link]ț ată) {
22 Compoziț ie
23 VectorApplier([Link]),
24 părinte
25 )
26 } altfel{
27 [Link]
28 }
29 [Link] = composition
30
31 3. Setează con ț inutul vectorului la valoarea compusă actualizată
32 [Link] {
33 // Composable-urile vector pot fi apelate doar în acest bloc
34 compozabil(vector.lă ț imeaVederii, vector.înăl ț imeaVederii)
35 }
36
37 returnacompoziț ie
38 }
39
40 // Integrarea interfe ț ei de pictor, este apelată de fiecare dată când sistemul
41 // trebuie să deseneze imaginea vectorială pe ecran
42 suprascrie func ț [Link]() {
43 cu(vector) {
44 desena()
45 }
46 }
47 }

1. Pictorul îș i menț ine propria compoziț ie, deoareceComposeNodecere aplicantului să se potrivească


orice lucru care este trecut la compunere ș i contextul UI foloseș te aplicator incompatibil cu vectorul
nodule.
Această compunere este reîmprospătată dacă pictorul nu a fost iniț ializat sau compunerea sa a ieș it din
domeniu.
3. După ce a fost creată compunerea, aceasta este populată prinseteazăConț inut, similar cu cel folosit
în interiorulVizualizareCompoziț ieOri de câte oriRenderVectorse numeș te cu diferiteconț inut, setaț i conț inutul
este executat din nou pentru a reîmprospăta structura vectorială. Conț inutul adaugă copiii larădăcinănodul care
este folosit mai târziu pentru a desena conț inutulPainter.

Cu asta, integrarea este terminată, ș iVectorPainterpoate acum să deseneze@Composableconț inuturi


9. Cazuri de utilizare avansate ale timpului de compunere 107

pe ecran. Composable-urile din interiorul painter-ului au, de asemenea, acces la stare ș i compunere
localii din compunerea UI pentru a-ș i conduce propriile actualizări.

Cu asta, ș tii cum să creezi un copac personalizat ș i să-l încorporezi în compoziț ia deja existentă.
În partea următoare, vom trece prin crearea unui sistem Compose de sine stătător bazat pe acelaș i.
principi… în Kotlin/JS.

Gestionarea DOM-ului cu Compose

Suportul multiplatformă este încă un lucru nou pentru Compose, având disponibil doar timpul de execuț ie ș i compilatorul.
în afara ecosistemului JVM. Aceste două module, totuș i, sunt tot ce avem nevoie pentru a crea o compoziț ie
ș i să rulezi ceva în el, ceea ce duce la mai multe experimente!

Compilatorul Compose de la Google suportă toate platformele Kotlin, dar timpul de execuț ie este dezactivat.
distribuit doar pentru Android. Jetbrains, totuș i, publicăpropria lor versiune (în mare parte neschimbată) de
Compunecu artefacte multiplatformă pentru JS de asemenea.
[Link]

Primul pas pentru a face magia Compose să se întâmple este să îț i dai seama de arborele pe care ar trebui să opereze.
Din fericire, browserele au deja un sistem de „vizualizare” bazat pe HTML/CSS. Putem
manipulează aceste elemente din JS prin DOMModelul obiectului document¹⁷) API, care este de asemenea
furnizat de biblioteca standard Kotlin/JS.
Înainte de a începe cu JS, să aruncăm o privire asupra reprezentării HTML în interiorul browserului.

[Link]

1 <div>
2 <ul>
3 <li>Articol 1<li>
4 <liElementul 2li>
5 <liArticolul 3li>
6 </ul>
7 </div>

HTML-ul de mai sus afiș ează o listă neordonată (cu puncte) cu trei elemente. Din perspectiva
browser, această structură arată astfel:
¹⁷[Link]
9. Cazuri avansate de utilizare a runtime-ului Compose 108

Reprezentarea arborelui HTML în browser

DOM-ul este o structură asemănătoare unui copac construită din elemente care sunt expuse în Kotlin/JS ca
[Link] relevante pentru noi sunt:

• Elementele HTML (subclase [Link]) reprezintă etichetele (de exempluli


saudiv). Ele pot fi create cu [Link](<tagName>)ș i browserul va
găsiț i automat implementarea corectă pentru un tag,
• Text între etichete (de exempluTestîn exemplele de mai sus) reprezentat ca [Link].
Instanț ele acestui element pot fi create [Link](<valoare>)

Folosind aceste elemente DOM, JS vede acest arbore în următorul mod:


9. Cazuri de utilizare avansate ale Compunerii Runtime 109

Reprezentarea arborelui HTML pentru JS

Aceste elemente vor oferi baza pentru arborele gestionat de Compose, similar cu modul în careVNodes sunt
folosit pentru compunerea imaginilor vectoriale în partea anterioară.

[Link]
1 @Compozabil
2 distractivTag(tag: String, content: @Composable () -> Unit) {

3 ComposeNode<HTMLElement, DomApplier>(
4 factory = { [Link](tag)caHTMLElement },
5 update = {},
6 content = content
7 )
8 }
9
10@Compozabil
11distrac ț ieText(valoare: String) {
12 ReusableComposeNode<Text, DomApplier>(
13 factory = { [Link]("") },
14 update = {
15 set(value) { [Link]= ea }
16 }
17 )
9. Advanced Compose Runtime use cases 110

18 }

Etichetele nu pot fi modificate pe loc, deoarece<audio>are o reprezentare complet diferită în browser


de la<div>, deci dacă numele etichetei s-a schimbat, ar trebui să fie recreat. Compose nu gestionează acest lucru.
în mod automat, astfel că este important să evitaț i să treceț i valori diferite pentru numele etichetelor în acelaș i
compozabil.

Cea mai simplă modalitate de a realiza recrearea nodurilor este să împachetezi fiecare nod într-un composabil separat
(de exempluDivș iUlpentru elementele corespunzătoare). Procedând astfel, creezi grupuri diferite la timpul compilării
pentru fiecare dintre ei, sugerând Compose că acele elemente ar trebui înlocuite complet în loc de
doar actualizând proprietăț ile lor.

Textelementele, totuș i, sunt structurale la fel ș i indicăm acest lucru cuReusableComposeNode.


This way, even when Compose finds these nodes inside different groups, it will reuse the instance.
Pentru a asigura corectitudinea, nodul de text este creat fără conț inut, iar valoarea este setată cuactualizare
parametru.
Pentru a combina elementele într-un arbore, Compose necesită unSolicitantinstanț ă care funcț ionează pe elemente DOM.
Logica pentru aceasta este foarte similară cuVectorApplierdeasupra, cu excepț ia metodelor nodului DOM pentru
adăugarea/eliminarea copiilor este uș or diferită. Cel mai mult din codul de acolo este complet mecanic
(mutarea elementelor la indici corecț i), aș a că voi omite asta aici. Dacă cauț i o referinț ă, eu
recomand să verificaț iAplificator folosit în Compose pentru Web¹⁸.

Compoziț ie autonomă în browser


Pentru a începe combinarea noilor noastre componente în UI, Compose necesită o compunere activă. În Com-
interfaț a UI, toate iniț ializările sunt deja făcute înVizualizareCompoziț ie, dar pentru mediu de browser
trebuie să fie creat de la zero.

Aceleaș i principii pot fi aplicate ș i pentru diferitele platforme, deoarece toate componentele
descrise mai jos există în codul Kotlin „comun”.

¹⁸[Link]
compose/web/[Link]#L63-L91
9. Cazuri de utilizare avansate ale Runtime-ului Compose 111

[Link]
1 distrac ț ierendereazăCompozabil(root:
HTMLElement, conț inut: @Compozabil () -> Unitate) {
2 [Link]()
3
4 valoarerecomposerContext= DefaultMonotonicFrameClock + [Link]
5 valoarerecomposer = Recomposer(recomposerContext)

6
7 valoarecomposition = ControlledComposition(
8 applier = DomApplier(root),
9 parent = recomposer
10 )
11
12 [Link](content)
13
14 CoroutineScope(recomposerContext).launch(start = UNDISPATCHED) {
15 [Link]()
16 }
17 }

renderizabilascunde toate detaliile de implementare ale compunerii de început, oferind un mod de


încărcaț i elementele compozabile într-un element DOM. Majoritatea configurărilor de interior este legată de iniț ializare
Recompunătorcu ceas corect ș i context de corutină:

• În primul rând, sistemul de snapshot (responsabil pentru actualizările stării) este iniț [Link]
este lăsat intenț ionat deoparte de la runtime, ș i îl poț i copia de laSurse Android¹⁹ dacă ț inta
platforma nu dispune de una furnizată. Este singura parte care nu este furnizată de runtime.
momentul.
• Apoi, contextul coroutine pentruRecompozitoreste creat cu setările implicite JS. Implicitul
CeasMonotonicpentru browsere este controlat curequestAnimationFrame(dacă foloseș ti
implementarea JetBrains), ș [Link] referă la singurul fir pe care JS operează.
Acest context este folosit pentru a rula recompuneri mai târziu.
• Acum suntem pregătiț i să creăm o compoziț ie. Aceasta se creează la fel ca în exemplul vectorial.
deasupra, dar acum recompozitorul este folosit ca părinte de compoziț ie (recompozitorul trebuie să fie întotdeauna
a parent of the top-most composition).
• Ulterior, conț inutul compoziț iei este stabilit. Toate actualizările pentru această compoziț ie ar trebui să aibă loc
în interiorul componentelor furnizate, ca noi invocări alerandareCompozabilăvoi recrea totul
de la zero.
Ultima parte este să începi procesul de recompuneri prin lansarea unei coroutine cu
[Link] Android, acest proces este de obicei legat de
ciclul de viaț ă al activităț ii/vederii, cu [Link]ă()pentru a opri procesul de recompunere.
¹⁹[Link]
compose/ui/platform/[Link]
9. Cazuri de utilizare avansată a Runtime-ului Compose 112

Aici, ciclul de viaț ă al compoziț iei este legat de durata de viaț ă a paginii, aș a că nu sunt anulări.
necesar.

Primitivii de mai sus pot fi acum combinaț i pentru a reda conț inutul unei pagini HTML:

[Link]

1 distractivmain(){
2 renderComposable([Link]!!) {
3 <button>Fă clic pe mine!</button>
4 Eticheta("buton") {
5 Text("Click me!")
6 }
7 }
8 }

Crearea de conț inut static, totuș i, poate fi realizată prin mijloace mult mai simple, iar Compose a fost necesar.
în primul rând pentru a realiza interactivitate. În cele mai multe cazuri, ne aș teptăm ca ceva să se întâmple atunci când
butonul este apăsat, iar în DOM se poate realiza cu, similar cu vizualizările Android, ascultători de clicuri.

În Compose UI, mulț i ascultători sunt definiț i prinModifierextensii, dar implementarea lor
este specific pentruLayoutNode, aș adar, nu este utilizabil pentru această bibliotecă web pentru jucării. Este posibil să copiaț iModificator
comportament din Compose UI ș i ajustaț i nodurile folosite aici pentru a oferi o integrare mai bună cu evenimentul
ascultători prin modificatori, dar este lăsat ca un exerciț iu pentru cititor.

[Link]

1 @Composable
2 distrac ț ieEticheta(

3 tag: String,
4 // acest callback este invocat la evenimentele de click
5 onClick: () -> Unit = {},
6 content: @Composable () -> Unit
7 ) {
8 ComposeNode<HTMLElement, DomApplier>(
9 factory = { createTagElement(tag) },
10 update = {
11 // când listener-ul se schimbă, listener-ul de pe nodul DOM este resetat
12 set(onClick) {
13 [Link] = { _ -> onClick() }
14 }
15 },
9. Cazuri avansate de utilizare a runtime-ului Compose 113

16 content = content
17 )
18 }

Fiecare etichetă poate acum să definească un ascultător de clicuri ca parametru lambda care este propagat către un nod DOM
cu utilonclickproprietate definită pentru toț iElement HTMLCu acea adăugare, clicurile pot acum să fie
gestionat prin transmitereonClick parametru laEtichetăcompozabil.

[Link]

1 distrac ț iemain()
{
2 renderComposable([Link]!!) {
3 // Counter state is updated on click
4 varstareContrardeaminte ș te-te { mutableStateOf(0) }
5
6 Tag("h1") {
7 Valoarea contorului: $counterState
8 }
9
10 Tag("buton", onClick = { counterState++ }) {
11 Incrementaț i!
12 }
13 }
14 }

De aici, există multiple modalităț i de a extinde această bibliotecă de jucării, adăugând suport pentru CSS, mai multe evenimente,
ș i elemente. Echipa JetBrains experimentează în prezent o versiune mai avansată a Compose
pentru Web. Este construit pe aceleaș i principii ca ș i versiunea de jucărie pe care am explorat-o în acest capitol, dar este mai
avansat în multe feluri pentru a susț ine o varietate de lucruri pe care le poț i construi pe web. Poț i încercathe
tech demo²⁰ începeț i cu proiectele Kotlin/JS pentru a învăț a mai multe.

Concluzie
În acest capitol, am explorat cum conceptele fundamentale ale Compose pot fi folosite pentru a construi sisteme în afara de
Compose UI. Custom compositions are harder to meet in the wild, but they are a great tool to have
în centura ta dacă lucrezi deja în mediu Kotlin/Compose.
Compoziț ia grafică vectorială este un bun exemplu de integrare a arborilor compozabili personalizaț i în
Compose UI. Aceleaș i principii pot fi utilizate pentru a crea alte elemente personalizate care pot fi cu uș urinț ă
interact with states/animations/composition locals from UI composition.
²⁰[Link]
9. Cazuri de utilizare avansată a runtime-ului Compose 114

De asemenea, este posibil să creati compoziț ii independente pe toate platformele Kotlin! Am explorat acest lucru prin
making a toy version of the DOM management library based on Compose runtime in a browser
prin puterea Kotlin/JS. În mod similar, runtime-ul Compose este deja folosit pentru a manipula
Arbori UI în unele proiecte în afara Android (veziMozaic²¹, părerea lui Jake Wharton despre CLI).
Îț i încurajez să experimentezi cu propriile idei folosind Compose ș i să oferi feedback pentru Compose
echipă în canalul Slack #compose Kotlin! Obiectivul lor principal este încă definit de Compose UI, dar ei
sunt foarte entuziasmaț i să afle mai multe despre alte lucruri pentru care este folosit Compose.

²¹[Link]

S-ar putea să vă placă și