OOZOU
تواصلوا معنا
العودة إلى المدونة

التعامل مع طلبات الشبكة المعقدة على Android باستخدام Kotlin Coroutine وRetrofit 2 في MVVM

في هذه المقالة سأوضّح كيف نستخدم coroutine من أجل: ربط عدة طلبات شبكة بالتتابع، وتنفيذ طلبات شبكة متوازية ومعالجة الاستجابات بعد انتهائها جميعًا.

26 يوليو 2018
6 دقائق للقراءة
التعامل مع طلبات الشبكة المعقدة على Android باستخدام Kotlin Coroutine وRetrofit 2 في MVVM

إذا عملتم على بعض تطبيقات Android، فربما واجهتم موقفًا تحتاجون فيه إلى إرسال عدة طلبات شبكة، متتابعة أو متوازية، ثم دمج نتائجها.

وللتعامل مع هذا الموقف، قد يقول البعض: “لنستخدم RxJava!!!” لكن… بصراحة، منحنى تعلّم RxJava ليس بالأمر الهيّن، على الأقل بالنسبة لي.

لا تفهموني خطأً، هذا لا يعني أنني أكره RxJava. فهي مكتبة قوية جدًا، لكن Kotlin coroutine تبدو أنسب لبنية تطبيقي الحالية.

في هذه المقالة سأوضّح كيف نستخدم coroutine من أجل

  • ربط عدة طلبات شبكة بالتتابع
  • تنفيذ طلبات شبكة متوازية ومعالجة الاستجابات بعد انتهائها جميعًا.

لن أدخل في تفاصيل كثيرة حول طريقة عمل coroutine، بل سأركّز على استخدامها عمليًا.

مقدمة

قبل أن نبدأ، أود أن أتناول باختصار المفهوم والدوال الأكثر استخدامًا في Coroutine.

المفهوم باختصار

لطالما كانت كتابة الكود غير المتزامن (asynchronous) مصدر إزعاج لي، خاصة عند التعامل مع طلبات الشبكة المعقدة. فمن الصعب كتابة كود سهل القراءة، وقد ينتهي بكم الأمر أحيانًا بكثير من الـ callbacks في الكود. لذلك، من أهداف Kotlin Coroutine مساعدتنا على كتابة كود أكثر ترتيبًا عند العمل بالبرمجة غير المتزامنة.

وهذا ما ورد في توثيق Kotlin الرسمي.

تبسّط الـ Coroutines البرمجة غير المتزامنة بنقل التعقيدات إلى المكتبات. إذ يمكن التعبير عن منطق البرنامج بشكل تسلسلي داخل coroutine، وتتولّى المكتبة الأساسية التعامل مع عدم التزامن نيابةً عنا.

Launch وAsync

launch وasync هما أكثر Coroutine builder استخدامًا.

وهذا هو التعريف الرسمي.

launch- تُطلق coroutine جديدة دون حجب الـ thread الحالي، وتُعيد مرجعًا إلى الـ coroutine على هيئة Job. وتُلغى الـ coroutine عند إلغاء_ الـ job الناتج. async- تُنشئ coroutine جديدة وتُعيد نتيجتها المستقبلية على هيئة تنفيذ لـ_ Deferred. وتُلغى الـ coroutine الجارية عند إلغاء_ الكائن الناتج._

لنلقِ نظرة على هذا الجزء من الكود كمثال.

launch {
  delay(2000)
  println("launch block")
}

val result: Deferred<String> = async {
  delay(3000)
  "async block"
}

println(result.await())

يتضح من المثال أن الفرق بين launch و async هو أن async تستطيع إعادة نتيجة مستقبلية من النوع Deferred<T>، ويمكننا استدعاء الدالة await() على متغير Deferred للحصول على نتيجة الـ Coroutine، بينما تكتفي launch بتنفيذ الكود داخل الكتلة دون إعادة أي نتيجة.Coroutine Scope

يحدّد Coroutine Scope النطاق الذي تعمل فيه الـ coroutines. فكل coroutine builder (مثل launch و async وغيرها) هو extension على CoroutineScope. وعندما ينتهي النطاق، تنتهي معه الـ Coroutines التي بداخله. ولحسن الحظ، توفّر مكتبة Android lifecycle-viewmodel-ktx طريقة سهلة جدًا للحصول على Coroutine Scope داخل الـ ViewModel، وسأوضّح لكم ذلك لاحقًا.

