Jetpack Compose y mejores prácticas que siempre debe recordar | de Kaaveh Mohamedi | diciembre 2022
Jetpack Compose y mejores prácticas que siempre debe recordar | de Kaaveh Mohamedi | diciembre 2022
⚠️ Este es mi resumen según el documento oficial y todas las charlas de relaciones con desarrolladores que he visto.Imagen del artículo de Ben Trengrove Veamos un ejemplo: @Composablefun ContactList(contacts: List,comparador: Comparador,modificador: modificador = modificador) { LazyColumn(modificador) {// NO HAGAS ESTOelementos(contactos.sortedWith (comparador)) { contacto ->// …}}} En este escenario quiero contactos ordenar y ver. Si es necesario volver a compilar ContactList por alguna razón, contacts.sortWith() se ejecutará nuevamente, incluso si los contactos no han cambiado. Esta ejecución innecesaria se puede evitar de la siguiente manera: @Composablefun ContactList(contacts: List,comparador: Comparador,modificador: modificador = modificador) {val sortedContacts = Remember(contacts, sortComparator) {contacts.sortedWith(sortComparator) }LazyColumn(modifier) {items(sortedContacts) {// …}}}El el uso de la API Remember evita que los contactos se clasifiquen en cada recomposición. Además, podemos indicarle al cuerpo lambda que vuelva a ejecutarse si cambian los contactos o el comparador de clasificación. ¡Pero espera! Sin embargo, hay un problema: ¡Cambio de configuración!Por ejemplo, si el usuario gira su dispositivo, activa o desactiva el modo oscuro, gira su dispositivo hacia adentro o hacia afuera y… ¡el valor de la memoria se pierde! Para evitar estas situaciones, ¡la API de RememberSavable es muy útil! Si queremos mostrar una lista de elementos, simplemente usamos LazyColumn:@Composablefun NotesList(notas: Lista) {LazyColumn {elementos (elementos = notas) { nota -> NoteRow (nota)}}} Este es un enfoque básico que puede encontrar buscando en Google. Pero en algunas situaciones, algunas recomposiciones innecesarias provocan:
Si el orden de los artículos ha cambiado
Cuando se agrega un elemento a la lista
Cuando se elimina un elemento de la lista
Para los tres escenarios anteriores, se incurre en la recomposición para todos los artículos, incluso si son los mismos artículos antes de los cambios. Solución: use Key DSL en Article API. Le dice al compilador de composición que reconstruya de manera más inteligente: @Composablefun NotesList(notes: List) {LazyColumn {items(items = notes,key = { note ->// Retorna una clave única y estable para notenote.id}) { note ->NoteRow(note)}}}En realidad dice: Hey Compose, typeset reensamblar cada elemento que ha cambiado su ID. Pero todavía hay un problema: ¿qué pasa si el contenido de un elemento ha cambiado? Por ejemplo, editemos el texto de una nota. Debido a que la identificación del artículo no se actualizó, el artículo no se volvió a ensamblar. Para resolver este problema, creo que podemos usar la marca de tiempo como ID. Cuando se actualiza un artículo, también actualizamos su ID. De esta manera, se puede desencadenar una recomposición. En algunas situaciones, el estado puede cambiar con frecuencia, provocando una serie de recomposiciones. Por ejemplo, cuando mostramos una lista, queremos saber qué elemento es el primer elemento visible. Si el primer elemento no está visible, visible, vaya al botón superior: val listState = RememberLazyListState()LazyColumn(state = listState) {// …}val showButton = listState.firstVisibleItemIndex > 0AnimatedVisibility(visible = showButton) {ScrollToTopButton ( )} Cuando el usuario arrastra, listState cambia rápidamente. Esto ha dado lugar a que se vuelva a montar la lista completa. Solución:drivedStateOf APIdrivedStateOf evita recomposiciones no deseadas. El código es el siguiente: val listState = RememberLazyListState()LazyColumn(state = listState) {// …}val showButton from Remember {derivedStateOf {listState.firstVisibleItemIndex > 0}}Visibilidad animada(visible = showButton) {ScrollToTopButton()} Aquí, el valor de showButton solo cambió cuando se actualizó la expresión en driveStateOf, no en función de cada actualización de listState. Mueva la lectura de un estado lo más abajo posible en el árbol de composición. Vea este ejemplo: @Composablefun SnackDetail() { // …Box(Modifier.fillMaxSize()) { // Recomposition Scope Startval scroll = RememberScrollState(0)// …Title(snack, scroll.value)// …} // Fin del ámbito de recomposición}@Composableprivate fun Title(snack: Snack, scroll: Int) {// …val offset = with(LocalDensity.current) { scroll.toDp() }Column(modifier = Modifier .offset(y = offset)) {// …}}Este es un ejemplo de Google Project JetSnack. Observe el scroll.value en SnackDetail. El cuadro principal se recompone cada vez que el usuario se desplaza, ya que es el siguiente objeto que se puede componer visible (siguiente área de composición). Pero como puede ver, ¡el scroll.value solo es necesario para crear el título! { // Recomposición Scope Startval scroll = RememberScrollState(0)// …Title(snack) { scroll.value }// …} // Recomposition Scope End}@Composableprivate fun Title(snack: Snack, scrollProvider: ( ) -> Int) {// …val offset = with(LocalDensity.current) { scrollProvider().toDp() }Column(modifier = Modifier.offset(y = offset)) {// …}} Por ahora, cada vez que se actualiza scroll.value, solo se puede recomponer el título. PD: El fragmento de código anterior se puede optimizar. Modifier.offset(x: Dp, y: Dp) tiene una versión lambda que garantiza que el estado se lee en la fase de diseño. Así es como: @Composableprivate fun Title(snack: Snack, scrollProvider: () -> Int) { // …Column(modifier = Modifier.offset { IntOffset(x = 0, y = scrollProvider()) } ) { / / …}}Siempre que cambia el estado de desplazamiento, pasa por alto la fase de composición y salta directamente a la fase de diseño. Hay otro ejemplo sobre este tema: imagina que quieres cambiar el color de fondo de un cuadro con animación entre dos colores. Esta es una solución ingenua: // Supongamos aquí que animateColorBetween() es una función que intercambia entre // dos colores colorsval por animateColorBetween(Color.Cyan, Color.Magenta)Box(Modifier.fillMaxSize().background( color))In cada cuadro el color cambia para que la caja se recomponga. Cuando utiliza la API drawBehind, el estado de lectura del color cambia a la fase de dibujo, y la fase de composición y la fase de diseño se omiten por completo: val color by animateColorBetween(Color.Cyan, Color.Magenta)Box(Modifier.fillMaxSize() .drawBehind { drawRect(color)})Compose asume que cuando lees un estado, no actualizas inmediatamente su valor en Composable. Cuando hagas esto, escribe al revés. Veamos este código: @Composablefun BadComposable() {var count by Remember { mutableStateOf(0) } // Provoca la recomposición en clickButton( = { count++ }, modifier = Modifier.wrapContentSize()) {Text(«Recompose») } Text(» $count»)count++ // Escribe hacia atrás, escribe el estado después de que se haya leído}Cada vez que incrementas el conteo, inmediatamente desencadena una recomposición, y así sucesivamente. Te quedas atrapado en un bucle sin fin. ¡Siempre actualizar un estado debe hacerse por un evento!