Modern kullanıcı arayüzleri nadiren statiktir. Kullanıcı, kullanıcı arayüzüyle etkileşimde bulunduğunda veya uygulamanın yeni veriler görüntülemesi gerektiğinde kullanıcı arayüzünün durumu değişir.
Bu belgede, kullanıcı arayüzü durumunun üretimi ve yönetimi ile ilgili yönergeler açıklanmaktadır. Etkinliğin sonunda:
- Kullanıcı arayüzü durumu oluşturmak için hangi API'leri kullanmanız gerektiğini bilin. Bu, tek yönlü veri akışı ilkelerini izleyerek eyalet sahiplerinizde mevcut olan durum değişikliği kaynaklarının doğasına bağlıdır.
- Sistem kaynaklarının bilincinde olmak için kullanıcı arayüzü durumunun üretim kapsamını nasıl belirlemeniz gerektiğini bilin.
- Kullanıcı arayüzü durumunu, kullanıcı arayüzünün kullanımına nasıl sunmanız gerektiğini öğrenin.
Eyalet üretimi temelde bu değişikliklerin kullanıcı arayüzü durumuna kademeli olarak uygulanmasıdır. Durum her zaman mevcuttur ve etkinlikler sonucunda değişir. Olaylar 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 devam eden | Her zaman vardır. |
Eyalet üretimi girdileri. | Eyalet üretimi çıktısı. |
Kullanıcı arayüzünün veya diğer kaynakların ürünü. | Kullanıcı arayüzü tarafından tüketilir. |
Yukarıdakileri özetleyen en iyi anımsatıcı durumdur; olaylar yaşanır. Aşağıdaki şema, bir zaman çizelgesinde etkinlikler meydana geldikçe durum değişikliklerinin görselleştirilmesine yardımcı olur. Her etkinlik, uygun eyalet sahibi tarafından işlenir ve bir durum değişikliğiyle sonuçlanır:
Etkinlikler şuralardan gelebilir:
- Kullanıcılar: Uygulamanın kullanıcı arayüzüyle etkileşimde bulundukları sırada.
- Diğer durum değişikliği kaynakları: Sırasıyla atıştırmalık çubuğu zaman aşımı etkinlikleri, kullanım alanları veya depolar gibi kullanıcı arayüzünden, alandan veya veri katmanlarından uygulama verileri sunan API'ler.
Kullanıcı arayüzü durumu üretim ardışık düzeni
Android uygulamalarında durum üretimi, aşağıdakileri içeren bir işleme ardışık düzeni olarak düşünülebilir:
- Girişler: Durum değişikliğinin kaynakları. Bunlar şunlar olabilir:
- Kullanıcı arayüzü katmanında yerel: Bunlar, bir kullanıcının görev yönetimi uygulamasında "yapılacaklar" için bir başlık girmesi gibi kullanıcı etkinlikleri veya kullanıcı arayüzü durumunda değişikliklere yol açan kullanıcı arayüzü mantığına erişim sağlayan API'ler olabilir. Örneğin, Jetpack Compose'da
DrawerState
içinopen
yöntemini çağırabilirsiniz. - Kullanıcı arayüzü katmanının dışında: Bunlar, kullanıcı arayüzü durumunda değişikliklere neden olan, alan
veya veri katmanlarından gelen kaynaklardır. Örneğin, bir
NewsRepository
kaynağından yükleme işlemi biten haberler veya diğer etkinlikler. - Yukarıdakilerin bir karışımı.
- Kullanıcı arayüzü katmanında yerel: Bunlar, bir kullanıcının görev yönetimi uygulamasında "yapılacaklar" için bir başlık girmesi gibi kullanıcı etkinlikleri veya kullanıcı arayüzü durumunda değişikliklere yol açan kullanıcı arayüzü mantığına erişim sağlayan API'ler olabilir. Örneğin, Jetpack Compose'da
- Durum sahipleri: Durum değişikliği kaynaklarına iş mantığı ve/veya kullanıcı arayüzü mantığı uygulayan türler ve kullanıcı arayüzü durumu oluşturmak için kullanıcı etkinliklerini işler.
- Çıkış: Kullanıcılara ihtiyaç duydukları bilgileri sağlamak için uygulamanın oluşturabileceği kullanıcı arayüzü durumudur.
Durum üretim API'leri
Ardışık düzenin hangi aşamasında olduğunuza bağlı olarak durum üretiminde kullanılan iki ana API vardır:
Ardışık düzen aşaması | API |
---|---|
Giriş | Kullanıcı arayüzü sorunlarını gidermek amacıyla kullanıcı arayüzü iş parçacığı üzerinde çalışmak için eşzamansız API'leri kullanmanız gerekir. Örneğin, Kotlin ve RxJava'daki eş yordamlar veya akışlar 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österilecek bir kullanıcı arayüzü durumunun olmasını garanti eder |
Bu iki seçenek arasından, giriş için eşzamansız API'nin seçilmesinin durum üretim hattının yapısı üzerinde, çıkış için gözlemlenebilir API seçiminden daha fazla etkisi vardır. Bunun nedeni, girişlerin ardışık düzene uygulanabilecek işleme türünü belirlemesidir.
Devlet üretim hattı derlemesi
Sonraki bölümlerde, çeşitli girişler için en uygun durum üretim teknikleri ve eşleşen çıkış API'leri ele alınmaktadır. Her durum üretim ardışık düzeni, giriş ve çıkışların bir kombinasyonudur ve:
- Yaşam döngüsüne duyarlı: Kullanıcı arayüzünün görünür veya etkin olmadığı durumlarda, durum üretim ardışık düzeni açıkça gerekli olmadığı sürece hiçbir kaynağı tüketmemelidir.
- Kullanımı kolay: Kullanıcı arayüzünün, oluşturulan kullanıcı arayüzü durumunu kolayca oluşturabilmesi gerekir. Durum üretim ardışık düzeni çıkışı için dikkat edilmesi gereken noktalar, View sistemi veya Jetpack Compose gibi farklı View API'leri arasında farklılık gösterir.
Durum üretim ardışık düzenlerindeki girişler
Durum üretim ardışık düzenindeki girişler, durum değişikliği kaynaklarını şunlar aracılığıyla sağlayabilir:
- Eşzamanlı veya eşzamansız olabilen tek seferlik işlemler (ör.
suspend
işlevlerine yapılan çağrılar). - Akış API'leri (örneğin,
Flows
). - Yukarıdakilerin tümü
Aşağıdaki bölümlerde, yukarıdaki girişlerin her biri için nasıl durum üretim ardışık düzeni derleyebileceğiniz ele alınmaktadır.
Durum değişikliği kaynakları olarak tek seferlik API'ler
MutableStateFlow
API'yi gözlemlenebilir ve değişebilir bir durum kapsayıcısı olarak kullanın. Jetpack Compose uygulamalarında, özellikle Compose metin API'leriyle çalışırken mutableStateOf
'i de kullanabilirsiniz. Her iki API de güncellemelerin eşzamanlı veya eşzamansız olup olmamasına bakılmaksızın, barındırdıkları değerlerde güvenli atom güncellemelerine olanak tanıyan yöntemler sunar.
Örneğin, basit bir zar atma uygulamasındaki durum güncellemelerini düşünün. Kullanıcıdan gelen her zar eşzamanlı Random.nextInt()
yöntemini çağırır ve sonuç kullanıcı arayüzü durumuna yazılır.
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
}
}
Kullanıcı arayüzü durumunu eşzamansız çağrılardan değiştirme
eşzamansız sonuç gerektiren durum değişiklikleri için uygun CoroutineScope
içinde bir eş yordam başlatın. Bu izin, CoroutineScope
iptal edildiğinde uygulamanın çalışmayı silmesine olanak tanır. Durum sahibi daha sonra askıya alma yöntemi çağrısının sonucunu, kullanıcı arayüzü durumunu göstermek için kullanılan gözlemlenebilir API'ye yazar.
Örneğin, Mimari örneğindeki AddEditTaskViewModel
öğesini göz önünde bulundurun. saveTask()
yönteminin askıya alınması bir görevi eşzamansız olarak kaydettiğinde MutableStateFlow'daki update
yöntemi 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
Kullanıcı arayüzü durumunun üretimi için ana görev dağıtıcıda eş yordamların başlatılması tercih edilir. Yani aşağıdaki kod snippet'lerinde withContext
bloğunun dışındadır. Ancak kullanıcı arayüzü durumunu farklı bir arka plan bağlamında güncellemeniz gerekiyorsa bunu aşağıdaki API'leri kullanarak yapabilirsiniz:
- Eş yordamları farklı bir eşzamanlı bağlamda çalıştırmak için
withContext
yöntemini kullanın. MutableStateFlow
kullanırken her zamanki gibiupdate
yöntemini kullanın.- Oluşturma Durumu'nu kullanırken, eşzamanlı bağlamda Durum'a atomik güncellemeleri garanti etmek için
Snapshot.withMutableSnapshot
özelliğini kullanın.
Örneğin, aşağıdaki DiceRollViewModel
snippet'inde SlowRandom.nextInt()
öğesinin CPU'ya bağlı bir Coroutine'den çağrılması gereken işlem açısından yoğun bir suspend
işlevi olduğunu varsayalım.
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 akış API'leri
Akışlarda zaman içinde birden fazla değer üreten durum değişikliği kaynakları için tüm kaynakların çıktılarının tutarlı bir bütün halinde toplanması, durum üretimine doğrudan bir yaklaşımdır.
Kotlin Flows'u kullanırken bu amaca ulaşmak için combine işlevini kullanabilirsiniz. Bunun bir örneği, InterestsViewModel'deki "Now Android'de" örneğinde görülebilir:
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ı, yalnızca kullanıcı arayüzü görünür olduğunda etkin olması gerekebileceği için kullanıcı arayüzüne durum üretim hattının etkinliği üzerinde daha ayrıntılı bir kontrol sağlar.
- Ardışık düzenin yalnızca, akışı yaşam döngüsüne duyarlı bir şekilde toplarken kullanıcı arayüzü görünür olduğunda etkin olması gerekiyorsa
SharingStarted.WhileSubscribed()
kullanın. - Ardışık düzenin, kullanıcı kullanıcı arayüzüne dönebileceği, yani kullanıcı arayüzü arka yığında veya ekran dışında başka bir sekmede olduğu sürece etkin olması gerekiyorsa
SharingStarted.Lazily
kullanın.
Akış tabanlı durum kaynaklarını birleştirmenin geçerli olmadığı durumlarda, Kotlin Flows gibi akış API'leri, akışların kullanıcı arayüzü durumuna işlenmesine yardımcı olmak için birleştirme, birleştirme gibi zengin bir dönüşüm grubu sunar.
Durum değişikliği kaynakları olarak tek seferlik ve akış API'leri
Durum üretim ardışık düzeninin durum değişikliği kaynağı olarak hem tek seferlik çağrılara hem de akışlara bağlı olduğu durumlarda belirleyici kısıtlama akışlardır. Bu nedenle, tek seferlik çağrıları akış API'lerine dönüştürün veya bunların çıkışını akışlara aktarıp işlemeye yukarıdaki akışlar bölümünde açıklandığı gibi devam edin.
Akışlar söz konusu olduğunda genellikle durum değişikliklerini yaymak için bir veya daha fazla gizli yedek MutableStateFlow
örneği oluşturmanız gerekir. Oluştur durumundan anlık görüntü akışları da oluşturabilirsiniz.
Aşağıdaki architecture-samples deposundaki TaskDetailViewModel
öğesini düşünün:
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 sunumunun yapısı 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 kullanmayı seçebilirsiniz. Bu aşamada göz önünde bulundurulması gereken noktalar:
- Yaşam döngüsüne duyarlı şekilde okuma durumu.
- Eyaletin, eyalet sahibine ait bir veya daha fazla alanda gösterilmesini isteyip istemediği.
Aşağıdaki tabloda, herhangi bir giriş ve tüketici için durum üretim ardışık düzeniniz için hangi API'lerin kullanılacağı özetlenmektedir:
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ı, ardışık düzenin çalışması için ilk koşulların ayarlanmasını içerir. Bu, ardışık düzenin başlatılması için kritik öneme sahip ilk giriş değerlerinin sağlanmasını (örneğin, bir haber makalesinin ayrıntılı görünümü için id
) veya eşzamansız yük başlatmayı içerebilir.
Sistem kaynaklarını korumak için mümkün olduğunda durum üretim ardışık düzenini gecikmeli olarak başlatmanız gerekir.
Pratikte bunun için genellikle sonuç tüketicisi olana kadar beklemek gerekir. Flow
API'leri buna stateIn
yöntemindeki started
bağımsız değişkeniyle izin verir. Bunun geçerli olmadığı durumlarda, aşağıdaki snippet'te gösterildiği gibi durum üretim hattını açıkça başlatmak için bir idempotent
initialize()
işlevi tanımlayın:
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
}
}
}
Sana Özel
Aşağıdaki Google örnekleri, kullanıcı arayüzü katmanında durum üretimini göstermektedir. Bu kılavuzu inceleyerek örnekleri inceleyin:
Sizin için önerilenler
- Not: Bağlantı metni JavaScript kapalıyken gösterilir
- Kullanıcı arayüzü katmanı
- Çevrimdışı öncelikli bir uygulama geliştirme
- Eyalet sahipleri ve Kullanıcı Arayüzü Durumu {:#mad-arch}