لنبدأ!

التبعيات (Dependencies)

هذه هي العناصر التي تحتاجون إلى إضافتها إلى ملف build.gradle

dependencies {
    // Retrofit
    implementation "com.squareup.retrofit2:retrofit:$latestRetrofitVersion"
    implementation "com.squareup.retrofit2:converter-gson:$latestRetrofitVersion"

    // OKHttp
    implementation "com.squareup.okhttp3:okhttp:$okHttpVersion"
    implementation "com.squareup.okhttp3:logging-interceptor:$okHttpVersion"
    implementation "com.squareup.okhttp3:okhttp-urlconnection:$okHttpVersion"

    // Kotlin & Coroutines
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion"

    // For Viewmodel to work with Coroutine
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleViewmodelKtxVersion"

}

واجهة الـ API

لننشئ interface للـ API يحدّد مسارات الـ API التي سنتعامل معها.

عادةً، عند تعريف المسارات في interface الـ API نكتب شيئًا كهذا، أليس كذلك؟

interface SomeApi {    
    @GET("path1/")  
    fun getObject1(): Call<Object1>

    @GET("path2/")   
    fun getObject2(): Call<Object2>   

    @GET("path3/{id}")   
    fun getObject3(@Path("id") objectId : String): Call<Object3> 
}

لكن لكي تعمل مع Coroutine، يمكننا كتابتها بهذه الطريقة.

interface SomeApi {    

   @GET("path1/")   
   suspend fun getObject1(): Response<Object1>

   @GET("path2/")
   suspend fun getObject2(): Response<Object2>    

   @GET("path3/{id}")   
   suspend fun getObject3(@Path("id") objectId : String): Response<Object3> 
}

ببساطة، جعلنا دوال interface الـ API من نوع suspend function حتى نتمكن من استخدامها بشكل غير متزامن لاحقًا. توفير Retrofit API

object Api {
    fun getApiService(): SomeApi {
        val gson = GsonBuilder().create()
        return Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(provideOkHttpClient(provideLoggingInterceptor()))
            .addConverterFactory(GsonConverterFactory.create(gson))
            .build().create(SomeApi::class.<em>java</em>)
    }

    private fun provideOkHttpClient(interceptor: HttpLoggingInterceptor): OkHttpClient {
        val b = OkHttpClient.Builder()
        b.addInterceptor(interceptor)
        return b.build()
    }

    private fun provideLoggingInterceptor(): HttpLoggingInterceptor {
        val interceptor = HttpLoggingInterceptor()
        interceptor.<em>level </em>= HttpLoggingInterceptor.<em>Level</em>.<em>BODY
        </em>return interceptor
    }
}

توفير الـ Repository

class SomeRepository(val apiService: SomeApi) {

    suspend fun getObject1(): Response<Object1> = apiService.getObject1()

    suspend fun getObject2(): Response<Object2> = apiService.getObject2()

    suspend fun getObject3(objectId : String): Response<Object3> = apiService.getObject3((objectId)

}

ربط طلبات الشبكة بالتتابع

لنفترض أننا نحتاج إلى إرسال طلبَي شبكة متتاليين، ونحتاج إلى نتيجة الطلب الأول لإنشاء الطلب الثاني.

في مثالنا، لنفترض أن مسار الـ endpoint path3/{id} يعتمد على نتيجة الـ endpoint path1/

قد يبدو الكود هكذا.

class SomeViewModel(
    private val repository: SomeRepository
) : ViewModel() {
    fun chainingRequest() {
        viewModelScope.launch <strong>{
            </strong>val object1 = repository.getObject1().body()
            val object3 = repository.getObject3(object1.<em>id</em>).body()
            processObject3(object3)
        <strong>}
    </strong>}
}

تنفيذ طلبات شبكة متوازية

لنفترض أننا نحتاج إلى إرسال طلبين في الوقت نفسه، ومعالجة النتائج بعد عودة استجابتَي الـ endpoint معًا. في حالتنا، لنستدعِ الـ endpoint /path1 و /path2 بالتوازي. سيبدو الكود هكذا.

class SomeViewModel(
    private val repository: SomeRepository
) : ViewModel() {

    fun parallelRequest() {
        viewModelScope.launch {
            try {
                val getObject1Task = async { apiService.getObject1() }
                val getObject2Task = async { apiService.getObject2() }

                processData(getObject1Task.await().body(), getObject2Task.await().body())
            } catch (exception: Exception) {
                Log.e("TAG", exception.message)
            }
        }
    }
}

لنشرح ذلك خطوة بخطوة.

