تعرف على Kotlin coroutines : الجزء الثاني

رجعنا مرة تاني، قبل ما نبدأ تأكد أنك قد قرأت الجزء الأول من الشرح على الموقع وفهمته كويس، في الجزء دا هنبدأ نتعمق شوية في مفاهيم Coroutine، وهنشرح جزء واحد من المرة اللي فاتت عشان نتأكد أنك فاهمه كويس، بس الأول أكيد لازم تكون ضايف coroutine في الgradle عشان تبدأ تستخدمها:

dependencies {
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.4'
}

:Coroutine Scope

أحنا شرحناها المرة اللي فاتت وقولنا انها بتعمل تصنيفات لCoroutine بحيث أنه يكون اسهل نعمل Manage ليهم، فبكدا ممكن نخلص أهمية الScope في تلت حجات :

  • نتتبع الCoroutine هي خلصت ولا لا
  • نوقف Coroutine اللي شغالة دلوقتي لأي سبب احنا عايزينه ونعمل Callback بالسبب
  • نعرف لو حصل أي فشل في Coroutine أو انها مكملتش

:Jobs

دا واحد من أهم العنواين في Coroutine، وفي الحقيقة أنت لما بتعمل launch أو async من CoroutineScope فهو كدا أصلاً بيرجعلك Job، يعني الكود دا

 CoroutineScope(Dispatchers.IO).launch {
            heavyTaskFunction()
        }

ممكن يكتب بطريقة تانية وهي :

val scope = CoroutineScope(Dispatchers.IO)
val job = scope.launch {
    heavyTaskFunction();
}

فكدا ممكن نعرف Job أن هي الشئ الرئيسي اللي بيعمل handle للcoroutine وأن Scope هو اللي بيعمل handle لJob أو أكتر من Job، ولازم تبقى عارف أن جوا Job ممكن أعمل أكتر من Job وهكذا فهيكون عندك زي شكل شجرة من الCoroutines، الجذر بتاعها بيكون CoroutineScope والباقي jobs تحتها jobs.

الأب ممكن يكون Coroutine تانية أو scope

طب ايه فايدة الJob؟ الjob مهمة جدًا لكذا حاجة بس الأهم أنها بتحدد lifecycle يعني امتى تبدأ وامتى تنتهي، وممكن تعرف هل job شغالة دلوقتي ولا خلصت شغلها، كل الحجات ديه بتساعدك في عمل المهام اللي بتاخد وقت كبير

ديه الحجات اللي هنستخدمها في Job وشرح كل واحدة :

  • ()job.cancel : لو عايز توقف الJob، وممكن تحط patameter من نوع Cancellation Exception عشان لو عايز تعرف ايه السبب مثلاً : مفيش اتصال بالانترنت كويس أو المستخدم هو اللي لغى الاتصال وديه هتفعك لو عايز تظهر رسالة للمستخدم بالسبب وممكن تستخدم job.isCancelled عشان تشوف هي اتعملها Cancel ولا لا وطبغًا هترجعلك True او False
 //cancel with speicfy the reason
 job.cancel(CancellationException("User canceled the requet"))

        //check if cancelled or not
        if (job.isCancelled) {
            //show message
        } else {
            //still running or completed succesfully
        }

عشان نعمل Handel لcancelException اللي عملنها فوق بنستخدم حاجة اسمها invokeOnCompletion وديه بتستدعى بشكل تلقائي أول ما يخلص job سواء حصله Cancel أو Complete وركز في الحتة ديه عشان هنضيف نقطة عليها قدام، لاحظ أن it ديه throwable في lambada ممكن تجيب منها message اللي كتبناها فوق:

 job.invokeOnCompletion {
            //Show error messsage to the user
            AlertDialog.text = it?.message
 }
  • ()Job.join: ديه لازم تستدعيها من داخل Coroutine او جوا suspend function وديه معناها “متنتقلش للسطر اللي بعده غير لما تخلص job الأول” .. نفس الموضوع ل()job.cancelAndJoin بس بيقول متنتقلش للسطر اللي بعده الا لما توقف الjob
     val parentJob = scope.launch {
            val job = launch { }
            job.join()

            //this method won't be excuted unless job is done
            updateUI()
        }

خلينا نتعمق أكتر في موضوع Join، فلنتفترض أنا عندي اتنين jobs عايز اعملهم في نفس الوقت، كل واحد بيتعمل ملهوش علاقة بالتاني خالص، وكل واحد ليه الوقت اللي هيخلص فيه، في الحقيقة دا اللي بيحصل في الوضع الطبيعي زي كدا :

 val parentJob = scope.launch {
            val firstJob = launch {
                //some heavy work
            }
            val secondJob = launch{
                //some heavy work
            }
            
}

لو بصينا الكود اللي فوق هيمشي ازاي، هنشوف أنه بدأ يعمل الparent job فدخل لقى أول job بدأ يعملها ولقاها هتاخد وقت، فسابها تكمل شغلها وانتقل لjob اللي بعدها، كل واحدة في العالم بتاعها وأول ما الاتنين يخلصوا يبقا كدا الparent job خلص، طب نشوف بقا لو حطينا join :

 val parentJob = scope.launch {
 
            val firstJob = launch {
                //some heavy work
            }
            
            firstJob.join()
            
            val secondJob = launch {
                //some heavy work
            }

        }

هنا نفس الكلام فيه parent job هيخش يلاقي first Job بتاخد وقت كتير هيخرج يلاقي مكتوب firstJob.join فمش هيتحرك من مكانه الا لما يخلص firstJob وأول ما يخلصها يبدأ يعمل SecondJob.

  • job.isActive ديه Boolean بتقول هل الjob شغالة دلوقتي ولا لا
  • job.isCompleted : برضو Boolean بتقول هل Job خلصت ولا لا وملهاش دعوة بقا هي cancelled ولا خلصت شغلها كلها، كل اللي هي بتقوله خلصت ولا لا، فحتى لو Cancelled هترجع True
Job Lifecycle

هيجي في نفسك السؤال المهم دلوقتي طب لو أن عايز في البرنامج أقول أن الJob ديه Complete، يعني مخلهاش هي بنفسها تحدد هي Complete ولا لا أعمل ايه؟ .. أنت لو حولت مش هتلاقي method اسمها Complete، كوتلن قررت تخليها في object لوحدوا وهو CompletableJob

:Completable Job

دا subclass لjob في حاجتين اساسيتن زيادة وهي ()Complete ديه method عشان أنت اللي تقرر أن الjob ديه خلصت، وعندك حاجة كمان وهي ()completeExceptionally وديه هتخلص الmethod وتعمل complete بس معاه Exception في حالة لو حصل خطأ وانت برضو بتحدد نوع Exception وبتحدد الmessage وبتظهر في invokeOnCompletion اللي قولتلك ركز فيها فوق :

  lateinit var completableJob: CompletableJob

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        completableJob = Job()
        completableJob.invokeOnCompletion { 
        //    
          Log.d(TAG,it!!.message )
            // Will print It's normal Excption
        }
        completableJob.completeExceptionally(Exception("It's normal Excption"))
        }

بكدا نكون خلصنا الjob، المرة الجاية هنتعرف ازاي نربطها Corourtine context، اتمنى تكون فهمت كويس ولو في أي سؤال، ممكن تسـأله في تعليق أو على الجروب.

الكاتب: Mohamed Saber

Pharmacist, Android developer and UI/UX enthusiast

اترك ردا