As corrotinas do Kotlin fornecem uma API que permite criar
um código assíncrono.
Com elas, é possível definir um
CoroutineScope
,
que ajuda a gerenciar quando as corrotinas serão executadas. Cada operação assíncrona
é executada em um escopo específico.
Os componentes que reconhecem o ciclo de vida oferecem compatibilidade de primeira classe
com as corrotinas para escopos lógicos no seu app, além de uma
camada de interoperabilidade com LiveData
.
Este tópico explica como usar corrotinas de maneira eficiente com componentes
que reconhecem o ciclo de vida.
Adicionar dependências KTX
Os escopos de corrotina integrados descritos neste tópico estão contidos nas extensões KTX de cada componente correspondente. Adicione as dependências adequadas ao usar esses escopos.
- Para
ViewModelScope
, useandroidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0
ou versões mais recentes. - Para
LifecycleScope
, useandroidx.lifecycle:lifecycle-runtime-ktx:2.4.0
ou versões mais recentes. - Para
liveData
, useandroidx.lifecycle:lifecycle-livedata-ktx:2.4.0
ou versões mais recentes.
Escopos de corrotina com reconhecimento de ciclo de vida
Os componentes com reconhecimento de ciclo de vida definem os seguintes escopos integrados que você pode usar no seu app.
ViewModelScope
Um ViewModelScope
é definido para cada
ViewModel
no app. Qualquer
corrotina iniciada nesse escopo será cancelada automaticamente se o ViewModel
for apagado. As corrotinas são úteis aqui quando você tem um trabalho que precisa ser
executado somente se o ViewModel
estiver ativo. Por exemplo, se você estiver computando alguns
dados de um layout, será necessário definir o escopo do trabalho como ViewModel
. Dessa forma,
o trabalho será cancelado automaticamente para evitar o consumo
de recursos caso o ViewModel
seja apagado.
É possível acessar o CoroutineScope
de um ViewModel
pela
propriedade viewModelScope
do ViewModel, conforme mostrado no exemplo a seguir:
class MyViewModel: ViewModel() {
init {
viewModelScope.launch {
// Coroutine that will be canceled when the ViewModel is cleared.
}
}
}
LifecycleScope
Um LifecycleScope
é definido para cada
objeto Lifecycle
. Qualquer corrotina iniciada nesse escopo será cancelada quando o Lifecycle
for destruído. Você pode
acessar o CoroutineScope
do Lifecycle
usando as
propriedades lifecycle.coroutineScope
ou lifecycleOwner.lifecycleScope
.
O exemplo abaixo demonstra como usar o lifecycleOwner.lifecycleScope
para
criar textos pré-computados de forma assíncrona:
class MyFragment: Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
val params = TextViewCompat.getTextMetricsParams(textView)
val precomputedText = withContext(Dispatchers.Default) {
PrecomputedTextCompat.create(longTextContent, params)
}
TextViewCompat.setPrecomputedText(textView, precomputedText)
}
}
}
Corrotinas reinicializáveis que reconhecem o ciclo de vida
Mesmo que o lifecycleScope
ofereça uma maneira adequada de cancelar
operações de longa duração automaticamente quando o Lifecycle
for DESTROYED
(destruído),
é possível que existam outros casos em que você precise iniciar a execução de um bloco
de código quando Lifecycle
estiver em um determinado estado e cancelá-la quando ele estiver em
outro estado. Por exemplo, você pode querer coletar um fluxo quando o
Lifecycle
for STARTED
(iniciado) e cancelar a coleta quando ele for STOPPED
(interrompido). Essa
abordagem processa as emissões de fluxo apenas quando a IU está visível na tela,
economizando recursos e possivelmente evitando falhas no app.
Para esses casos, Lifecycle
e LifecycleOwner
fornecem a API repeatOnLifecycle
de suspensão que faz exatamente isso. O exemplo abaixo contém um
bloco de código que é executado sempre que o Lifecycle
associado está pelo menos no
estado STARTED
e é cancelado quando o Lifecycle
é STOPPED
(interrompido):
class MyFragment : Fragment() {
val viewModel: MyViewModel by viewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Create a new coroutine in the lifecycleScope
viewLifecycleOwner.lifecycleScope.launch {
// repeatOnLifecycle launches the block in a new coroutine every time the
// lifecycle is in the STARTED state (or above) and cancels it when it's STOPPED.
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
// Trigger the flow and start listening for values.
// This happens when lifecycle is STARTED and stops
// collecting when the lifecycle is STOPPED
viewModel.someDataFlow.collect {
// Process item
}
}
}
}
}
Coleta de fluxo com reconhecimento de ciclo de vida
Se você precisar fazer a coleta com reconhecimento de ciclo de vida em um único fluxo,
use o método
Flow.flowWithLifecycle()
para simplificar seu código:
viewLifecycleOwner.lifecycleScope.launch {
exampleProvider.exampleFlow()
.flowWithLifecycle(viewLifecycleOwner.lifecycle, Lifecycle.State.STARTED)
.collect {
// Process the value.
}
}
Se precisar fazer em vários fluxos em
paralelo, colete cada um deles em corrotinas diferentes. Nesse caso,
é mais eficiente usar repeatOnLifecycle()
diretamente:
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
// Because collect is a suspend function, if you want to
// collect multiple flows in parallel, you need to do so in
// different coroutines.
launch {
flow1.collect { /* Process the value. */ }
}
launch {
flow2.collect { /* Process the value. */ }
}
}
}
Suspender corrotinas que reconhecem o ciclo de vida
Mesmo que o CoroutineScope
forneça uma forma de cancelar
operações de longa duração automaticamente, é possível que existam outros casos em que você precise
suspender a execução de um bloco de código, a menos que Lifecycle
esteja em
determinado estado. Por exemplo, para executar uma FragmentTransaction
, é necessário aguardar até que o
Lifecycle
seja pelo menos STARTED
. Para esses casos, Lifecycle
fornece
outros métodos: lifecycle.whenCreated
, lifecycle.whenStarted
e
lifecycle.whenResumed
. Qualquer execução de corrotina dentro desses blocos será suspensa se
o Lifecycle
não estiver no estado mínimo desejado.
O exemplo abaixo contém um bloco de código que só é executado quando o Lifecycle
associado
está pelo menos no estado STARTED
:
class MyFragment: Fragment {
init { // Notice that we can safely launch in the constructor of the Fragment.
lifecycleScope.launch {
whenStarted {
// The block inside will run only when Lifecycle is at least STARTED.
// It will start executing when fragment is started and
// can call other suspend methods.
loadingView.visibility = View.VISIBLE
val canAccess = withContext(Dispatchers.IO) {
checkUserAccess()
}
// When checkUserAccess returns, the next line is automatically
// suspended if the Lifecycle is not *at least* STARTED.
// We could safely run fragment transactions because we know the
// code won't run unless the lifecycle is at least STARTED.
loadingView.visibility = View.GONE
if (canAccess == false) {
findNavController().popBackStack()
} else {
showContent()
}
}
// This line runs only after the whenStarted block above has completed.
}
}
}
Se o Lifecycle
for destruído enquanto uma corrotina estiver ativa usando um dos métodos
when
, a corrotina será cancelada automaticamente. No exemplo abaixo,
o bloco finally
é executado quando o estado Lifecycle
é DESTROYED
(destruído):
class MyFragment: Fragment {
init {
lifecycleScope.launchWhenStarted {
try {
// Call some suspend functions.
} finally {
// This line might execute after Lifecycle is DESTROYED.
if (lifecycle.state >= STARTED) {
// Here, since we've checked, it is safe to run any
// Fragment transactions.
}
}
}
}
}
Usar corrotinas com LiveData
Ao usar LiveData
, pode ser necessário
calcular valores de forma assíncrona. Por exemplo, caso você queira recuperar as
preferências de um usuário e exibi-las na IU. Nesses
casos, você pode usar a função do builder liveData
para chamar uma função suspend
,
mostrando o resultado como um objeto LiveData
.
No exemplo abaixo, loadUser()
é uma função de suspensão declarada em outro lugar. Use
a função do builder liveData
para chamar loadUser()
de forma assíncrona e, em seguida,
use emit()
para emitir o resultado:
val user: LiveData<User> = liveData {
val data = database.loadUser() // loadUser is a suspend function.
emit(data)
}
O bloco de criação liveData
funciona como um
primitivo de simultaneidade estruturada
entre as corrotinas e LiveData
. O bloco de código começa a ser executado quando
LiveData
fica ativo e é cancelado automaticamente após um tempo limite configurável
quando o LiveData
fica inativo. Se cancelado antes
da conclusão, ele será reiniciado caso o LiveData
fique ativo novamente. Se
tiver sido concluído com êxito em uma execução anterior, ele não será reiniciado. Ele só será
reiniciado se for cancelado automaticamente. Se o bloco for cancelado por qualquer outro motivo
(por exemplo, geração de uma CancellationException
), ele não será reiniciado.
Também é possível emitir vários valores a partir do bloco. Cada chamada emit()
suspende
a execução do bloco até que o valor LiveData
seja definido na linha de execução principal.
val user: LiveData<Result> = liveData {
emit(Result.loading())
try {
emit(Result.success(fetchUser()))
} catch(ioException: Exception) {
emit(Result.error(ioException))
}
}
Também é possível combinar liveData
com
Transformations
, conforme mostrado
no exemplo a seguir:
class MyViewModel: ViewModel() {
private val userId: LiveData<String> = MutableLiveData()
val user = userId.switchMap { id ->
liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) {
emit(database.loadUserById(id))
}
}
}
É possível emitir vários valores de um LiveData
chamando a função emitSource()
sempre que quiser emitir um novo valor. Cada chamada para emit()
ou emitSource()
remove a origem adicionada anteriormente.
class UserDao: Dao {
@Query("SELECT * FROM User WHERE id = :id")
fun getUser(id: String): LiveData<User>
}
class MyRepository {
fun getUser(id: String) = liveData<User> {
val disposable = emitSource(
userDao.getUser(id).map {
Result.loading(it)
}
)
try {
val user = webservice.fetchUser(id)
// Stop the previous emission to avoid dispatching the updated user
// as `loading`.
disposable.dispose()
// Update the database.
userDao.insert(user)
// Re-establish the emission with success type.
emitSource(
userDao.getUser(id).map {
Result.success(it)
}
)
} catch(exception: IOException) {
// Any call to `emit` disposes the previous one automatically so we don't
// need to dispose it here as we didn't get an updated value.
emitSource(
userDao.getUser(id).map {
Result.error(exception, it)
}
)
}
}
}
Para mais informações relacionadas a corrotinas, consulte os seguintes links:
- Melhorar o desempenho do app com corrotinas de Kotlin
- Visão geral de corrotinas (link em inglês)
- Linhas de execução no CoroutineWorker
Outros recursos
Para saber mais sobre o uso de corrotinas com componentes que reconhecem o ciclo de vida, consulte os recursos a seguir.
Exemplos
Blogs (em inglês)
- Corrotinas no Android: padrões de aplicativos
- Corrotinas no Android: viewModelScope
- Como testar duas emissões de LiveData consecutivas em corrotinas
Recomendados para você
- Observação: o texto do link aparece quando o JavaScript está desativado.
- Visão geral do LiveData
- Como gerenciar ciclos de vida com componentes que os reconhecem
- Carregar e exibir dados paginados