Kullanıcı Arayüzü Durumu üretimi

Modern kullanıcı arayüzleri nadiren statiktir. Kullanıcı arayüzün durumu, kullanıcı arayüzüyle veya uygulamanın yeni veriler görüntülemesi gerektiğinde etkileşime girdiğinde.

Bu belgede, kullanıcı arayüzü üretimi ve yönetimi için yönergeler açıklanmaktadır durumu. Belgenin sonunda şunları yapmalısınız:

  • Kullanıcı arayüzü durumu oluşturmak için hangi API'leri kullanmanız gerektiğini bilin. Bu işlem, devlet sahiplerinizin elinde bulunan durum değişikliği kaynaklarının niteliği, tek yönlü veri akışı ilkelerini uygulayın.
  • Kullanıcı arayüzü durumu üretiminin kapsamını nasıl belirlemeniz gerektiğini sistem kaynakları.
  • Kullanıcı arayüzü kullanımı için kullanıcı arayüzü durumunu nasıl göstereceğinizi öğrenin.

Esasen, eyalet üretimi bu değişikliklerin artımlı uygulanmasıdır. bunu değiştirebilirsiniz. Durum her zaman vardır ve etkinliklerin sonucunda değişir. İlgili içeriği oluşturmak için kullanılan etkinlikler ve durum arasındaki farklar aşağıdaki tabloda özetlenmiştir:

Etkinlikler Eyalet
Geçici, öngörülemez ve sınırlı bir süre boyunca var olan bir değişimdir. Her zaman vardır.
Durum üretimi girişleri. Durum üretimi çıktısı.
Kullanıcı arayüzü veya diğer kaynakların ürünü. Kullanıcı arayüzü tarafından kullanılır.

Yukarıdakileri özetleyen mükemmel bir hatırlatma: durum; nasıl meydana geldiğine bakalım. İlgili içeriği oluşturmak için kullanılan diyagram, olaylar bir zaman çizelgesinde gerçekleştikçe, durumdaki değişikliklerin görselleştirilmesine yardımcı olur. Her etkinlik, uygun eyalet sahibi tarafından işlenir ve durum değişikliği:

Etkinlikler ve durum
Şekil 1: Etkinlikler durumun değişmesine neden olur

Etkinliklerin kaynağı:

  • Kullanıcılar: Uygulamanın kullanıcı arayüzüyle etkileşim kurarken.
  • Diğer durum değişikliği kaynakları: Kullanıcı arayüzünden uygulama verileri sunan API'ler. veya atıştırmalık çubuğu zaman aşımı etkinlikleri, kullanım alanları veya kod depoları bulunur.

Kullanıcı arayüzü durumu üretim ardışık düzeni

Android uygulamalarında durum üretimi, bir işleme ardışık düzeni olarak düşünülebilir şunları içerir:

  • Girişler: Durum değişikliğinin kaynakları. Bu ayarlar şunlardan biri olabilir:
    • Kullanıcı arayüzü katmanında yerel: Bunlar, bir kullanıcının "yapılacak" başlığı API’larda da gösterilebilir. Kullanıcı arayüzü durumundaki değişiklikleri tetikleyen kullanıcı arayüzü mantığına erişim. Örneğin, Jetpack Compose'daki DrawerState ile ilgili open yöntemi çağrılıyor.
    • Kullanıcı arayüzü katmanının harici: Bunlar, alan adından veya verilerden gelen kaynaklardır katmanlardan daha az katmanla seçim yapabilirsiniz. Örneğin, sona ermiş haberler NewsRepository veya başka bir etkinlikten yükleniyor.
    • Yukarıdakilerin bir karışımı.
  • Eyalet sahipleri: İş mantığı ve/veya geçerli olan türler Durum değişikliğinin kaynaklarına kullanıcı arayüzü mantığı ve kullanıcı etkinliklerini üreterek işler Kullanıcı arayüzü durumu.
  • Çıkış: Kullanıcılara anlayabiliyorsunuz.
ziyaret edin.
Durum üretim ardışık düzeni
Şekil 2: Durum üretim ardışık düzeni

Durum üretim API'leri

Eyalet üretiminde kullanılan aşamaya bağlı olarak iki ana API vardır. mevcut iş hattınız:

Ardışık düzen aşaması API
Giriş Kullanıcı arayüzü iş parçacığının olumsuz etkilenmemesi amacıyla kullanıcı arayüzü iş parçacığı üzerinde çalışmak için eşzamansız API'ler kullanmanız gerekir. Örneğin, Kotlin'deki Kotlinler veya Akışlar, RxJava ya da Java Programlama Dili'ndeki geri çağırmalar.
Çıkış Durum değiştiğinde kullanıcı arayüzünü geçersiz kılmak ve yeniden oluşturmak için gözlemlenebilir veri sahibi API'lerini kullanmanız gerekir. Örneğin, StateFlow, Compose State veya LiveData. Gözlemlenebilir veri sahipleri, kullanıcı arayüzünün her zaman ekranda görüntülenecek bir kullanıcı arayüzü durumuna sahip olacağını garanti eder

Giriş için eşzamansız API'nin seçilmesi, bu iki yöntemden daha fazla etkiye sahiptir: gözlemlenebilir API seçiminden çok durum üretim hattının yapısı çıktı. Bunun nedeni, girişlerin etkili olabilecek ardışık düzene uygulanması gerekir.

Durum üretim ardışık düzeni montajı

Sonraki bölümlerde, birçok farklı işletme türüne en uygun devlet üretim tekniklerini ve eşleşen çıkış API'leri gösterilir. Her eyalet üretim ardışık düzeni bir giriş ve çıkışlarının birleşiminden oluşur ve şöyle olmalıdır:

  • Yaşam döngüsüne duyarlı: Kullanıcı arayüzünün görünür veya etkin olmadığı durumlarda, devlet üretim ardışık düzeni, açıkça incelenmediği sürece gereklidir.
  • Kullanımı kolay: Kullanıcı arayüzü, üretilen kullanıcı arayüzünü kolayca oluşturabilmelidir. durumu. Eyalet üretim ardışık düzeninin çıktısıyla ilgili dikkat edilmesi gereken noktalar View sistemi veya Jetpack Compose gibi farklı View API'lerinde değişiklik gösterebilir.
ziyaret edin.

Durum üretim ardışık düzenlerindeki girişler

