ViewModel için Kayıtlı 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, rotasyonlarda veya diğer durumlarda durum hakkında endişelenmenize gerek yoktur. Ancak sistem tarafından başlatılan işlem sonlandırma işlemini ele almanız gerekiyorsa yedek olarak SavedStateHandle API'yi kullanabilirsiniz.

Kullanıcı arayüzü durumu genellikle etkinliklerde değil, ViewModel nesnelerinde depolanır veya bu nesnelere referans verilir. Bu nedenle, onSaveInstanceState() veya rememberSaveable kullanmak için kayıtlı durum modülünün sizin için halledebileceği bazı şablonlar gerekir.

Bu modül kullanılırken ViewModel nesneleri, yapıcıları aracılığıyla bir SavedStateHandle nesnesi alır. Bu nesne, kayıtlı durumdaki nesneleri yazıp almanıza olanak tanıyan bir anahtar/değer haritası. Bu değerler, süreç sistem tarafından sonlandırıldıktan sonra da aynı nesneyle kullanılabilir durumda kalır.

Kayıtlı durum, görev grubunuza bağlıdır. Görev grubunuzu kaldırırsanız kaydedilen durumunuz da kaldırılır. Bu durum, bir uygulamayı zorla durdurduğunuzda, uygulamayı son uygulamalar menüsünden kaldırdığınızda veya cihazı yeniden başlattığınızda ortaya çıkabilir. Bu gibi durumlarda görev grubu kaybolur ve bilgileri kayıtlı durumda geri yükleyemezsiniz. Kullanıcı tarafından başlatılan kullanıcı arayüzü durumunun kapatılması senaryolarında, kayıtlı durum geri yüklenmez. Sistem tarafından başlatılan senaryolarda bu geçerlidir.

Kurulum

Fragment 1.2.0 veya onun geçişli bağımlılığı Activity 1.1.0'dan itibaren, ViewModel için bir kurucu bağımsız değişkeni olarak SavedStateHandle 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 yapmadan ViewModel örneğinizi alabilirsiniz. Varsayılan ViewModel fabrikası, ViewModel cihazınız için 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 bir ViewModelProvider.Factory örneği sağlarken AbstractSavedStateViewModelFactory öğesini genişleterek SavedStateHandle kullanımını etkinleştirebilirsiniz.

SavedStateHandle ile çalışma

SavedStateHandle sınıfı, set() ve get() yöntemleri aracılığıyla kayıtlı durumdaki verileri yazmanıza ve bu verileri bu durumdan almanıza olanak tanıyan bir anahtar/değer haritası.

SavedStateHandle kullanıldığında sorgu değeri, işlem sona erdiğinde korunur. Böylece kullanıcı, yeniden oluşturma işleminden önce ve sonra aynı filtrelenmiş veri grubunu görür. Bu durumda, etkinliğin veya parçanın bu değeri manuel olarak kaydetmesi, geri yüklemesi ve ViewModel'e geri göndermesi gerekmez.

SavedStateHandle, anahtar/değer haritalarıyla etkileşimde bulunurken kullanabileceğ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 tutucusu kullanarak SavedStateHandle öğesinden değer alabilirsiniz. Desteklenen türlerin listesi:

LiveData

getLiveData() kullanılarak SavedStateHandle kaynağından LiveData gözlemlenebilir bir değere sarmalanmış değerleri alın. Anahtarın değeri güncellendiğinde LiveData yeni değeri alır. Değer genellikle kullanıcı etkileşimleri nedeniyle belirlenir (ör. bir veri listesini filtrelemek için sorgu girme). Bu güncellenmiş değer daha sonra LiveData değerini dönüştürmek 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);
    }
}

StateFlow

getStateFlow() kullanılarak SavedStateHandle kaynağından StateFlow gözlemlenebilir bir değere sarmalanmış değerleri alın. Anahtarın değerini güncellediğinizde StateFlow yeni değeri alır. Değeri genellikle kullanıcı etkileşimlerine göre ayarlayabilirsiniz (ör. bir veri listesini filtrelemek için sorgu girme). Ardından, 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 Oluşturma'nın durum desteği

lifecycle-viewmodel-compose yapı, SavedStateHandle ile Compose'un Saver arasında birlikte çalışabilirliğe olanak tanıyan deneysel saveable API'lerini sağlar. Böylece, özel bir Saver ile rememberSaveable üzerinden kaydedebileceğiniz tüm State'leri SavedStateHandle ile de kaydedebilirsiniz.

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

Bir SavedStateHandle içinde tutulan veriler, etkinlik veya parça için savedInstanceState'in geri kalanıyla birlikte Bundle olarak kaydedilir ve geri yüklenir.

Doğrudan desteklenen türler

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

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 listedekilerden birini genişletmiyorsa @Parcelize Kotlin ek açıklamasını ekleyerek veya Parcelable doğrudan uygulayarak sınıfı paketlenebilir hale getirebilirsiniz.

Bölünemeyen sınıfları kaydetme

Bir sınıf Parcelable veya Serializable'ü uygulamazsa ve bu arayüzlerden birini uygulamak için değiştirilemezse söz konusu 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 Bundle olarak kaydetme ve geri yükleme 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 döndüren tek bir saveState() yöntemi tanımlayan bir arayüzdür. SavedStateHandle durumunu kaydetmeye hazır olduğunda SavedStateProvider'dan Bundle'yi almak için saveState()'u çağırır ve ilişkili anahtar için Bundle'yi kaydeder.

ACTION_IMAGE_CAPTURE intent'i aracılığıyla kamera uygulamasından resim isteyen ve kameranın resmi nereye sakladığını belirten geçici bir dosya gönderen bir uygulama örneğini düşünün. TempFileViewModel, bu geçici dosyayı oluşturma mantığını kapsar.

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

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 bölümünde 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 geldiğinde File verilerini geri yüklemek için SavedStateHandle'tan temp_file Bundle'yi alın. Bu, saveTempFile() tarafından sağlanan ve mutlak yolu içeren Bundle ile aynıdır. Daha sonra mutlak yol, yeni bir File örneği oluşturmak 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 SavedStateHandle

Bağımlılık olarak SavedStateHandle alan bir ViewModel'yi test etmek için, ihtiyaç duyduğu test değerlerini içeren yeni bir SavedStateHandle örneği oluşturun ve 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 Kayıtlı Durum modülü hakkında daha fazla bilgi edinmek isterseniz aşağıdaki kaynaklara göz atın.

Codelab uygulamaları