استفاده از Compose State خارج از واسط کاربر

‫Jetpack Compose State<T> و MutableState<T> نگهدارنده‌های مقدار با کاربرد عمومی هستند که توسط سیستم عکس آنی Compose پشتیبانی می‌شوند. اگرچه این حالت‌ها بیشتر برای هدایت بازترکیب رابط کاربری استفاده می‌شوند، سیستم عکس آنی به‌خودی‌خود به پردازش رابط کاربری وابسته نیست.

با کتابخانه‌های AppState—Transform و StateStore—می‌توانید Compose State را در ViewModel، مدیران، مخزن‌ها، و سرویس‌های پس‌زمینه نگه دارید، مشتق کنید، مشاهده کنید، و حفظ کنید.

Why use Compose State beyond the UI?

وقتی Compose State را در یک بلوک مشاهده‌کننده وضعیت می‌خوانید—مانند یک UI composable، transform، یا derivedStateOf—زمان اجرای Compose دو مزیت کلیدی در هر لایه از معماری شما ارائه می‌دهد:

  • ردگیری وابستگی خودکار و پویا: خواندن .value یک حالت (یا استفاده از واگذاری ویژگی by) به‌طور خودکار آن حالت را به‌عنوان وابستگی بلوک ثبت می‌کند. چون ردیابی پویا است، وابستگی‌ها در زمان اجرا براساس اینکه کدام مسیرهای کد اجرا می‌شوند تعیین می‌شوند. اگر شاخه شرطی اجرا نشود (برای مثال، پس‌از بررسی if یا return زودهنگام)، حالت‌های داخل آن شاخه خوانده نمی‌شوند و تغییرات در آن‌ها باعث بازمحاسبه‌های غیرضروری نمی‌شود.
  • ثبات و دانه‌بندی: می‌توانید ویژگی‌های MutableState جداگانه و با دانه‌بندی ریز را اعلام کنید و نوشتن‌های چندویژگی را در Snapshot.withMutableSnapshot قرار دهید. به‌روزرسانی‌های میانی به عکس آنی تغییرپذیر محدود می‌شوند و وقتی بلوک تکمیل می‌شود، به‌صورت اتمی اعمال می‌شوند، بنابراین ناظرانی که داخل عکس آنی را می‌خوانند هرگز وضعیت میانی ناسازگار را نمی‌بینند و فقط زمانی دوباره محاسبه می‌کنند که ویژگی‌های خاصی که می‌خوانند تغییر کند. برخلاف StateFlow، لازم نیست بین یک StateFlow<UiState> یکپارچه (که با تغییر هر فیلد، همه جمع‌آورنده‌ها را نامعتبر می‌کند) و ویژگی‌های جداگانه StateFlow (که نمی‌توانند باهم در یک تراکنش اتمی به‌روز شوند) یکی را انتخاب کنید.

انتخاب میانای برنامه‌سازی کاربردی وضعیت مناسب در سراسر معماری

انتخاب کنید که چگونه Compose State را براساس مدت‌زمان ماندگاری یک قطعه از وضعیت و اینکه کدام مؤلفه‌ها باید به آن دسترسی داشته باشند، نگه دارید و حفظ کنید:

دسته‌بندی ایالت طول عمر و دامنه مثال‌ها میاناهای برنامه‌سازی کاربردی توصیه‌شده Compose State
وضعیت زنده همیشه در حافظه ذخیره می‌شود؛ در زمان راه‌اندازی به‌صورت تازه پُرسمان می‌شود و درطول مرگ پردازش باقی نمی‌ماند. وضعیت اتصال شبکه، وضعیت اتصال بلوتوث، وضعیت موقت واسط کاربر مانند انتخاب مورد.
وضعیت میانای کاربر گذرا دربرابر تغییرات پیکربندی و مرگ پردازش آغازشده توسط سیستم دوام می‌آورد، به‌طور نامحدود ذخیره نمی‌شود؛ محدود به یک صفحه یا فعالیت است. عناصر میانای کاربر گسترده یا جمع‌شده، موقعیت پیمایش، پرسمان جستجوی صفحه، یا کلیدهای فیلتر.
وضعیت جلسه گذرا پس‌از مرگ فرایند پس‌زمینه‌ای باقی می‌ماند، به‌طور نامحدود ذخیره نمی‌شود؛ در فعالیت‌ها، سرویس‌ها، یا ابزاره‌ها هم‌رسانی می‌شود. زمان‌سنج تمرکز فعال، سبد خرید چندمرحله‌ای، یا پیش‌نویس فرم درحال تکمیل.
  • StateStore در مدیر یا مخزن کپسوله‌سازی شده است
  • کلیدهایی که PersistToDataStore را پیاده‌سازی می‌کنند و بااستفاده از syncToDataStore همگام‌سازی شده‌اند
