O que é isso
Quem ainda não leu a documentação - eu recomendo que você se familiarize.
O que Jetbrain escreve:
As corotinas simplificam a programação assíncrona, deixando todas as complicações dentro das bibliotecas. A lógica do programa pode ser expressa seqüencialmente em corotinas, e a biblioteca base irá implementá-lo de forma assíncrona para nós. A biblioteca pode agrupar as partes correspondentes do código do usuário nos retornos de chamada que assinam os eventos correspondentes e despachar a execução para diferentes threads (ou mesmo para máquinas diferentes!). O código permanecerá tão simples como se fosse executado estritamente em sequência.
Em termos simples, essa é uma biblioteca para execução de código síncrono / assíncrono.
Porque
Porque o RxJava não está mais na moda (apenas brincando).
Em primeiro lugar, eu queria tentar algo novo e, em segundo lugar, me deparei com um artigo - uma comparação da velocidade do corutin e outros métodos.
Exemplo
Por exemplo, você precisa executar uma operação em segundo plano.
Para começar - adicione à nossa dependência build.gradle do corutin:
| dependencies { |
| implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1" |
| implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1" |
| .... |
| } |
Usamos o método em nosso código:
| suspend fun <T> withContext( |
| context: CoroutineContext, |
| block: suspend CoroutineScope.() -> T |
| ) |
Onde no contexto - especificamos o pool de threads de que precisamos - em casos simples, esses são IO, Main e Default
IO - para operações simples com a API, operações com o banco de dados, preferências compartilhadas etc.
Principal - thread da interface do usuário, de onde podemos acessar a visualização
Padrão - para operações pesadas com alta carga de CPU
(Mais neste artigo )
Block - o lambda que queremos executar
| var result = 1.0 |
| withContext(IO) { |
| for (i in 1..1000) { |
| result += i * i |
| } |
| } |
| Log.d("coroutines example", "result = $result") |
Em princípio, é tudo, obtemos o resultado da soma dos quadrados de 1 a 1000 e, ao mesmo tempo, não bloqueamos o segmento principal, o que significa que não há ANR
No entanto, se a nossa rotina for executada por 20 segundos e, durante esse tempo, fizermos 2 giros no dispositivo, teremos 3 blocos de corrida simultaneamente. Opa
E se passássemos um link para a atividade do bloco - um vazamento e a falta da capacidade de executar operações com exibição em blocos antigos. Opa duas vezes.
Então o que fazer?
Fazendo melhor
| private var viewModelJob = Job() |
| private val viewModelScope = CoroutineScope(Dispatchers.Main + viewModelJob) |
| |
| fun doWork() { |
| var result = 1.0 |
| viewModelScope.launch { |
| withContext(IO) { |
| for (i in 1..1000) { |
| result += i * i |
| } |
| } |
| } |
| Log.d("coroutines example", " result = $result") |
| } |
| |
| fun cancelJob() { |
| viewModelJob.cancel() |
| } |
Assim, conseguimos interromper a execução do nosso código no fluxo, por exemplo, quando a tela é girada.
O CoroutineScope tornou possível combinar o escopo de todas as corotinas aninhadas e, ao chamar job.cancel (), interrompeu sua execução
Se você planeja reutilizar o escopo depois de interromper a execução, precisa usar job.cancelChildren () em vez de job.cancel (), obrigado Neikist pelo comentário
Ao mesmo tempo, ainda temos a oportunidade de controlar os fluxos:
| fun doWork() { |
| var result = 1.0 |
| var result2 = 1.0 |
| viewModelScope.launch { |
| withContext(IO) { |
| for (i in 1..1000) { |
| result += i * i |
| } |
| } |
| withContext(Default) { |
| for (i in 1..1000) { |
| result2 += i * i |
| } |
| } |
| } |
| Log.d("coroutines example", "running result = $result, result 2 = $result2") |
| } |
| |
| fun cancelJob() { |
| viewModelJob.cancel() |
| } |
Nós conectamos retrofit2
Adicione dependências ao granizo:
| |
| dependencies { |
| implementation "com.squareup.retrofit2:retrofit:$retrofitVersion" |
| implementation "com.squareup.retrofit2:converter-moshi:$converterMoshiVersion" |
| implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:$retrofitCoroutinesVersion" |
| ... |
| } |
Usamos a caneta https://my-json-server.typicode.com/typicode/demo/posts como exemplo
Nós descrevemos a interface de modernização:
| interface RetrofitPosts { |
| |
| @GET("posts") |
| fun getPosts(): Deferred<Response<List<Post>>> |
| |
| } |
Descreva o modelo de postagem retornado:
| data class Post(val id: Int, val title: String) |
Nosso BaseRepository:
| abstract class BaseRepository<Params, Result> { |
| |
| abstract suspend fun doWork(params: Params): Result |
| |
| } |
Implementação do PostsRepository:
| class PostsRepository : |
| BaseRepository<PostsRepository.Params, PostsRepository.Result>() { |
| |
| override suspend fun doWork(params: Params): Result { |
| val retrofitPosts = Retrofit |
| .Builder() |
| .baseUrl("https://jsonplaceholder.typicode.com") |
| .addConverterFactory(MoshiConverterFactory.create()) |
| .addCallAdapterFactory(CoroutineCallAdapterFactory()) |
| .build() |
| .create(RetrofitPosts::class.java) |
| val result = retrofitPosts |
| .getPosts() |
| .await() |
| |
| return Result(result.body()) |
| } |
| |
| class Params |
| data class Result(val posts: List<Post>?) |
| } |
Nosso BaseUseCase:
| abstract class BaseUseCase<Params, Result> { |
| |
| abstract suspend fun doWork(params: Params): Result |
| |
| } |
Implementação de GetPostsListUseCase:
| class GetListOfPostsUseCase |
| : BaseUseCase<GetListOfPostsUseCase.Params, GetListOfPostsUseCase.Result>() { |
| |
| override suspend fun doWork(params: Params): Result { |
| return Result( |
| PostsRepository() |
| .doWork(PostsRepository.Params()) |
| .response |
| .posts |
| ) |
| } |
| |
| class Params |
| class Result(val posts: List<Post>?) |
| } |
Aqui está o resultado:
| fun doWork() { |
| val useCase = GetListOfPostsUseCase() |
| viewModelScope.launch { |
| withContext(Dispatchers.IO) { |
| |
| val result = useCase.doWork( |
| GetListOfPostsUseCase.Params() |
| ) |
| Log.d("coroutines example", "get list of posts = ${result.posts}") |
| } |
| } |
| |
| } |
| |
| fun cancelJob() { |
| viewModelJob.cancel() |
| } |
Melhorando
Sou uma criatura preguiçosa e toda vez que não quero arrastar toda a folha de código, desenvolvi os métodos necessários no BaseViewModel:
| abstract class BaseViewModel : ViewModel() { |
| |
| private var viewModelJob = Job() |
| private val viewModelScope = CoroutineScope(Dispatchers.Main + viewModelJob) |
| private var isActive = true |
| |
| // Do work in IO |
| fun <P> doWork(doOnAsyncBlock: suspend CoroutineScope.() -> P) { |
| doCoroutineWork(doOnAsyncBlock, viewModelScope, IO) |
| } |
| |
| // Do work in Main |
| // doWorkInMainThread {...} |
| fun <P> doWorkInMainThread(doOnAsyncBlock: suspend CoroutineScope.() -> P) { |
| doCoroutineWork(doOnAsyncBlock, viewModelScope, Main) |
| } |
| |
| // Do work in IO repeately |
| // doRepeatWork(1000) {...} |
| // then we need to stop it calling stopRepeatWork() |
| fun <P> doRepeatWork(delay: Long, doOnAsyncBlock: suspend CoroutineScope.() -> P) { |
| isActive = true |
| viewModelScope.launch { |
| while (this@BaseViewModel.isActive) { |
| withContext(IO) { |
| doOnAsyncBlock.invoke(this) |
| } |
| if (this@BaseViewModel.isActive) { |
| delay(delay) |
| } |
| } |
| } |
| } |
| |
| fun stopRepeatWork() { |
| isActive = false |
| } |
| |
| override fun onCleared() { |
| super.onCleared() |
| isActive = false |
| viewModelJob.cancel() |
| } |
| |
| private inline fun <P> doCoroutineWork( |
| crossinline doOnAsyncBlock: suspend CoroutineScope.() -> P, |
| coroutineScope: CoroutineScope, |
| context: CoroutineContext |
| ) { |
| coroutineScope.launch { |
| withContext(context) { |
| doOnAsyncBlock.invoke(this) |
| } |
| } |
| } |
| } |
Agora, a obtenção da lista de publicações fica assim:
| class PostViewModel : BaseViewModel() { |
| |
| val lengthOfPostsList = MutableLiveData<String>() |
| |
| fun getListOfPosts() { |
| doWork { |
| val result = GetListOfPostsUseCase() |
| .doWork(GetListOfPostsUseCase.Params()) |
| Log.d("coroutines example", "get list of posts = ${result.posts}") |
| lengthOfPostsList.postValue(result.posts?.size.toString()) |
| } |
| } |
Conclusão
Usei corotinas no produto e o código realmente ficou mais limpo e legível.
UPD:
Retrofit - descrição do tratamento de exceções, ver comentário