ViewModel-এর জন্য সেভ করা স্টেট মডিউল (ভিউ)   Android Jetpack-এর অংশ।

ধারণা ও Jetpack Compose-এর প্রয়োগ

UI স্টেট সেভ করা বিভাগে যেমন উল্লেখ করা হয়েছে, ViewModel অবজেক্ট কনফিগারেশন সংক্রান্ত পরিবর্তন ম্যানেজ করতে পারে, তাই রোটেশন বা অন্যান্য ক্ষেত্রে স্টেট নিয়ে আপনাকে চিন্তা করতে হবে না। তবে, সিস্টেম-ইনিশিয়েটেড প্রসেস বন্ধ করার বিষয়টি আপনাকে ম্যানেজ করতে হলে, ব্যাক-আপ হিসেবে আপনি SavedStateHandle API ব্যবহার করতে চাইতে পারেন।

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

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

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

সেট-আপ করুন

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

Kotlin

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 প্রদান করে।

Kotlin

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 থেকে ভ্যালু ফিরিয়ে আনতে পারবেন। কাজ করে এমন ধরনের তালিকা:

LiveData

SavedStateHandle থেকে ভ্যালু ফিরিয়ে আনুন যা LiveData-এ র‍্যাপ করা আছে getLiveData() ব্যবহার করে পর্যবেক্ষণযোগ্য। কী-এর ভ্যালু আপডেট করা হলে, 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
    }
}

জাভা

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 আপনাকে setSavedStateProvider() পদ্ধতি ব্যবহার করে Bundle হিসেবে আপনার অবজেক্ট সেভ ও রিস্টোর করার জন্য নিজস্ব লজিক প্রদান করে যেকোনও অবজেক্ট সেভ করতে দেয়। SavedStateRegistry.SavedStateProvider হল এমন একটি ইন্টারফেস যা একটি একক saveState() পদ্ধতিকে সংজ্ঞায়িত করে যা আপনি যে স্টেট সেভ করতে চান তা সহ একটি Bundle রিটার্ন করে। SavedStateHandle তার স্ট্যাটাস সেভ করার জন্য রেডি হলে, এটি SavedStateProvider থেকে Bundle ফিরিয়ে আনতে saveState()-কে কল করে এবং সংশ্লিষ্ট 'কী'-এর জন্য Bundle সেভ করে।

এমন একটি অ্যাপের উদাহরণ বিবেচনা করুন যা ACTION_IMAGE_CAPTURE ইনটেন্টের মাধ্যমে ক্যামেরা অ্যাপ থেকে একটি ছবির অনুরোধ করে, যেখানে ক্যামেরা ছবিটি স্টোর করবে তার জন্য একটি অস্থায়ী ফাইল পাস করে। TempFileViewModel-এ সেই সাময়িক ফাইল তৈরি করার লজিক এনক্যাপসুলেট করা থাকে।

Kotlin

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-এ এটি একটি পরিষেবা প্রদানকারী হিসেবে সেট করুন:

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

জাভা

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 ইনস্ট্যান্টিয়েট করা যাবে।

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

জাভা

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