ViewModel için SavedState 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, döndürme veya diğer durumlarda durum hakkında endişelenmeniz gerekmez. Ancak sistem tarafından başlatılan işlem sonlandırmayı işlemeniz gerekiyorsa yedek olarak SavedStateHandle API'yi kullanabilirsiniz.

Kullanıcı arayüzü durumu genellikle ViewModel nesnelerinde depolanır veya referans verilir. Bu nedenle, Compose'da rememberSaveable kullanmak, kaydedilmiş durum modülünün sizin için işleyebileceği bazı standart kodlar gerektirir.

Bu modülü kullanırken ViewModel nesneleri, oluşturucusu aracılığıyla bir SavedStateHandle nesnesi alır. Bu nesne, kaydedilen duruma nesne yazmanıza ve kaydedilen durumdan nesne almanıza olanak tanıyan bir anahtar/değer eşlemesidir. Bu değerler, işlem sistem tarafından sonlandırıldıktan sonra da kalır ve aynı nesne üzerinden kullanılabilir.

Kaydedilen durum, görev yığınınıza bağlıdır. Görev yığınınız kaybolursa kaydedilen durumunuz da kaybolur. Bu durum, bir uygulamayı zorla durdurduğunuzda, uygulamayı son kullanılanlar menüsünden kaldırdığınızda veya cihazı yeniden başlattığınızda ortaya çıkabilir. Bu gibi durumlarda görev yığını kaybolur ve kaydedilen durumdaki bilgileri geri yükleyemezsiniz. Kullanıcı tarafından başlatılan kullanıcı arayüzü durumu kapatma senaryolarında, kaydedilen durum geri yüklenmez. Sistem tarafından başlatılan senaryolarda ise bu durum geçerlidir.

Kurulum

SavedStateHandle özelliğini kullanmak için ViewModel öğenize oluşturucu bağımsız değişkeni olarak kabul edin.

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

Ardından, ek yapılandırma olmadan composable'larınızda ViewModel örneğinizi alabilirsiniz. Varsayılan ViewModel fabrikası, ViewModel için uygun SavedStateHandle sağlar.

class MyViewModel : ViewModel() { /*...*/ }

// import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun MyScreen(
    viewModel: MyViewModel = viewModel()
) {
    // use viewModel here
}

Özel bir ViewModelProvider.Factory örneği sağlarken CreationExtras ve viewModelFactory DSL'yi kullanarak SavedStateHandle kullanımını etkinleştirebilirsiniz.

SavedStateHandle ile çalışma

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

SavedStateHandle kullanıldığında sorgu değeri, işlem sonlandırma boyunca korunur. Böylece kullanıcı, etkinliğin veya parçanın bu değeri manuel olarak kaydetmesine, geri yüklemesine ve ViewModel öğesine geri iletmesine gerek kalmadan yeniden oluşturma işleminden önce ve sonra aynı filtrelenmiş veri grubunu görür.

SavedStateHandle, anahtar-değer eşlemesiyle etkileşim kurarken bekleyebileceğiniz başka yöntemlere de sahiptir:

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

Ayrıca, gözlemlenebilir bir veri tutucu kullanarak SavedStateHandle üzerinden değerleri alabilirsiniz. Desteklenen türlerin listesi şunları içerir:

StateFlow

StateFlow gözlemlenebilir nesnesine sarmalanmış SavedStateHandle değerlerini alabilirsiniz. Değeri doğrudan değiştirmeniz gerekip gerekmediğine bağlı olarak, salt okunur veya değiştirilebilir bir akış arasında seçim yapabilirsiniz:

  • getStateFlow(): Yalnızca durumu okumanız gerekiyorsa bunu kullanın. Anahtarın değerini SavedStateHandle içinde başka bir yerde güncellediğinizde StateFlow yeni değeri alır. Salt okunur bir akışı göstermek ve Flow operatörlerini kullanarak dönüştürmek istediğinizde bu yöntem idealdir.
  • getMutableStateFlow(): Hem okuma hem de yazma erişimine ihtiyacınız varsa bunu kullanın. Döndürülen MutableStateFlow öğesinin .value değerini güncellemek, temel SavedStateHandle değerini otomatik olarak günceller. Böylece anahtarı manuel olarak ayarlamanız gerekmez.

Çoğu zaman bu değerleri, bir veri listesini filtrelemek için sorgu girme gibi kullanıcı etkileşimleri nedeniyle güncellersiniz.

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

    // Use getMutableStateFlow to read and write the query directly
    private val _query = savedStateHandle.getMutableStateFlow("query", "")
    val query: StateFlow = _query.asStateFlow()

    // Use getStateFlow if you only need a read-only stream to react to changes
    val filteredData: StateFlow<List> =
        query.flatMapLatest {
            repository.getFilteredData(it)
        }
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = emptyList()
        )

    fun setQuery(newQuery: String) {
        // Updating the MutableStateFlow automatically updates the SavedStateHandle
        _query.value = newQuery
    }
}

