UI-এর বাইরে Compose State ব্যবহার করা

Jetpack Compose State<T> এবং MutableState<T> হল সাধারণ-উদ্দেশ্যের ভ্যালু হোল্ডার যা Compose স্ন্যাপশট সিস্টেমের মাধ্যমে ব্যাক-আপ করা হয়। যদিও UI রিকম্পোজিশন ড্রাইভ করার জন্য এগুলি সবচেয়ে বেশি ব্যবহার করা হয়, স্ন্যাপশট সিস্টেম নিজে UI রেন্ডারিংয়ের সাথে যুক্ত নয়।

AppState লাইব্রেরি—Transform এবং StateStore—এর সাহায্যে আপনি ViewModel, ম্যানেজার, রিপোজিটরি এবং ব্যাকগ্রাউন্ড পরিষেবা জুড়ে Compose State হোল্ড, ডিরাইভ, অবজার্ভ এবং পারসিস্ট করতে পারবেন।

UI-এর বাইরে Compose State কেন ব্যবহার করবেন?

আপনি যখন স্টেট-অবজার্ভিং ব্লকের মধ্যে Compose State পড়েন—যেমন UI কম্পোজেবল, transform, অথবা derivedStateOf—Compose রানটাইম আপনার আর্কিটেকচারের যেকোনও লেয়ার জুড়ে দুটি মূল সুবিধা প্রদান করে:

  • অটোমেটিক ও ডায়নামিক ডিপেন্ডেন্সি ট্র্যাকিং: কোনও স্টেট .value (বা by প্রপার্টি ডেলিগেশন ব্যবহার করে) রিড করলে, অটোমেটিক সেই স্টেটকে ব্লকের ডিপেন্ডেন্সি হিসেবে রেজিস্টার করে। ট্র্যাকিং ডায়নামিক হওয়ার কারণে, কোন কোড পাথ এক্সিকিউট হবে তার উপর নির্ভর করে রানটাইমে ডিপেন্ডেন্সি নির্ধারণ করা হয়। কোনও কন্ডিশনাল ব্রাঞ্চ নেওয়া না হলে (যেমন, if চেক বা আগাম return-এর পরে), সেই ব্রাঞ্চের ভেতরে থাকা স্টেটগুলি পড়া হয় না এবং সেগুলিতে করা পরিবর্তন অপ্রয়োজনীয় রিকম্পিউটেশন ট্রিগার করে না।
  • ধারাবাহিকতা ও গ্র্যানুলারিটি: আপনি আলাদা, ফাইন-গ্রেনড MutableState প্রপার্টি ঘোষণা করতে এবং Snapshot.withMutableSnapshot-এ মাল্টি-প্রপার্টি রাইট র‍্যাপ করতে পারবেন। ইন্টারমিডিয়েট আপডেট মিউটেবল স্ন্যাপশট থেকে আলাদা করা হয় এবং ব্লক সম্পূর্ণ হলে অ্যাটমিক্যালি প্রয়োগ করা হয়, তাই স্ন্যাপশটের মধ্যে রিডিং নেওয়া অবজারভার কখনও অসঙ্গত ইন্টারমিডিয়েট স্টেট দেখতে পায় না এবং তাদের রিড করা নির্দিষ্ট প্রপার্টি পরিবর্তন হলে তবেই আবার গণনা করে। StateFlow-এর মতো, আপনাকে একটি একক মনোলিথিক StateFlow<UiState> (যা কোনও ফিল্ড পরিবর্তন হলে প্রতিটি কালেক্টরকে বাতিল করে দেয়) এবং আলাদা StateFlow প্রপার্টি (যা একটি একক অ্যাটমিক ট্রানজ্যাকশনে একসাথে আপডেট করা যায় না) এর মধ্যে বেছে নিতে হবে না।

আপনার আর্কিটেকচার জুড়ে সঠিক স্টেট API বেছে নিন

কোনও স্টেট কতক্ষণ থাকবে এবং কোন কম্পোনেন্টকে এটি অ্যাক্সেস করতে হবে তার উপর ভিত্তি করে Compose State কীভাবে হোল্ড ও পারসিস্ট করতে হবে তা বেছে নিন:

রাজ্যের বিভাগ লাইফটাইম ও স্কোপ উদাহরণ সাজেস্ট করা Compose State API
লাইভ স্ট্যাটাস সবসময় মেমরিতে স্টোর করা থাকে; স্টার্টআপে নতুন করে কোয়েরি করা হয় এবং প্রসেস বন্ধ করার সময় জুড়ে থাকে না। নেটওয়ার্ক কানেকশন স্ট্যাটাস, ব্লুটুথ কানেকশন স্ট্যাটাস, আইটেম বেছে নেওয়ার মতো সাময়িক UI স্ট্যাটাস।
  • ViewModel বা DI-স্কোপড @Singleton ম্যানেজারে mutableStateOf
  • স্টেট পেতে transform
  • listener রাজ্য পর্যবেক্ষণ করতে