وضعیت دائمی به‌طور نامحدود در دیسک نگهداری می‌شود. اولویت‌ها و تنظیمات کاربر (DataStore)، داده‌های ساختاریافته برنامه و کتابخانه‌های رسانه (Room). جریان‌های DataStore یا Room جمع‌آوری‌شده در transform بااستفاده از collectAsState

استفاده از «وضعیت Compose» در ViewModel

درون ViewModel، می‌توانید ورودی‌های مستقل صفحه‌نمایش را به‌عنوان ویژگی‌های دانه‌ای MutableState اعلام کنید، آن‌ها را در سراسر مرگ پردازش با SavedStateHandle.saveable حفظ کنید، چندین ویژگی را به‌صورت اتمی با Snapshot.withMutableSnapshot به‌روز کنید، و آن‌ها را با جریان‌های مخزن Flow درون transform ترکیب کنید:

sealed interface TaskListState {
    data object Loading : TaskListState
    data class Success(
        val tasks: List<Task>,
        val selectedTaskIds: Set<String>,
        val isOnline: Boolean = true,
    ) : TaskListState
}

@HiltViewModel
class TaskListViewModel @Inject constructor(
    private val tasksRepository: TasksRepository,
    savedStateHandle: SavedStateHandle,
) : ViewModel() {

    var selectedTaskIds by mutableStateOf(emptySet<String>())
        private set

    var searchQuery by savedStateHandle.saveable { mutableStateOf("") }
        private set

    fun updateSearchQuery(query: String) {
        Snapshot.withMutableSnapshot {
            searchQuery = query
            selectedTaskIds = emptySet()
        }
    }

    val tasksState: State<TaskListState> = transform<TaskListState>(
        initialValue = TaskListState.Loading,
        scope = viewModelScope,
    ) {
        val tasks = remember(searchQuery) {
            tasksRepository.getTasks(searchQuery)
        }.collectAsState(initial = null).value
            ?: return@transform TaskListState.Loading

        TaskListState.Success(
            tasks = tasks,
            selectedTaskIds = selectedTaskIds,
        )
    }
}

الگوهای کلیدی در این ViewModel:

  • استفاده از SavedStateHandle.saveable برای حالت ذخیره‌شده واکنشی: واگذاری searchQuery به SavedStateHandle.saveable با mutableStateOf (از androidx.lifecycle:lifecycle-viewmodel-compose) مقدار را در سراسر مرگ پردازش به‌عنوان MutableState حفظ می‌کند، بنابراین transform خواندن‌ها را به‌طور خودکار ردیابی می‌کند. از SavedStateHandle.saved خالی که به‌جای Compose State یک ویژگی Kotlin ساده برمی‌گرداند و درصورت تغییر، ترکیب‌بندی مجدد را راه‌اندازی نمی‌کند، اجتناب کنید.
  • نوشتن‌های اتمی چندویژگی با Snapshot.withMutableSnapshot: به‌روزرسانی searchQuery و selectedTaskIds در Snapshot.withMutableSnapshot نوشتن‌ها را جدا می‌کند و هر دو تغییر را در یک تراکنش عکس آنی اتمی واحد ثبت می‌کند، بنابراین ناظران (ازجمله transform) هر دو به‌روزرسانی را باهم در یک گذر بازترکیب می‌بینند، به‌جای اینکه searchQuery جدید را جفت‌شده با selectedTaskIds قدیمی مشاهده کنند.
  • استفاده مجدد از جریان‌ها در ترکیب‌بندی‌های مجدد با remember(key): پیچیدن tasksRepository.getTasks(searchQuery) در remember با searchQuery به‌عنوان کلید تضمین می‌کند که وقتی selectedTaskIds تغییر می‌کند و بلوک transform را مجدداً ترکیب‌بندی می‌کند، Compose از پایگاه داده موجود Flow استفاده مجدد می‌کند به‌جای اینکه پُرسمان را بازراه‌اندازی کند.

