ViewModel (Views) के लिए सेव की गई स्थिति का मॉड्यूल   Android Jetpack का हिस्सा.

सिद्धांत और Jetpack Compose को लागू करना

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

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

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

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

सेटअप

Fragment 1.2.0 या इसकी ट्रांज़िटिव डिपेंडेंसी Activity 1.1.0 से शुरू करके, ViewModel के कंस्ट्रक्टर आर्ग्युमेंट के तौर पर SavedStateHandle को स्वीकार किया जा सकता है.

Kotlin

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

Java

public class SavedStateViewModel extends ViewModel {
    private SavedStateHandle state;

    public SavedStateViewModel(SavedStateHandle savedStateHandle) {
        state = savedStateHandle;
    }

    ...
}

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

Kotlin

class MainFragment : Fragment() {
    val vm: SavedStateViewModel by viewModels()

    ...
}

Java

class MainFragment extends Fragment {
    private SavedStateViewModel vm;

    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        vm = new ViewModelProvider(this).get(SavedStateViewModel.class);

        ...

    }

    ...
}

कस्टम ViewModelProvider.Factory इंस्टेंस देते समय, SavedStateHandle के इस्तेमाल की सुविधा चालू की जा सकती है. इसके लिए, AbstractSavedStateViewModelFactory को बढ़ाएं.

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

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

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

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

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

इसके अलावा, ऑब्ज़र्वेबल डेटा होल्डर का इस्तेमाल करके, SavedStateHandle से वैल्यू वापस पाई जा सकती हैं. इन फ़ाइल टाइप का इस्तेमाल किया जा सकता है:

LiveData

getLiveData() का इस्तेमाल करके, SavedStateHandle से ऐसी वैल्यू पाएं जो LiveData ऑब्ज़र्वेबल में रैप की गई हैं. जब कुंजी की वैल्यू अपडेट की जाती है, तो LiveData को नई वैल्यू मिलती है. ज़्यादातर मामलों में, यह वैल्यू उपयोगकर्ता की कार्रवाइयों की वजह से सेट होती है. जैसे, डेटा की सूची को फ़िल्टर करने के लिए कोई क्वेरी डालना. इसके बाद, अपडेट की गई इस वैल्यू का इस्तेमाल ट्रांसफ़ॉर्म LiveData करने के लिए किया जा सकता है.

Kotlin

class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
    val filteredData: LiveData<List<String>> =
        savedStateHandle.getLiveData<String>("query").switchMap { query ->
        repository.getFilteredData(query)
    }

    fun setQuery(query: String) {
        savedStateHandle["query"] = query
    }
}

Java

public class SavedStateViewModel extends ViewModel {
    private SavedStateHandle savedStateHandle;
    public LiveData<List<String>> filteredData;
    public SavedStateViewModel(SavedStateHandle savedStateHandle) {
        this.savedStateHandle = savedStateHandle;
        LiveData<String> queryLiveData = savedStateHandle.getLiveData("query");
        filteredData = Transformations.switchMap(queryLiveData, query -> {
            return repository.getFilteredData(query);
        });
    }

    public void setQuery(String query) {
        savedStateHandle.set("query", query);
    }
}

इस तरह के कोड काम करते हैं

SavedStateHandle में रखा गया डेटा, Bundle के तौर पर सेव और वापस लाया जाता है. साथ ही, गतिविधि या फ़्रैगमेंट के लिए savedInstanceState का बाकी डेटा भी सेव और वापस लाया जाता है.

पार्सल नहीं की जा सकने वाली क्लास सेव करना

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

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

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

Kotlin

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

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

Java

class TempFileViewModel extends ViewModel {
    private File tempFile = null;

    public TempFileViewModel() {
    }

    @NonNull
    public File createOrGetTempFile() {
        if (tempFile == null) {
            tempFile = File.createTempFile("temp", null);
        }
        return tempFile;
    }
}

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

Kotlin

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
        }
    }
}

Java

class TempFileViewModel extends ViewModel {
    private File tempFile = null;

    public TempFileViewModel(SavedStateHandle savedStateHandle) {
        savedStateHandle.setSavedStateProvider("temp_file",
            new TempFileSavedStateProvider());
    }
    @NonNull
    public File createOrGetTempFile() {
        if (tempFile == null) {
            tempFile = File.createTempFile("temp", null);
        }
        return tempFile;
    }

    private class TempFileSavedStateProvider implements SavedStateRegistry.SavedStateProvider {
        @NonNull
        @Override
        public Bundle saveState() {
            Bundle bundle = new Bundle();
            if (tempFile != null) {
                bundle.putString("path", tempFile.getAbsolutePath());
            }
            return bundle;
        }
    }
}

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

Kotlin

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
      }
    }
}

Java

class TempFileViewModel extends ViewModel {
    private File tempFile = null;

    public TempFileViewModel(SavedStateHandle savedStateHandle) {
        Bundle tempFileBundle = savedStateHandle.get("temp_file");
        if (tempFileBundle != null) {
            tempFile = TempFileSavedStateProvider.restoreTempFile(tempFileBundle);
        }
        savedStateHandle.setSavedStateProvider("temp_file", new TempFileSavedStateProvider());
    }

    @NonNull
    public File createOrGetTempFile() {
        if (tempFile == null) {
            tempFile = File.createTempFile("temp", null);
        }
        return tempFile;
    }

    private class TempFileSavedStateProvider implements SavedStateRegistry.SavedStateProvider {
        @NonNull
        @Override
        public Bundle saveState() {
            Bundle bundle = new Bundle();
            if (tempFile != null) {
                bundle.putString("path", tempFile.getAbsolutePath());
            }
            return bundle;
        }

        @Nullable
        private static File restoreTempFile(Bundle bundle) {
            if (bundle.containsKey("path") {
                return File(bundle.getString("path"));
            }
            return null;
        }
    }
}