Durum üretim hattındaki girişler, kendi durum kaynaklarını sağlayabilir şununla değiştir:

  • Eşzamanlı veya eşzamansız olabilecek tek seferlik işlemler (ör. suspend işlevlerine çağrı.
  • Akış API'leri (ör. Flows).
  • Yukarıdakilerin tümü.

Aşağıdaki bölümlerde durum üretim ardışık düzenini nasıl derleyebileceğiniz açıklanmaktadır yukarıdaki girişlerin her biri için.

Durum değişikliği kaynakları olarak tek seferlik API'ler

MutableStateFlow API'yi gözlemlenebilir ve değişebilir. durum kapsayıcısıdır. Jetpack Compose uygulamalarında şu içerikleri de kullanabilirsiniz: mutableStateOf, özellikle de Metin API'leri oluşturun. Her iki API de güvenli olan yöntemler sunar. olmalarına bakılmaksızın barındırdıkları değerlerde yapılacak atomik güncellemeler senkronize edilebilir.

Örneğin, basit bir zar atma uygulamasında durum güncellemelerini ele alabilirsiniz. Her zar Kullanıcının zarı eşzamanlı Random.nextInt() yöntemini kullanır ve sonuç, Kullanıcı arayüzü durumu.

Durum Akışı

data class DiceUiState(
    val firstDieValue: Int? = null,
    val secondDieValue: Int? = null,
    val numberOfRolls: Int = 0,
)

class DiceRollViewModel : ViewModel() {

    private val _uiState = MutableStateFlow(DiceUiState())
    val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()

    // Called from the UI
    fun rollDice() {
        _uiState.update { currentState ->
            currentState.copy(
            firstDieValue = Random.nextInt(from = 1, until = 7),
            secondDieValue = Random.nextInt(from = 1, until = 7),
            numberOfRolls = currentState.numberOfRolls + 1,
            )
        }
    }
}

Oluşturma Durumu

@Stable
interface DiceUiState {
    val firstDieValue: Int?
    val secondDieValue: Int?
    val numberOfRolls: Int?
}

private class MutableDiceUiState: DiceUiState {
    override var firstDieValue: Int? by mutableStateOf(null)
    override var secondDieValue: Int? by mutableStateOf(null)
    override var numberOfRolls: Int by mutableStateOf(0)
}

class DiceRollViewModel : ViewModel() {

    private val _uiState = MutableDiceUiState()
    val uiState: DiceUiState = _uiState

    // Called from the UI
    fun rollDice() {
        _uiState.firstDieValue = Random.nextInt(from = 1, until = 7)
        _uiState.secondDieValue = Random.nextInt(from = 1, until = 7)
        _uiState.numberOfRolls = _uiState.numberOfRolls + 1
    }
}

Eşzamansız çağrılarda kullanıcı arayüzü durumunu değiştirme

Eşzamansız sonuç gerektiren durum değişiklikleri için uygun CoroutineScope. Bu sayede, CoroutineScope iptal edildi. Eyalet sahibi daha sonra askıya alma yöntemi çağrısı, kullanıcı arayüzü durumunu göstermek için kullanılan gözlemlenebilir API'ye

Örneğin, AddEditTaskViewModel Mimari örneği. Askıya alma saveTask() yöntemi bir görevi eşzamansız olarak kaydeder.update MutableStateFlow, durum değişikliğini kullanıcı arayüzü durumuna yayar.

Durum Akışı

data class AddEditTaskUiState(
    val title: String = "",
    val description: String = "",
    val isTaskCompleted: Boolean = false,
    val isLoading: Boolean = false,
    val userMessage: String? = null,
    val isTaskSaved: Boolean = false
)

class AddEditTaskViewModel(...) : ViewModel() {

   private val _uiState = MutableStateFlow(AddEditTaskUiState())
   val uiState: StateFlow<AddEditTaskUiState> = _uiState.asStateFlow()

   private fun createNewTask() {
        viewModelScope.launch {
            val newTask = Task(uiState.value.title, uiState.value.description)
            try {
                tasksRepository.saveTask(newTask)
                // Write data into the UI state.
                _uiState.update {
                    it.copy(isTaskSaved = true)
                }
            }
            catch(cancellationException: CancellationException) {
                throw cancellationException
            }
            catch(exception: Exception) {
                _uiState.update {
                    it.copy(userMessage = getErrorMessage(exception))
                }
            }
        }
    }
}

Oluşturma Durumu

@Stable
interface AddEditTaskUiState {
    val title: String
    val description: String
    val isTaskCompleted: Boolean
    val isLoading: Boolean
    val userMessage: String?
    val isTaskSaved: Boolean
}

private class MutableAddEditTaskUiState : AddEditTaskUiState() {
    override var title: String by mutableStateOf("")
    override var description: String by mutableStateOf("")
    override var isTaskCompleted: Boolean by mutableStateOf(false)
    override var isLoading: Boolean by mutableStateOf(false)
    override var userMessage: String? by mutableStateOf<String?>(null)
    override var isTaskSaved: Boolean by mutableStateOf(false)
}

class AddEditTaskViewModel(...) : ViewModel() {

   private val _uiState = MutableAddEditTaskUiState()
   val uiState: AddEditTaskUiState = _uiState

   private fun createNewTask() {
        viewModelScope.launch {
            val newTask = Task(uiState.value.title, uiState.value.description)
            try {
                tasksRepository.saveTask(newTask)
                // Write data into the UI state.
                _uiState.isTaskSaved = true
            }
            catch(cancellationException: CancellationException) {
                throw cancellationException
            }
            catch(exception: Exception) {
                _uiState.userMessage = getErrorMessage(exception))
            }
        }
    }
}

Arka plandaki iş parçacıklarından kullanıcı arayüzü durumunu değiştirme

Eş yordamların üretimin ana sevk görevlisinde başlatılması tercih edilir durumunu gösterir. Yani, kod snippet'lerindeki withContext bloğunun dışında bölümüne göz atın. Ancak, kullanıcı arayüzü durumunu farklı bir arka planda güncellemeniz gerekirse aşağıdaki API'leri kullanarak bunu yapabilirsiniz:

  • Eş yordamları çalıştırmak için withContext yöntemini kullanın farklı eşzamanlı bağlam
  • MutableStateFlow kullanılırken update yöntemini şu şekilde kullanın: her zamanki gibi.
  • Oluşturma Durumunu kullanırken Snapshot.withMutableSnapshot kullanarak Eş zamanlı bağlamda Durum güncellemelerini garanti edebilir.

Örneğin aşağıdaki DiceRollViewModel snippet'inde SlowRandom.nextInt(), işlem yükü açısından yoğun bir suspend fonksiyonunu çağırın.

