0Pricing
Kotlin Academy · درس

تحويل Flow البارد إلى ساخن باستخدام shareIn وstateIn

حوّل Flows الباردة إلى تدفقات ساخنة باستخدام معاملات المشاركة.

تحويل Flow البارد إلى ساخن باستخدام shareIn وstateIn درس مجاني في Kotlin Academy على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Kotlin Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Kotlin Academy 4 دروس في المجموع.

التدفقات الباردة مقابل الساخنة

تُعاد التدفقات الباردة من البداية لكل جامع، بينما تشارك التدفقات الساخنة اشتراكًا واحدًا في المصدر upstream. يؤدي تحويل التدفق البارد إلى ساخن إلى تجنب العمل المتكرر عند اشتراك عدة جامعين.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun expensiveFlow() = flow {
    println("Starting upstream work")  // runs once if shared
    repeat(3) { delay(100); emit(it) }
}
fun main() = runBlocking {
    val cold = expensiveFlow()
    // Two collectors = two executions:
    launch { cold.collect { } }
    launch { cold.collect { } }
    delay(500)
    coroutineContext.cancelChildren()
}

أساسيات shareIn

يحوّل flow.shareIn(scope, started, replay) تدفقًا باردًا من نوع Flow إلى SharedFlow، مع مشاركة اشتراك واحد في المصدر upstream.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
    val cold = flow {
        println("Upstream started once")
        repeat(5) { delay(100); emit(it) }
    }
    val hot = cold.shareIn(this, SharingStarted.Eagerly, replay = 0)
    launch { hot.collect { println("A: $it") } }
    launch { hot.collect { println("B: $it") } }
    delay(600)
    coroutineContext.cancelChildren()
}

أساسيات stateIn

يحوّل flow.stateIn(scope, started, initialValue) التدفق إلى StateFlow — الذي تكون له قيمة دائمًا ويعيد بث قيمة واحدة.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
    val cold = flow { delay(100); emit(42) }
    val state: StateFlow<Int> = cold.stateIn(
        scope = this,
        started = SharingStarted.Eagerly,
        initialValue = 0
    )
    println(state.value)  // 0 immediately
    delay(200)
    println(state.value)  // 42 after upstream emits
    coroutineContext.cancelChildren()
}

SharingStarted.Eagerly

Eagerly: يبدأ المصدر upstream فورًا عند استدعاء shareIn/stateIn، بغض النظر عن وجود مشتركين.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
    val flow = flow {
        println("Eagerly started")
        emit(1)
    }.shareIn(this, SharingStarted.Eagerly)
    // Upstream already running even before any collect
    delay(50)
    launch { flow.collect { println(it) } }
    delay(100)
    coroutineContext.cancelChildren()
}

SharingStarted.Lazily

Lazily: يبدأ المصدر upstream عند أول مشترك ولا يتوقف أبدًا، حتى إذا انخفض عدد المشتركين إلى الصفر.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
    val flow = flow {
        println("Lazily started on first subscriber")
        repeat(3) { delay(100); emit(it) }
    }.shareIn(this, SharingStarted.Lazily, replay = 1)
    delay(50)  // no subscriber yet — not started
    launch { flow.collect { println(it) } }  // triggers start
    delay(400)
    coroutineContext.cancelChildren()
}

SharingStarted.WhileSubscribed

WhileSubscribed(stopTimeout): يبدأ المصدر upstream عند انضمام أول مشترك، ويتوقف بعد stopTimeout مللي ثانية من مغادرة آخر مشترك. وهو مثالي لـ ViewModels.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
    val flow = flow {
        println("Started")
        repeat(10) { delay(100); emit(it) }
    }.shareIn(this, SharingStarted.WhileSubscribed(500))
    val job = launch { flow.collect { print("$it ") } }
    delay(300)
    job.cancel()    // subscriber left
    delay(200)      // within 500ms stop timeout — still running
    launch { flow.collect { print("resume $it ") } }
    delay(500)
    coroutineContext.cancelChildren()
}

النمط المعتاد في ViewModel

في Android ViewModels، حوّل تدفقات المستودع باستخدام stateIn(viewModelScope, WhileSubscribed(5000), initialValue) لمشاركتها والاستمرار عبر تغييرات الإعدادات.

import kotlinx.coroutines.flow.*
// In ViewModel:
// val uiState: StateFlow<UiState> = repository
//     .dataFlow()
//     .map { UiState.Success(it) }
//     .stateIn(
//         scope = viewModelScope,
//         started = SharingStarted.WhileSubscribed(5_000),
//         initialValue = UiState.Loading
//     )
fun main() { println("WhileSubscribed(5000) is the recommended ViewModel pattern") }

