ViewModel (ভিউ)-এর জন্য সংরক্ষিত অবস্থা মডিউল , যা অ্যান্ড্রয়েড জেটপ্যাকের একটি অংশ।

ধারণা এবং জেটপ্যাক কম্পোজ বাস্তবায়ন

UI স্টেট সংরক্ষণ (Saving UI States) অংশে যেমন উল্লেখ করা হয়েছে, ViewModel অবজেক্টগুলো কনফিগারেশন পরিবর্তন সামলাতে পারে, তাই রোটেশন বা অন্যান্য ক্ষেত্রে স্টেট নিয়ে আপনাকে চিন্তা করতে হবে না। তবে, যদি সিস্টেম-জনিত প্রসেস বন্ধ হয়ে যাওয়া সামলানোর প্রয়োজন হয়, তাহলে ব্যাকআপ হিসেবে আপনি SavedStateHandle API ব্যবহার করতে পারেন।

UI স্টেট সাধারণত অ্যাক্টিভিটিতে নয়, বরং ViewModel অবজেক্টে সংরক্ষিত বা রেফারেন্স করা হয়। তাই onSaveInstanceState() ব্যবহার করার জন্য কিছু বয়লারপ্লেট কোডের প্রয়োজন হয়, যা সেভড স্টেট মডিউল আপনার হয়ে সামলে নিতে পারে।

এই মডিউলটি ব্যবহার করার সময়, ViewModel অবজেক্টগুলো তাদের কনস্ট্রাক্টরের মাধ্যমে একটি SavedStateHandle অবজেক্ট গ্রহণ করে। এই অবজেক্টটি একটি কী-ভ্যালু ম্যাপ, যা আপনাকে সেভড স্টেটে অবজেক্ট লিখতে এবং সেখান থেকে তা পুনরুদ্ধার করতে দেয়। সিস্টেম দ্বারা প্রসেসটি বন্ধ করে দেওয়ার পরেও এই ভ্যালুগুলো থেকে যায় এবং একই অবজেক্টের মাধ্যমে সেগুলো উপলব্ধ থাকে।

সংরক্ষিত অবস্থা আপনার টাস্ক স্ট্যাকের সাথে সংযুক্ত থাকে। যদি আপনার টাস্ক স্ট্যাক মুছে যায়, তবে আপনার সংরক্ষিত অবস্থাও মুছে যায়। কোনো অ্যাপকে ফোর্স স্টপ করলে, রিসেন্টস মেনু থেকে অ্যাপটি সরিয়ে দিলে, বা ডিভাইসটি রিবুট করলে এমনটা হতে পারে। এই ধরনের ক্ষেত্রে, টাস্ক স্ট্যাকটি অদৃশ্য হয়ে যায় এবং আপনি সংরক্ষিত অবস্থার তথ্য পুনরুদ্ধার করতে পারেন না। ব্যবহারকারীর দ্বারা UI স্টেট বাতিল করার ক্ষেত্রে, সংরক্ষিত অবস্থা পুনরুদ্ধার হয় না। সিস্টেমের দ্বারা বাতিল করার ক্ষেত্রে, এটি পুনরুদ্ধার হয়।

সেটআপ

Fragment 1.2.0 অথবা এর ট্রানজিটিভ ডিপেন্ডেন্সি Activity 1.1.0 থেকে শুরু করে, আপনি আপনার ViewModel এর কনস্ট্রাক্টর আর্গুমেন্ট হিসেবে একটি SavedStateHandle গ্রহণ করতে পারবেন।

কোটলিন

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

জাভা

public class SavedStateViewModel extends ViewModel {
    private SavedStateHandle state;

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

    ...
}

এরপর আপনি কোনো অতিরিক্ত কনফিগারেশন ছাড়াই আপনার ViewModel এর একটি ইনস্ট্যান্স পেতে পারেন। ডিফল্ট ViewModel ফ্যাক্টরি আপনার ViewModel জন্য উপযুক্ত SavedStateHandle প্রদান করে।

কোটলিন

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

    ...
}

জাভা

class MainFragment extends Fragment {
    private SavedStateViewModel vm;

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

        ...

    }

    ...
}

একটি কাস্টম ViewModelProvider.Factory ইনস্ট্যান্স প্রদান করার সময়, আপনি AbstractSavedStateViewModelFactory এক্সটেন্ড করে SavedStateHandle এর ব্যবহার সক্ষম করতে পারেন।

