0Pricing
Kotlin Academy · บทเรียน

แปลง Cold Flow เป็น Hot ด้วย shareIn และ stateIn

แปลง Flow แบบเย็นเป็นสตรีมแบบร้อนด้วยตัวดำเนินการแชร์

แปลง Cold Flow เป็น Hot ด้วย shareIn และ stateIn เป็นบทเรียน Kotlin Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Kotlin Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Kotlin Academy มีบทเรียนทั้งหมด 4 บทเรียน

โฟลว์เย็นกับโฟลว์ร้อน

โฟลว์เย็นจะเริ่มทำงานใหม่สำหรับตัวเก็บข้อมูลแต่ละตัว ส่วนโฟลว์ร้อนจะแชร์การสมัครรับข้อมูลจากต้นทางเพียงครั้งเดียว การแปลงโฟลว์เย็นเป็นโฟลว์ร้อนช่วยหลีกเลี่ยงงานซ้ำเมื่อมีตัวเก็บข้อมูลหลายตัวสมัครรับข้อมูล

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 โดยแชร์การสมัครรับข้อมูลจากต้นทางเพียงครั้งเดียว

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 ซึ่งมีค่าอยู่เสมอและเล่นซ้ำ 1 ค่า

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: ต้นทางจะเริ่มทำงานทันทีเมื่อเรียก 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: ต้นทางจะเริ่มทำงานเมื่อมีผู้สมัครรับข้อมูลรายแรก และจะไม่หยุด แม้จำนวนผู้สมัครรับข้อมูลจะลดลงเหลือศูนย์

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): ต้นทางจะเริ่มทำงานเมื่อผู้สมัครรับข้อมูลรายแรกเข้ามา และหยุดหลังจากผู้สมัครรับข้อมูลรายสุดท้ายออกไปแล้ว 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 (ไม่มีค่าปัจจุบันและกำหนดการเล่นซ้ำได้) 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=1 (stateIn) ก็เพียงพอแล้ว ส่วนบันทึกเหตุการณ์อาจต้องใช้การเล่นซ้ำจำนวนมากกว่า

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 จะเรียกใช้ต้นทางใหม่ ทำให้เกิดการเรียกเครือข่ายและการค้นหาฐานข้อมูลซ้ำ ควรแชร์โฟลว์ที่ใช้ทรัพยากรมากเสมอ

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

การล้างทรัพยากรเมื่อแชร์ข้อมูล

เมื่อขอบเขตของโฟลว์ที่แชร์ถูกยกเลิก โฟลว์ต้นทางจะถูกยกเลิกด้วย และบล็อก 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 เพื่อประหยัดทรัพยากร การแชร์ข้อมูลช่วยป้องกันการทำงานของต้นทางซ้ำเมื่อมีตัวเก็บข้อมูลหลายตัวสมัครรับข้อมูล

คำถามที่พบบ่อย

บทเรียน “แปลง Cold Flow เป็น Hot ด้วย shareIn และ stateIn” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “แปลง Cold Flow เป็น Hot ด้วย shareIn และ stateIn” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Kotlin Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Kotlin Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “แปลง Cold Flow เป็น Hot ด้วย shareIn และ stateIn”

แปลง Flow แบบเย็นเป็นสตรีมแบบร้อนด้วยตัวดำเนินการแชร์ คุณปฏิบัติ Kotlin Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Kotlin Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Kotlin Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน

บทเรียน “แปลง Cold Flow เป็น Hot ด้วย shareIn และ stateIn” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Kotlin Academy นี้ได้ไหม

ได้ บทเรียน Kotlin Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. StateFlow: ตัวเก็บสถานะแบบร้อนสำหรับ UI
  2. SharedFlow: บัสเหตุการณ์และเหตุการณ์แบบครั้งเดียว
  3. แปลง Cold Flow เป็น Hot ด้วย shareIn และ stateIn
  4. ทดสอบ StateFlow และ SharedFlow ด้วย Turbine
← กลับไปที่ Kotlin Academy