Organisasi arsitektur sederhana dalam aplikasi Android dengan sekelompok ViewModel + LiveData, Retrofit + Coroutines

Tanpa perkenalan panjang, saya akan memberi tahu Anda cara mengatur arsitektur aplikasi Anda dengan cepat dan mudah. Materi ini akan berguna bagi mereka yang tidak terbiasa dengan pola mvvm dan coroutine Kotlin.

Jadi, kami memiliki tugas sederhana: untuk menerima dan memproses permintaan jaringan, untuk menampilkan hasilnya dalam tampilan.

Tindakan kami: dari aktivitas (fragmen), kami memanggil metode yang diinginkan ViewModel -> ViewModel mengakses pegangan retrofit, mengeksekusi permintaan melalui coroutine -> respons dikirim ke data langsung sebagai peristiwa -> dalam aktivitas yang menerima peristiwa kami mentransfer data ke tampilan.

Penyiapan proyek


Ketergantungan


//Retrofit implementation 'com.squareup.retrofit2:retrofit:2.6.2' implementation 'com.squareup.retrofit2:converter-gson:2.6.2' implementation 'com.squareup.okhttp3:logging-interceptor:4.2.1' //Coroutines implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0' //ViewModel lifecycle implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0' implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-rc01" 

Terwujud


 <manifest ...> <uses-permission android:name="android.permission.INTERNET" /> </manifest> 

Pengaturan Retrofit


