Os padrões da arquitetura do fluxo de dados unidirecional (UDF, na sigla em inglês) funcionam perfeitamente com o Compose. Caso o app use outros tipos de padrão de arquitetura, como o Model View Presenter (MVP), recomendamos migrar essa parte da IU para a arquitetura UDF antes ou durante a adoção do Compose.
ViewModels no Compose
Se você usar a biblioteca de Componentes da Arquitetura do
ViewModel, vai poder acessar um
ViewModel
em qualquer elemento de composição
chamando a função
viewModel()
,
conforme explicado na documentação da integração do Compose com bibliotecas
comuns.
Ao adotar o Compose, tenha cuidado ao usar o mesmo tipo de ViewModel
em
diferentes elementos de composição, considerando que os elementos ViewModel
seguem os escopos do ciclo de vida da visualização. O
escopo será a atividade do host, o fragmento ou o gráfico de navegação se a
biblioteca Navigation for usada.
Por exemplo, se os elementos de composição forem hospedados em uma atividade, o viewModel()
sempre
vai retornar a mesma instância que só será limpa quando a atividade for concluída.
No exemplo abaixo, o mesmo usuário ("user1") é recebido duas vezes porque
a mesma instância de GreetingViewModel
é reutilizada em todos os elementos de composição na
atividade do host. A primeira instância ViewModel
criada é reutilizada em outros
elementos de composição.
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 os gráficos de navegação também incluem o escopo de elementos ViewModel
, os elementos de composição que também são um
destino em um gráfico de navegação têm uma instância diferente do ViewModel
.
Nesse caso, o escopo do ViewModel
é definido como o ciclo de vida do destino e
será apagado quando o destino for removido da backstack. No exemplo
a seguir, quando o usuário navega para a tela Profile, uma nova
instância do GreetingViewModel
é criada.
@Composable fun MyApp() { NavHost(rememberNavController(), startDestination = "profile/{userId}") { /* ... */ composable("profile/{userId}") { backStackEntry -> GreetingScreen(backStackEntry.arguments?.getString("userId") ?: "") } } }
Fonte da verdade do estado
Quando você adota o Compose em uma parte da IU, é possível que o código do Compose e do sistema de visualização precisem compartilhar dados. Quando possível, recomendamos encapsular esse estado compartilhado em outra classe que siga as práticas recomendadas de UDF usadas pelas duas plataformas, como em um ViewModel que expõe um stream dos dados compartilhados para emitir atualizações de dados.
No entanto, isso nem sempre é possível se os dados a serem compartilhados forem mutáveis ou estiverem estreitamente vinculados a um elemento da IU. Nesse caso, um sistema precisa ser a fonte da verdade. Ele também precisa compartilhar as atualizações de dados com o outro sistema. Como regra geral, a fonte da verdade precisa ser de propriedade do elemento que estiver mais próximo da raiz da hierarquia da IU.
Compose como a fonte da verdade
Use o
elemento combinável SideEffect
para publicar o estado do Compose em um código que não seja dele. Nesse caso, a
fonte da verdade é armazenada em um elemento que pode ser composto que envia atualizações de estado.
Por exemplo, sua biblioteca de análise pode permitir segmentar a população
de usuários anexando metadados personalizados (nesse caso, propriedades do usuário)
a todos os eventos de análise subsequentes. Para comunicar o tipo de
usuário atual à biblioteca de análise, use o SideEffect
para atualizar o valor da biblioteca.
@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
}
Para ver mais informações, consulte a documentação sobre Efeitos colaterais.
Sistema de visualização como fonte da verdade
Se o sistema de visualização é proprietário do estado e o compartilha com o Compose, recomendamos que
você una o estado em objetos mutableStateOf
para torná-lo seguro para linhas de execução
no Compose. Se você usar essa abordagem, as funções compostas serão simplificadas, porque
não terão mais a fonte da verdade. Mas o sistema de visualização precisará atualizar o
estado imutável e as visualizações que usam esse estado.
No exemplo abaixo, um CustomViewGroup
contém uma TextView
e uma
ComposeView
com um elemento de composição TextField
. A TextView
precisa mostrar
o conteúdo digitado pelo usuário no 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 } }