ViewModel के लिए Saved State मॉड्यूल   यह Android Jetpack का हिस्सा है.

यूज़र इंटरफ़ेस (यूआई) की स्थितियों को सेव करने के बारे में बताए गए तरीके के मुताबिक, ViewModel ऑब्जेक्ट, कॉन्फ़िगरेशन में होने वाले बदलावों को मैनेज कर सकते हैं. इसलिए, आपको रोटेशन या अन्य मामलों में स्थिति के बारे में चिंता करने की ज़रूरत नहीं है. हालांकि, अगर आपको सिस्टम की ओर से शुरू की गई प्रोसेस बंद होने की समस्या को मैनेज करना है, तो बैकअप के तौर पर SavedStateHandle एपीआई का इस्तेमाल किया जा सकता है.

यूज़र इंटरफ़ेस (यूआई) की स्थिति को आम तौर पर ViewModel ऑब्जेक्ट में सेव किया जाता है या रेफ़रंस के तौर पर इस्तेमाल किया जाता है. इसलिए, Compose में rememberSaveable का इस्तेमाल करने के लिए, कुछ बॉयलरप्लेट की ज़रूरत होती है. इसे सेव की गई स्थिति वाला मॉड्यूल मैनेज कर सकता है.

इस मॉड्यूल का इस्तेमाल करने पर, ViewModel ऑब्जेक्ट को उसके कंस्ट्रक्टर के ज़रिए SavedStateHandle ऑब्जेक्ट मिलता है. यह ऑब्जेक्ट, की-वैल्यू मैप होता है. इसकी मदद से, सेव की गई स्थिति में ऑब्जेक्ट लिखे जा सकते हैं और वहां से ऑब्जेक्ट वापस पाए जा सकते हैं. सिस्टम की ओर से प्रोसेस को बंद किए जाने के बाद भी, ये वैल्यू बनी रहती हैं. साथ ही, ये उसी ऑब्जेक्ट के ज़रिए उपलब्ध रहती हैं.

सेव की गई स्थिति, आपके टास्क स्टैक से जुड़ी होती है. अगर आपका टास्क स्टैक हट जाता है, तो सेव की गई स्थिति भी हट जाती है. ऐसा तब हो सकता है, जब किसी ऐप्लिकेशन को ज़बरदस्ती बंद किया जाए, ऐप्लिकेशन को हाल ही के मेन्यू से हटाया जाए या डिवाइस को रीबूट किया जाए. ऐसे मामलों में, टास्क स्टैक गायब हो जाता है और सेव की गई स्थिति में जानकारी को वापस नहीं लाया जा सकता. उपयोगकर्ता की ओर से यूज़र इंटरफ़ेस (यूआई) की स्थिति को खारिज करने की स्थितियों में, सेव की गई स्थिति को पहले जैसा नहीं किया जाता. सिस्टम से शुरू किए गए मामलों में, ऐसा होता है.

सेटअप

SavedStateHandle का इस्तेमाल करने के लिए, इसे अपने ViewModel के लिए कंस्ट्रक्टर आर्ग्युमेंट के तौर पर स्वीकार करें.

class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }

इसके बाद, बिना किसी अतिरिक्त कॉन्फ़िगरेशन के, अपने कंपोज़ेबल में ViewModel का इंस्टेंस वापस पाया जा सकता है. डिफ़ॉल्ट ViewModel फ़ैक्ट्री, आपके ViewModel को सही SavedStateHandle उपलब्ध कराती है.

class MyViewModel : ViewModel() { /*...*/ }

// import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyScreen(
    viewModel: MyViewModel = viewModel()
) {
    // use viewModel here
}

कस्टम ViewModelProvider.Factory इंस्टेंस उपलब्ध कराते समय, CreationExtras और viewModelFactory डीएसएल का इस्तेमाल करके, SavedStateHandle के इस्तेमाल को चालू किया जा सकता है.

SavedStateHandle का इस्तेमाल करना

SavedStateHandle क्लास, कुंजी-वैल्यू का मैप है. इसकी मदद से, set() और get() तरीकों का इस्तेमाल करके, सेव की गई स्थिति में डेटा लिखा और वापस पाया जा सकता है.