KotlinX Serialization desteği

Karmaşık kullanıcı arayüzü durumu için KotlinX Serialization ile birlikte saved özellik temsilcisini kullanabilirsiniz. Bu temsilci, özel @Serializable veri sınıflarını doğrudan SavedStateHandle içinde kalıcı hale getirmenize olanak tanır. Bu sayede, işlem sonlandırma sırasında ViewModel'inizin durumu korunur. Böylece Compose kullanıcı arayüzünüz, yeniden oluşturulduğunda durumunu sorunsuz bir şekilde geri yükleyebilir.

Bu özelliği kullanmak için veri sınıfınıza @Serializable ile ek açıklama ekleyin ve ViewModel'inizde saved temsilcisini kullanın:

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
// Ensure you have the savedstate-ktx dependency
import androidx.savedstate.serialization.saved
import kotlinx.serialization.Serializable

@Serializable
data class UserFilterState(
    val searchQuery: String,
    val minAge: Int,
    val includeInactive: Boolean
)

class FilterViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {

    // The state is automatically serialized to a Bundle on process death,
    // and deserialized upon recreation.
    var filterState by savedStateHandle.saved {
        UserFilterState(searchQuery = "", minAge = 18, includeInactive = false)
    }

    fun updateQuery(newQuery: String) {
        // Mutating the property automatically updates the underlying SavedStateHandle
        filterState = filterState.copy(searchQuery = newQuery)
    }
}

Compose State desteği

Durumunuz KotlinX Serialization yerine Compose'un Saver API'lerini kullanıyorsa lifecycle-viewmodel-compose yapısı saveable temsilcisini sağlar. Bu sayede SavedStateHandle ile Compose'un Saver öğesi arasında birlikte çalışabilirlik sağlanır. Böylece, rememberSaveable aracılığıyla özel bir Saver ile kaydedebileceğiniz tüm State öğeleri SavedStateHandle ile de kaydedilebilir.

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 tutulan veriler, uygulamanızın diğer savedInstanceState ile birlikte Bundle olarak kaydedilir ve geri yüklenir.

Doğrudan desteklenen türler

Varsayılan olarak, SavedStateHandle üzerinde set() ve get() yöntemlerini çağırarak Bundle ile aynı veri türlerini kullanabilirsiniz. Bu durum aşağıdaki örnekte gösterilmiştir:

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 alan sınıflardan birini genişletmiyorsa @Parcelize Kotlin ek açıklamasını ekleyerek veya doğrudan Parcelable uygulayarak sınıfı parcelable yapabilirsiniz.

Paketlenebilir olmayan sınıfları kaydetme

Bir sınıf Parcelable veya Serializable uygulamıyorsa ve bu arayüzlerden birini uygulayacak şekilde değiştirilemiyorsa bu sınıfın bir örneğini doğrudan SavedStateHandle içine kaydetmek mümkün değildir.

Lifecycle 2.3.0-alpha03 sürümünden itibaren SavedStateHandle, setSavedStateProvider() yöntemini kullanarak nesnenizi Bundle olarak kaydetme ve geri yükleme mantığınızı sağlayarak herhangi bir nesneyi kaydetmenize olanak tanır. SavedStateRegistry.SavedStateProvider, kaydetmek istediğiniz durumu içeren bir Bundle döndüren tek bir saveState() yöntemini tanımlayan bir arayüzdür. SavedStateHandle, durumunu kaydetmeye hazır olduğunda SavedStateProvider öğesinden Bundle değerini almak için saveState() yöntemini çağırır ve ilişkili anahtar için Bundle değerini kaydeder.

Kamera uygulamasından ACTION_IMAGE_CAPTURE intent'i aracılığıyla resim isteyen ve kameranın resmi depolaması gereken yer için geçici bir dosya ileten bir uygulama örneğini ele alalım. TempFileViewModel, bu geçici dosyayı oluşturma mantığını kapsar.

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

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

Etkinliğin işlemi sonlandırılıp daha sonra geri yüklendiğinde geçici dosyanın kaybolmaması için TempFileViewModel, verilerini kalıcı hale getirmek üzere SavedStateHandle kullanabilir. TempFileViewModel uygulamasının verilerini kaydetmesine izin vermek için SavedStateProvider uygulayın ve bunu ViewModel öğesinin SavedStateHandle öğesinde sağlayıcı olarak ayarlayın:

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

Kullanıcı geri döndüğünde File verilerini geri yüklemek için SavedStateHandle üzerinden temp_file Bundle değerini alın. Bu, saveTempFile() tarafından sağlanan ve mutlak yolu içeren Bundle ile aynıdır. Mutlak yol, yeni bir File örneği oluşturmak için kullanılabilir.

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

Testlerde SavedStateHandle

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

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 Saved State modülü hakkında daha fazla bilgi edinmek istiyorsanız aşağıdaki kaynaklara göz atın.

Codelab uygulamaları

İçerik görüntüleme