Kullanıcı arayüzü etkinlikleri, kullanıcı arayüzü tarafından veya kullanıcı arayüzü katmanında işlenmesi gereken işlemlerdir
veya ViewModel'e göre belirlenir. En yaygın etkinlik türü kullanıcı etkinlikleridir. Kullanıcı
uygulamayla etkileşim kurarak kullanıcı etkinlikleri oluşturur (örneğin,
hareketler oluşturarak ekranda görebilirsiniz. Kullanıcı arayüzü, daha sonra bu etkinlikleri
onClick()
dinleyicisi gibi geri çağırmalar.
ViewModel normalde bir modelin iş mantığını ele almaktan
belirli bir kullanıcı etkinliği (örneğin, kullanıcı bir öğeyi yenileyerek
dışı verilerdir. ViewModel genellikle bunu kullanıcı arayüzünün
çağrısına bir tıklama URL'si eklemeniz gerekir. Kullanıcı etkinlikleri, kullanıcı arayüzünün işleyebileceği kullanıcı arayüzü davranış mantığına da sahip olabilir.
doğrudan (örneğin farklı bir ekrana giderek veya bir
Snackbar
.
Aynı uygulamanın farklı mobil cihazlarda iş mantığı aynı kalırken platformlar veya form faktörleriyse, kullanıcı arayüzü davranış mantığı bir uygulama ayrıntısıdır. bu durumlarda farklı olabilir. Kullanıcı arayüzü katmanı page, bu mantığı şöyle olur:
- İş mantığı, durum değişiklikleriyle ne yapılacağını ifade eder. Örneğin, ödeme yapma veya kullanıcı tercihlerini depolama. Alan ve veri katmanları bu mantığın üstesinden gelebilir. Bu kılavuzda, Mimari Bileşenleri ViewModel sınıfı bir iş mantığını ele alan sınıflar için düşünceli bir çözüm sunar.
- Kullanıcı arayüzü davranış mantığı veya kullanıcı arayüzü mantığı, nasıl gösterilir? durumunu ifade eder. (ör. gezinme mantığı veya mesajların kullanıcıya nasıl gösterileceği) hakkında daha fazla bilgi edinmenizi sağlar. İlgili içeriği oluşturmak için kullanılan Kullanıcı arayüzü bu mantığı işler.
Kullanıcı arayüzü etkinliği karar ağacı
Aşağıdaki şemada en iyi yaklaşımı bulmak için bir karar ağacı örneğin ele alınır. Bu kılavuzun geri kalanında, ayrıntılı olarak inceleyeceğiz.
Kullanıcı etkinliklerini yönetme
Bu etkinlikler, durumu (örneğin, genişletilebilir bir öğenin durumu). Etkinlik ekrandaki verileri yenilemek gibi bir iş mantığı yürütmeyi gerektiriyorsa ViewModel tarafından işlenmesi gerekir.
Aşağıdaki örnekte, kullanıcı arayüzünü genişletmek için farklı düğmelerin nasıl kullanıldığı gösterilmektedir öğesini (kullanıcı arayüzü mantığı) ve ekrandaki verileri yenilemek için (iş mantığı):
Görüntüleme sayısı
class LatestNewsActivity : AppCompatActivity() {
private lateinit var binding: ActivityLatestNewsBinding
private val viewModel: LatestNewsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
/* ... */
// The expand details event is processed by the UI that
// modifies a View's internal state.
binding.expandButton.setOnClickListener {
binding.expandedSection.visibility = View.VISIBLE
}
// The refresh event is processed by the ViewModel that is in charge
// of the business logic.
binding.refreshButton.setOnClickListener {
viewModel.refreshNews()
}
}
}
Oluştur
@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")
}
}
}
RecyclerViews'taki kullanıcı etkinlikleri
İşlem, RecyclerView
gibi kullanıcı arayüzü ağacında daha aşağılarda gerçekleşiyorsa
öğe veya özel bir View
ise, taşıma işlemini yapan kullanıcı ViewModel
olmalıdır.
etkinlikler.
Örneğin, NewsActivity
kaynağından gelen tüm haber öğelerinin yer işareti içerdiğini varsayalım
düğmesini tıklayın. ViewModel
uygulamasının, yer işareti koyulan haber öğesinin kimliğini bilmesi gerekir. Zaman
kullanıcı bir haber öğesine yer işareti koyduğunda RecyclerView
bağdaştırıcısı
ViewModel
değerindeki açıkta kalan addBookmark(newsId)
işlevi
ViewModel
bağımlılığı var. Bunun yerine, ViewModel
bir durum nesnesi gösterir
NewsItemUiState
;
etkinlik:
data class NewsItemUiState(
val title: String,
val body: String,
val bookmarked: Boolean = false,
val publicationDate: String,
val onBookmark: () -> Unit
)
class LatestNewsViewModel(
private val formatDateUseCase: FormatDateUseCase,
private val repository: NewsRepository
)
val newsListUiItems = repository.latestNews.map { news ->
NewsItemUiState(
title = news.title,
body = news.body,
bookmarked = news.bookmarked,
publicationDate = formatDateUseCase(news.publicationDate),
// Business logic is passed as a lambda function that the
// UI calls on click events.
onBookmark = {
repository.addBookmark(news.id)
}
)
}
}
Bu şekilde, RecyclerView
bağdaştırıcısı yalnızca ihtiyaç duyduğu verilerle çalışır:
NewsItemUiState
nesne içeren liste. Adaptör, tüm cihazlara
ViewModel'in gösterdiği performansı kötüye kullanma olasılığını
ViewModel. Yalnızca etkinlik sınıfının ViewModel ile çalışmasına izin verdiğinizde
size zaman kazandırabilir. Bu, görünümler gibi kullanıcı arayüzüne özgü nesnelerin
veya RecyclerView
bağdaştırıcıları doğrudan ViewModel ile etkileşime girmez.
Kullanıcı etkinliği işlevleri için adlandırma kuralları
Bu kılavuzda, kullanıcı etkinliklerini işleyen ViewModel işlevleri
fiil, gerçekleştirdikleri işleme göre değişir. Örneğin: addBookmark(id)
veya
logIn(username, password)
.
ViewModel etkinliklerini işleme
ViewModel etkinliklerinden (ViewModel etkinlikleri) kaynaklanan kullanıcı arayüzü işlemleri her zaman kullanıcı arayüzü durumunun güncellenmesine neden olur. Bu Tek Yönlü Veri ilkelerine uygundur. Akış. Etkinliklerin ve kullanıcı arayüzü işlemlerinin kaybolmayacağını garanti eder. İsteğe bağlı olarak, Ayrıca, kaydedilen kayıtlı öğe sayısını kullanıyorsanız işlem ölümünden sonra durum modülü'ne gidin.
Kullanıcı arayüzü işlemlerinin kullanıcı arayüzü durumuyla eşlenmesi her zaman basit bir süreç değildir ancak daha basit bir mantığa dönüştü. Düşünce süreciniz, projenizin başarısını nasıl belirleyeceğinize karar vermekle kullanıcı arayüzünün belirli bir ekrana gitmesini sağlayabilirsiniz. Birlikte çalıştığınız ve kullanıcı arayüzü durumunuzda bu kullanıcı akışını nasıl yansıtacağınızı düşünün. diğer bir deyişle, kullanıcı arayüzünün hangi işlemleri yapması gerektiğini düşünmeyin. CANNOT TRANSLATE kullanıcı arayüzünün durumunu nasıl etkilediğini açıklayacağım.
Örneğin, kullanıcı açıkken ana ekrana gitmeyi kullanıcı giriş yapmıştır. Bunu, kullanıcı arayüzü durumunda aşağıdaki gibi modelleyebilirsiniz:
data class LoginUiState(
val isLoading: Boolean = false,
val errorMessage: String? = null,
val isUserLoggedIn: Boolean = false
)
Bu kullanıcı arayüzü, isUserLoggedIn
durumunda yapılan değişikliklere tepki verir ve
Gerektiğinde doğru hedefi kullanın:
Görüntüleme sayısı
class LoginViewModel : ViewModel() {
private val _uiState = MutableStateFlow(LoginUiState())
val uiState: StateFlow<LoginUiState> = _uiState.asStateFlow()
/* ... */
}
class LoginActivity : AppCompatActivity() {
private val viewModel: LoginViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
/* ... */
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { uiState ->
if (uiState.isUserLoggedIn) {
// Navigate to the Home screen.
}
...
}
}
}
}
}
Oluştur
class LoginViewModel : ViewModel() {
var uiState by mutableStateOf(LoginUiState())
private set
/* ... */
}
@Composable
fun LoginScreen(
viewModel: LoginViewModel = viewModel(),
onUserLogIn: () -> Unit
) {
val currentOnUserLogIn by rememberUpdatedState(onUserLogIn)
// Whenever the uiState changes, check if the user is logged in.
LaunchedEffect(viewModel.uiState) {
if (viewModel.uiState.isUserLoggedIn) {
currentOnUserLogIn()
}
}
// Rest of the UI for the login screen.
}
Etkinlikleri kullanmak durum güncellemelerini tetikleyebilir
Kullanıcı arayüzünde belirli ViewModel etkinliklerini kullanmak başka kullanıcı arayüzü durumlarıyla sonuçlanabilir güncellemelerine göz atın. Örneğin, geçici mesajları ekranda gösterirken kullanıcı bir şey olduğunu anlarsa, kullanıcı arayüzünün ViewModel'e Mesaj ekranda gösterildiğinde başka bir durum güncellemesini tetikleyebilir. İlgili içeriği oluşturmak için kullanılan Kullanıcı iletiyi tükettiğinde (iletiyi kapatarak veya sonra) "kullanıcı girişi" olarak değerlendirilebilir. ve bu nedenle ViewModel bunu dikkate almalıdır. Bu durumda, kullanıcı arayüzü durumu modellendiği gibi:
// 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
)
ViewModel, iş mantığı aşağıdaki gibi kullanıcı arayüzü durumunu günceller: kullanıcıya yeni bir geçici mesaj gösterilmesini gerektirir:
Görüntüleme sayısı
class LatestNewsViewModel(/* ... */) : ViewModel() {
private val _uiState = MutableStateFlow(LatestNewsUiState(isLoading = true))
val uiState: StateFlow<LatestNewsUiState> = _uiState
fun refreshNews() {
viewModelScope.launch {
// If there isn't internet connection, show a new message on the screen.
if (!internetConnection()) {
_uiState.update { currentUiState ->
currentUiState.copy(userMessage = "No Internet connection")
}
return@launch
}
// Do something else.
}
}
fun userMessageShown() {
_uiState.update { currentUiState ->
currentUiState.copy(userMessage = null)
}
}
}
Oluştur
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
ekran; yalnızca gösterilmesi gereken bir kullanıcı mesajı olduğunu bilir. Bir kez
geçici mesaj gösterildiğine göre, kullanıcı arayüzünün ViewModel'e
Bu da başka bir kullanıcı arayüzü durumu güncellemesinin userMessage
özelliğini temizlemesine neden olur:
Görüntüleme sayısı
class LatestNewsActivity : AppCompatActivity() {
private val viewModel: LatestNewsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
/* ... */
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { uiState ->
uiState.userMessage?.let {
// TODO: Show Snackbar with userMessage.
// Once the message is displayed and
// dismissed, notify the ViewModel.
viewModel.userMessageShown()
}
...
}
}
}
}
}
Oluştur
@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 ziyarette ekranda görünenlerin güvenilir bir şekilde temsil edilmesi bir nokta olabilir. Kullanıcı mesajı gösteriliyor veya gösterilmiyor.
Gezinme etkinlikleri
Tüketme etkinlikleri durum güncellemelerini tetikleyebilir bölümünde, tıklayın. Gezinme etkinlikleri de Android uygulamalarında yaygın olarak kullanılan bir etkinlik türüdür.
Etkinlik, kullanıcı bir düğmeye dokunduğu için kullanıcı arayüzünde tetiklenirse kullanıcı arayüzü bunun için gezinme denetleyicisini çağırır veya etkinliği görüntüler composable'a uygun şekilde iletebilir.
Görüntüleme sayısı
class LoginActivity : AppCompatActivity() {
private lateinit var binding: ActivityLoginBinding
private val viewModel: LoginViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
/* ... */
binding.helpButton.setOnClickListener {
navController.navigate(...) // Open help screen
}
}
}
Oluştur
@Composable
fun LoginScreen(
onHelp: () -> Unit, // Caller navigates to the right screen
viewModel: LoginViewModel = viewModel()
) {
// Rest of the UI
Button(onClick = onHelp) {
Text("Get help")
}
}
Veri girişi navigasyondan önce birtakım iş mantığı doğrulaması gerektiriyorsa, ViewModel'in bu durumu kullanıcı arayüzüne sunması gerekir. Kullanıcı arayüzü o durum değişikliğine göre hareket etmeniz gerekir. Handle ViewModel etkinlikleri bölümündeki öğrenebilirsiniz. Benzer bir kod aşağıda verilmiştir:
Görüntüleme sayısı
class LoginActivity : AppCompatActivity() {
private val viewModel: LoginViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
/* ... */
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { uiState ->
if (uiState.isUserLoggedIn) {
// Navigate to the Home screen.
}
...
}
}
}
}
}
Oluştur
@Composable
fun LoginScreen(
onUserLogIn: () -> Unit, // Caller navigates to the right screen
viewModel: LoginViewModel = viewModel()
) {
Button(
onClick = {
// ViewModel validation is triggered
viewModel.login()
}
) {
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, uygulama beklendiği gibi çalışır. Bunun nedeni, mevcut hedefin Giriş, arka yığında saklanmaz. Kullanıcılar, geri tuşuna basın. Ancak bu gibi durumlarda çözüm, ek mantık gerektirir.
Hedef arka grupta tutulduğunda gerçekleşen gezinme etkinlikleri
ViewModel, ekrandan bir gezinme etkinliği oluşturan bir durum belirlediğinde B ekranı için A ekranı ve A ekranı gezinme geri yığınında tutulur. B'ye otomatik olarak ilerlemeye devam etmemek için ek bir mantık vardır. Bunu uygulamak için kullanıcı arayüzünün diğer ekrana gitmenizi öneririz. Normalde bu eyalet çünkü Gezinme mantığı ViewModel'le değil, kullanıcı arayüzüyle ilgilidir. Bunu göstermek için aşağıdaki kullanım alanını inceleyelim.
Uygulamanızın kayıt akışında olduğunuzu varsayalım. tarihinde doğum doğrulama ekranında, kullanıcı bir tarih girdiğinde tarih kullanıcı "Devam" düğmesine dokunduğunda ViewModel'i düğmesini tıklayın. ViewModel doğrulama mantığı için veri katmanına yetki verir. Tarih geçerliyse ve kullanıcı bir sonraki ekrana geçer. Ek bir özellik olarak kullanıcılar geri dönüp geçiş yapmak istediklerinde kullanmaları için farklı kayıt ekranları arasında bazı verilerdir. Bu nedenle, kayıt akışındaki tüm hedefler görebilirsiniz. Bu gereksinimler göz önünde bulundurulduğunda, bu ekranı şu şekilde:
Görüntüleme sayısı
// Key that identifies the `validationInProgress` state in the Bundle
private const val DOB_VALIDATION_KEY = "dobValidationKey"
class DobValidationFragment : Fragment() {
private var validationInProgress: Boolean = false
private val viewModel: DobValidationViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = // ...
validationInProgress = savedInstanceState?.getBoolean(DOB_VALIDATION_KEY) ?: false
binding.continueButton.setOnClickListener {
viewModel.validateDob()
validationInProgress = true
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.uiState
.flowWithLifecycle(viewLifecycleOwner.lifecycle)
.collect { uiState ->
// Update other parts of the UI ...
// If the input is valid and the user wants
// to navigate, navigate to the next screen
// and reset `validationInProgress` flag
if (uiState.isDobValid && validationInProgress) {
validationInProgress = false
navController.navigate(...) // Navigate to next screen
}
}
}
return binding
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBoolean(DOB_VALIDATION_KEY, validationInProgress)
}
}
Oluştur
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ğrulaması, ViewModel'in şu iş mantığıdır:
çok önemli. Çoğu zaman ViewModel bu mantığı
veri katmanından yararlanın. Kullanıcıyı sonraki ekrana taşıma mantığı
Bu gereksinimler kullanıcı arayüzüne bağlı olarak değişebileceğinden, kullanıcı arayüzü mantığıdır.
yapılandırma. Örneğin, her ekip üyesine otomatik olarak
aynı anda birden çok kayıt adımını gösteriyorsanız, tablette
anlamına gelir. Yukarıdaki kodda bulunan validationInProgress
değişkeni
olup olmadığını kontrol eder ve kullanıcı arayüzünde gezinme
doğum tarihi geçerli olduğunda ve kullanıcı istediği zaman otomatik olarak
bağlantısını tıklayın.
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ülemediğini düşünüyorsanız uygulamanızdaki veri akışını yeniden değerlendirmeniz gerekebilir. Aşağıdakileri göz önünde bulundurun ilkeleri:
- Her sınıf daha fazla değil, sorumlu olduğu işi yapmalıdır. Kullanıcı arayüzü gezinme çağrıları, tıklama izleme gibi ekrana özgü davranış mantığının ve izin isteklerinin alınmasıyla ilgili bilgiler edinebilirsiniz. ViewModel, işletme verilerini içerir. mantığın temelini oluşturur ve hiyerarşinin alt katmanlarındaki sonuçları kullanıcı arayüzüne dönüştürür. durumu.
- Etkinliğin nereden gerçekleştiğini düşünün. Kararı uygulayın başlangıcında verilen ağaç ve her bir sorumluluklarının bilincinde olmalarına yardımcı olur. Örneğin, kullanıcı arayüzünden oluşturulur ve gezinme etkinliği ile sonuçlanır; ardından bu etkinlik kullanıcı arayüzünde işlenmesi gerekir. Bazı mantık değerleri ViewModel'e devredilebilir, Ancak etkinliğin işlenmesi tamamen ViewModel'e devredilemez.
- Birden fazla tüketiciniz varsa ve etkinliğin gerçekleşeceği konusunda endişeleriniz varsa veya birden fazla kez tüketiliyorsa uygulama mimarinizi yeniden gözden geçirmeniz gerekebilir. Birden fazla tüketicinin aynı anda olması tam olarak bir kez yayınlanır. çok zor bir iş haline gelecektir. Bu da, sözleşmenin karmaşıklık ve karmaşık davranışlar patlıyor. Bu sorunu yaşıyorsanız bu endişeleri kullanıcı arayüzü ağacınızda yukarıya taşıyabilirsiniz; bir ekip üyesine hiyerarşinin daha üst sıralarında bulunan farklı bir varlık.
- Durumun ne zaman tüketilmesi gerektiğini düşünün. Bazı durumlarda,
uygulama bu moddayken tüketim durumunu
arka plan (örneğin,
Toast
gösteriliyor). Bu tür durumlarda durumu gösterir.
Örnekler
Aşağıdaki Google örnekleri, kullanıcı arayüzü katmanı. Uygulamadaki bu rehberliği görmek için bu yöntemleri inceleyin:
Sizin için önerilenler
- Not: JavaScript kapalıyken bağlantı metni gösterilir
- Kullanıcı arayüzü katmanı
- Eyalet sahipleri ve Kullanıcı Arayüzü Durumu {:#mad-arch}
- Uygulama mimarisi rehberi