آموزش State یا StateFlow در Jetpack Compose

آموزش State یا StateFlow در Jetpack Compose
در این پست می‌خوانید:

با اومدن Jetpack Compose خیلی از مفاهیمی که قبلاً توی توسعه اندروید باهاشون درگیر بودیم ساده‌تر شدن، اما در عوض سؤال‌های جدیدی هم به وجود اومدن. یکی از رایج‌ترین این سؤال‌ها، مخصوصاً برای برنامه‌نویس‌های تازه‌کار، اینه که:

برای مدیریت State در ViewModel بهتره از Compose State استفاده کنیم یا StateFlow ؟

اگه یه کم توی مقاله‌ها، شبکه‌های اجتماعی یا مخزن‌های کد بگردین، احتمالاً می‌بینید خیلی‌ها سریع و بدون توضیح می‌گن: «StateFlow حرفه‌ای‌تره، از همون استفاده کن.» اما واقعیت اینه که این انتخاب همیشه بهترین گزینه نیست.

توی این مطلب قراره خیلی ساده، روان و خودمونی بررسی کنیم:

  • State و StateFlow چی هستن،
  • چه تفاوتی دارن
  • برای یک UI معمولی با Jetpack Compose کدوم انتخاب منطقی‌تریه.

 

اول بفهمیم داریم درباره چی حرف می‌زنیم

Compose State چیه؟

Compose State در ساده‌ترین حالت، یه متغیر معمولیه که وقتی مقدارش تغییر می‌کنه، UI به‌صورت خودکار Recompose می‌شه.

var counter by mutableStateOf(0)

ویژگی‌ها:

– ساده و خوانا

– دقیقاً برای UI طراحی شده

– بدون نیاز به Flow و coroutine اضافه

نکته
  اگر درمورد استفاده از کوروتین ها در کامپوز اطلاعاتی ندارید به این مطلب مراجعه کنید  

StateFlow چیه؟

StateFlow در اصل یه Flowـه که همیشه آخرین مقدار رو نگه می‌داره و هر کسی collectش کنه، آخرین state رو می‌گیره.

private val _counter = MutableStateFlow(0)
val counter = _counter.asStateFlow()

ویژگی‌ها:

– قدرتمند

– مناسب stream داده

– کمی پیچیده‌تر برای UI ساده

نکته
  اگر درمورد Flowها اطلاعاتی ندارید به این مطلب مراجعه کنید  

چرا خیلی‌ها می‌گن StateFlow بهتره؟

معمولاً این دلایل مطرح می‌شه:

  • Flowها operatorهای زیادی دارن (مثل map و combine)
  • با بسته شدن برنامه (process death) بهتر کنار میان
  • ViewModel قابل استفاده‌ی مجدد می‌شه
  •  برای تغییرات پشت‌سرهم داده مناسب‌ترن

حالا بیایم اینا رو از دید UI بررسی کنیم.

 Flow operatorها بهترن؟

درسته که Flow operatorهای خوبی داره، ولی برای UI معمولاً زیادی شلوغ و پیچیده‌ست.

 با StateFlow:

private val _color = MutableStateFlow(0xFFFFFF)
val color = _color.asStateFlow()

val nextColor = color
    .map { it + 1 }
    .stateIn(viewModelScope)

 با State:

var color by mutableStateOf(0xFFFFFF)
    private set

val nextColor
    get() = color + 1

State مثل یه متغیر معمولی رفتار می‌کنه و برای محاسبات ساده‌ی UI، این سادگی یه مزیت بزرگه.

StateFlow و process death

StateFlow می‌تونه با SavedStateHandle مقدارش رو بعد از بسته شدن برنامه نگه داره:

class MyViewModel(
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    val greeting =
        savedStateHandle.getStateFlow("greeting", "hello")

    fun setGreeting(value: String) {
        savedStateHandle["greeting"] = value
    }
}

اما در Compose، برای stateهای UI معمولاً این روش ساده‌تره:

var greeting by rememberSaveable {
    mutableStateOf("hello")
}

