ViewModel için Kaydedilen Durum modülü Android Jetpack'in bir parçasıdır.

Kullanıcı arayüzü durumlarını kaydetme bölümünde belirtildiği gibi, ViewModel nesneleri yapılandırma değişikliklerini işleyebilir. Bu nedenle, rotasyonlardaki veya diğer durumlarda durum hakkında endişelenmenize gerek yoktur. Ancak sistem tarafından başlatılan süreç ölümünü ele almanız gerekiyorsa yedek olarak SavedStateHandle API'yi kullanabilirsiniz.

Kullanıcı arayüzü durumu genellikle etkinliklerde değil ViewModel nesnelerinde depolanır veya buna referans verilir. Bu nedenle, onSaveInstanceState() veya rememberSaveable kullanımı, kayıtlı durum modülünün sizin için işleyebileceği bazı ortak metin gerektirir.

Bu modülü kullanırken ViewModel nesneleri, oluşturucusu üzerinden bir SavedStateHandle nesnesi alır. Bu nesne, kayıtlı durumlarla ilgili olarak nesne yazmanıza ve almanıza olanak tanıyan bir anahtar/değer eşlemesidir. Bu değerler, süreç sistem tarafından sonlandırıldıktan sonra kalıcı olur ve aynı nesne üzerinden kullanılabilir durumda kalır.

Kaydedilen durum görev yığınınıza bağlıdır. Görev yığınınız kaybolursa kayıtlı durumunuz da kaybolur. Bu durum, bir uygulamayı zorla durdururken, son kullanılanlar menüsünden kaldırırken veya cihazı yeniden başlatırken meydana gelebilir. Bu gibi durumlarda, görev yığını kaybolur ve bilgileri kayıtlı duruma geri yükleyemezsiniz. Kullanıcı tarafından başlatılan kullanıcı arayüzü durumu kapatma senaryolarında, kayıtlı durum geri yüklenmez. Sistem tarafından başlatılan senaryolarda böylesi yoktur.

Kurulum

Fragment 1.2.0 veya geçişli bağımlılığından (Etkinlik 1.1.0) başlayarak, ViewModel için kurucu bağımsız değişkeni olarak SavedStateHandle öğesini kabul edebilirsiniz.

Kotlin

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

Java

public class SavedStateViewModel extends ViewModel {
    private SavedStateHandle state;

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

    ...
}

Ardından, ek yapılandırma gerekmeden ViewModel örneğinizi alabilirsiniz. Varsayılan ViewModel fabrikası, ViewModel cihazınıza uygun SavedStateHandle sağlar.

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

        ...


    }

    ...
}

Özel ViewModelProvider.Factory örneği sağlarken AbstractSavedStateViewModelFactory örneğini genişleterek SavedStateHandle kullanımını etkinleştirebilirsiniz.

saveStateHandle ile çalışma

SavedStateHandle sınıfı, set() ve get() yöntemleri aracılığıyla kayıtlı duruma gelen/kaydedilen durumdan veri yazıp almanıza olanak tanıyan bir anahtar/değer eşlemesidir.

SavedStateHandle kullanıldığında, sorgu değeri işlem ölümü boyunca korunur. Böylece kullanıcının, etkinlik veya parçanın manuel olarak kaydedilmesi, geri yüklenmesi ve ViewModel öğesine geri yönlendirilmesine gerek kalmadan, filtrelenen veri kümesini yeniden oluşturma işleminden önce ve sonra aynı şekilde görmesi sağlanır.

SavedStateHandle, bir anahtar/değer eşleşmesiyle etkileşimde bulunurken bekleyebileceğiniz başka yöntemler de sunar:

  • contains(String key) - Verilen anahtar için bir değer olup olmadığını kontrol eder.
  • remove(String key) - Belirli bir anahtarın değerini kaldırır.
  • keys() - SavedStateHandle içindeki tüm anahtarları döndürür.

Ayrıca, gözlemlenebilir bir veri tutucu kullanarak SavedStateHandle öğesinden değerler alabilirsiniz. Desteklenen türlerin listesi:

LiveData

getLiveData() ile LiveData gözlemlenebilirliğine sarmalanmış olan SavedStateHandle değerlerini alın. Anahtarın değeri güncellendiğinde LiveData yeni değeri alır. Değer genellikle bir veri listesini filtrelemek için sorgu girilmesi gibi kullanıcı etkileşimlerine ayarlanır. Bu güncellenen değer, daha sonra LiveData dönüşümü için kullanılabilir.

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

