Kullanıcı arayüzü durumunu Compose'da kaydet

Durumunuzun nereye taşındığına ve gereken mantığa bağlı olarak kullanıcı arayüzü durumunuzu depolamak ve geri yüklemek için farklı API'ler kullanabilirsiniz. Her uygulama, bunu en iyi şekilde başarmak için API'lerin bir kombinasyonunu kullanır.

Herhangi bir Android uygulaması, etkinlik veya işlem yeniden oluşturma nedeniyle kullanıcı arayüzü durumunu kaybedebilir. Bu durum kaybı aşağıdaki olaylardan kaynaklanabilir:

Bu etkinliklerden sonra durumun korunması, olumlu bir kullanıcı deneyimi için çok önemlidir. Hangi durumun kalıcı olacağını seçmek, uygulamanızın benzersiz kullanıcı işlemleri akışlarına bağlıdır. En iyi uygulama olarak, en azından kullanıcı girişini ve gezinmeyle ilgili durumu korumanız gerekir. Örneğin, bir listenin kaydırma konumu, kullanıcının daha fazla ayrıntı istediği öğenin kimliği, kullanıcı tercihlerinin devam eden seçimi veya metin alanlarındaki girişler buna örnek olarak verilebilir.

Bu sayfada, durumunuzun nereye taşındığına ve bunu gerektiren mantığa bağlı olarak kullanıcı arayüzü durumunu depolamak için kullanılabilecek API'ler özetlenmektedir.

Kullanıcı arayüzü mantığı

Durumunuz kullanıcı arayüzünde, composable işlevlerde veya oluşturma kapsamındaki basit durum bilgisi depolayıcı sınıflarında üst öğeye taşınmışsa etkinliğin ve işlemin yeniden oluşturulması sırasında durumu korumak için rememberSaveable kullanabilirsiniz.

Aşağıdaki snippet'te, tek bir boole kullanıcı arayüzü öğesi durumunu depolamak için rememberSaveable kullanılır:

@Composable
fun ChatBubble(
    message: Message
) {
    var showDetails by rememberSaveable { mutableStateOf(false) }

    ClickableText(
        text = AnnotatedString(message.content),
        onClick = { showDetails = !showDetails }
    )

    if (showDetails) {
        Text(message.timestamp)
    }
}

Şekil 1. Sohbet mesajı balonu, dokunulduğunda genişler ve daralır.

showDetails, sohbet balonunun daraltılmış mı yoksa genişletilmiş mi olduğunu depolayan bir boole değişkenidir.

rememberSaveable, kaydedilen örnek durumu mekanizması aracılığıyla Bundle içinde kullanıcı arayüzü öğesi durumunu depolar.

İlkel türleri otomatik olarak pakete kaydedebilir. Durumunuz veri sınıfı gibi ilkel olmayan bir türde tutuluyorsa farklı depolama mekanizmaları kullanabilirsiniz. Örneğin, Parcelize ek açıklamasını kullanabilir, listSaver ve mapSaver gibi Compose API'lerini kullanabilir veya Compose çalışma zamanı Saver sınıfını genişleten özel bir kaydedici sınıfı uygulayabilirsiniz. Bu yöntemler hakkında daha fazla bilgi edinmek için Durumu depolama yöntemleri dokümanlarına göz atın.

Aşağıdaki snippet'te, rememberLazyListState Compose API'si, rememberSaveable kullanarak bir LazyColumn veya LazyRow öğesinin kaydırma durumundan oluşan LazyListState öğesini depolar. Kaydırma durumunu depolayabilen ve geri yükleyebilen özel bir kaydedici olan LazyListState.Saver kullanılır. Bir etkinlik veya işlem yeniden oluşturulduktan sonra (örneğin, cihaz yönünü değiştirme gibi bir yapılandırma değişikliğinden sonra) kaydırma durumu korunur.

@Composable
fun rememberLazyListState(
    initialFirstVisibleItemIndex: Int = 0,
    initialFirstVisibleItemScrollOffset: Int = 0
): LazyListState {
    return rememberSaveable(saver = LazyListState.Saver) {
        LazyListState(
            initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset
        )
    }
}

En iyi uygulama