هم‌رسانی وضعیت سیستم زنده در سراسر برنامه

برای وضعیت سیستم زنده که در چندین ViewModel، فعالیت، یا سرویس هم‌رسانی می‌شود (مانند وضعیت اتصال شبکه یا بلوتوث)، نمی‌خواهید وضعیت قدیمی را درصورت مرگ پردازش در دیسک حفظ کنید. درعوض، وضعیت فعلی سیستم را در زمان راه‌اندازی پرسمان کنید، آن را در یک ویژگی خصوصی mutableStateOf در داخل یک سینگلتون با محدوده برنامه نگه دارید، و یک State<T> فقط خواندنی را نمایش دهید:

interface NetworkMonitor {
    val isOnline: State<Boolean>
}

@Singleton
class LiveNetworkMonitor @Inject constructor(
    @ApplicationContext context: Context,
) : NetworkMonitor {
    private val connectivityManager =
        context.getSystemService(ConnectivityManager::class.java)

    private val _isOnline = mutableStateOf(checkCurrentConnectivity())
    override val isOnline: State<Boolean> = _isOnline

    private val callback = object : ConnectivityManager.NetworkCallback() {
        override fun onCapabilitiesChanged(
            network: Network,
            networkCapabilities: NetworkCapabilities,
        ) {
            _isOnline.value = networkCapabilities.hasCapability(
                NetworkCapabilities.NET_CAPABILITY_INTERNET
            )
        }

        override fun onLost(network: Network) {
            _isOnline.value = false
        }
    }

    init {
        connectivityManager.registerDefaultNetworkCallback(callback)
    }

    private fun checkCurrentConnectivity(): Boolean {
        val network = connectivityManager.activeNetwork ?: return false
        val capabilities =
            connectivityManager.getNetworkCapabilities(network) ?: return false
        return capabilities.hasCapability(
            NetworkCapabilities.NET_CAPABILITY_INTERNET
        )
    }
}

MutableState از خواندن و به‌روزرسانی‌های سازگار از هر رشته‌ای پشتیبانی می‌کند، بنابراین NetworkCallback می‌تواند _isOnline.value را مستقیماً از رشته فراخوانی پس‌زمینه‌اش به‌روز کند. اگر یک فراخوانی پس‌زمینه چندین ویژگی MutableState را باهم به‌روز می‌کند، نوشتن‌ها را در Snapshot.withMutableSnapshot قرار دهید تا به‌صورت اتمی اعمال شوند.

هر مؤلفه‌ای در برنامه‌تان می‌تواند networkMonitor.isOnline.value را بخواند—چه به‌عنوان بررسی یک‌باره، چه در UI composable، یا در بلوک مشاهده وضعیت مانند listener، derivedStateOf، یا transform. برای مثال، TaskListViewModel می‌تواند networkMonitor.isOnline.value را در بلوک transform خود بخواند:

val tasksState: State<TaskListState> = transform<TaskListState>(
    initialValue = TaskListState.Loading,
    scope = viewModelScope,
) {
    val tasks = remember(searchQuery) {
        tasksRepository.getTasks(searchQuery)
    }.collectAsState(initial = null).value
        ?: return@transform TaskListState.Loading

    TaskListState.Success(
        tasks = tasks,
        selectedTaskIds = selectedTaskIds,
        isOnline = networkMonitor.isOnline.value,
    )
}

چون transform networkMonitor.isOnline.value را درکنار searchQuery، selectedTaskIds، و مخزن Flow ردیابی می‌کند، هرگونه تغییر در اتصال‌پذیری به‌طور خودکار TaskListState.Success را بدون پرس‌وجوی مجدد از پایگاه داده دوباره محاسبه می‌کند.