وحدة "الحالة المحفوظة" لـ ViewModel   جزء من Android Jetpack.

كما هو موضّح في مقالة حفظ حالات واجهة المستخدم، يمكن للكائنات ViewModel التعامل مع تغييرات الإعدادات، لذا لا داعي للقلق بشأن الحالة في عمليات التدوير أو الحالات الأخرى. ومع ذلك، إذا كنت بحاجة إلى معالجة عملية الإنهاء التي يبدأها النظام، يمكنك استخدام واجهة برمجة التطبيقات SavedStateHandle كخيار احتياطي.

يتم عادةً تخزين حالة واجهة المستخدم أو الإشارة إليها في عناصر ViewModel وليس في الأنشطة، لذا يتطلّب استخدام onSaveInstanceState() أو rememberSaveable بعض النص الجاهز الذي يمكن أن تتعامل معه وحدة الحالة المحفوظة نيابةً عنك.

عند استخدام هذه الوحدة، تتلقّى عناصر ViewModel عنصرًا SavedStateHandle من خلال الدالة الإنشائية. هذا العنصر هو خريطة مفاتيح وقيم تتيح لك قراءة الكائنات واستعادتها من الحالة المحفوظة وإليها. وتظلّ هذه القيم مستمرّة بعد أن يوقف النظام العملية وتظلّ متاحة من خلال الكائن نفسه.

تكون الحالة المحفوظة مرتبطة بحزمة المهام. إذا اختفت حزمة المهام، ستختفي الحالة المحفوظة أيضًا. ويمكن أن يحدث ذلك عند فرض إيقاف أحد التطبيقات أو إزالة التطبيق من قائمة التطبيقات المستخدَمة مؤخرًا أو إعادة تشغيل الجهاز. وفي هذه الحالات، تختفي ملفّات مهمة المعالجة ولا يمكنك استعادة المعلومات في الحالة المحفوظة. في سيناريوهات إغلاق حالة واجهة المستخدم التي بدأها المستخدم ، لا تتم استعادة الحالة المحفوظة. في سيناريوهات البدء من النظام ، يكون ذلك صحيحًا.

ضبط إعدادات الجهاز

اعتبارًا من الإصدار الإصدار 1.2.0 من Fragment أو الإصدار الإصدار 1.1.0 من Activity الذي يعتمد عليه، يمكنك قبول SavedStateHandle كمَعلمة دالة إنشاء لعنصر ViewModel.

Kotlin

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

Java

public class SavedStateViewModel extends ViewModel {
    private SavedStateHandle state;

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

    ...
}

يمكنك بعد ذلك استرداد مثيل من ViewModel بدون أي عملية إعداد إضافية. توفّر شركة ViewModel التلقائية SavedStateHandle المناسب لجهاز ViewModel.

Kotlin

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

    ...
}

Java

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 القيمة الجديدة. في معظم الأحيان، يتم ضبط القيمة بسبب تفاعلات المستخدِم، مثل إدخال طلب بحث لfiltrare قائمة البيانات. ويمكن بعد ذلك استخدام هذه القيمة المعدَّلة للقيام بعملية تحويل 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
    }
}

Java

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 القيمة الجديدة. في معظم الأحيان، قد تضبط القيمة استنادًا إلى تفاعلات المستخدِم، مثل إدخال query لتصفية قائمة بالبيانات. يمكنك بعد ذلك تحويل هذه القيمة المعدَّلة باستخدام عوامل تشغيل تدفق أخرى.

Kotlin

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

إتاحة حالة ميزة "الإنشاء" التجريبية

.

يوفّر العنصر lifecycle-viewmodel-compose واجهات برمجة التطبيقات التجريبية saveable التي تتيح إمكانية التشغيل التفاعلي بين SavedStateHandle وSaver في أداة Compose، بحيث يمكن أيضًا حفظ أي State يمكنك حفظه من خلال rememberSaveable باستخدام Saver مخصّص باستخدام SavedStateHandle.

Kotlin

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 intent، مع إدخال ملف مؤقت للمكان الذي يجب أن تخزِّن فيه الكاميرا الصورة. يُحاط TempFileViewModel بمنطق إنشاء هذا الملف المؤقت.

Kotlin

class TempFileViewModel : ViewModel() {
    private var tempFile: File? = null

    fun createOrGetTempFile(): File {
        return tempFile ?: File.createTempFile("temp", null).also {
            tempFile = it
        }
    }
}

Java

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:

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

Java

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.

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

Java

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 التي تختبرها.

Kotlin

class MyViewModelTest {

    private lateinit var viewModel: MyViewModel

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

مصادر إضافية

لمزيد من المعلومات عن وحدة "الحالة المحفوظة" في ViewModel، يُرجى الاطّلاع على الموارد التالية.

الدروس التطبيقية حول الترميز