  • تُرسل الطلبات في val getObject1Task = async { apiService.getObject1() } و val getObject2Task = async { apiService.getObject2() }. ورغم أن الكود مكتوب بشكل تسلسلي، فإن الطلبات تُنفَّذ بشكل غير متزامن.
  • بعد ذلك نمرّر نتائج الطلبين لمعالجتها في successHandler. ونحصل على النتائج عبر getObject1Task.await().body() و getObject2Task.await().body()- لن تُستدعى processData إلا بعد انتهاء getObject1Task و getObject2Task كليهما.

الخلاصة

بشكل عام، أُعجبت بمدى قدرة coroutine على تبسيط البرمجة غير المتزامنة. كما أنها تُخرجنا من جحيم الـ callbacks (callback hell) وتسهّل التعامل مع طلبات الشبكة المعقدة. وأعتقد أنه لم يعد هناك عذر لعدم تجربة coroutine الآن.

المصادر

  • https://medium.com/@andrea.bresolin/playing-with-kotlin-in-android-coroutines-and-how-to-get-rid-of-the-callback-hell-a96e817c108b
  • https://proandroiddev.com/kotlin-coroutines-handling-concurrency-like-a-pro-retrofit2-coroutines-31cd0611fd91
  • كتاب Kotlin for Android Developers من تأليف Antonio Leiva(https://antonioleiva.com/kotlin-android-developers-book/)

Related Articles

كيف أصمّم برمجيات؟ دليل المبتدئين لأصحاب الأعمال

كيف أصمّم برمجيات؟ دليل المبتدئين لأصحاب الأعمال

تصميم البرمجيات عملية ممتعة لكنها معقدة، تتطلب الإبداع والتفكير المنطقي ومهارات حل المشكلات.

اقرأوا المزيد
مستقبل الحوسبة الطرفية: ما تحتاج الشركات إلى معرفته

مستقبل الحوسبة الطرفية: ما تحتاج الشركات إلى معرفته

يتطور المشهد الرقمي بسرعة، وعلى الشركات أن تتكيّف لتواكب الطلب المتزايد على السرعة والكفاءة والمعالجة في الوقت الفعلي.

اقرأوا المزيد
مستقبل قطاع التجزئة: الذكاء الاصطناعي والواقع المعزّز واتجاهات التحوّل الرقمي

مستقبل قطاع التجزئة: الذكاء الاصطناعي والواقع المعزّز واتجاهات التحوّل الرقمي

يتطور قطاع التجزئة بوتيرة غير مسبوقة بفضل الذكاء الاصطناعي (AI) والواقع المعزّز (AR) وتقنيات التحوّل الرقمي. فلم تعد المتاجر التقليدية مجرد أماكن لبيع المنتجات، بل أصبحت متاجر ذكية وتفاعلية.

اقرأوا المزيد

Have a Project in Mind?

Let's discuss how we can help bring your ideas to life with our expertise in web and mobile development.

ابدأوا المحادثةhello@oozou.com
OOZOU Logo

بانكوك · سنغافورة · هونغ كونغ

X
Facebook
Instagram
LinkedIn
OOZOU Co., Ltd.
548 One City Centre
Ploenchit Road
Bangkok 10330
Thailand
+66 2544 0000
hello@oozou.com

الخدمات

  • تطوير الويب
  • وكلاء الذكاء الاصطناعي والذكاء الاصطناعي التوليدي
  • تطوير تطبيقات الجوال
  • تحليلات البيانات وهندستها
  • تصميم تجربة المستخدم وواجهته (UX/UI) وتصميم المنتجات
  • التحوّل الرقمي

الشركة

  • من نحن
  • الوظائف
  • المدونة
  • دراسات الحالة
  • عروض حية
  • تعلّمت اليوم
  • تواصل معنا

المزيد

  • القطاعات
  • شبكة الشركاء
© 2026 OOZOU. جميع الحقوق محفوظة.
سياسة الخصوصيةمدونة قواعد السلوكسياسة مكافحة الرشوة والفساد