Pengalaman dengan Coroutines dan Retrofit2

Apa ini


Siapa yang belum membaca dokumentasi - Saya sangat menyarankan Anda untuk membiasakan diri.


Apa yang ditulis Jetbrain:


Coroutines menyederhanakan pemrograman asinkron, meninggalkan semua komplikasi di dalam perpustakaan. Logika program dapat diekspresikan secara berurutan dalam coroutine, dan pustaka dasar akan mengimplementasikannya secara asinkron untuk kita. Pustaka dapat membungkus bagian-bagian yang sesuai dari kode pengguna dalam panggilan balik yang berlangganan acara terkait, dan mengirimkan eksekusi ke utas yang berbeda (atau bahkan ke mesin yang berbeda!). Kode akan tetap sesederhana jika dijalankan secara berurutan.

Secara sederhana, ini adalah pustaka untuk eksekusi kode sinkron / asinkron.


Mengapa


Karena RxJava tidak lagi dalam mode (hanya bercanda).


Pertama, saya ingin mencoba sesuatu yang baru, dan kedua, saya menemukan artikel - perbandingan kecepatan corutin dan metode lainnya.


Contoh


Misalnya, Anda perlu melakukan operasi di latar belakang.


Untuk memulai - tambahkan dependensi build.gradle kami pada corutin:


dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1"
....
}
view raw build.gradle hosted with ❤ by GitHub

Kami menggunakan metode dalam kode kami:


suspend fun <T> withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
)
view raw gistfile1.txt hosted with ❤ by GitHub

Di mana dalam konteks - kami menentukan kumpulan utas yang kami butuhkan - dalam kasus sederhana, ini adalah IO, Utama dan Default

IO - untuk operasi sederhana dengan API, operasi dengan database, preferensi bersama, dll.
Main - UI thread, dari mana kita dapat mengakses tampilan
Default - untuk operasi berat dengan beban CPU yang tinggi
(Lebih banyak di artikel ini)


Block - lambda yang ingin kita eksekusi


var result = 1.0
withContext(IO) {
for (i in 1..1000) {
result += i * i
}
}
Log.d("coroutines example", "result = $result")
view raw Example.kt hosted with ❤ by GitHub

Pada prinsipnya, itu saja, kami mendapatkan hasil dari jumlah kuadrat dari 1 hingga 1000 dan pada saat yang sama kami tidak memblokir utas utama, yang berarti tidak ada ANR


Namun, jika coroutine kami dijalankan selama 20 detik dan selama waktu ini kami telah membuat 2 putaran perangkat, maka kami akan memiliki 3 blok yang berjalan secara bersamaan. Ups


Dan jika kami melewati tautan ke aktivitas ke blok - kebocoran dan kurangnya kemampuan untuk melakukan operasi dengan tampilan di blok lama. Ups dua kali.


Jadi apa yang harus dilakukan?


Melakukan yang lebih baik


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()
}
view raw Example.kt hosted with ❤ by GitHub

Dengan demikian, kami dapat menghentikan eksekusi kode kami di arus, misalnya, ketika layar diputar.


CoroutineScope memungkinkan untuk menggabungkan ruang lingkup semua coroutine bersarang dan, ketika memanggil job.cancel (), menghentikan eksekusi mereka


Jika Anda berencana untuk menggunakan kembali ruang lingkup setelah menghentikan eksekusi, Anda perlu menggunakan job.cancelChildren () alih-alih job.cancel () terima kasih Neikist untuk komentar


Pada saat yang sama, kami masih memiliki kesempatan untuk mengontrol arus:


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()
}
view raw Example.kt hosted with ❤ by GitHub

Kami menghubungkan retrofit2


Tambahkan dependensi ke hujan es:


dependencies {
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
implementation "com.squareup.retrofit2:converter-moshi:$converterMoshiVersion"
implementation "com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:$retrofitCoroutinesVersion"
...
}
view raw build.gradle hosted with ❤ by GitHub

Kami menggunakan pena https://my-json-server.typicode.com/typicode/demo/posts sebagai contoh


Kami menjelaskan antarmuka retrofit:


interface RetrofitPosts {
@GET("posts")
fun getPosts(): Deferred<Response<List<Post>>>
}
view raw RetrofitPosts.kt hosted with ❤ by GitHub

Jelaskan model Pos yang dikembalikan:


data class Post(val id: Int, val title: String)
view raw Post.kt hosted with ❤ by GitHub

BaseRepository kami:


abstract class BaseRepository<Params, Result> {
abstract suspend fun doWork(params: Params): Result
}
view raw BaseRepository.kt hosted with ❤ by GitHub

Implementasi dari 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>?)
}

BaseUseCase Kami:


abstract class BaseUseCase<Params, Result> {
abstract suspend fun doWork(params: Params): Result
}
view raw BaseUseCase.kt hosted with ❤ by GitHub

Implementasi 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>?)
}

Inilah hasilnya:


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()
}
view raw Example.kt hosted with ❤ by GitHub

Membuatnya Lebih Baik


Saya adalah makhluk yang malas dan setiap kali saya tidak ingin menyeret seluruh lembar kode, jadi saya membuat metode yang diperlukan di 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)
}
}
}
}
view raw BaseViewModel.kt hosted with ❤ by GitHub

Sekarang mendapatkan daftar Posting terlihat seperti ini:


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())
}
}
view raw PostViewModel.kt hosted with ❤ by GitHub

Kesimpulan


Saya menggunakan coroutine di prod dan kodenya benar-benar menjadi lebih bersih dan lebih mudah dibaca.


UPD:
Perkecualian perkuatan menangani deskripsi lihat komentar

Source: https://habr.com/ru/post/id445242/


All Articles