rememberSaveable, kullanıcı arayüzü durumunu depolamak için Bundle kullanır. Bu durum, etkinliğinizdeki onSaveInstanceState() çağrıları gibi diğer API'ler tarafından da paylaşılır. Ancak bu Bundle öğesinin boyutu sınırlıdır ve büyük nesnelerin depolanması, çalışma zamanında TransactionTooLarge istisnalarına yol açabilir. Bu durum, özellikle uygulamanın tamamında aynı Bundle öğesinin kullanıldığı tek Activity uygulamalarında sorunlara yol açabilir.

Bu tür kilitlenmeleri önlemek için büyük karmaşık nesneleri veya nesne listelerini pakette depolamamanız gerekir.

Bunun yerine, kimlikler veya anahtarlar gibi gereken minimum durumu depolayın ve bunları, daha karmaşık kullanıcı arayüzü durumunu kalıcı depolama alanı gibi diğer mekanizmalara geri yüklemek için kullanın.

Bu tasarım seçimleri, uygulamanızın özel kullanım alanlarına ve kullanıcılarınızın uygulamanızdan beklediği davranışlara bağlıdır.

Durum geri yüklemeyi doğrulama

Etkinlik veya işlem yeniden oluşturulduğunda, Compose öğelerinizde rememberSaveable ile depolanan durumun doğru şekilde geri yüklendiğini doğrulayabilirsiniz. Bunu yapmak için StateRestorationTester gibi özel API'ler vardır. Daha fazla bilgi edinmek için Test belgelerine göz atın.

İş mantığı

Kullanıcı arayüzü öğesi durumunuz, iş mantığı gereği ViewModel sınıfına taşındıysa ViewModel API'lerini kullanabilirsiniz.

Android uygulamanızda ViewModel kullanmanın en önemli avantajlarından biri, yapılandırma değişikliklerini ücretsiz olarak işlemesidir. Yapılandırma değişikliği olduğunda ve etkinlik yok edilip yeniden oluşturulduğunda ViewModel'e yükseltilen kullanıcı arayüzü durumu bellekte tutulur. Yeniden oluşturma işleminden sonra eski ViewModel örneği yeni etkinlik örneğine eklenir.

Ancak ViewModel örneği, sistem tarafından başlatılan işlem sonlandırmadan sonra devam etmez. Kullanıcı arayüzü durumunun bu işlemden etkilenmemesi için SavedStateHandle API'sini içeren ViewModel için Saved State modülünü kullanın.

En iyi uygulama

SavedStateHandle, kullanıcı arayüzü durumunu depolamak için Bundle mekanizmasını da kullanır. Bu nedenle, yalnızca basit kullanıcı arayüzü öğesi durumunu depolamak için kullanmanız gerekir.

İş kuralları uygulanarak ve uygulamanızın kullanıcı arayüzü dışındaki katmanlarına erişilerek oluşturulan ekran kullanıcı arayüzü durumu, karmaşık ve büyük olabileceğinden SavedStateHandle içinde depolanmamalıdır. Karmaşık veya büyük verileri depolamak için yerel kalıcı depolama alanı gibi farklı mekanizmalar kullanabilirsiniz. İşlem yeniden oluşturulduktan sonra ekran, SavedStateHandle içinde depolanan geri yüklenen geçici durumla (varsa) yeniden oluşturulur ve ekran kullanıcı arayüzü durumu, veri katmanından tekrar üretilir.

SavedStateHandle API'leri

SavedStateHandle, kullanıcı arayüzü öğesi durumunu depolamak için farklı API'lere sahiptir. Bunlardan en önemlileri şunlardır:

Oluştur State saveable()
StateFlow getStateFlow()

State oluşturun

Kullanıcı arayüzü öğesi durumunu MutableState olarak okumak ve yazmak için SavedStateHandle öğesinin saveable API'sini kullanın. Böylece, minimum kod kurulumuyla etkinlik ve süreç yeniden oluşturma işlemlerinden etkilenmez.

saveable API'si, temel türleri kullanıma hazır olarak destekler ve tıpkı rememberSaveable() gibi özel kaydedicileri kullanmak için bir stateSaver parametresi alır.

