Kullanıcı arayüzü durumunu kaldıracağınız bir Compose uygulamasında kullanıcı arayüzü mantığının mı iş mantığının mı bunu gerektirdiğine bağlıdır. Bu belgede bu iki ana senaryo anlatılmıştır.
En iyi uygulama
Kullanıcı arayüzü durumunu, onu okuyan ve yazan tüm composable'lar arasındaki en düşük ortak üst öğeye yükseltmeniz gerekir. Durumu tüketildiği yere en yakın tutmalısınız. Durumu değiştirmek için eyalet sahibinden, tüketicilere sabit durumu ve etkinlikleri gösterin.
En düşük ortak üst öğe, Bestenin dışında da olabilir. İş mantığıyla ilgili olduğu için ViewModel
konumunda durum kaldırma buna örnek gösterilebilir.
Bu sayfada, bu en iyi uygulama ayrıntılı olarak açıklanmakta ve unutulmaması gereken bir uyarı bulunmaktadır.
Kullanıcı arayüzü durumu ve kullanıcı arayüzü mantığı türleri
Aşağıda, bu belgede kullanılan kullanıcı arayüzü durumu türlerinin ve mantığın tanımları bulunmaktadır.
Kullanıcı arayüzü durumu
Kullanıcı arayüzü durumu, kullanıcı arayüzünü açıklayan özelliktir. İki tür kullanıcı arayüzü durumu vardır:
- Ekran kullanıcı arayüzü durumu, ekranda görüntülemeniz gerekendir. Örneğin, bir
NewsUiState
sınıfı, kullanıcı arayüzünü oluşturmak için gereken haber makalelerini ve diğer bilgileri içerebilir. Bu durum, uygulama verileri içerdiği için genellikle hiyerarşinin diğer katmanlarıyla bağlantılıdır. - Kullanıcı arayüzü öğesi durumu, oluşturulma şeklini etkileyen kullanıcı arayüzü öğelerine özgü özellikleri ifade eder. Kullanıcı arayüzü öğeleri gösterilebilir veya gizlenmiş olabilir ve
belirli bir yazı tipine, yazı tipi boyutuna veya yazı tipi rengine sahip olabilir. Android Görünümleri'nde View, doğası gereği durum bilgili olduğundan, durumunu değiştirmeye veya sorgulamaya yönelik yöntemler sunduğu için bu durumu kendisini yönetir. Bunun bir örneği, metni için
TextView
sınıfınınget
veset
yöntemleridir. Jetpack Composer'da durum, composable'ın dışındadır. Hatta bunu composable'ın yakınlarından, çağrı yapan composable işlevine ya da bir durum sahibine bile kaldırabilirsiniz.Scaffold
composable içinScaffoldState
buna örnek olarak gösterilebilir.
Mantık
Bir uygulamanın mantığı, iş mantığı veya kullanıcı arayüzü mantığı olabilir:
- İş mantığı, uygulama verileri için ürün gereksinimlerinin uygulanmasıdır. Örneğin, kullanıcı düğmeye dokunduğunda haber okuyucu uygulamasında bir makaleye yer işareti koymak. Yer işaretini bir dosyaya veya veritabanına kaydetme mantığı genellikle alana ya da veri katmanlarına yerleştirilir. Durum sahibi genellikle bu mantığı, açığa çıkardıkları yöntemleri çağırarak bu katmanlara aktarır.
- Kullanıcı arayüzü mantığı, ekranda kullanıcı arayüzü durumunun nasıl gösterileceğiyle ilgilidir. Örneğin, kullanıcı bir kategori seçtiğinde doğru arama çubuğu ipucunu alma, listeyi kaydırarak bir listede belirli bir öğeye gitme veya kullanıcı bir düğmeyi tıkladığında belirli bir ekrana gitme mantığını alma.
Kullanıcı arayüzü mantığı
UI mantığı için okuma veya yazma durumu gerektiğinde, kullanıcı yaşam döngüsünü takip ederek durum kapsamını kullanıcı arayüzüne ayarlamanız gerekir. Bunu başarmak için composable bir fonksiyonda durumu doğru düzeyde kaldırmanız gerekir. Alternatif olarak, bu işlemi kullanıcı arayüzü yaşam döngüsü kapsamına alınmış bir düz durum tutucu sınıfında da yapabilirsiniz.
Aşağıda hem çözümlerin hem de hangisinin ne zaman kullanılacağına dair bir açıklama yer almaktadır.
Eyalet sahibi olarak composable'lar
composable'larda kullanıcı arayüzü mantığının ve UI öğesi durumunun olması, durum ve mantık basitse iyi bir yaklaşımdır. Gerektiğinde eyaletinizi composable'a veya yük asansöre bırakabilirsiniz.
Şehir kaldırma gerekli değil
Kaldırım durumu her zaman gerekli değildir. Durum, başka bir composable'ın kontrol etmesi gerekmediğinde bir composable'da dahili olarak tutulabilir. Bu snippet'te, dokunulduğunda genişleyen ve daraltılan bir composable var:
@Composable fun ChatBubble( message: Message ) { var showDetails by rememberSaveable { mutableStateOf(false) } // Define the UI element expanded state ClickableText( text = AnnotatedString(message.content), onClick = { showDetails = !showDetails } // Apply simple UI logic ) if (showDetails) { Text(message.timestamp) } }
showDetails
değişkeni, bu kullanıcı arayüzü öğesinin dahili durumudur. Yalnızca bu composable'da okunup değiştirilmiş, ayrıca mantık sadece çok basit.
Bu durumda eyaletin kaldırılması fazla fayda sağlamayacağı için
dahili bırakabilirsiniz. Böylece composable, genişletilmiş durumun sahibi ve tek bilgi kaynağı haline gelir.
composable'lar içinde kaldırma
Kullanıcı arayüzü öğenizin durumunu diğer composable'larla paylaşmanız ve farklı yerlerde kullanıcı arayüzü mantığını uygulamanız gerekiyorsa bunu kullanıcı arayüzü hiyerarşisinde daha üst sıraya çıkarabilirsiniz. Bu sayede composable'lar tekrar kullanılabilir ve test edilmesi daha kolay olur.
Aşağıdaki örnek, iki işlev uygulayan bir sohbet uygulamasıdır:
JumpToBottom
düğmesi, mesaj listesini en alta kaydırır. Düğme, liste durumu üzerinde kullanıcı arayüzü mantığını gerçekleştirir.- Kullanıcı yeni mesajlar gönderdikten sonra
MessagesList
listesi en alta kaydırılır. UserInput, liste durumu üzerinde kullanıcı arayüzü mantığı gerçekleştirir.
composable hiyerarşi aşağıdaki gibidir:
LazyColumn
durumu, ileti dizisi ekranına çekilir. Böylece uygulama, kullanıcı arayüzü mantığını gerçekleştirebilir ve bunu gerektiren tüm composable'ların durumunu okuyabilir:
Son olarak composable'lar:
Bu kod aşağıdaki gibidir:
@Composable private fun ConversationScreen(/*...*/) { val scope = rememberCoroutineScope() val lazyListState = rememberLazyListState() // State hoisted to the ConversationScreen MessagesList(messages, lazyListState) // Reuse same state in MessageList UserInput( onMessageSent = { // Apply UI logic to lazyListState scope.launch { lazyListState.scrollToItem(0) } }, ) } @Composable private fun MessagesList( messages: List<Message>, lazyListState: LazyListState = rememberLazyListState() // LazyListState has a default value ) { LazyColumn( state = lazyListState // Pass hoisted state to LazyColumn ) { items(messages, key = { message -> message.id }) { item -> Message(/*...*/) } } val scope = rememberCoroutineScope() JumpToBottom(onClicked = { scope.launch { lazyListState.scrollToItem(0) // UI logic being applied to lazyListState } }) }
LazyListState
, uygulanması gereken kullanıcı arayüzü mantığı için gereken kadar yükseğe kaldırılır. composable bir işlevde başlatıldığı için yaşam döngüsünü takip ederek Beste'de depolanır.
lazyListState
öğesinin, MessagesList
yönteminde varsayılan rememberLazyListState()
değeriyle tanımlandığını unutmayın. Bu, Compose'da sık kullanılan bir kalıptır.
Komposable'ları yeniden kullanılabilir ve esnek hale getirir. Ardından, composable'ı, uygulamanın durumunu kontrol etmesi gerekmeyen
farklı bölümlerinde kullanabilirsiniz. Bu durum genellikle bir composable'ı test ederken veya önizlerken geçerlidir. LazyColumn
, durumunu tam olarak bu şekilde tanımlar.
Eyalet sahibi olarak düz eyalet sahibi sınıfı
Bir composable, bir kullanıcı arayüzü öğesinin bir veya birden fazla durum alanını içeren karmaşık bir kullanıcı arayüzü mantığı içerdiğinde, bu sorumluluğu, basit bir eyalet sahibi sınıfı gibi eyalet sahiplerine devretmelidir. Bu, composable'ın mantığını tek başına daha test edilebilir hale getirir ve karmaşıklığını azaltır. Bu yaklaşım, endişelerin ayrılması ilkesini destekler: composable, kullanıcı arayüzü öğelerini yaymaktan sorumludur ve durum tutucu, kullanıcı arayüzü mantığını ve kullanıcı arayüzü öğesinin durumunu içerir.
Düz durum tutucu sınıfları, composable işlevinizi çağıran kullanıcılara pratik işlevler sunar. Böylece bu mantığı kendilerinin yazmak zorunda kalmazlar.
Bu sade sınıflar Bestede oluşturulur ve hatırlanır. composable'ın yaşam döngüsünü takip ettikleri için Oluşturma kitaplığı tarafından sağlanan rememberNavController()
veya rememberLazyListState()
gibi türleri alabilirler.
Bunun bir örneği, LazyColumn
veya LazyRow
kullanıcı arayüzü karmaşıklığını kontrol etmek için Compose'da uygulanan LazyListState
düz durum tutucu sınıfıdır.
// LazyListState.kt @Stable class LazyListState constructor( firstVisibleItemIndex: Int = 0, firstVisibleItemScrollOffset: Int = 0 ) : ScrollableState { /** * The holder class for the current scroll position. */ private val scrollPosition = LazyListScrollPosition( firstVisibleItemIndex, firstVisibleItemScrollOffset ) suspend fun scrollToItem(/*...*/) { /*...*/ } override suspend fun scroll() { /*...*/ } suspend fun animateScrollToItem() { /*...*/ } }
LazyListState
, bu kullanıcı arayüzü öğesi için scrollPosition
öğesini depolayan LazyColumn
durumunu kapsüller. Ayrıca, örneğin belirli bir öğeye kaydırarak kaydırma konumunu değiştirme yöntemleri de ortaya çıkar.
Gördüğünüz gibi bir composable'ın sorumluluklarını artırmak eyalet sahibine yönelik gereksinimi de artırır. Sorumluluklar kullanıcı arayüzü mantığında ya da sadece izlenecek durum miktarında olabilir.
Yaygın olarak kullanılan diğer bir kalıp, uygulamadaki root composable işlevlerinin karmaşıklığını yönetmek için düz durum tutucu sınıfı kullanmaktır. Böyle bir sınıfı, gezinme durumu ve ekran boyutlandırma gibi uygulama düzeyindeki durumu kapsamak için kullanabilirsiniz. Bunun eksiksiz bir açıklamasını Kullanıcı arayüzü mantığı ve durum bilgisi sayfasında bulabilirsiniz.
İş mantığı
composable'lar ve düz durum sahibi sınıfları, kullanıcı arayüzü mantığından ve UI öğesi durumundan sorumluysa bir ekran düzeyinde durum sahibi aşağıdaki görevlerden sorumludur:
- Genellikle hiyerarşinin iş ve veri katmanları gibi diğer katmanlarına yerleştirilen uygulamanın iş mantığına erişim sağlar.
- Uygulama verilerinin, ekran kullanıcı arayüzü durumuna dönüşen belirli bir ekranda sunulması için hazırlanması.
Eyalet sahibi olarak ViewModels
AAC ViewModel'in Android geliştirmedeki avantajları, bunları iş mantığına erişim sağlamaya ve uygulama verilerini ekranda sunum için hazırlamaya uygun hale getirir.
Kullanıcı arayüzünün durumunu ViewModel
içinde kaldırdığınızda, bunu Beste'nin dışına taşırsınız.
ViewModel'ler, Bestenin bir parçası olarak depolanmaz. Bunlar çerçeve tarafından sağlanır ve etkinlik, parça, gezinme grafiği veya gezinme grafiğinin hedefi olabilecek bir ViewModelStoreOwner
'e ayarlanır. ViewModel
kapsamları hakkında daha fazla bilgi için dokümanları inceleyebilirsiniz.
Bu durumda ViewModel
, bilginin kaynağı ve kullanıcı arayüzü durumunun en küçük ortak üst öğesidir.
Ekran kullanıcı arayüzü durumu
Yukarıdaki tanımlara göre, ekran kullanıcı arayüzü durumu iş kuralları uygulanarak oluşturulur. Bundan ekran düzeyinde durum sahibinin sorumlu olduğu düşünülürse bu, ekran kullanıcı arayüzü durumunun genellikle ekran düzeyi durum tutucusunda (bu örnekte ViewModel
) kaldırılacağı anlamına gelir.
Bir sohbet uygulamasının ConversationViewModel
özelliğini ve bunu değiştirmek için ekran kullanıcı arayüzü durumunu ve etkinlikleri nasıl gösterdiğini düşünün:
class ConversationViewModel( channelId: String, messagesRepository: MessagesRepository ) : ViewModel() { val messages = messagesRepository .getLatestMessages(channelId) .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = emptyList() ) // Business logic fun sendMessage(message: Message) { /* ... */ } }
Oluşturulabilir öğeler, ViewModel
içinde kaldırılmış ekran kullanıcı arayüzünü kullanır. İş mantığına erişim sağlamak için ViewModel
örneğini ekran düzeyindeki composable'larınıza eklemeniz gerekir.
Aşağıda, ekran düzeyinde composable'da kullanılan bir ViewModel
örneği verilmiştir.
Burada, composable ConversationScreen()
, ViewModel
içinde kaldırılan ekran kullanıcı arayüzü durumunu kullanır:
@Composable private fun ConversationScreen( conversationViewModel: ConversationViewModel = viewModel() ) { val messages by conversationViewModel.messages.collectAsStateWithLifecycle() ConversationScreen( messages = messages, onSendMessage = { message: Message -> conversationViewModel.sendMessage(message) } ) } @Composable private fun ConversationScreen( messages: List<Message>, onSendMessage: (Message) -> Unit ) { MessagesList(messages, onSendMessage) /* ... */ }
Mülk sondajı
"Mülk ayrıntılı incelemesi", verilerin birkaç iç içe yerleştirilmiş alt bileşenden okundukları konuma geçirilmesini ifade eder.
Oluştur'da mülk ayrıntılandırma özelliğinin gösterilebileceği tipik bir örnek, ekran düzeyi durum tutucusunu üst düzeye ekleyip durumu ve etkinlikleri alt composable'lara aktarmanızdır. Bu da ayrıca aşırı miktarda composable işlev imzasına neden olabilir.
Etkinlikleri ayrı lambda parametreleri olarak göstermek işlev imzasını aşırı yükleyebilir ancak composable işlev sorumluluklarının görünürlüğünü en üst düzeye çıkarır. Neler yaptığını bir bakışta görebilirsiniz.
Eyaletleri ve etkinlikleri tek bir yerde toplamak için sarmalayıcı sınıfları oluşturmak yerine mülk sondajı tercih edilir. Çünkü bu, composable sorumlulukların görünürlüğünü azaltır. Sarmalayıcı sınıflarına sahip olmadığınızda, composable'lara yalnızca ihtiyaç duydukları parametreleri iletme olasılığınız da artar. Bu, en iyi uygulamadır.
Bu etkinlikler gezinme etkinlikleri olduğunda da aynı en iyi uygulama geçerlidir. Bu konuda daha fazla bilgiyi gezinme belgelerinde bulabilirsiniz.
Bir performans sorunu tespit ettiyseniz durumun okunmasını ertelemeyi de seçebilirsiniz. Daha fazla bilgi edinmek için performans belgelerine göz atabilirsiniz.
Kullanıcı arayüzü öğesi durumu
Okuması veya yazması gereken bir iş mantığı varsa kullanıcı arayüzü öğesinin durumunu ekran düzeyindeki durum sahibine yükseltebilirsiniz.
Sohbet uygulaması örneğine devam edecek olursak, kullanıcı @
yazıp bir ipucu yazdığında uygulama, grup sohbetinde kullanıcı önerilerini gösterir. Bu öneriler veri katmanından gelir ve kullanıcı önerileri listesi hesaplama mantığı iş mantığı olarak kabul edilir. Özellik şuna benzer:
Bu özelliği uygulayan ViewModel
şöyle görünür:
class ConversationViewModel(/*...*/) : ViewModel() { // Hoisted state var inputMessage by mutableStateOf("") private set val suggestions: StateFlow<List<Suggestion>> = snapshotFlow { inputMessage } .filter { hasSocialHandleHint(it) } .mapLatest { getHandle(it) } .mapLatest { repository.getSuggestions(it) } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = emptyList() ) fun updateInput(newInput: String) { inputMessage = newInput } }
inputMessage
, TextField
durumunu depolayan bir değişkendir. Kullanıcı her yeni giriş yazdığında, uygulama suggestions
üretmek için iş mantığını çağırır.
suggestions
, ekran kullanıcı arayüzü durumudur ve StateFlow
öğesinden toplanarak Compose kullanıcı arayüzünde tüketilir.
Uyarı
Bazı Compose kullanıcı arayüzü öğesi durumları için ViewModel
öğesine yükseltme işleminde özel noktalara dikkat edilmesi gerekebilir. Örneğin, Compose kullanıcı arayüzü öğelerinin bazı durum sahipleri, durumu değiştirme yöntemleri gösterir. Bunların bazıları, animasyonları tetikleyen askıya alma işlevleri olabilir. Bu askıya alma işlevleri, bunları Beste kapsamında olmayan bir CoroutineScope
dan çağırırsanız istisnalar atabilir.
Uygulama çekmecesinin içeriğinin dinamik olduğunu ve kapatıldıktan sonra veri katmanından içeriği getirip yenilemeniz gerektiğini varsayalım. Durum sahibinden bu öğedeki kullanıcı arayüzünü ve iş mantığını çağırabilmek için çekmece durumunu ViewModel
öğesine kaldırmanız gerekir.
Ancak, Oluştur kullanıcı arayüzünden viewModelScope
, DrawerState
close()
yönteminin çağrılması, "MonotonicFrameClock
mesajını içeren IllegalStateException
türünde bir çalışma zamanı istisnasına neden olur CoroutineContext”
.
Bunu düzeltmek için Besteyi kapsama alan bir CoroutineScope
kullanın. CoroutineContext
içinde, askıya alma işlevlerinin çalışması için gerekli olan bir MonotonicFrameClock
sağlar.
Bu kilitlenmeyi düzeltmek için ViewModel
içindeki eş yordamın CoroutineContext
değerini Beste kapsamındaki bir eşle değiştirin. Şöyle görünebilir:
class ConversationViewModel(/*...*/) : ViewModel() { val drawerState = DrawerState(initialValue = DrawerValue.Closed) private val _drawerContent = MutableStateFlow(DrawerContent.Empty) val drawerContent: StateFlow<DrawerContent> = _drawerContent.asStateFlow() fun closeDrawer(uiScope: CoroutineScope) { viewModelScope.launch { withContext(uiScope.coroutineContext) { // Use instead of the default context drawerState.close() } // Fetch drawer content and update state _drawerContent.update { content } } } } // in Compose @Composable private fun ConversationScreen( conversationViewModel: ConversationViewModel = viewModel() ) { val scope = rememberCoroutineScope() ConversationScreen(onCloseDrawer = { conversationViewModel.closeDrawer(uiScope = scope) }) }
Daha fazla bilgi
State ve Jetpack Compose hakkında daha fazla bilgi edinmek için aşağıdaki ek kaynaklara başvurun.
Numuneler
Codelab uygulamaları
Videolar
Sizin için önerilenler
- Not: Bağlantı metni JavaScript kapalıyken görüntülenir
- Oluşturma penceresinde kullanıcı arayüzü durumunu kaydetme
- Listeler ve ızgaralar
- Compose kullanıcı arayüzünüzü tasarlama