Saved State module for ViewModel (Views)   Part of Android Jetpack.

مفاهیم و پیاده‌سازی Jetpack Compose

همان‌طور که در ذخیره کردن حالت‌های میانای کاربر ذکر شد، اشیای ViewModel می‌توانند تغییرات پیکربندی را مدیریت کنند، بنابراین لازم نیست نگران حالت در چرخش‌ها یا موارد دیگر باشید. بااین‌حال، اگر لازم است مرگ پردازش آغازشده توسط سیستم را مدیریت کنید، شاید بخواهید از SavedStateHandle API به‌عنوان پشتیبان استفاده کنید.

حالت میانای کاربر معمولاً در اشیاء ViewModel ذخیره یا ارجاع داده می‌شود و نه فعالیت‌ها، بنابراین استفاده از onSaveInstanceState() به مقداری کد تکراری نیاز دارد که مدول حالت ذخیره‌شده می‌تواند آن را برای شما مدیریت کند.

هنگام استفاده از این ماژول، اشیاء ViewModel یک شیء SavedStateHandle را ازطریق سازنده آن دریافت می‌کنند. این شیء یک نقشه کلید-مقدار است که به شما امکان می‌دهد اشیاء را در وضعیت ذخیره‌شده بنویسید و بازیابی کنید. این مقادیر پس‌از توقف پردازش توسط سیستم باقی می‌مانند و ازطریق همان شیء دردسترس خواهند بود.

وضعیت ذخیره‌شده به پشته تکلیف شما گره خورده است. اگر پشته تکلیف شما ازبین برود، وضعیت ذخیره‌شده شما نیز ازبین می‌رود. این اتفاق ممکن است هنگام توقف اجباری برنامه، برداشتن برنامه از منو موارد اخیر، یا بازراه‌اندازی دستگاه رخ دهد. دراین‌صورت، پشته تکلیف ناپدید می‌شود و نمی‌توانید اطلاعات را در حالت ذخیره‌شده بازیابی کنید. در سناریوهای رد کردن وضعیت میانای کاربر، وضعیت ذخیره‌شده بازیابی نمی‌شود. در سناریوهای راه‌اندازی‌شده توسط سیستم، بله.

راه‌اندازی

با شروع از Fragment 1.2.0 یا وابستگی متعدی آن Activity 1.1.0، می‌توانید SavedStateHandle را به‌عنوان آرگومان سازنده برای ViewModel خود بپذیرید.

کاتلین

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

جاوا

public class SavedStateViewModel extends ViewModel {
    private SavedStateHandle state;

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

    ...
}

سپس می‌توانید نمونه‌ای از ViewModel خود را بدون هیچ پیکربندی اضافی بازیابی کنید. کارخانه پیش‌فرض ViewModel، SavedStateHandle مناسب را برای ViewModel شما فراهم می‌کند.

کاتلین

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 سفارشی، می‌توانید استفاده از SavedStateHandle را با گسترش دادن AbstractSavedStateViewModelFactory فعال کنید.

کار کردن با SavedStateHandle

کلاس SavedStateHandle یک نقشه کلید-مقدار است که به شما امکان می‌دهد داده‌ها را ازطریق روش‌های set() و get() در وضعیت ذخیره‌شده بنویسید و بازیابی کنید.

بااستفاده از SavedStateHandle، مقدار پُرسمان درطول مرگ پردازش حفظ می‌شود و مطمئن می‌شوید که کاربر همان مجموعه داده‌های فیلترشده را قبل و بعداز بازآفرینی می‌بیند، بدون اینکه فعالیت یا قطعه نیاز به ذخیره، بازیابی، و ارسال دستی آن مقدار به ViewModel داشته باشد.

SavedStateHandle همچنین روش‌های دیگری دارد که ممکن است هنگام تعامل با نقشه مقدار کلید انتظار داشته باشید:

  • contains(String key) - بررسی می‌کند که آیا مقداری برای کلید داده‌شده وجود دارد یا نه.
  • remove(String key) - مقدار کلید داده‌شده را برمی‌دارد.
  • keys() - همه کلیدهای موجود در SavedStateHandle را برمی‌گرداند.

علاوه‌براین، می‌توانید مقادیر را از SavedStateHandle بااستفاده از نگهدارنده داده‌های دیده‌شدنی بازیابی کنید. فهرست انواع پشتیبانی‌شده:

LiveData

مقادیر را از SavedStateHandle که در LiveData پیچیده شده است بااستفاده از getLiveData() بازیابی کنید. وقتی مقدار کلید به‌روز می‌شود، 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 به‌عنوان Bundle ذخیره و بازیابی می‌شوند، همراه با بقیه savedInstanceState برای فعالیت یا تکه.

ذخیره کردن کلاس‌های غیرقابل‌تجزیه

اگر کلاسی Parcelable یا Serializable را پیاده‌سازی نکند و نتوان آن را برای پیاده‌سازی یکی از این رابط‌ها اصلاح کرد، امکان ذخیره مستقیم نمونه‌ای از آن کلاس در SavedStateHandle وجود ندارد.

با شروع از Lifecycle 2.3.0-alpha03، SavedStateHandle به شما امکان می‌دهد هر شیئی را با ارائه منطق خودتان برای ذخیره و بازیابی شیء به‌عنوان Bundle بااستفاده از روش setSavedStateProvider() ذخیره کنید. ‫SavedStateRegistry.SavedStateProvider میانایی است که یک روش saveState() را تعریف می‌کند که Bundle حاوی وضعیتی را که می‌خواهید ذخیره کنید برمی‌گرداند. وقتی SavedStateHandle آماده ذخیره کردن وضعیتش است، saveState() را برای بازیابی Bundle از SavedStateProvider فراخوانی می‌کند و 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 را پیاده‌سازی کنید و آن را به‌عنوان ارائه‌دهنده در SavedStateHandle ViewModel تنظیم کنید:

کاتلین

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 هنگام بازگشت کاربر، temp_file Bundle را از SavedStateHandle بازیابی کنید. این همان 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
      }
    }
}

جاوا

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