Los patrones de arquitectura de flujo unidireccional de datos (UDF) funcionan perfectamente con Compose. Si la app usa otros tipos de patrones de arquitectura, como Model View Presenter (MVP), te recomendamos que migres esa parte de la IU a UDF antes de implementar Compose o mientras lo haces.
ViewModels en Compose
Cuando utilizas la biblioteca de ViewModel de componentes de la arquitectura, puedes acceder a un ViewModel
desde cualquier elemento que admite composición llamando a la función viewModel()
, como se explica en la documentación sobre la integración de Compose con bibliotecas comunes.
Si implementas Compose, ten cuidado con usar el mismo tipo de ViewModel
en distintos elementos que admiten composición, ya que los elementos ViewModel
tienen alcances de ciclo de vida de View. Si se utiliza la biblioteca de navegación, el alcance será la actividad del host, el fragmento o el gráfico de navegación.
Por ejemplo, si los elementos que admiten composición están alojados en una actividad, viewModel()
siempre mostrará la misma instancia, que recién se borrará cuando termine la actividad.
En el siguiente ejemplo, se le dará la bienvenida al mismo usuario ("user1") dos veces porque la misma instancia de GreetingViewModel
se volvió a usar en todos los elementos que admiten composición dentro de la actividad de host. La primera instancia de ViewModel
creada se vuelve a utilizar en otros elementos que admiten composición.
class GreetingActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MaterialTheme { Column { GreetingScreen("user1") GreetingScreen("user2") } } } } } @Composable fun GreetingScreen( userId: String, viewModel: GreetingViewModel = viewModel( factory = GreetingViewModelFactory(userId) ) ) { val messageUser by viewModel.message.observeAsState("") Text(messageUser) } class GreetingViewModel(private val userId: String) : ViewModel() { private val _message = MutableLiveData("Hi $userId") val message: LiveData<String> = _message } class GreetingViewModelFactory(private val userId: String) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create(modelClass: Class<T>): T { return GreetingViewModel(userId) as T } }
Como los gráficos de navegación también definen el alcance de los elementos ViewModel
, los elementos que admiten composición que sean un destino en un gráfico de navegación tendrán una instancia distinta del ViewModel
.
En este caso, el alcance de ViewModel
se define según el ciclo de vida del destino, y se borrará cuando se quite el destino de la pila de actividades. En el siguiente ejemplo, cuando el usuario navega a la pantalla de perfil, se crea una nueva instancia de GreetingViewModel
.
@Composable fun MyApp() { NavHost(rememberNavController(), startDestination = "profile/{userId}") { /* ... */ composable("profile/{userId}") { backStackEntry -> GreetingScreen(backStackEntry.arguments?.getString("userId") ?: "") } } }
El estado como fuente de confianza
Si implementas Compose en una parte de la IU, es posible que Compose y el código de sistema de View necesiten compartir datos. Cuando sea posible, recomendamos encapsular ese estado compartido en otra clase que cumpla las prácticas recomendadas de UDF que utilizan las dos plataformas; por ejemplo, en un ViewModel que exponga un flujo de los datos compartidos para emitir actualizaciones de datos.
Sin embargo, no siempre es posible si los datos que se compartirán son mutables o si están estrechamente vinculados a un elemento de la IU. En ese caso, un sistema debe ser la fuente de confianza. Además, ese sistema debe compartir todas las actualizaciones de datos con el otro sistema. Como regla general, la fuente de confianza debe ser propiedad del elemento que esté más cerca de la raíz en la jerarquía de IU.
Compose como fuente de confianza
Usa el elemento que admite composición SideEffect
para convertir el estado basado en Compose en código no basado en Compose. En este caso, la fuente de confianza se conserva en un elemento que admite composición y que envía actualizaciones de estado.
Como ejemplo, tu biblioteca de estadísticas podría permitirte segmentar tu población de usuarios adjuntando metadatos personalizados (en este ejemplo, propiedades del usuario) a todos los eventos de estadísticas posteriores. Para comunicar el tipo de usuario actual a la biblioteca de estadísticas, usa SideEffect
a fin de actualizar su valor.
@Composable
fun rememberFirebaseAnalytics(user: User): FirebaseAnalytics {
val analytics: FirebaseAnalytics = remember {
/* ... */
}
// On every successful composition, update FirebaseAnalytics with
// the userType from the current User, ensuring that future analytics
// events have this metadata attached
SideEffect {
analytics.setUserProperty("userType", user.userType)
}
return analytics
}
Si deseas obtener más información, consulta la documentación de Efectos secundarios.
El sistema de vistas como fuente de confianza
Si el sistema de vistas es propietario del estado y lo comparte con Compose, te recomendamos que unas el estado de objetos mutableStateOf
para que sea seguro a nivel de los subprocesos para Compose. Si usas este enfoque, las funciones que admiten composición se simplifican debido a que ya no tienen la fuente de confianza. Sin embargo, el sistema de View necesita actualizar el estado mutable y las vistas que usan ese estado.
En el siguiente ejemplo, un CustomViewGroup
contiene una TextView
y una ComposeView
con un elemento que admite composición TextField
en su interior. TextView
necesita mostrar el contenido de lo que escribe el usuario en el TextField
.
class CustomViewGroup @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : LinearLayout(context, attrs, defStyle) { // Source of truth in the View system as mutableStateOf // to make it thread-safe for Compose private var text by mutableStateOf("") private val textView: TextView init { orientation = VERTICAL textView = TextView(context) val composeView = ComposeView(context).apply { setContent { MaterialTheme { TextField(value = text, onValueChange = { updateState(it) }) } } } addView(textView) addView(composeView) } // Update both the source of truth and the TextView private fun updateState(newValue: String) { text = newValue textView.text = newValue } }