SavedStateHandle का इस्तेमाल करने पर, ऐप्लिकेशन की प्रोसेस बंद होने के बाद भी क्वेरी की वैल्यू सेव रहती है. इससे यह पक्का होता है कि उपयोगकर्ता को फिर से बनाए जाने से पहले और बाद में, फ़िल्टर किए गए डेटा का एक जैसा सेट दिखे. इसके लिए, गतिविधि या फ़्रैगमेंट को मैन्युअल तरीके से सेव करने, वापस लाने, और उस वैल्यू को ViewModel पर वापस भेजने की ज़रूरत नहीं होती.

SavedStateHandle में ऐसे अन्य तरीके भी हैं जिनकी उम्मीद आपको कुंजी-मान मैप के साथ इंटरैक्ट करते समय हो सकती है:

  • contains(String key) - यह जांच करता है कि दी गई कुंजी के लिए कोई वैल्यू है या नहीं.
  • remove(String key) - दी गई कुंजी की वैल्यू हटाता है.
  • keys() - इससे SavedStateHandle में मौजूद सभी कुंजियां दिखती हैं.

इसके अलावा, मॉनिटर किए जा सकने वाले डेटा होल्डर का इस्तेमाल करके, SavedStateHandle से वैल्यू हासिल की जा सकती हैं. इनमें ये टाइप शामिल हैं:

StateFlow

StateFlow ऑब्ज़र्वेबल में रैप किए गए SavedStateHandle से वैल्यू वापस पाई जा सकती हैं. आपको वैल्यू में सीधे तौर पर बदलाव करना है या नहीं, इस आधार पर रीड-ओनली या म्यूटेबल स्ट्रीम में से कोई एक विकल्प चुना जा सकता है:

  • getStateFlow(): अगर आपको सिर्फ़ स्थिति देखनी है, तो इसका इस्तेमाल करें. SavedStateHandle में किसी दूसरी जगह पर कुंजी की वैल्यू अपडेट करने पर, StateFlow को नई वैल्यू मिलती है. यह तब काम आता है, जब आपको सिर्फ़ पढ़ने के लिए उपलब्ध स्ट्रीम को एक्सपोज़ करना हो और Flow ऑपरेटर का इस्तेमाल करके उसे बदलना हो.
  • getMutableStateFlow(): अगर आपको पढ़ने और लिखने, दोनों का ऐक्सेस चाहिए, तो इसका इस्तेमाल करें. वापस किए गए MutableStateFlow के .value को अपडेट करने से, उसमें मौजूद SavedStateHandle अपने-आप अपडेट हो जाता है. इससे आपको मैन्युअल तरीके से कुंजी सेट करने की ज़रूरत नहीं पड़ती.

ज़्यादातर मामलों में, इन वैल्यू को उपयोगकर्ता के इंटरैक्शन की वजह से अपडेट किया जाता है. जैसे, डेटा की सूची को फ़िल्टर करने के लिए क्वेरी डालना.

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

    // Use getMutableStateFlow to read and write the query directly
    private val _query = savedStateHandle.getMutableStateFlow("query", "")
    val query: StateFlow = _query.asStateFlow()

    // Use getStateFlow if you only need a read-only stream to react to changes
    val filteredData: StateFlow<List> =
        query.flatMapLatest {
            repository.getFilteredData(it)
        }
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = emptyList()
        )

    fun setQuery(newQuery: String) {
        // Updating the MutableStateFlow automatically updates the SavedStateHandle
        _query.value = newQuery
    }
}

KotlinX Serialization के साथ काम करना

यूज़र इंटरफ़ेस (यूआई) की जटिल स्थिति के लिए, KotlinX Serialization के साथ saved प्रॉपर्टी डेलिगेट का इस्तेमाल किया जा सकता है. इस डेलिगेट की मदद से, कस्टम @Serializable डेटा क्लास को सीधे SavedStateHandle में सेव किया जा सकता है. इससे, ऐप्लिकेशन की प्रोसेस बंद होने पर भी ViewModel की स्थिति सेव रहती है. इसलिए, Compose यूज़र इंटरफ़ेस (यूआई) को फिर से बनाने पर, उसकी स्थिति को आसानी से वापस लाया जा सकता है.