Durum Akışı

class DiceRollViewModel(
    private val defaultDispatcher: CoroutineScope = Dispatchers.Default
) : ViewModel() {

    private val _uiState = MutableStateFlow(DiceUiState())
    val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()

  // Called from the UI
  fun rollDice() {
        viewModelScope.launch() {
            // Other Coroutines that may be called from the current context
            …
            withContext(defaultDispatcher) {
                _uiState.update { currentState ->
                    currentState.copy(
                        firstDieValue = SlowRandom.nextInt(from = 1, until = 7),
                        secondDieValue = SlowRandom.nextInt(from = 1, until = 7),
                        numberOfRolls = currentState.numberOfRolls + 1,
                    )
                }
            }
        }
    }
}

Oluşturma Durumu

class DiceRollViewModel(
    private val defaultDispatcher: CoroutineScope = Dispatchers.Default
) : ViewModel() {

    private val _uiState = MutableDiceUiState()
    val uiState: DiceUiState = _uiState

  // Called from the UI
  fun rollDice() {
        viewModelScope.launch() {
            // Other Coroutines that may be called from the current context
            …
            withContext(defaultDispatcher) {
                Snapshot.withMutableSnapshot {
                    _uiState.firstDieValue = SlowRandom.nextInt(from = 1, until = 7)
                    _uiState.secondDieValue = SlowRandom.nextInt(from = 1, until = 7)
                    _uiState.numberOfRolls = _uiState.numberOfRolls + 1
                }
            }
        }
    }
}

Durum değişikliği kaynakları olarak API'lerin akışı

Akışlarda zaman içinde birden çok değer oluşturan durum değişikliği kaynakları için Tüm kaynakların çıktılarının çıktılarını tutarlı bir bütün halinde toplamak, basit bir yaklaşım ortaya koyduk.

Kotlin Akışları'nı kullanırken bunu birleştirme işlevini kullanın. Bunun bir örneğini "Artık Android'de" sample'ı ekleyin:

class InterestsViewModel(
    authorsRepository: AuthorsRepository,
    topicsRepository: TopicsRepository
) : ViewModel() {

    val uiState = combine(
        authorsRepository.getAuthorsStream(),
        topicsRepository.getTopicsStream(),
    ) { availableAuthors, availableTopics ->
        InterestsUiState.Interests(
            authors = availableAuthors,
            topics = availableTopics
        )
    }
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = InterestsUiState.Loading
    )
}

StateFlows oluşturmak için stateIn operatörünün kullanılması, kullanıcı arayüzünün daha ayrıntılı olmasını sağlar. resmi üretim hattının faaliyetleri üzerinde kontrol sahibi olmayı yalnızca kullanıcı arayüzü görünür olduğunda etkin olmalıdır.

  • Ardışık düzenin yalnızca etkin olması gerekiyorsa SharingStarted.WhileSubscribed() kullanın yaşam döngüsüne duyarlı bir şekilde akışı toplarken kullanıcı arayüzü görünür olduğunda şekilde ele alacağız.
  • Ardışık düzeninSharingStarted.Lazily kullanıcı arayüzüne geri dönebilir. Yani, kullanıcı arayüzü geri yığınında tıklayın.

Akışa dayalı durum kaynaklarını toplamanın geçerli olmadığı durumlarda, akış Kotlin Flows gibi API'ler, dijital reklam izlemede olduğu gibi birleştirme, birleştirme vb. akışları kullanıcı arayüzü durumuna işleme konusunda yardım alabilirsiniz.

Durum değişikliği kaynakları olarak tek seferlik ve akış API'leri

Durum üretim hattının her iki tek seferlik çağrıya bağlı olduğu durumlarda ve akışlar, durum değişikliği kaynağı olarak akışlar ise belirleyici kısıtlamadır. Bu nedenle, tek seferlik çağrıları akışlara dönüştürün. API'ler veya çıkışları akışlara bağlayın ve açıklanan şekilde işlemeyi devam ettirin. "İçerikler ve Denemeler"i seçin.

Akışlar söz konusu olduğunda genellikle bir veya daha fazla gizli arka plan Durum değişikliklerini yaymak için MutableStateFlow örnek. Ayrıca transkriptinizi Oluştur durumundan anlık görüntü akışları oluşturabilirsiniz.

Şuradan TaskDetailViewModel kullanabilirsiniz: architecture-samples deposu aşağıdaki gibidir:

