Que es esto
¿Quién no ha leído la documentación todavía? Le recomiendo que se familiarice.
Lo que escribe Jetbrain:
Las rutinas simplifican la programación asincrónica, dejando todas las complicaciones dentro de las bibliotecas. La lógica del programa se puede expresar secuencialmente en corutinas, y la biblioteca base lo implementará de forma asincrónica para nosotros. La biblioteca puede ajustar las partes correspondientes del código de usuario en devoluciones de llamada que se suscriben a los eventos correspondientes y enviar la ejecución a diferentes subprocesos (¡o incluso a diferentes máquinas!). El código seguirá siendo tan simple como si se ejecutara estrictamente secuencialmente.
En términos simples, esta es una biblioteca para la ejecución de código síncrono / asíncrono.
Por qué
Porque RxJava ya no está de moda (es broma).
En primer lugar, quería probar algo nuevo y, en segundo lugar, me encontré con un artículo : una comparación de la velocidad de la corutina y otros métodos.
Ejemplo
Por ejemplo, debe realizar una operación en segundo plano.
Para comenzar, agregue a nuestra dependencia build.gradle de corutin:
| dependencies { |
| implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1" |
| implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1" |
| .... |
| } |
Usamos el método en nuestro código:
| suspend fun <T> withContext( |
| context: CoroutineContext, |
| block: suspend CoroutineScope.() -> T |
| ) |
Donde en contexto, especificamos el grupo de subprocesos que necesitamos, en casos simples, estos son IO, Main y Default
IO: para operaciones simples con la API, operaciones con la base de datos, preferencias compartidas, etc.
Principal: subproceso de interfaz de usuario, desde donde podemos acceder a la vista
Predeterminado: para operaciones pesadas con alta carga de CPU
(Más en este artículo )
Bloque: la lambda que queremos ejecutar
| var result = 1.0 |
| withContext(IO) { |
| for (i in 1..1000) { |
| result += i * i |
| } |
| } |
| Log.d("coroutines example", "result = $result") |
En principio, eso es todo, obtenemos el resultado de la suma de cuadrados de 1 a 1000 y al mismo tiempo no bloqueamos el hilo principal, lo que significa que no hay ANR
Sin embargo, si nuestra rutina se ejecuta durante 20 segundos y durante este tiempo hemos realizado 2 vueltas del dispositivo, tendremos 3 bloques ejecutados simultáneamente. Ups
Y si pasamos un enlace a la actividad al bloque, una fuga y la falta de la capacidad de realizar operaciones con vista en bloques antiguos. Dos veces oops.
Entonces que hacer?
Mejor
| 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() |
| } |
Por lo tanto, pudimos detener la ejecución de nuestro código en la transmisión, por ejemplo, cuando se gira la pantalla.
CoroutineScope hizo posible combinar el alcance de todas las corutinas anidadas y, al llamar a job.cancel (), detuvo su ejecución
Si planea reutilizar el alcance después de detener la ejecución, debe usar job.cancelChildren () en lugar de job.cancel () gracias Neikist por su comentario
Al mismo tiempo, todavía tenemos la oportunidad de controlar los flujos:
| 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() |
| } |
Conectamos retrofit2
Añadir dependencias al 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 la pluma https://my-json-server.typicode.com/typicode/demo/posts como ejemplo
Describimos la interfaz de actualización:
| interface RetrofitPosts { |
| |
| @GET("posts") |
| fun getPosts(): Deferred<Response<List<Post>>> |
| |
| } |
Describa el modelo de publicación devuelto:
| data class Post(val id: Int, val title: String) |
Nuestro BaseRepository:
| abstract class BaseRepository<Params, Result> { |
| |
| abstract suspend fun doWork(params: Params): Result |
| |
| } |
Implementación de 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>?) |
| } |
Nuestra BaseUseCase:
| abstract class BaseUseCase<Params, Result> { |
| |
| abstract suspend fun doWork(params: Params): Result |
| |
| } |
Implementación 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>?) |
| } |
Aquí está el 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() |
| } |
Haciéndolo mejor
Soy una criatura perezosa y cada vez que no quiero arrastrar toda la hoja de código, hice los métodos necesarios en 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) |
| } |
| } |
| } |
| } |
Ahora obtener la lista de publicaciones se ve así:
| 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()) |
| } |
| } |
Conclusión
Usé corutinas en el producto y el código realmente resultó ser más limpio y más legible.
UPD:
Descripción de manejo de excepciones de modificación ver comentario