Les schémas d'architecture de flux de données unidirectionnel (UDF) fonctionnent parfaitement avec Compose. Si l'application utilise d'autres types de schémas d'architecture à la place, par exemple un modèle MVP (Model View Presenter), nous vous recommandons de migrer cette partie de l'interface utilisateur vers l'UDF avant ou pendant l'adoption de Compose.
ViewModels dans Compose
Si vous utilisez la bibliothèque Architecture Components ViewModel, vous pouvez accéder à un ViewModel
à partir de n'importe quel composable en appelant la fonction viewModel()
, comme expliqué dans la documentation sur l'intégration de Compose aux bibliothèques courantes.
Lorsque vous adoptez Compose, veillez à utiliser le même type ViewModel
dans différents composables, car les éléments ViewModel
suivent les champs d'application du cycle de vie View. Le champ d'application correspondra à l'activité hôte, au fragment ou au graphique de navigation si la bibliothèque Navigation est utilisée.
Par exemple, si les composables sont hébergés dans une activité, viewModel()
renvoie toujours la même instance qui ne sera effacée qu'une fois l'activité terminée.
Dans l'exemple suivant, le même utilisateur ("user1") sera accueilli deux fois, car la même instance GreetingViewModel
est réutilisée dans tous les composables de l'activité hôte. La première instance ViewModel
créée est réutilisée dans d'autres composables.
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 } }
Comme les graphiques de navigation couvrent également les éléments ViewModel
, les composables qui sont une destination dans un graphique de navigation ont une instance différente de ViewModel
.
Dans ce cas, le champ d'application de ViewModel
est limité au cycle de vie de la destination et est effacé lorsque la destination est supprimée de la pile "Retour". Dans l'exemple suivant, lorsque l'utilisateur accède à l'écran Profil, une instance de GreetingViewModel
est créée.
@Composable fun MyApp() { NavHost(rememberNavController(), startDestination = "profile/{userId}") { /* ... */ composable("profile/{userId}") { backStackEntry -> GreetingScreen(backStackEntry.arguments?.getString("userId") ?: "") } } }
Source de référence de l'état
Lorsque vous adoptez Compose dans une partie de l'interface utilisateur, il est possible qu'il doive partager des données avec le code du système View. Dans la mesure du possible, nous vous recommandons d'encapsuler cet état partagé dans une autre classe qui respecte les bonnes pratiques d'UDF utilisées par les deux plates-formes, par exemple dans un ViewModel qui expose un flux de données partagées afin d'émettre des mises à jour de données.
Toutefois, ce n'est pas toujours possible si les données à partager sont mutables ou si elles sont étroitement liées à un élément d'interface utilisateur. Dans ce cas, l'un des systèmes doit être la source de référence et ce système doit partager toutes les mises à jour de données avec l'autre système. En règle générale, la source de référence doit appartenir à l'élément le plus proche de la racine de la hiérarchie de l'interface utilisateur.
Compose comme source de référence
Utilisez le composable SideEffect
pour publier l'état de Compose dans du code non-Compose. Dans ce cas, la source de référence est conservée dans un composable qui envoie des mises à jour d'état.
Par exemple, votre bibliothèque d'analyse peut vous permettre de segmenter votre population d'utilisateurs en associant des métadonnées personnalisées (propriétés utilisateur dans cet exemple) à tous les événements d'analyse suivants. Pour communiquer le type d'utilisateur de l'utilisateur actuel à votre bibliothèque d'analyse, utilisez SideEffect
afin de mettre à jour sa valeur.
@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
}
Pour en savoir plus, consultez la documentation sur les effets secondaires.
Système View comme source de référence
Si le système View est propriétaire de l'état et le partage avec Compose, nous vous recommandons de l'encapsuler dans des objets mutableStateOf
afin qu'il soit thread-safe pour Compose. Si vous utilisez cette approche, les fonctions modulables sont simplifiées, car elles ne disposent plus de la source de référence. Toutefois, le système View doit mettre à jour l'état modifiable et les vues qui utilisent cet état.
Dans l'exemple suivant, CustomViewGroup
contient une TextView
et une ComposeView
contenant un composable TextField
. TextView
doit afficher le contenu de ce que l'utilisateur saisit dans 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 } }