ViewModel için Kaydedilmiş 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, döndürme veya diğer durumlarda durumla ilgili endişelenmenize gerek yoktur. Ancak sistem tarafından başlatılan süreç sonlandırmalarını işlemeniz gerekiyorsa yedek olarak SavedStateHandle API'sini kullanabilirsiniz.

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

Bu modül kullanılırken ViewModel nesneleri, oluşturucusu aracılığıyla SavedStateHandle nesnesi alır. Bu nesne, kaydedilmiş duruma nesne yazmanıza ve nesne almanıza olanak tanıyan bir anahtar/değer çifti haritasıdır. Bu değerler, işlem sistem tarafından sonlandırıldıktan sonra da kalıcı olur ve aynı nesne üzerinden kullanılmaya devam eder.

Kaydedilen durum, görev yığınına bağlıdır. Görev yığınınız kaybolursa kaydedilen durumunuz da kaybolur. Bu durum, bir uygulama zorla durdurulduğunda, 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ü durumunu kapatma senaryolarında, kaydedilen durum geri yüklenmez. Sistem tarafından başlatılan senaryolarda bu durum geçerlidir.

Kurulum

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

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

Daha sonra, ek yapılandırma yapmadan composable'larınızda ViewModel örneğini alabilirsiniz. Varsayılan ViewModel fabrikası, ViewModel için uygun SavedStateHandle değerini 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öntemleriyle kaydedilmiş durumdan veri yazmanıza ve veri almanıza olanak tanıyan bir anahtar/değer eşlemesidir.

SavedStateHandle kullanıldığında sorgu değeri, işlem sonlandığında korunur. Böylece, etkinlik veya parçanın bu değeri manuel olarak kaydetmesi, geri yüklemesi ve SavedStateHandle'ya geri iletmesi gerekmeden, kullanıcı yeniden oluşturmadan önce ve sonra aynı filtrelenmiş veri grubunu görür.ViewModel

SavedStateHandle ayrıca bir anahtar/değer çifti haritasıyla etkileşimde bulunurken bekleyebileceğiniz başka yöntemler de içerir:

  • contains(String key): Belirli bir anahtar için değer olup olmadığını kontrol eder.
  • remove(String key): Belirli bir 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 değerlerini de alabilirsiniz. Desteklenen türlerin listesinde şunlar yer alır:

StateFlow

SavedStateHandle hizmetinden StateFlow observable içine sarılmış değerleri alabilirsiniz. Değeri doğrudan değiştirmeniz gerekip gerekmediğine bağlı olarak salt okunur veya değiştirilebilir bir akış seçebilirsiniz:

  • 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. Bu, salt okunur bir akışı kullanıma sunmak ve akış operatörlerini kullanarak dönüştürmek istediğinizde 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 alınan SavedStateHandle öğesini otomatik olarak günceller. Böylece anahtarı manuel olarak ayarlamanız gerekmez.

Bu değerleri en sık olarak, 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çine kalıcı olarak yerleştirmenize olanak tanır. Bu, ViewModel'inizin durumunu işlem sonlandırma boyunca korur. Böylece Compose kullanıcı arayüzünüz, yeniden oluşturulduğunda durumunu sorunsuz bir şekilde geri yükleyebilir.

Bu işlevi kullanmak için veri sınıfınıza @Serializable ekleyin ve ViewModel'inizde saved delegesini 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 arasında birlikte çalışabilirlik sağlanır. Böylece, rememberSaveable üzerinden özel bir Saver ile kaydedebileceğiniz tüm State, 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 savedInstanceState geri kalanıyla birlikte Bundle olarak kaydedilir ve geri yüklenir.

Doğrudan desteklenen türler

Varsayılan olarak, aşağıdaki örnekte gösterildiği gibi set() ve get() işlevlerini SavedStateHandle üzerinde Bundle ile aynı veri türleri için ç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 alan sınıflardan birini genişletmiyorsa @Parcelize Kotlin ek açıklamasını ekleyerek veya Parcelable doğrudan uygulayarak sınıfı paketlenebilir hale getirmeyi düşünebilirsiniz.

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'ten 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 durumu kaydetmeye hazır olduğunda saveState() işlevini çağırarak SavedStateProvider içindeki Bundle değerini alır ve ilişkili anahtar için Bundle değerini kaydeder.

Kamera uygulamasından ACTION_IMAGE_CAPTURE intent'i aracılığıyla resim isteyen bir uygulamayı ele alalım. Bu uygulama, kameranın resmi depolayacağı yer için geçici bir dosya gönderir. 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üklenirse 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 ViewModel uygulamasının SavedStateHandle üzerinde 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 temp_file Bundle değerini SavedStateHandle konumundan alın. Bu, mutlak yolu içeren saveTempFile() tarafından sağlanan Bundle ile aynıdır. Daha sonra mutlak yol, yeni bir File 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

SavedStateHandle bağımlılığını kullanan bir ViewModel işlevini test etmek için SavedStateHandle işlevinin yeni bir örneğini, gerektirdiği test değerleriyle 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 Kayıtlı Durum modülü hakkında daha fazla bilgi edinmek için aşağıdaki kaynaklara bakın.

Codelab uygulamaları

İçeriği görüntüleme