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 de arquitetura 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 os
componentes de arquitetura.
Adicionar dependências KTX
Os escopos de corrotina integrados descritos neste tópico estão contidos nas extensões KTX de cada componente de arquitetura correspondente. Adicione as dependências adequadas ao usar esses escopos.
- Para
ViewModelScope
, useandroidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0
ou versões mais recentes. - Para
LifecycleScope
, useandroidx.lifecycle:lifecycle-runtime-ktx:2.2.0
ou versões mais recentes. - Para
liveData
, useandroidx.lifecycle:lifecycle-livedata-ktx:2.2.0
ou versões mais recentes.
Escopos de corrotina com reconhecimento de ciclo de vida
Os componentes de arquitetura 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 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)
}
}
}
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 por meio de um dos métodos
when
, a corrotina será cancelada automaticamente. No exemplo abaixo,
o bloco finally
é executado quando o estado Lifecycle
é DESTROYED
:
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
,
exibindo 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 como usar corrotinas com componentes de arquitetura, consulte os recursos a seguir.
Amostras
- Corrotinas de Kotlin com componentes de arquitetura (link em inglês)