ماژول وضعیت ذخیره شده برای ViewModel (Views) بخشی از Android Jetpack .

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

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

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

کار با SavedStateHandle

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

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

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

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

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

لایو دیتا

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

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

اگر یک کلاس 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;
        }
    }
}
{% کلمه به کلمه %} {% فعل کمکی %} {% کلمه به کلمه %} {% فعل کمکی %}