इसका इस्तेमाल करने के लिए, अपनी डेटा क्लास को @Serializable के साथ एनोटेट करें और अपने ViewModel में saved डेलिगेट का इस्तेमाल करें:

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
// Ensure you have the savedstate-ktx dependency
import androidx.savedstate.serialization.saved
import kotlinx.serialization.Serializable

@Serializable
data class UserFilterState(
    val searchQuery: String,
    val minAge: Int,
    val includeInactive: Boolean
)

class FilterViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {

    // The state is automatically serialized to a Bundle on process death,
    // and deserialized upon recreation.
    var filterState by savedStateHandle.saved {
        UserFilterState(searchQuery = "", minAge = 18, includeInactive = false)
    }

    fun updateQuery(newQuery: String) {
        // Mutating the property automatically updates the underlying SavedStateHandle
        filterState = filterState.copy(searchQuery = newQuery)
    }
}

Compose में स्टेट को मैनेज करने की सुविधा

अगर आपका स्टेट, KotlinX सीरियलाइज़ेशन के बजाय Compose के Saver एपीआई पर निर्भर करता है, तो lifecycle-viewmodel-compose आर्टफ़ैक्ट, saveable डेलिगेट उपलब्ध कराता है. इससे SavedStateHandle और Compose के Saver के बीच इंटरऑपरेबिलिटी की सुविधा मिलती है. इससे, कस्टम Saver के साथ rememberSaveable की मदद से सेव किए जा सकने वाले किसी भी State को SavedStateHandle के साथ भी सेव किया जा सकता है.

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

    var filteredData: List<String> by savedStateHandle.saveable {
        mutableStateOf(emptyList())
    }

    fun setQuery(query: String) {
        withMutableSnapshot {
            filteredData += query
        }
    }
}

इस्तेमाल किए जा सकने वाले ज़्यादा बेहतर नतीजे (रिच रिज़ल्ट)

SavedStateHandle में मौजूद डेटा को Bundle के तौर पर सेव और वापस लाया जाता है. साथ ही, आपके ऐप्लिकेशन के लिए बाकी savedInstanceState को भी सेव और वापस लाया जाता है.

सीधे तौर पर इस्तेमाल किए जा सकने वाले टाइप

डिफ़ॉल्ट रूप से, SavedStateHandle पर set() और get() को कॉल किया जा सकता है. इसके लिए, Bundle के जैसे ही डेटा टाइप का इस्तेमाल किया जा सकता है. इसके बारे में यहां बताया गया है:

टाइप/क्लास के लिए सहायता अरे के लिए सहायता
double double[]
int int[]
long long[]
String String[]
byte byte[]
char char[]
CharSequence CharSequence[]
float float[]
Parcelable Parcelable[]
Serializable Serializable[]
short short[]
SparseArray
Binder
Bundle
ArrayList
Size (only in API 21+)
SizeF (only in API 21+)

अगर क्लास, ऊपर दी गई सूची में से किसी भी क्लास को एक्सटेंड नहीं करती है, तो @Parcelize Kotlin एनोटेशन जोड़कर या सीधे Parcelable को लागू करके, क्लास को पार्सल करने लायक बनाएं.

नॉन-पार्सलेबल क्लास सेव करना

अगर कोई क्लास Parcelable या Serializable को लागू नहीं करती है और इनमें से किसी इंटरफ़ेस को लागू करने के लिए उसमें बदलाव नहीं किया जा सकता, तो उस क्लास के इंस्टेंस को सीधे SavedStateHandle में सेव नहीं किया जा सकता.

Lifecycle 2.3.0-alpha03 से, SavedStateHandle आपको किसी भी ऑब्जेक्ट को सेव करने की सुविधा देता है. इसके लिए, आपको setSavedStateProvider() तरीके का इस्तेमाल करके, अपने ऑब्जेक्ट को Bundle के तौर पर सेव और वापस लाने के लिए अपना लॉजिक देना होगा. SavedStateRegistry.SavedStateProvider एक ऐसा इंटरफ़ेस है जो एक saveState() तरीके को तय करता है. यह तरीका, सेव किए जाने वाले स्टेटस के साथ Bundle दिखाता है. जब SavedStateHandle अपनी स्थिति सेव करने के लिए तैयार होता है, तो यह SavedStateProvider से Bundle को वापस पाने के लिए saveState() को कॉल करता है. साथ ही, असोसिएट की गई कुंजी के लिए Bundle को सेव करता है.