Buat objek NetworkService Kotlinovsky. Ini akan menjadi klien jaringan kami - singleton
Singleton UPD digunakan untuk kemudahan pemahaman. Komentar menunjukkan bahwa lebih tepat untuk menggunakan inversi kontrol, tetapi ini adalah topik yang terpisah.

 object NetworkService { private const val BASE_URL = " http://www.mocky.io/v2/" // HttpLoggingInterceptor       private val loggingInterceptor = run { val httpLoggingInterceptor = HttpLoggingInterceptor() httpLoggingInterceptor.apply { httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY } } private val baseInterceptor: Interceptor = invoke { chain -> val newUrl = chain .request() .url .newBuilder() .build() val request = chain .request() .newBuilder() .url(newUrl) .build() return@invoke chain.proceed(request) } private val client: OkHttpClient = OkHttpClient .Builder() .addInterceptor(loggingInterceptor) .addInterceptor(baseInterceptor) .build() fun retrofitService(): Api { return Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .client(client) .build() .create(Api::class.java) } } 

Antarmuka api


Kami menggunakan permintaan yang dikunci ke layanan palsu.

Jeda kesenangan, di sini dimulai keajaiban corutin.

Kami menandai fungsi kami dengan kata kunci yang menyenangkan ...

Retrofit belajar untuk bekerja dengan fungsi menangguhkan Kotlin dari versi 2.6.0, sekarang secara langsung menjalankan permintaan jaringan dan mengembalikan objek dengan data:

 interface Api { @GET("5dcc12d554000064009c20fc") suspend fun getUsers( @Query("page") page: Int ): ResponseWrapper<Users> @GET("5dcc147154000059009c2104") suspend fun getUsersError( @Query("page") page: Int ): ResponseWrapper<Users> } 

ResponseWrapper adalah kelas pembungkus sederhana untuk permintaan jaringan kami:

 class ResponseWrapper<T> : Serializable { @SerializedName("response") val data: T? = null @SerializedName("error") val error: Error? = null } 

Tanggal kelas Pengguna

 data class Users( @SerializedName("count") var count: Int?, @SerializedName("items") var items: List<Item?>? ) { data class Item( @SerializedName("first_name") var firstName: String?, @SerializedName("last_name") var lastName: String? ) } 

ViewModel


Kami membuat kelas BaseViewModel abstrak dari mana semua ViewModel kami akan diwarisi. Di sini kita tinggal lebih detail:

 abstract class BaseViewModel : ViewModel() { var api: Api = NetworkService.retrofitService() //       requestWithLiveData  // requestWithCallback,       //           // .       suspend , //             //    .      fun <T> requestWithLiveData( liveData: MutableLiveData<Event<T>>, request: suspend () -> ResponseWrapper<T>) { //        liveData.postValue(Event.loading()) //     ViewModel,  viewModelScope. //        //    . //   IO     this.viewModelScope.launch(Dispatchers.IO) { try { val response = request.invoke() if (response.data != null) { //     postValue  IO  liveData.postValue(Event.success(response.data)) } else if (response.error != null) { liveData.postValue(Event.error(response.error)) } } catch (e: Exception) { e.printStackTrace() liveData.postValue(Event.error(null)) } } } fun <T> requestWithCallback( request: suspend () -> ResponseWrapper<T>, response: (Event<T>) -> Unit) { response(Event.loading()) this.viewModelScope.launch(Dispatchers.IO) { try { val res = request.invoke() //   ,    //       ,  //    //    //  context  launch(Dispatchers.Main) { if (res.data != null) { response(Event.success(res.data)) } else if (res.error != null) { response(Event.error(res.error)) } } } catch (e: Exception) { e.printStackTrace() // UPD (  )   catch     Main  launch(Dispatchers.Main) { response(Event.error(null)) } } } } } 

Acara


Solusi keren dari Google adalah membungkus kelas tanggal dalam kelas pembungkus Peristiwa di mana kami dapat memiliki beberapa negara, biasanya LOADING, SUCCESS, dan ERROR.

 data class Event<out T>(val status: Status, val data: T?, val error: Error?) { companion object { fun <T> loading(): Event<T> { return Event(Status.LOADING, null, null) } fun <T> success(data: T?): Event<T> { return Event(Status.SUCCESS, data, null) } fun <T> error(error: Error?): Event<T> { return Event(Status.ERROR, null, error) } } } enum class Status { SUCCESS, ERROR, LOADING } 

Begini cara kerjanya. Selama permintaan jaringan, kami membuat acara dengan status LOADING. Kami sedang menunggu respons dari server dan kemudian membungkus data dengan acara tersebut dan mengirimkannya dengan status yang ditentukan lebih lanjut. Dalam tampilan kami memeriksa jenis acara dan, tergantung pada negara, mengatur negara yang berbeda untuk tampilan. Pola arsitektur MVI didasarkan pada filosofi yang sama.

ActivityViewModel


 class ActivityViewModel : BaseViewModel() { //       val simpleLiveData = MutableLiveData<Event<Users>>() //  .    requestWithLiveData //  BaseViewModel     , //          //     api.getUsers //         //    fun getUsers(page: Int) { requestWithLiveData(simpleLiveData) { api.getUsers( page = page ) } } //  ,       // UPD           fun getUsersError(page: Int, callback: (data: Event<Users>) -> Unit) { requestWithCallback({ api.getUsersError( page = page ) }) { callback(it) } } } 

Dan akhirnya

Mainaktivitas


 class MainActivity : AppCompatActivity() { private lateinit var activityViewModel: ActivityViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) activityViewModel = ViewModelProviders.of(this).get(ActivityViewModel::class.java) observeGetPosts() buttonOneClickListener() buttonTwoClickListener() } //     //         private fun observeGetPosts() { activityViewModel.simpleLiveData.observe(this, Observer { when (it.status) { Status.LOADING -> viewOneLoading() Status.SUCCESS -> viewOneSuccess(it.data) Status.ERROR -> viewOneError(it.error) } }) } private fun buttonOneClickListener() { btn_test_one.setOnClickListener { activityViewModel.getUsers(page = 1) } } //      ,   private fun buttonTwoClickListener() { btn_test_two.setOnClickListener { activityViewModel.getUsersError(page = 2) { when (it.status) { Status.LOADING -> viewTwoLoading() Status.SUCCESS -> viewTwoSuccess(it.data) Status.ERROR -> viewTwoError(it.error) } } } } private fun viewOneLoading() { //  ,    } private fun viewOneSuccess(data: Users?) { val usersList: MutableList<Users.Item>? = data?.items as MutableList<Users.Item>? usersList?.shuffle() usersList?.let { Toast.makeText(applicationContext, "${it}", Toast.LENGTH_SHORT).show() } } private fun viewOneError(error: Error?) { //   } private fun viewTwoLoading() {} private fun viewTwoSuccess(data: Users?) {} private fun viewTwoError(error: Error?) { error?.let { Toast.makeText(applicationContext, error.errorMsg, Toast.LENGTH_SHORT).show() } } } 

Kode sumber

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


All Articles