Kullanıcı arayüzü etkinlikleri, kullanıcı arayüzü katmanında kullanıcı arayüzü veya ViewModel tarafından işlenmesi gereken işlemlerdir. En yaygın etkinlik türü kullanıcı etkinlikleridir. Kullanıcı, uygulamayla etkileşim kurarak (ör. ekrana dokunarak veya hareketler oluşturarak) kullanıcı etkinlikleri oluşturur. Kullanıcı arayüzü daha sonra bu etkinlikleri, farklı composable'larda tanımlanan lambda gibi geri çağırma işlevlerini kullanarak tüketir.
ViewModel, normalde belirli bir kullanıcı etkinliğinin (ör. kullanıcının bazı verileri yenilemek için bir düğmeyi tıklaması) iş mantığını işlemeyle sorumludur. Genellikle ViewModel, kullanıcı arayüzünün çağırabileceği işlevleri kullanıma sunarak bu durumu yönetir. Kullanıcı etkinlikleri, kullanıcı arayüzünün doğrudan işleyebileceği kullanıcı arayüzü davranış mantığına da sahip olabilir. Örneğin, farklı bir ekrana gitme veya Snackbar gösterme.
İş mantığı, farklı mobil platformlardaki veya form faktörlerindeki aynı uygulama için aynı kalsa da kullanıcı arayüzü davranış mantığı, bu durumlar arasında farklılık gösterebilecek bir uygulama ayrıntısıdır. Kullanıcı arayüzü katmanı sayfası, bu mantık türlerini aşağıdaki gibi tanımlar:
- İş mantığı, durum değişiklikleriyle ne yapılacağını ifade eder. Örneğin, ödeme yapma veya kullanıcı tercihlerini saklama. Bu mantık genellikle alan ve veri katmanları tarafından işlenir. Bu kılavuzda, iş mantığını işleyen sınıflar için Architecture Components ViewModel sınıfı, belirli bir görüşe dayalı çözüm olarak kullanılır.
- Kullanıcı arayüzü davranış mantığı veya kullanıcı arayüzü mantığı, durum değişikliklerinin nasıl gösterileceğini ifade eder. Örneğin, gezinme mantığı veya kullanıcılara mesajların nasıl gösterileceği. Bu mantıkla kullanıcı arayüzü ilgilenir.
Kullanıcı arayüzü etkinliği karar ağacı
Aşağıdaki diyagramda, belirli bir etkinlik kullanım alanını ele almak için en iyi yaklaşımı bulmaya yönelik bir karar ağacı gösterilmektedir. Bu kılavuzun geri kalanında bu yaklaşımlar ayrıntılı olarak açıklanmaktadır.
Kullanıcı etkinliklerini işleme
Kullanıcı arayüzü, kullanıcı etkinlikleri bir kullanıcı arayüzü öğesinin durumunu (ör. genişletilebilir bir öğenin durumu) değiştirmekle ilgiliyse bu etkinlikleri doğrudan işleyebilir. Etkinlik, ekrandaki verileri yenileme gibi iş mantığı işlemlerinin yapılmasını gerektiriyorsa ViewModel tarafından işlenmelidir.
Aşağıdaki örnekte, bir kullanıcı arayüzü öğesini (kullanıcı arayüzü mantığı) genişletmek ve ekrandaki verileri yenilemek (iş mantığı) için farklı düğmelerin nasıl kullanıldığı gösterilmektedir:
@Composable
fun LatestNewsScreen(viewModel: LatestNewsViewModel = viewModel()) {
// State of whether more details should be shown
var expanded by remember { mutableStateOf(false) }
Column {
Text("Some text")
if (expanded) {
Text("More details")
}
Button(
// The expand details event is processed by the UI that
// modifies this composable's internal state.
onClick = { expanded = !expanded }
) {
val expandText = if (expanded) "Collapse" else "Expand"
Text("$expandText details")
}
// The refresh event is processed by the ViewModel that is in charge
// of the UI's business logic.
Button(onClick = { viewModel.refreshNews() }) {
Text("Refresh data")
}
}
}
Tembel listelerdeki kullanıcı etkinlikleri
İşlem, kullanıcı arayüzü ağacında daha aşağıda, örneğin bir LazyColumn
öğesinde oluşturulursa kullanıcı etkinliklerini yine ViewModel işlemelidir.
Örneğin, tıklanabilir öğelerin listesini ele alalım. ViewModel örneğini liste composable'ına (MyList) aktarmayın. Bu, kullanıcı arayüzü bileşenini uygulama ayrıntılarına sıkı bir şekilde bağlar.
Bunun yerine, etkinliği composable'da bir lambda işlevi parametresi olarak kullanıma sunun. Bu sayede liste, etkinliği kimin ve nasıl işlediğini bilmeden tetikleyebilir.
data class MyItem(val id: Int)
@Composable
fun MyList(
items: List<String>,
onItemClick: (MyItem) -> Unit
) {
Card {
LazyColumn {
itemsIndexed(items) { index, string ->
ListItem(
modifier = Modifier.clickable {
onItemClick(MyItem(index))
},
headlineContent = {
Text(text = string)
}
)
}
}
}
}
Bu yaklaşımda, MyList composable yalnızca gösterdiği verilerle ve sunduğu etkinliklerle çalışır. ViewModel'e erişimi yoktur. Etkinlik, önceki composable'da yükseltilir ve ViewModel'e iletilir.
Etkinlik işleme hakkında daha fazla bilgi için Compose'daki etkinlikler başlıklı makaleyi inceleyin.
Kullanıcı etkinliği işlevleri ve etkinlik işleyicileri için adlandırma kuralları
Bu kılavuzda, kullanıcı etkinliklerini işleyen ViewModel işlevleri, işledikleri işleme göre bir fiille adlandırılır. Örneğin: validateInput() veya login().
Compose'daki etkinlik işleyiciler, veri akışını belirgin hale getirmek için standart bir adlandırma kuralına uyar:
- Parametre adı:
on+Verb+Target(ör.onExpandClickedveyaonValueChange). - Lambda ifadesi: Oluşturulabilir işlev çağrıldığında lambda genellikle yalnızca bu etkinliğin uygulanmasıdır.
ViewModel etkinliklerini işleme
ViewModel'dan kaynaklanan kullanıcı arayüzü işlemleri (ViewModel etkinlikleri) her zaman kullanıcı arayüzü durumu güncellemesiyle sonuçlanmalıdır. Bu, Tek Yönlü Veri Akışı ilkelerine uygundur. Yapılandırma değişikliklerinden sonra etkinliklerin yeniden üretilmesini sağlar ve kullanıcı arayüzü işlemlerinin kaybolmamasını garanti eder. İsteğe bağlı olarak, saved state modülünü kullanıyorsanız işlem sonlandırıldıktan sonra da etkinlikleri yeniden oluşturabilirsiniz.
Kullanıcı arayüzü işlemlerini kullanıcı arayüzü durumuna eşlemek her zaman basit bir işlem olmasa da daha basit bir mantıkla sonuçlanır. Düşünce süreciniz, örneğin kullanıcı arayüzünün belirli bir ekrana nasıl gideceğini belirlemekle sona ermemelidir. Daha fazla düşünmeniz ve bu kullanıcı akışını kullanıcı arayüzü durumunuzda nasıl temsil edeceğinizi göz önünde bulundurmanız gerekir. Diğer bir deyişle: Kullanıcı arayüzünün hangi işlemleri yapması gerektiğini düşünmeyin; bu işlemlerin kullanıcı arayüzü durumunu nasıl etkilediğini düşünün.
Örneğin, giriş ekranını ele alalım. Bu ekranın kullanıcı arayüzü durumunu aşağıdaki gibi modelleyebilirsiniz:
data class LoginUiState(
val isLoginInProgress: Boolean = false,
val errorMessage: String? = null,
val isUserLoggedIn: Boolean = false
)
Giriş ekranı, kullanıcı arayüzü durumundaki değişikliklere tepki verir.
class LoginViewModel : ViewModel() {
var uiState by mutableStateOf(LoginUiState())
fun tryLogin(username: String, password: String) {
viewModelScope.launch {
// Emit a new state indicating that login is in progress
uiState = uiState.copy(isLoginInProgress = true)
uiState = if (login(username, password)) {
// Emit a new state indicating that login was successful
uiState.copy(isLoginInProgress = false, isUserLoggedIn = true)
} else {
// Emit a new state with the error message
LoginUiState(isLoginInProgress = false, errorMessage = "Login failed")
}
}
}
private suspend fun login(username: String, password: String): Boolean {
delay(1000)
return (username == "Hello" && password == "World!")
}
}
@Composable
fun LoginScreen(viewModel: LoginViewModel, onSuccessfulLogin: () -> Unit) {
val uiState = viewModel.uiState
LaunchedEffect(uiState) {
if (uiState.isUserLoggedIn) {
onSuccessfulLogin()
}
}
if (uiState.isLoginInProgress) {
CircularProgressIndicator()
} else {
LoginForm(
onLoginAttempt = { username, password ->
viewModel.tryLogin(username, password)
},
errorMessage = uiState.errorMessage
)
}
}
Etkinlik tüketimi durum güncellemelerini tetikleyebilir
Kullanıcı arayüzünde belirli ViewModel etkinliklerinin kullanılması, diğer kullanıcı arayüzü durumu güncellemelerine neden olabilir. Örneğin, ekranda geçici mesajlar göstererek kullanıcıya bir şey olduğunu bildirmek için mesaj ekranda gösterildiğinde kullanıcı arayüzünün ViewModel'i başka bir durum güncellemesini tetikleyecek şekilde bilgilendirmesi gerekir. Kullanıcı mesajı kapattığında veya zaman aşımından sonra gerçekleşen etkinlik, "kullanıcı girişi" olarak değerlendirilebilir. Bu nedenle, ViewModel bu durumun farkında olmalıdır. Bu durumda, kullanıcı arayüzü durumu şu şekilde modellenebilir:
// Models the UI state for the Latest news screen.
data class LatestNewsUiState(
val news: List<News> = emptyList(),
val isLoading: Boolean = false,
val userMessage: String? = null
)
İş mantığı, kullanıcıya yeni bir geçici mesaj gösterilmesini gerektirdiğinde ViewModel, kullanıcı arayüzü durumunu aşağıdaki gibi günceller:
class LatestNewsViewModel(/* ... */) : ViewModel() {
var uiState by mutableStateOf(LatestNewsUiState())
private set
fun refreshNews() {
viewModelScope.launch {
// If there isn't internet connection, show a new message on the screen.
if (!internetConnection()) {
uiState = uiState.copy(userMessage = "No Internet connection")
return@launch
}
// Do something else.
}
}
fun userMessageShown() {
uiState = uiState.copy(userMessage = null)
}
}
ViewModel'in, kullanıcı arayüzünün ekranda mesajı nasıl gösterdiğini bilmesi gerekmez. Yalnızca gösterilmesi gereken bir kullanıcı mesajı olduğunu bilir. Geçici mesaj gösterildikten sonra kullanıcı arayüzünün ViewModel'i bu konuda bilgilendirmesi gerekir. Bu da userMessage özelliğini temizlemek için başka bir kullanıcı arayüzü durumu güncellemesine neden olur:
@Composable
fun LatestNewsScreen(
snackbarHostState: SnackbarHostState,
viewModel: LatestNewsViewModel = viewModel(),
) {
// Rest of the UI content.
// If there are user messages to show on the screen,
// show it and notify the ViewModel.
viewModel.uiState.userMessage?.let { userMessage ->
LaunchedEffect(userMessage) {
snackbarHostState.showSnackbar(userMessage)
// Once the message is displayed and dismissed, notify the ViewModel.
viewModel.userMessageShown()
}
}
}
İleti geçici olsa da kullanıcı arayüzü durumu, her an ekranda gösterilenlerin doğru bir temsilidir. Kullanıcı mesajı ya gösterilir ya da gösterilmez.
Gezinme etkinlikleri
Tüketilen etkinlikler durum güncellemelerini tetikleyebilir bölümünde, kullanıcı mesajlarını ekranda göstermek için kullanıcı arayüzü durumunu nasıl kullandığınız açıklanmaktadır. Gezinme etkinlikleri de Android uygulamalarındaki yaygın etkinlik türlerinden biridir.
Etkinlik, kullanıcı bir düğmeye dokunduğu için kullanıcı arayüzünde tetiklenirse kullanıcı arayüzü, etkinliği çağıran composable'a sunarak bu durumu ele alır.
@Composable
fun LoginScreen(
onHelp: () -> Unit, // Caller navigates to the help screen
viewModel: LoginViewModel = viewModel()
) {
// Rest of the UI
Button(
onClick = dropUnlessResumed { onHelp() }
) {
Text("Get help")
}
}
dropUnlessResumed, Lifecycle kitaplığının bir parçasıdır ve yaşam döngüsü en az RESUMED olduğunda onHelp işlevini çalıştırmanıza olanak tanır.
Veri girişi, gezinmeden önce bazı işletme mantığı doğrulaması gerektiriyorsa ViewModel'ın bu durumu kullanıcı arayüzüne göstermesi gerekir. Kullanıcı arayüzü bu durum değişikliğine tepki verir ve buna göre gezinir. ViewModel etkinliklerini işleme bölümünde bu kullanım alanı ele alınmaktadır. Benzer bir kodu aşağıda bulabilirsiniz:
@Composable
fun LoginScreen(
onUserLogIn: () -> Unit, // Caller navigates to the right screen
viewModel: LoginViewModel = viewModel()
) {
Button(
onClick = {
// ViewModel validation is triggered
viewModel.tryLogin()
}
) {
Text("Log in")
}
// Rest of the UI
val lifecycle = LocalLifecycleOwner.current.lifecycle
val currentOnUserLogIn by rememberUpdatedState(onUserLogIn)
LaunchedEffect(viewModel, lifecycle) {
// Whenever the uiState changes, check if the user is logged in and
// call the `onUserLogin` event when `lifecycle` is at least STARTED
snapshotFlow { viewModel.uiState }
.filter { it.isUserLoggedIn }
.flowWithLifecycle(lifecycle)
.collect {
currentOnUserLogIn()
}
}
}
Yukarıdaki örnekte, mevcut hedef olan Giriş, geri yığında tutulmayacağından uygulama beklendiği gibi çalışır. Kullanıcılar geri düğmesine basarsa bu sayfaya geri dönemez. Ancak bu durumun yaşanabileceği durumlarda çözüm için ek mantık gerekir.
Hedef, eski yığında tutulduğunda gezinme etkinlikleri
Bir ViewModel, A ekranından B ekranına gezinme etkinliği oluşturan bir durum ayarladığında ve A ekranı gezinme eski yığınında tutulduğunda B ekranına otomatik olarak ilerlemeyi engellemek için ek mantık kullanmanız gerekebilir. Bunu uygulamak için kullanıcı arayüzünün diğer ekrana gidip gitmeyeceğini belirten ek bir duruma ihtiyacınız vardır. Normalde bu durum kullanıcı arayüzünde tutulur. Bunun nedeni, gezinme mantığının ViewModel'in değil, kullanıcı arayüzünün sorumluluğunda olmasıdır. Bunu göstermek için aşağıdaki kullanım alanını inceleyelim.
Uygulamanızın kayıt akışında olduğunuzu varsayalım. Doğum tarihi doğrulama ekranında, kullanıcı bir tarih girdiğinde bu tarih, kullanıcı "Devam" düğmesine dokunduğunda ViewModel tarafından doğrulanır. ViewModel doğrulama mantığını veri katmanına devreder. Tarih geçerliyse kullanıcı bir sonraki ekrana yönlendirilir. Ek bir özellik olarak, kullanıcılar bazı verileri değiştirmek isterlerse farklı kayıt ekranları arasında ileri ve geri gidebilirler. Bu nedenle, kayıt akışındaki tüm hedefler aynı geri yığında tutulur. Bu şartlar göz önünde bulundurulduğunda, bu ekranı aşağıdaki gibi uygulayabilirsiniz:
class DobValidationViewModel(/* ... */) : ViewModel() {
var uiState by mutableStateOf(DobValidationUiState())
private set
}
@Composable
fun DobValidationScreen(
onNavigateToNextScreen: () -> Unit, // Caller navigates to the right screen
viewModel: DobValidationViewModel = viewModel()
) {
// TextField that updates the ViewModel when a date of birth is selected
var validationInProgress by rememberSaveable { mutableStateOf(false) }
Button(
onClick = {
viewModel.validateInput()
validationInProgress = true
}
) {
Text("Continue")
}
// Rest of the UI
/*
* The following code implements the requirement of advancing automatically
* to the next screen when a valid date of birth has been introduced
* and the user wanted to continue with the registration process.
*/
if (validationInProgress) {
val lifecycle = LocalLifecycleOwner.current.lifecycle
val currentNavigateToNextScreen by rememberUpdatedState(onNavigateToNextScreen)
LaunchedEffect(viewModel, lifecycle) {
// If the date of birth is valid and the validation is in progress,
// navigate to the next screen when `lifecycle` is at least STARTED,
// which is the default Lifecycle.State for the `flowWithLifecycle` operator.
snapshotFlow { viewModel.uiState }
.filter { it.isDobValid }
.flowWithLifecycle(lifecycle)
.collect {
validationInProgress = false
currentNavigateToNextScreen()
}
}
}
}
Doğum tarihi doğrulama, ViewModel'in sorumlu olduğu bir iş mantığıdır. ViewModel, çoğu zaman bu mantığı veri katmanına devreder. Kullanıcıyı bir sonraki ekrana yönlendirme mantığı, kullanıcı arayüzü mantığıdır. Çünkü bu şartlar, kullanıcı arayüzü yapılandırmasına bağlı olarak değişebilir. Örneğin, aynı anda birden fazla kayıt adımı gösteriyorsanız tablette otomatik olarak başka bir ekrana geçmek istemeyebilirsiniz. Yukarıdaki kodda yer alan validationInProgress değişkeni bu işlevi uygular ve doğum tarihi geçerli olduğunda ve kullanıcı bir sonraki kayıt adımına geçmek istediğinde kullanıcı arayüzünde otomatik olarak gezinilip gezinilmeyeceğini belirler.
Diğer kullanım alanları
Kullanıcı arayüzü etkinliği kullanım alanınızın, kullanıcı arayüzü durumu güncellemeleriyle çözülemeyeceğini düşünüyorsanız uygulamanızdaki veri akışını yeniden gözden geçirmeniz gerekebilir. Aşağıdaki ilkeleri göz önünde bulundurun:
- Her sınıf, sorumluluğunda olanı yapmalı, daha fazlasını değil. Kullanıcı arayüzü; gezinme çağrıları, tıklama etkinlikleri ve izin isteklerini alma gibi ekrana özgü davranış mantığından sorumludur. ViewModel, iş mantığını içerir ve hiyerarşinin alt katmanlarındaki sonuçları kullanıcı arayüzü durumuna dönüştürür.
- Etkinliğin kaynağını düşünün. Bu kılavuzun başında sunulan karar ağacını izleyin ve her sınıfın sorumlu olduğu işi yapmasını sağlayın. Örneğin, etkinlik kullanıcı arayüzünden kaynaklanıyorsa ve bir gezinme etkinliğiyle sonuçlanıyorsa bu etkinlik kullanıcı arayüzünde işlenmelidir. Bazı mantıklar ViewModel'e devredilebilir ancak etkinliğin işlenmesi tamamen ViewModel'e devredilemez.
- Birden fazla tüketiciniz varsa ve etkinliğin birden fazla kez tüketilmesinden endişeleniyorsanız uygulama mimarinizi yeniden gözden geçirmeniz gerekebilir. Aynı anda birden fazla tüketicinin olması, tam olarak bir kez teslim edildi sözleşmesinin garanti edilmesini son derece zorlaştırır. Bu nedenle, karmaşıklık ve ince davranış miktarı artar. Bu sorunu yaşıyorsanız endişelerinizi kullanıcı arayüzü ağacınızda yukarıya taşıyabilirsiniz. Hiyerarşide daha yukarıda farklı bir kapsamlı öğeye ihtiyacınız olabilir.
- Durumun ne zaman kullanılması gerektiğini düşünün. Belirli durumlarda, uygulama arka plandayken durumu tüketmeye devam etmek istemeyebilirsiniz. Örneğin,
Toastgöstermek. Bu gibi durumlarda, kullanıcı arayüzü ön plandayken durumu kullanmayı düşünebilirsiniz.
Örnekler
Aşağıdaki Google örneklerinde, kullanıcı arayüzü katmanındaki kullanıcı arayüzü etkinlikleri gösterilmektedir. Bu kılavuzun nasıl uygulandığını görmek için aşağıdaki kaynakları inceleyin:
Ek kaynaklar
Kullanıcı arayüzü etkinlikleri hakkında daha fazla bilgi için aşağıdaki ek kaynaklara bakın:
Codelab uygulamaları
Belgeler
İçeriği görüntüleme
Sizin için önerilenler
- Not: JavaScript kapalıyken bağlantı metni gösterilir.
- Kullanıcı arayüzü katmanı
- Durum tutucular ve kullanıcı arayüzü durumu {:#mad-arch}
- Uygulama mimarisi kılavuzu