Durum Akışı

getStateFlow() ile StateFlow gözlemlenebilirliğine sarmalanmış olan SavedStateHandle değerlerini alın. Anahtarın değerini güncellediğinizde StateFlow yeni değeri alır. Çoğu zaman, bu değeri kullanıcı etkileşimleri (ör. bir veri listesini filtrelemek için sorgu girmek) nedeniyle de ayarlayabilirsiniz. Daha sonra, diğer Akış operatörlerini kullanarak bu güncellenmiş değeri dönüştürebilirsiniz.

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

Deneysel Compose'un Durum desteği

lifecycle-viewmodel-compose yapısı, SavedStateHandle ile Compose'un Saver uygulamaları arasında birlikte çalışabilme olanağı sağlayan deneysel saveable API'lerini sunar. Böylece, rememberSaveable aracılığıyla özel bir Saver ile kaydedebileceğiniz tüm State öğeleri SavedStateHandle ile de kaydedilebilir.

Kotlin

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

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

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

Desteklenen türler

SavedStateHandle içinde saklanan veriler Bundle ile birlikte etkinlik veya parçanın savedInstanceState geri kalanıyla birlikte kaydedilir ve geri yüklenir.

Doğrudan desteklenen türler

Varsayılan olarak, aşağıda gösterildiği gibi Bundle ile aynı veri türleri için SavedStateHandle üzerinde set() ve get() çağırabilirsiniz:

Tür/Sınıf desteği Dizi desteği
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+)

Sınıf yukarıdaki listede yer alanlardan birine sahip değilse @Parcelize Kotlin ek açıklamasını ekleyerek veya Parcelable'ı doğrudan uygulayarak sınıfı ayrıştırılabilir hale getirebilirsiniz.

Ayrıştırılamayan sınıfları kaydetme

Bir sınıf Parcelable veya Serializable kullanmıyorsa ve bu arayüzlerden birini uygulamak için değiştirilemezse bu sınıfın bir örneğini SavedStateHandle dosyasına doğrudan kaydetmek mümkün değildir.

Yaşam döngüsü 2.3.0-alpha03'ten itibaren SavedStateHandle, setSavedStateProvider() yöntemini kullanarak nesnenizi Bundle olarak kaydedip geri yüklemek için kendi mantığınızı sağlayarak herhangi bir nesneyi kaydetmenize olanak tanır. SavedStateRegistry.SavedStateProvider, kaydetmek istediğiniz durumu içeren bir Bundle değeri döndüren tek bir saveState() yöntemini tanımlayan arayüzdür. SavedStateHandle durumunu kaydetmeye hazır olduğunda, SavedStateProvider hizmetinden Bundle öğesini almak için saveState() yöntemini çağırır ve ilişkili anahtar için Bundle değerini kaydeder.

ACTION_IMAGE_CAPTURE niyetiyle kamera uygulamasından resim isteyen ve kameranın resmi depolaması gereken geçici bir dosya ileten bir uygulama örneğini düşünün. TempFileViewModel, bu geçici dosyayı oluşturma mantığını içerir.

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

İşlemin sonlandırılması ve daha sonra geri yüklenmesi halinde geçici dosyanın kaybolmadığından emin olmak için TempFileViewModel, verilerinin kalıcı olmasını sağlamak üzere SavedStateHandle kullanabilir. TempFileViewModel adlı iş ortağının verilerini kaydetmesine izin vermek için SavedStateProvider politikasını uygulayın ve ViewModel öğesinin SavedStateHandle öğesinde sağlayıcı olarak ayarlayın:

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

Kullanıcı geri döndüğünde File verilerini geri yüklemek için SavedStateHandle kaynağından temp_file Bundle kodunu alın. Bu, saveTempFile() tarafından sağlanan Bundle ile mutlak yolu içeren aynıdır. Daha sonra mutlak yol, yeni bir File örneklendirmek için kullanılabilir.

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

Testlerde KaydedilenStateHandle

SavedStateHandle öğesini bağımlılık olarak alan bir ViewModel test etmek için, gerektirdiği test değerleriyle yeni bir SavedStateHandle örneği oluşturun ve bunu test ettiğiniz ViewModel örneğine iletin.

Kotlin

class MyViewModelTest {

    private lateinit var viewModel: MyViewModel

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

Ek kaynaklar

ViewModel için Kaydedilen Durum modülü hakkında daha fazla bilgi edinmek için aşağıdaki kaynaklara bakın.

Codelab uygulamaları