SavedStateHandle নিয়ে কাজ করা

SavedStateHandle ক্লাসটি একটি কী-ভ্যালু ম্যাপ, যা আপনাকে set() এবং get() মেথডের মাধ্যমে সংরক্ষিত স্টেটে ডেটা লিখতে এবং সেখান থেকে ডেটা পুনরুদ্ধার করতে দেয়।

SavedStateHandle ব্যবহার করার মাধ্যমে, প্রসেস বন্ধ হয়ে গেলেও কোয়েরির মান সংরক্ষিত থাকে। এর ফলে, অ্যাক্টিভিটি বা ফ্র্যাগমেন্টকে ম্যানুয়ালি সেই মানটি সেভ, রিস্টোর এবং ViewModel এ ফেরত পাঠানোর প্রয়োজন ছাড়াই, ব্যবহারকারী পুনরায় চালু হওয়ার আগে ও পরে একই ফিল্টার করা ডেটা দেখতে পান।

একটি কী-ভ্যালু ম্যাপের সাথে কাজ করার সময় আপনি SavedStateHandle আরও কিছু মেথড আশা করতে পারেন:

  • contains(String key) - প্রদত্ত কী-টির জন্য কোনো ভ্যালু আছে কিনা তা যাচাই করে।
  • remove(String key) - প্রদত্ত কী-এর মান মুছে দেয়।
  • keys() - SavedStateHandle এর অন্তর্ভুক্ত সমস্ত কী ফেরত দেয়।

এছাড়াও, আপনি একটি অবজার্ভেবল ডেটা হোল্ডার ব্যবহার করে SavedStateHandle থেকে ভ্যালু পুনরুদ্ধার করতে পারেন। সমর্থিত টাইপগুলোর তালিকা হলো:

লাইভডেটা

getLiveData() ব্যবহার করে LiveData অবজার্ভেবলের মধ্যে থাকা SavedStateHandle থেকে ভ্যালুগুলো পুনরুদ্ধার করুন। যখন কী-এর ভ্যালু আপডেট করা হয়, তখন LiveData নতুন ভ্যালুটি গ্রহণ করে। বেশিরভাগ ক্ষেত্রে, ব্যবহারকারীর কার্যকলাপের ফলে ভ্যালুটি সেট হয়, যেমন ডেটার একটি তালিকা ফিল্টার করার জন্য কোয়েরি প্রবেশ করানো। এই আপডেট করা ভ্যালুটি পরবর্তীতে LiveData রূপান্তর করতে ব্যবহার করা যেতে পারে।

কোটলিন

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

জাভা

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 মধ্যে থাকা ডেটা, অ্যাক্টিভিটি বা ফ্র্যাগমেন্টের বাকি savedInstanceState এর সাথে একটি Bundle হিসেবে সংরক্ষিত ও পুনরুদ্ধার করা হয়।

অ-পার্সেলযোগ্য ক্লাস সংরক্ষণ করা

যদি কোনো ক্লাস Parcelable বা Serializable ইন্টারফেস ইমপ্লিমেন্ট না করে এবং সেটিকে পরিবর্তন করে এই ইন্টারফেসগুলোর কোনো একটি ইমপ্লিমেন্ট করানো না যায়, তাহলে সেই ক্লাসের কোনো ইনস্ট্যান্সকে সরাসরি SavedStateHandle এ সেভ করা সম্ভব নয়।

লাইফসাইকেল 2.3.0-alpha03 থেকে শুরু করে, SavedStateHandle আপনাকে setSavedStateProvider() মেথড ব্যবহার করে আপনার অবজেক্টকে একটি Bundle (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
        }
    }
}

জাভা

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 এ একটি প্রোভাইডার হিসেবে সেট করুন।

কোটলিন

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

জাভা

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 নিন। এটি saveTempFile() দ্বারা প্রদত্ত সেই একই Bundle , যাতে অ্যাবসোলিউট পাথ থাকে। এরপর সেই অ্যাবসোলিউট পাথ ব্যবহার করে একটি নতুন 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
      }
    }
}

জাভা

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;
        }
    }
}
{% হুবহু %} {% endverbatim %} {% হুবহু %} {% endverbatim %}