برای stateهایی که فقط به UI مربوط هستن، `rememberSaveable` کاملاً کافیه و حتی تمیزتره.

 استفاده‌ی مجدد ViewModel

اگه قراره ViewModel توی چند جای مختلف استفاده بشه (مثلاً XML، Compose یا حتی یه لایه‌ی دیگه)، StateFlow انتخاب مناسبیه.

اما اگه ViewModel فقط برای Compose نوشته شده:

– استفاده از State ساده‌تره

– کد خواناتر می‌شه

پیچیدگی اضافه نداره

تغییرات پشت‌سرهم داده

اگه با stream واقعی داده کار می‌کنی (مثل WebSocket یا eventهای پشت‌سرهم)، State اصلاً انتخاب درستی نیست.

اما UI معمولاً فقط آخرین وضعیت رو می‌خواد، نه کل مسیر تغییرات.
برای همین State برای UI کاملاً منطقیه.

آیا Stateها همیشه Mutable هستن؟

نه الزاماً.

var count by mutableStateOf(0)
    private set

از بیرون کلاس:

– فقط یه `Int` دیده می‌شه

– امکان تغییرش وجود نداره

پس State می‌تونه کاملاً امن و قابل کنترل باشه.

State و Thread

اگه طراحی‌ات درست باشه، معمولاً به مشکل Thread هم نمی‌خوری

var result by mutableStateOf(0)
    private set

fun performComputation() {
    viewModelScope.launch {
        result = heavyComputation()
    }
}

private suspend fun heavyComputation(): Int {
    return withContext(Dispatchers.Default) {
        // کار سنگین
        42
    }
}

کد ساده، خوانا و بدون سوییچ‌های اضافی بین Threadها.

نکته‌ی مهم اینجاست که خودِ State در Compose مسئول مدیریت Thread نیست؛ این مسئولیت کاملاً به عهده‌ی جاییه که State رو تغییر می‌ده (که دراینجا ViewModel این مسئولیت رو داره ).

در این مثال:

  • viewModelScope.launch به‌صورت پیش‌فرض روی Main Thread اجرا می‌شه

  • تابع heavyComputation با withContext(Dispatchers.Default) به یک Background Thread منتقل می‌شه

  • بعد از تموم شدن محاسبه، Coroutine دوباره به Main Thread برمی‌گرده

  • و فقط همون‌جا مقدار result تغییر می‌کنه

به خاطر همین ترتیب درست:

  • هیچ Stateای از Thread بک‌گراند تغییر داده نمی‌شه

  • Compose هم بدون مشکل Recompose انجام می‌ده

  • و نیازی به درگیری مستقیم با Flow، emit یا سوییچ‌های دستی بین Threadها نیست

در واقع اگر قانون ساده‌ی زیر رو رعایت کنی، مشکلی از نظر Thread نخواهی داشت:

State رو فقط روی Main Thread تغییر بده، کار سنگین رو بفرست Background

این الگو دقیقاً همون چیزیه که Coroutineها و ViewModel برایش طراحی شدن.
پس برای Stateهای ساده‌ی UI، استفاده از mutableStateOf نه‌تنها امنه، بلکه خواناتر و کم‌هزینه‌تر هم هست

جمع‌بندی نهایی

هیچ قانون مطلقی وجود نداره که همیشه StateFlow بهتر از State باشه.

برای UIهایی که فقط با Jetpack Compose ساخته شدن:
State انتخاب ساده‌تر، خواناتر و منطقی‌تریه.

StateFlow زمانی انتخاب خوبیه که:

– ViewModel قراره چندجا استفاده بشه

– واقعاً stream داده داشته باشی

– با Flowها راحت باشی

و در اخر :

قانون طلایی

اگه شک داری → State
اگه بعداً نیاز داشتی → می‌تونی به StateFlow مهاجرت کنی

Compose برای سادگی ساخته شده؛ بی‌دلیل پیچیده‌اش نکن 😎

دیدگاه‌ها ۰
ارسال دیدگاه جدید