Aşağıdaki snippet'te message, TextField içine yazılan kullanıcı girişini depolar:

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

    var message by savedStateHandle.saveable(stateSaver = TextFieldValue.Saver) {
        mutableStateOf(TextFieldValue(""))
    }
        private set

    fun update(newMessage: TextFieldValue) {
        message = newMessage
    }

    /*...*/
}

val viewModel = ConversationViewModel(SavedStateHandle())

@Composable
fun UserInput(/*...*/) {
    TextField(
        value = viewModel.message,
        onValueChange = { viewModel.update(it) }
    )
}

saveable API'yi kullanma hakkında daha fazla bilgi için SavedStateHandle belgelerine bakın.

StateFlow

Kullanıcı arayüzü öğesi durumunu depolamak ve SavedStateHandle'dan akış olarak kullanmak için getStateFlow() kullanın. StateFlow salt okunurdur ve API, yeni bir değer yayınlamak için akışı değiştirebilmeniz amacıyla bir anahtar belirtmenizi gerektirir. Yapılandırdığınız anahtarla StateFlow değerini alabilir ve en son değeri toplayabilirsiniz.

Aşağıdaki snippet'te savedFilterType, bir sohbet uygulamasındaki sohbet kanalları listesine uygulanan filtre türünü depolayan bir StateFlow değişkenidir:

private const val CHANNEL_FILTER_SAVED_STATE_KEY = "ChannelFilterKey"

class ChannelViewModel(
    channelsRepository: ChannelsRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    private val savedFilterType: StateFlow<ChannelsFilterType> = savedStateHandle.getStateFlow(
        key = CHANNEL_FILTER_SAVED_STATE_KEY, initialValue = ChannelsFilterType.ALL_CHANNELS
    )

    private val filteredChannels: Flow<List<Channel>> =
        combine(channelsRepository.getAll(), savedFilterType) { channels, type ->
            filter(channels, type)
        }.onStart { emit(emptyList()) }

    fun setFiltering(requestType: ChannelsFilterType) {
        savedStateHandle[CHANNEL_FILTER_SAVED_STATE_KEY] = requestType
    }

    /*...*/
}

enum class ChannelsFilterType {
    ALL_CHANNELS, RECENT_CHANNELS, ARCHIVED_CHANNELS
}

Kullanıcı yeni bir filtre türü seçtiğinde setFiltering çağrılır. Bu işlem, _CHANNEL_FILTER_SAVED_STATE_KEY_ anahtarıyla depolanan SavedStateHandle alanına yeni bir değer kaydeder. savedFilterType, anahtarda depolanan en son değeri yayan bir akıştır. filteredChannels, kanal filtreleme işlemini gerçekleştirmek için akışa abone oldu.

getStateFlow() API hakkında daha fazla bilgi için SavedStateHandle belgelerine bakın.

Özet

Aşağıdaki tabloda, bu bölümde ele alınan API'ler ve kullanıcı arayüzü durumunu kaydetmek için her birinin ne zaman kullanılacağı özetlenmiştir:

Etkinlik Kullanıcı arayüzü mantığı ViewModel içinde iş mantığı
Yapılandırma değişiklikleri rememberSaveable Otomatik
Sistem tarafından başlatılan işlem sonlandırma rememberSaveable SavedStateHandle

Kullanılacak API, durumun nerede tutulduğuna ve gerektirdiği mantığa bağlıdır. Kullanıcı arayüzü mantığında kullanılan durum için rememberSaveable kullanın. İş mantığında kullanılan durumlar için, durumu bir ViewModel içinde tutuyorsanız SavedStateHandle kullanarak kaydedin.

Küçük miktarlarda kullanıcı arayüzü durumunu depolamak için paket API'lerini (rememberSaveable ve SavedStateHandle) kullanmanız gerekir. Bu veriler, diğer depolama mekanizmalarıyla birlikte kullanıcı arayüzünü önceki durumuna geri yüklemek için gereken minimum verilerdir. Örneğin, kullanıcının baktığı bir profilin kimliğini pakette saklıyorsanız profil ayrıntıları gibi ağır verileri veri katmanından getirebilirsiniz.

Kullanıcı arayüzü durumunu kaydetmenin farklı yolları hakkında daha fazla bilgi için genel Kullanıcı Arayüzü Durumunu Kaydetme dokümanlarına ve mimari kılavuzunun veri katmanı sayfasına bakın.