Jetpack Compose
És una forma declarativa i reactiva de definir les interfícies. Similar a React i a QML.
Funcions composables
Una funcio composable declara l'estructura d'una interfície similarment a com ho faría React.
Igual que React, el component es recalcula quan (i només quan) canvien els paràmetres/attributs o l'estat.
Estat permanent
Els components poden tenir un estat permanent
que son els remembers.
L'equivalent amb React seria el useState.
Com s'aconsella que l'estat estigui el més amunt possible,
es fan servir sovint block de callback com a parametres
per pujar l'estat (onClick...) .
Layouts i modifiers
El Modifier es un objecte que accepten la majoria de composables que especifica parametres comuns, normalment de layout pero també alguns de comportament.
Normalment s'omplen cridant mètodes setters en cascada.
En aquest cas, que estem setejant attributs de layout el paral·lelisme amb React serien els estils, però també es fa servir el modifier per a establir paràmetres d'interactivitat (disabled, clickable, scrollable...), callbacks de resposta a esdeveniments, atributs d'accessibilitat...
DisposableEffect
Un DisposableEffect permet insertar codi que s'executa: - quan hi ha certs canvis, o - quan el composable surt de l'arbre.
Paral·lelisme total amb useEffect de React.
- Els objectes observats són els primers paràmetres de la funció.
- Si és
Unitnomés s'executa un cop, en entrar a l'arbre. - El darrer paràmetre es el bloc de codi que s'executa
- El context te un métode
onDisposeque si el cridem amb un altre block s'executarà en sortir de l'arbre.
// dins d'una funció composable
DisposableEffect(Unit) { // or observables
// init code goes here (or update code if observables)
onDispose {
// cleanup code goes here
}
}
Estat permanent centralitzat (ViewModel)
https://developer.android.com/jetpack/compose/state#viewmodel-state https://developer.android.com/topic/libraries/architecture/viewmodel
Similar al Context de React. Sense scope??
class MyViewModel : ViewModel() {
var counter = mutableStateOf(0)
}
@Composable
fun Screen(viewModel: MyViewModel = viewModel()) {
val count by viewModel.counter
Button(onClick = { viewModel.counter.value++ }) {
Text("Count: $count")
}
}
- Simplifica fluxe d'informació
- Complica l'anàlisi de side effects, tot i que marca on es produeixen
- Persisteix després de recomposicions i reconfiguracions (rotacio, tema, idioma...)
derivedStateOf
Equivalent al useMemo
Corutines LaunchedEffect
Si hem de executar coses en paral·lel o més enllà de la composició.
Es cancel·la la corutina si canvien les dependencies o surt de la composició.
Pot sortir de la composició la corutina si en una composició no s'executa.
La cancel·lació es produeix llençant un CancellationException.
Podem capturar-ho a la corutina si volem fer res en sortir.
SideEffect
S'executen incondicionalment després de cada composició.
Equivalent a useEffect de React sense dependencies (null, no array buit).
És útil per sincronitzar elements legacy (Views). També per sincronitzar amb sistemes externs.
Patrons
State: Snapshot state
https://developer.android.com/jetpack/compose/state
Les crides a la funció declarativa son stateless. Sovint cal mantenir un cert estat permanent. Es proporcionen els mecanismes per mantenir aquest estat:
remember { }crea una variable que es mantè entre crides.mutableStateOfcrea una dada observable- Quan un observable canvia Compose actualitza els elements que la contenen.
State: State Hoisting
https://developer.android.com/jetpack/compose/state#state-hoisting
Si l'estat d'un component fill queda dins costa molt de testejar
El pare manté el estat sempre que sigui possible. (lifting state up de React) Els canvis li arriben al pare via callbacks que passem com a paràmetres al fill.
State: Single source of truth
https://developer.android.com/jetpack/compose/state#source-of-truth
Problema: Dispersió del coneixement -> Complexitat de sincronització Problema2: Recreació dels components (rotacio, tema, idioma...), recomposició...
L'estat s'agafa del view model.
State: Derive state
https://developer.android.com/jetpack/compose/state#derivedstateof
No guardis estat que ens passen duplicant-ho.
Si es derivat, recalcula'l cada cop.
Recalcular pot ser car, llavors
recalcula-ho només quan canviin les dades d'entrada.
Per facilitar-ho tenim el derivedStateOf
Composició: Single responsability
https://developer.android.com/jetpack/compose/philosophy
Fer components petits per a que siguin reutilitzables i testejables.
Equivalent als components purs de React.
Composició: Components petits i enfocats
Problema: Encara que tingui una sola responsabilitat, si depén de masses coses o cobreix massa part de la interficie com el Composable es l'element de recomposició, la recomposició es produirà masses vegades i afectarà a masses elements de la ui.
Enfocar els elements a una unitat d'actualització.
El single responsability parla de lògica, el components petits parla de estructura i tamany.
Composició: Modificador com a primer paràmetre opcional
https://developer.android.com/jetpack/compose/style#parameters
Si un component gestiona l'espai intern sense exposar-ho, resulta menys reutilitzable. D'aquesta manera el pare del component decideix coses com ara la disposició, el coixí, el marge...
Composició: No side effects
https://developer.android.com/jetpack/compose/side-effects
Problema: El cos d'una funcio composable s'executa múltiples vegades i d'una forma que el programador no pot controlar. Si cal generar efectes laterals (Toast, Logs, canvis a la BBDD...), ho hem de fer de forma controlada.
LaunchedEffectper crides a corrutinesDisposableEffectper cicle de vida i netejaSideEffectper sincronitzar sistemes externs
TODO: Com funcionen aquestes funcions
Composició: Slot APIs (children equivalents)
https://developer.android.com/jetpack/compose/layouts/basics#slot-apis
Problema: Un component que té la UI interna fixa es menys reutilitzable.
Solució: Permetre passar per paràmetres parts de la UI (slots) per a que el pare pugui definir-la.
@Composable
fun Card(
header: @Composable () -> Unit,
content: @Composable () -> Unit
) {
Column {
header()
content()
}
}
@Composable
fun Example() {
Card(
header = { Text("Capçalera") },
content = { Text("Cos de la card") }
)
}
Composició: Unidirectional Data Flow (UDF)
https://developer.android.com/jetpack/compose/architecture#udf
MVVM + Compose
https://developer.android.com/topic/architecture https://developer.android.com/jetpack/compose/architecture
Navigation via NavHost
https://developer.android.com/jetpack/compose/navigation
Performance patterns
https://developer.android.com/jetpack/compose/performance
Interoperabilitat
Composable dintre d'un layout XML
Al layout inserim un ComposeView
<androidx.compose.ui.platform.ComposeView
android:id="@+id/composeView"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
Al codi de l'activity, recuperem el ComposeView
li cridem al mètode setContent passant-li el composable.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_layout)
val composeView = findViewById<ComposeView>(R.id.composeView)
composeView.setContent {
Greeting("Marc")
}
}
}
Composable com a fragment
També podem crear un fragment basat en el Composable i fer-ho servir com a tal a la nostra vista clàssica.
class ComposeFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return ComposeView(requireContext()).apply {
setContent {
MyComposable()
}
}
}
}
Composable com a Item d'un RecyclerView
class ComposeViewHolder(val composeView: ComposeView) : RecyclerView.ViewHolder(composeView)
override fun onBindViewHolder(holder: ComposeViewHolder, position: Int) {
holder.composeView.setContent {
ItemCard(itemList[position])
}
}
View dintre d'un Composable
AndroidView és una funció embolcall de Views clàssiques.
Té dos paràmetres: factory i update.
factory es un callback que donat un context, crea la vista i la inicialitza,
semblant al que faría un onCreate.
update rep la vista com a paràmetre i es crida cada cop que la vista s'actualitza.
@Composable
fun LegacyViewInsideCompose(state) {
AndroidView(
factory = { context ->
TextView(context).apply {
text = "Hola des d'una View!"
textSize = 20f
}
},
update = { view ->
view.text = "Text actualitzat ${state}!"
}
)
}
Comunicació
Cicle de vida
Si la vista legacy necessita fer crides de cicle de vida, cal fer un DisposableEffect