मान लें कि कोई ऐप्लिकेशन, ACTION_IMAGE_CAPTURE इंटेंट का इस्तेमाल करके, कैमरा ऐप्लिकेशन से किसी इमेज का अनुरोध करता है. इसके लिए, वह एक अस्थायी फ़ाइल पास करता है, जिसमें कैमरा इमेज को सेव कर सके. TempFileViewModel में, उस अस्थायी फ़ाइल को बनाने का लॉजिक शामिल होता है.

class TempFileViewModel : ViewModel() {
    private var tempFile: File? = null

    fun createOrGetTempFile(): File {
        return tempFile ?: File.createTempFile("temp", null).also {
            tempFile = it
        }
    }
}

अगर गतिविधि की प्रोसेस बंद हो जाती है और बाद में उसे वापस लाया जाता है, तो यह पक्का करने के लिए कि अस्थायी फ़ाइल न खो जाए, TempFileViewModel अपने डेटा को बनाए रखने के लिए SavedStateHandle का इस्तेमाल कर सकता है. TempFileViewModel को अपना डेटा सेव करने की अनुमति देने के लिए, SavedStateProvider को लागू करें और इसे ViewModel के SavedStateHandle पर सेवा देने वाली कंपनी के तौर पर सेट करें:

private fun File.saveTempFile() = bundleOf("path", absolutePath)

class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
    private var tempFile: File? = null
    init {
        savedStateHandle.setSavedStateProvider("temp_file") { // saveState()
            if (tempFile != null) {
                tempFile.saveTempFile()
            } else {
                Bundle()
            }
        }
    }

    fun createOrGetTempFile(): File {
        return tempFile ?: File.createTempFile("temp", null).also {
            tempFile = it
        }
    }
}

उपयोगकर्ता के वापस आने पर File का डेटा वापस लाने के लिए, SavedStateHandle से temp_file Bundle को वापस पाएं. यह वही Bundle है जो saveTempFile() ने दिया है. इसमें ऐब्सलूट पाथ शामिल है. इसके बाद, ऐब्सलूट पाथ का इस्तेमाल करके, नया File इंस्टैंशिएट किया जा सकता है.

private fun File.saveTempFile() = bundleOf("path", absolutePath)

private fun Bundle.restoreTempFile() = if (containsKey("path")) {
    File(getString("path"))
} else {
    null
}

class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
    private var tempFile: File? = null
    init {
        val tempFileBundle = savedStateHandle.get<Bundle>("temp_file")
        if (tempFileBundle != null) {
            tempFile = tempFileBundle.restoreTempFile()
        }
        savedStateHandle.setSavedStateProvider("temp_file") { // saveState()
            if (tempFile != null) {
                tempFile.saveTempFile()
            } else {
                Bundle()
            }
        }
    }

    fun createOrGetTempFile(): File {
      return tempFile ?: File.createTempFile("temp", null).also {
          tempFile = it
      }
    }
}

टेस्ट में SavedStateHandle

किसी ऐसे ViewModel को टेस्ट करने के लिए जो डिपेंडेंसी के तौर पर SavedStateHandle का इस्तेमाल करता है, SavedStateHandle का एक नया इंस्टेंस बनाएं. इसमें टेस्ट के लिए ज़रूरी वैल्यू डालें और इसे उस ViewModel इंस्टेंस को पास करें जिसे टेस्ट किया जा रहा है.

class MyViewModelTest {

    private lateinit var viewModel: MyViewModel

    @Before
    fun setup() {
        val savedState = SavedStateHandle(mapOf("someIdArg" to testId))
        viewModel = MyViewModel(savedState = savedState)
    }
}

अन्य संसाधन

ViewModel के लिए, सेव की गई स्थिति वाले मॉड्यूल के बारे में ज़्यादा जानने के लिए, यहां दिए गए लेख पढ़ें.

कोडलैब

कॉन्टेंट देखना