State<T> وMutableState<T> في Jetpack Compose هما حاويتان للقيم ذات الأغراض العامة، وتعتمدان على نظام اللقطات في Compose. على الرغم من أنّها تُستخدم عادةً لإعادة تركيب واجهة المستخدم، إلا أنّ نظام اللقطات نفسه غير مرتبط بعرض واجهة المستخدم.
باستخدام مكتبات AppState، وهي Transform وStateStore، يمكنك الاحتفاظ بحالة State في Compose واستخلاصها ومراقبتها ومواصلة استخدامها في ViewModels والمدراء والمستودعات والخدمات التي تُشغَّل في الخلفية.
لماذا يجب استخدام Compose State خارج واجهة المستخدم؟
عند قراءة Compose State داخل كتلة مراقبة الحالة، مثل واجهة مستخدم
قابلة للإنشاء أو transform أو
derivedStateOf، يوفّر وقت تشغيل Compose
ميزتَين رئيسيتَين في أي طبقة من البنية:
- التتبُّع التلقائي والديناميكي للتبعية: تؤدي قراءة
.valueالحالة (أو استخدام تفويض السمةby) إلى تسجيل هذه الحالة تلقائيًا على أنّها تبعية للكتلة. بما أنّ التتبُّع ديناميكي، يتم تحديد التبعيات في وقت التشغيل من خلال المسارات البرمجية التي يتم تنفيذها. إذا لم يتم استخدام فرع شرطي (على سبيل المثال، بعد التحقّق منifأوreturnالمبكر)، لا تتم قراءة الحالات داخل هذا الفرع ولا تؤدي التغييرات التي تطرأ عليها إلى إعادة إجراء عمليات حسابية غير ضرورية. - الاتساق والدقة: يمكنك تحديد سمات
MutableStateمنفصلة ودقيقة، وتجميع عمليات الكتابة المتعددة السمات فيSnapshot.withMutableSnapshot. يتم عزل التعديلات المؤقتة في اللقطة القابلة للتغيير ويتم تطبيقها بشكل ذري عند اكتمال الكتلة، وبالتالي لا يرى المراقبون الذين يقرأون داخل اللقطة حالة مؤقتة غير متسقة ويعيدون الحساب فقط عندما تتغير الخصائص المحددة التي يقرأونها. على عكسStateFlow، ليس عليك الاختيار بينStateFlow<UiState>واحد متكامل (يبطل كل أداة تجميع عند تغيير أي حقل) وخصائصStateFlowمنفصلة (لا يمكن تحديثها معًا في معاملة واحدة).
اختيار واجهة برمجة التطبيقات المناسبة لإدارة الحالة في بنية تطبيقك
يمكنك اختيار كيفية الاحتفاظ ببيانات Compose State ومواصلة استخدامها استنادًا إلى المدة التي يجب أن تظل فيها الحالة نشطة والمكوّنات التي تحتاج إلى الوصول إليها:
| فئة الولاية | الفترة منذ الإنشاء والنطاق | أمثلة | واجهات برمجة التطبيقات المقترَحة في Compose State |
|---|---|---|---|
| الحالة المباشرة | يتم تخزينها دائمًا في الذاكرة، ويتم الاستعلام عنها من جديد عند بدء التشغيل ولا يتم الاحتفاظ بها بعد إيقاف العملية نهائيًا. | حالة الاتصال بالشبكة وحالة اتصال البلوتوث وحالة واجهة المستخدم المؤقتة، مثل اختيار العناصر |
|
| حالة واجهة المستخدم المؤقتة | يحتفظ بالبيانات عند تغيير الإعدادات أو إيقاف العملية نهائيًا من قِبل النظام، ولا يتم تخزينها إلى أجل غير مسمى، ويقتصر نطاقها على شاشة أو نشاط واحد. | عناصر في واجهة المستخدم الموسَّعة أو المصغَّرة، أو موضع الانتقال، أو طلب البحث على الشاشة، أو مفاتيح تبديل الفلاتر |
|
| حالة الجلسة المؤقتة | يستمر بعد إيقاف العملية في الخلفية نهائيًا، ولا يتم تخزينه إلى أجل غير مسمى، وتتم مشاركته بين الأنشطة أو الخدمات أو التطبيقات المصغّرة. | مؤقّت تركيز نشط أو سلة تسوّق تتطلّب خطوات متعددة لإتمام الدفع أو مسودة نموذج قيد التقدم |
|
| الحالة الدائمة | يتم الاحتفاظ بها على القرص إلى أجل غير مسمى. | إعدادات المستخدم المفضّلة (DataStore) وبيانات التطبيق المنظَّمة ومكتبات الوسائط (Room). | collectAsState يجمع تدفقات DataStore أو Room داخل transform |
استخدام حالة 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البسيط الذي يعرض سمة Kotlin عادية بدلاً منStateفي Compose ولا يؤدي إلى إعادة الإنشاء عند تغييره. - عمليات الكتابة الذرية المتعددة الخصائص باستخدام
Snapshot.withMutableSnapshot: يؤدي تعديلsearchQueryوselectedTaskIdsداخلSnapshot.withMutableSnapshotإلى عزل عمليات الكتابة وتنفيذ كلا التغييرين في معاملة لقطة ذرية واحدة، لذلك يرى المراقبون (بما في ذلكtransform) كلا التعديلين معًا في مسار إعادة تركيب واحد بدلاً من ملاحظةsearchQueryالجديد المقترن بـselectedTaskIdsالقديم. - إعادة استخدام التدفقات في عمليات إعادة الإنشاء باستخدام
remember(key): يضمن التفافtasksRepository.getTasks(searchQuery)فيrememberمعsearchQueryكمفتاح أنّه عندما يتغيّرselectedTaskIdsويعيد إنشاء كتلةtransform، يعيد Compose استخدام قاعدة البيانات الحاليةFlowبدلاً من إعادة تشغيل طلب البحث.
مشاركة حالة النظام المباشرة في تطبيقك
بالنسبة إلى حالة النظام المباشرة التي تتم مشاركتها عبر عدة ViewModels أو أنشطة أو
خدمات، مثل حالة اتصال الشبكة أو البلوتوث، لا يُنصح
بالاحتفاظ بالحالة القديمة على القرص في حال إيقاف العملية نهائيًا. بدلاً من ذلك، يمكنك الاستعلام عن حالة النظام الحالية عند بدء التشغيل، والاحتفاظ بها في سمة 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، سواء كعملية تحقّق لمرة واحدة أو داخل دالة مركّبة لواجهة المستخدم أو داخل وحدة تتبُّع الحالة، مثل 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 تلقائيًا بدون
إعادة طلب البحث في قاعدة البيانات.