---
title: "التعامل مع طلبات الشبكة المعقدة على Android باستخدام Kotlin Coroutine وRetrofit 2 في MVVM"
description: "في هذه المقالة سأوضّح كيف نستخدم coroutine من أجل: ربط عدة طلبات شبكة بالتتابع، وتنفيذ طلبات شبكة متوازية ومعالجة الاستجابات بعد انتهائها جميعًا."
canonical: https://oozou.com/ar/blog/handle-complex-network-call-on-android-with-kotlin-coroutine-retrofit-2-in-mvvm-9
locale: ar
date: 2018-07-26
author: "OOZOU Team"
---

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

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

Published: 2018-07-26 · Author: OOZOU Team (Engineering) · 6 min read

إذا عملتم على بعض تطبيقات 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_](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/index.html)_. وتُلغى الـ coroutine عند_ [_إلغاء_](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/cancel.html)_ الـ job الناتج.
> _**_async_**_\- تُنشئ coroutine جديدة وتُعيد نتيجتها المستقبلية على هيئة تنفيذ لـ_ [_Deferred_](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/index.html)_. وتُلغى الـ coroutine الجارية عند_ [_إلغاء_](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/cancel.html)_ الكائن الناتج._

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

```kotlin
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](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html) و [async](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html) وغيرها) هو extension على [CoroutineScope](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.md). وعندما ينتهي النطاق، تنتهي معه الـ Coroutines التي بداخله. ولحسن الحظ، توفّر مكتبة Android **lifecycle-viewmodel-ktx** طريقة سهلة جدًا للحصول على **Coroutine Scope** داخل الـ ViewModel، وسأوضّح لكم ذلك لاحقًا.

**لنبدأ!**

**التبعيات (Dependencies)**

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

```groovy
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 نكتب شيئًا كهذا، أليس كذلك؟

```kotlin
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، يمكننا كتابتها بهذه الطريقة.

```kotlin
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**

```kotlin
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**

```kotlin
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/`

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

```kotlin
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** بالتوازي. سيبدو الكود هكذا.

```kotlin
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://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](https://proandroiddev.com/kotlin-coroutines-handling-concurrency-like-a-pro-retrofit2-coroutines-31cd0611fd91)
- كتاب Kotlin for Android Developers من تأليف [Antonio Leiva](https://medium.com/u/a199eb0fa9a0?source=post_page-----30a6cd1e0189----------------------)([https://antonioleiva.com/kotlin-android-developers-book/](https://antonioleiva.com/kotlin-android-developers-book/))

---

Contact OOZOU: hello@oozou.com · +66 2544 0000 · https://oozou.com/ar/contact

More for AI agents: https://oozou.com/llms.txt · every page has a markdown twin at its URL plus ".md"
