تنظيم بنية بسيطة في تطبيق Android مع مجموعة من ViewModel + LiveData و Retrofit + Coroutines

بدون مقدمات طويلة ، سوف أخبرك بكيفية تنظيم بنية ملائمة لتطبيقك بسرعة وسهولة. ستكون المادة مفيدة لأولئك الذين ليسوا على دراية بنمط mvvm و corotines Kotlin.

لذلك ، لدينا مهمة بسيطة: تلقي ومعالجة طلب شبكة ، لعرض النتيجة في طريقة عرض.

إجراءاتنا: من النشاط (الجزء) ، نسمي الطريقة المطلوبة ViewModel -> يصل ViewModel إلى مقبض التعديل التحديثي ، ويقوم بتنفيذ الطلب من خلال coroutines -> يتم إرسال الاستجابة إلى البيانات المباشرة كحدث -> في النشاط الذي يستقبل الحدث ، نقوم بنقل البيانات إلى العرض.

إعداد المشروع


اعتمادا على


//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" 

بيان رسمي


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

التحديثية الإعداد


إنشاء كائن Kotlinovsky NetworkService . سيكون هذا عميل شبكتنا - سينجلتون
يستخدم UPD المفرد لسهولة الفهم. أشارت التعليقات إلى أنه من الأنسب استخدام انقلاب التحكم ، ولكن هذا موضوع منفصل.

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

واجهة API


نستخدم الطلبات المقفلة للخدمة المزيفة.

وقفة المرح ، وهنا يبدأ سحر corutin.

نحتفل وظائفنا مع الكلمة تعليق المرح ....

التعديل التحديثي الذي تم تعلمه للعمل مع Kotlin Suspendوظائف من الإصدار 2.6.0 ، والآن ينفذ مباشرة طلب شبكة ويعيد كائنًا مع البيانات:

 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 هي فئة مجمّع بسيطة لطلبات شبكتنا:

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

تاريخ فئة المستخدمين

 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


نقوم بإنشاء فئة BaseViewModel مجردة والتي سيتم من خلالها توريث جميع أنواع ViewModel الخاصة بنا. هنا نسهب في مزيد من التفاصيل:

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

حدث


أحد الحلول الرائعة من Google هو الالتفاف على فصول التاريخ في فئة مجمّع الأحداث ، والتي يمكن أن يكون لدينا فيها عدة حالات ، عادةً التحميل ، والنجاح ، والخطأ.

 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 } 

إليك كيف تعمل. أثناء طلب الشبكة ، نقوم بإنشاء حدث بالحالة LOADING. نحن في انتظار استجابة من الخادم ثم نلف البيانات بالحدث ونرسلها بالحالة المحددة. في العرض ، نتحقق من نوع الحدث ونعتمد على الحالات المختلفة للعرض بناءً على الحالة. يعتمد النمط المعماري MVI على نفس الفلسفة.

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

وأخيرا

MainActivity


 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() } } } 

شفرة المصدر

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


All Articles