Durum Akışı

class TaskDetailViewModel @Inject constructor(
    private val tasksRepository: TasksRepository,
    savedStateHandle: SavedStateHandle
) : ViewModel() {

    private val _isTaskDeleted = MutableStateFlow(false)
    private val _task = tasksRepository.getTaskStream(taskId)

    val uiState: StateFlow<TaskDetailUiState> = combine(
        _isTaskDeleted,
        _task
    ) { isTaskDeleted, task ->
        TaskDetailUiState(
            task = taskAsync.data,
            isTaskDeleted = isTaskDeleted
        )
    }
        // Convert the result to the appropriate observable API for the UI
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = TaskDetailUiState()
        )

    fun deleteTask() = viewModelScope.launch {
        tasksRepository.deleteTask(taskId)
        _isTaskDeleted.update { true }
    }
}

Oluşturma Durumu

class TaskDetailViewModel @Inject constructor(
    private val tasksRepository: TasksRepository,
    savedStateHandle: SavedStateHandle
) : ViewModel() {

    private var _isTaskDeleted by mutableStateOf(false)
    private val _task = tasksRepository.getTaskStream(taskId)

    val uiState: StateFlow<TaskDetailUiState> = combine(
        snapshotFlow { _isTaskDeleted },
        _task
    ) { isTaskDeleted, task ->
        TaskDetailUiState(
            task = taskAsync.data,
            isTaskDeleted = isTaskDeleted
        )
    }
        // Convert the result to the appropriate observable API for the UI
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = TaskDetailUiState()
        )

    fun deleteTask() = viewModelScope.launch {
        tasksRepository.deleteTask(taskId)
        _isTaskDeleted = true
    }
}

Durum üretim ardışık düzenlerindeki çıkış türleri

Kullanıcı arayüzü durumu için çıkış API'sinin seçimi ve gösterilme şekli büyük ölçüde uygulamanızın kullanıcı arayüzünü oluşturmak için kullandığı API'ye bağlıdır. Android uygulamalarında, Görünümler veya Jetpack Compose'u seçebilir. Dikkat edilmesi gereken noktalar şunlardır:

Aşağıdaki tabloda durum üretiminiz için hangi API'lerin kullanılacağı özetlenmiştir. iş akış düzeni oluşturmaya devam eder:

Giriş Tüketici Çıkış
Tek seferlik API'ler Görüntüleme sayısı StateFlow veya LiveData
Tek seferlik API'ler Oluştur StateFlow veya Oluştur State
Akış API'leri Görüntüleme sayısı StateFlow veya LiveData
Akış API'leri Oluştur StateFlow
Tek seferlik ve akış API'leri Görüntüleme sayısı StateFlow veya LiveData
Tek seferlik ve akış API'leri Oluştur StateFlow

Durum üretim ardışık düzeni başlatma

Durum üretim ardışık düzenlerinin başlatılması, başlangıç koşullarının ayarlanmasını içerir Ardışık düzenin çalışmasını sağlar. Bu işlem, başlangıçtaki giriş değerlerinin sağlanmasını içerebilir Ardışık düzenin başlatılması açısından kritik öneme sahiptir, örneğin bir id görüntüleme veya eşzamansız yükleme başlatma.

Mümkün olduğunda durum üretim ardışık düzenini gecikmeli olarak ilk kullanıma hazırlamanız gerekir. koruyabilirsiniz. Pratikte bu, çoğu zaman söz konusu ürünün tüketicisi çıktı. Flow API'leri stateIn içindeki started bağımsız değişkeni yöntemidir. Bunun geçerli olmadığı durumlarda, idempotent tanımlayın Durum üretim ardışık düzenini açıkça başlatmak için initialize() işlevi aşağıdaki snippet'te gösterildiği gibidir:

class MyViewModel : ViewModel() {

    private var initializeCalled = false

    // This function is idempotent provided it is only called from the UI thread.
    @MainThread
    fun initialize() {
        if(initializeCalled) return
        initializeCalled = true

        viewModelScope.launch {
            // seed the state production pipeline
        }
    }
}

Örnekler

Aşağıdaki Google örnekleri, Amerika Birleşik Devletleri'ndeki kullanıcı arayüzü katmanı. Uygulamadaki bu rehberliği görmek için bu yöntemleri inceleyin:

ziyaret edin.