shareIn مقابل stateIn

shareIn → SharedFlow (من دون قيمة حالية، مع إمكانية ضبط replay). stateIn → StateFlow (له قيمة دائمًا، وreplay=1). اختر بينهما بناءً على حاجة المستهلكين إلى حالة حالية.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun main() = runBlocking {
    val cold = (1..3).asFlow().map { it * 10 }

    // SharedFlow — no initial value:
    val shared: SharedFlow<Int> = cold.shareIn(this, SharingStarted.Eagerly, replay = 1)

    // StateFlow — always has a value:
    val state: StateFlow<Int> = cold.stateIn(this, SharingStarted.Eagerly, 0)

    delay(100)
    println("shared cache: ${shared.replayCache}")
    println("state value: ${state.value}")
    coroutineContext.cancelChildren()
}

المفاضلات المتعلقة بذاكرة replay المؤقتة

تعني قيمة replay الأعلى أن المشتركين المتأخرين سيرون سجلًا أكبر، لكنها تستهلك مزيدًا من الذاكرة. بالنسبة إلى حالة واجهة المستخدم، تكفي قيمة replay=1 (في stateIn). أما سجلات الأحداث فقد تحتاج إلى قيمة replay أعلى.

import kotlinx.coroutines.flow.*
// replay=0: no history, only future events
// replay=1: last value (equivalent to stateIn)
// replay=N: last N events — use for message feeds, logs

// Trade-off: memory vs subscriber freshness
fun main() { println("Choose replay based on late-subscriber requirements") }

مخاطر التدفق غير المشترك

من دون مشاركة، يعيد كل جامع في Compose أو كل مراقب في ViewModel تنفيذ المصدر upstream — ما يؤدي إلى استدعاءات شبكة واستعلامات قاعدة بيانات متكررة. احرص دائمًا على مشاركة التدفقات المكلفة.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun expensiveApi() = flow {
    println("API CALL")  // without sharing: once per collector
    emit("data")
}
fun main() = runBlocking {
    val shared = expensiveApi().shareIn(this, SharingStarted.Lazily, replay = 1)
    launch { shared.collect { } }  // one API call, shared
    launch { shared.collect { } }  // same emission
    delay(200)
    coroutineContext.cancelChildren()
}

تنظيف الموارد عند المشاركة

عند إلغاء نطاق التدفق المشترك، يُلغى التدفق المصدر upstream وتُنفَّذ كتل finally الخاصة به، وبذلك تُنظَّف الموارد بشكل صحيح.

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
fun resourceFlow() = flow {
    try { repeat(10) { delay(100); emit(it) } }
    finally { println("Upstream cleaned up") }
}
fun main() = runBlocking {
    val scope = CoroutineScope(SupervisorJob())
    val shared = resourceFlow().shareIn(scope, SharingStarted.Eagerly)
    launch { shared.collect { print("$it ") } }
    delay(250)
    scope.cancel()  // upstream cleanup runs
    delay(100)
}

تحقق سريع

ما استراتيجية SharingStarted الموصى بها في Android ViewModels؟

مراجعة

يحوّل shareIn التدفق إلى SharedFlow، بينما يحوّل stateIn التدفق إلى StateFlow. استخدم WhileSubscribed في ViewModels لتوفير الموارد. تمنع المشاركة عمليات التنفيذ المتكررة للمصدر upstream عند اشتراك عدة جامعين.

الأسئلة الشائعة

هل درس «تحويل Flow البارد إلى ساخن باستخدام shareIn وstateIn» مجاني؟

نعم — نص درس «تحويل Flow البارد إلى ساخن باستخدام shareIn وstateIn» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Kotlin Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Kotlin Academy 4 دروس في المجموع.

ماذا ستتعلم في «تحويل Flow البارد إلى ساخن باستخدام shareIn وstateIn»؟

حوّل Flows الباردة إلى تدفقات ساخنة باستخدام معاملات المشاركة. تتمرن على Kotlin Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Kotlin Academy؟

لا تُشترط خبرة سابقة. Kotlin Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.

كم من الوقت يستغرق درس «تحويل Flow البارد إلى ساخن باستخدام shareIn وstateIn»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Kotlin Academy هذا؟

نعم. كل درس في Kotlin Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. ‏StateFlow: حاوية حالة ساخنة لواجهة المستخدم
  2. ‏SharedFlow: حافلات الأحداث والأحداث أحادية الإرسال
  3. تحويل Flow البارد إلى ساخن باستخدام shareIn وstateIn
  4. اختبار StateFlow وSharedFlow باستخدام Turbine
← العودة إلى Kotlin Academy