ماژول حالت ذخیره شده برای ViewModel بخشی از Android Jetpack .

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

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

هنگام استفاده از این ماژول، اشیاء 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);
    }
}

StateFlow

مقادیری را از SavedStateHandle که در یک StateFlow قابل مشاهده با استفاده از getStateFlow() پیچیده شده اند، بازیابی کنید. هنگامی که مقدار کلید را به روز می کنید، StateFlow مقدار جدید را دریافت می کند. اغلب، ممکن است به دلیل تعاملات کاربر، مانند وارد کردن یک پرس و جو برای فیلتر کردن لیستی از داده ها، مقدار را تنظیم کنید. سپس می توانید این مقدار به روز شده را با استفاده از سایر عملگرهای Flow تبدیل کنید.

کاتلین

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

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

پشتیبانی دولتی Experimental Compose

مصنوع lifecycle-viewmodel-compose APIهای آزمایشی saveable را ارائه می‌کند که امکان همکاری بین SavedStateHandle و Compose's Saver را فراهم می‌کند، به طوری که هر State که می‌توانید از طریق rememberSaveable با یک Saver سفارشی ذخیره کنید، می‌تواند با SavedStateHandle نیز ذخیره شود.

کاتلین

class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {

    var filteredData: List<String> by savedStateHandle.saveable {
        mutableStateOf(emptyList())
    }

    fun setQuery(query: String) {
        withMutableSnapshot {
            filteredData += query
        }
    }
}

انواع پشتیبانی شده

داده‌هایی که در یک SavedStateHandle نگهداری می‌شوند به‌عنوان یک Bundle ، همراه با بقیه savedInstanceState برای فعالیت یا قطعه، ذخیره و بازیابی می‌شوند.

انواع پشتیبانی مستقیم

به‌طور پیش‌فرض، می‌توانید set() و get() در یک SavedStateHandle برای انواع داده‌های مشابه یک Bundle فراخوانی کنید، همانطور که در زیر نشان داده شده است:

پشتیبانی از نوع/کلاس پشتیبانی از آرایه
double double[]
int int[]
long long[]
String String[]
byte byte[]
char char[]
CharSequence CharSequence[]
float float[]
Parcelable Parcelable[]
Serializable Serializable[]
short short[]
SparseArray
Binder
Bundle
ArrayList
Size (only in API 21+)
SizeF (only in API 21+)

اگر کلاس یکی از موارد موجود در لیست بالا را گسترش نمی‌دهد، با اضافه کردن حاشیه‌نویسی @Parcelize Kotlin@ یا پیاده‌سازی Parcelable به طور مستقیم، کلاس را قابل بسته‌بندی کنید.

صرفه جویی در کلاس های غیر قابل بسته بندی

اگر کلاسی 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;
        }
    }
}

SavedStateHandle در تست ها

برای آزمایش یک ViewModel که یک SavedStateHandle را به عنوان وابستگی می گیرد، یک نمونه جدید از SavedStateHandle با مقادیر تستی که نیاز دارد ایجاد کنید و آن را به نمونه ViewModel که در حال آزمایش هستید ارسال کنید.

کاتلین

class MyViewModelTest {

    private lateinit var viewModel: MyViewModel

    @Before
    fun setup() {
        val savedState = SavedStateHandle(mapOf("someIdArg" to testId))
        viewModel = MyViewModel(savedState = savedState)
    }
}

منابع اضافی

برای اطلاعات بیشتر در مورد ماژول وضعیت ذخیره شده برای ViewModel ، به منابع زیر مراجعه کنید.

Codelabs

{% کلمه به کلمه %} {% آخر کلمه %} {% کلمه به کلمه %} {% آخر کلمه %}