ট্রানজিয়েন্ট UI স্টেট কনফিগারেশন পরিবর্তন এবং সিস্টেম-ইনিশিয়েটেড প্রসেস বন্ধ করার পরেও ডেটা থেকে যায়, অনির্দিষ্টকালের জন্য স্টোর করা হয় না; একটি স্ক্রিন বা অ্যাক্টিভিটিতে সীমাবদ্ধ। বড় বা ছোট করা UI এলিমেন্ট, স্ক্রল পজিশন, স্ক্রিন সার্চ কোয়েরি বা ফিল্টার টগল।
ট্রানজিয়েন্ট সেশন স্টেট ব্যাকগ্রাউন্ড প্রসেস বন্ধ হয়ে গেলেও ডেটা থেকে যায়, তবে অনির্দিষ্টকালের জন্য স্টোর করা হয় না; অ্যাক্টিভিটি, পরিষেবা বা উইজেট জুড়ে শেয়ার করা হয়। অ্যাক্টিভ ফোকাস টাইমার, মাল্টি-স্টেপ চেকআউট কার্ট বা প্রোগ্রেসে থাকা ফর্ম ড্রাফ্ট।
  • StateStore ম্যানেজার বা রিপোজিটরিতে এনক্যাপসুলেট করা আছে
  • PersistToDataStore প্রয়োগকারী এবং syncToDataStore ব্যবহার করে সিঙ্ক করা 'কী'
স্থায়ী রাজ্য ডিস্কে অনির্দিষ্টকালের জন্য রাখা হয়েছে। ব্যবহারকারীর পছন্দ ও সেটিংস (DataStore), স্ট্রাকচার্ড অ্যাপ ডেটা ও মিডিয়া লাইব্রেরি (Room)। collectAsState ব্যবহার করে transform-এর মধ্যে সংগ্রহ করা DataStore বা Room ফ্লো

ViewModel-এ Compose স্টেট ব্যবহার করা

ViewModel-এর মধ্যে, আপনি স্বাধীন স্ক্রিন ইনপুটকে গ্র্যানুলার MutableState প্রপার্টি হিসেবে ঘোষণা করতে পারেন, SavedStateHandle.saveable-এর মাধ্যমে প্রসেস বন্ধ করা জুড়ে সেগুলি বজায় রাখতে পারেন, Snapshot.withMutableSnapshot-এর মাধ্যমে একাধিক প্রপার্টি অ্যাটমিক্যালি আপডেট করতে পারেন এবং transform-এর মধ্যে রিপোজিটরি Flow স্ট্রিমের সাথে সেগুলি একত্রিত করতে পারেন:

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 ব্যবহার করুন: mutableStateOf (androidx.lifecycle:lifecycle-viewmodel-compose থেকে) সহ SavedStateHandle.saveable-এ searchQuery ডেলিগেট করা প্রসেস বন্ধ করা জুড়ে MutableState হিসেবে ভ্যালু বজায় রাখে, তাই transform অটোমেটিক রিড ট্র্যাক করে। শুধু SavedStateHandle.saved ব্যবহার করবেন না, যা Compose State-এর পরিবর্তে একটি সাধারণ Kotlin প্রপার্টি রিটার্ন করে এবং পরিবর্তন করা হলে রিকম্পোজিশন ট্রিগার করে না।
  • Snapshot.withMutableSnapshot-এর সাথে অ্যাটমিক মাল্টি-প্রপার্টি রাইট: Snapshot.withMutableSnapshot-এর মধ্যে searchQuery এবং selectedTaskIds আপডেট করা রাইটগুলিকে আলাদা করে এবং একটি অ্যাটমিক স্ন্যাপশট ট্রানজ্যাকশনে দুটি পরিবর্তনই কমিট করে, যাতে পর্যবেক্ষকরা (transform সহ) একটি রিকম্পোজিশন পাসে দুটি আপডেট একসাথে দেখতে পান, পুরনো selectedTaskIds-এর সাথে নতুন searchQuery পেয়ার করা দেখার পরিবর্তে।
  • 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 কম্পোজেবলের মধ্যে বা স্টেট-অবজার্ভিং ব্লকের মধ্যে, যেমন listener, derivedStateOf, অথবা transform। যেমন, TaskListViewModel তার transform ব্লকের মধ্যে networkMonitor.isOnline.value পড়তে পারে:

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 অটোমেটিক পুনরায় গণনা করা হয়।