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 Architecture Components
ViewModel, poderá acessar um
ViewModel
em qualquer elemento que possa ser composto
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 que podem ser compostos, 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 que podem ser compostos forem hospedados em uma atividade, o viewModel()
sempre
retornará a mesma instância que só será limpa quando a atividade for concluída.
No exemplo a seguir, o mesmo usuário será recebido duas vezes, porque a mesma instância de
GreetingViewModel
será reutilizada em todos os elementos que podem ser compostos na atividade
do host. A primeira instância ViewModel
criada é reutilizada em outros elementos que podem ser compostos.
class ExampleActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Column {
Greeting("user1")
Greeting("user2")
}
}
}
}
}
@Composable
fun Greeting(userId: String) {
val greetingViewModel: GreetingViewModel = viewModel(
factory = GreetingViewModelFactory(userId)
)
val messageUser by greetingViewModel.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 que podem ser compostos que 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 MyScreen() {
NavHost(rememberNavController(), startDestination = "profile/{userId}") {
/* ... */
composable("profile/{userId}") { backStackEntry ->
Greeting(backStackEntry.arguments?.getString("userId") ?: "")
}
}
}
@Composable
fun Greeting(userId: String) {
val greetingViewModel: GreetingViewModel = viewModel(
factory = GreetingViewModelFactory(userId)
)
val messageUser by greetingViewModel.message.observeAsState("")
Text(messageUser)
}
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 que pode ser composto 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 a seguir, um CustomViewGroup
contém uma TextView
e uma
ComposeView
com um elemento que pode ser composto 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
}
}