Views'dan Compose'a geçiş tamamen kullanıcı arayüzüyle ilgili olsa da güvenli ve kademeli bir geçiş yapmak için dikkate alınması gereken birçok nokta vardır. Bu sayfada, Görünüm tabanlı uygulamanızı Compose'a taşırken dikkat etmeniz gereken bazı noktalar yer almaktadır.
Uygulamanızın temasını taşıma
Material Design, Android uygulamalarını temalandırmak için önerilen tasarım sistemidir.
Görünüme dayalı uygulamalarda üç Material sürümü kullanılabilir:
- AppCompat kitaplığının kullanıldığı Material Design 1 (ör.
Theme.AppCompat.*) - MDC-Android kitaplığını (ör.
Theme.MaterialComponents.*) kullanarak Material Design 2 - MDC-Android kitaplığını (ör.
Theme.Material3.*) kullanarak Material Design 3
Compose uygulamaları için iki Materyal sürümü mevcuttur:
- Compose Material kitaplığını kullanarak Material Design 2
(ör.
androidx.compose.material.MaterialTheme) - Compose Material 3 kitaplığını kullanarak Material Design 3
(ör.
androidx.compose.material3.MaterialTheme)
Uygulamanızın tasarım sistemi destekliyorsa en yeni sürümü (Material 3) kullanmanızı öneririz. Hem Views hem de Compose için taşıma kılavuzları mevcuttur:
- Görünümlerde 1. Materyalden 2. Materyale
- Görünümlerde Material 2'den Material 3'e geçiş
- Compose'da Material 2'den Material 3'e geçiş
Compose'da yeni ekranlar oluştururken, kullandığınız Material Design sürümünden bağımsız olarak, Compose Material kitaplıklarından kullanıcı arayüzü yayan tüm composable'lardan önce MaterialTheme uyguladığınızdan emin olun. Material bileşenleri (Button, Text vb.), MaterialTheme öğesinin yerinde olmasına bağlıdır ve bu öğe olmadan davranışları tanımlanmamıştır.
Tüm Jetpack Compose örnekleri, MaterialTheme üzerine kurulu özel bir Compose teması kullanır.
Daha fazla bilgi edinmek için Compose'da tasarım sistemleri ve XML temalarını Compose'a taşıma başlıklı makaleleri inceleyin.
Navigasyon
Uygulamanızda Navigation bileşenini kullanıyorsanız daha fazla bilgi için Compose ile gezinme - Birlikte çalışabilirlik ve Jetpack Navigation'ı Navigation Compose'a taşıma başlıklı makaleleri inceleyin.
Karma Compose/Görünümler kullanıcı arayüzünüzü test etme
Uygulamanızın bazı bölümlerini Compose'a taşıdıktan sonra, herhangi bir şeyi bozmadığınızdan emin olmak için test yapmanız çok önemlidir.
Bir etkinlik veya parça Compose'u kullandığında ActivityScenarioRule yerine
createAndroidComposeRule
kullanmanız gerekir. createAndroidComposeRule, Compose ve View kodunu aynı anda test etmenizi sağlayan bir ComposeTestRule ile ActivityScenarioRule entegre olur.
class MyActivityTest { @Rule @JvmField val composeTestRule = createAndroidComposeRule<MyActivity>() @Test fun testGreeting() { val greeting = InstrumentationRegistry.getInstrumentation() .targetContext.resources.getString(R.string.greeting) composeTestRule.onNodeWithText(greeting).assertIsDisplayed() } }
Test hakkında daha fazla bilgi edinmek için Oluşturma düzeninizi test etme başlıklı makaleyi inceleyin. Kullanıcı arayüzü testi çerçeveleriyle birlikte çalışabilirlik için Espresso ile birlikte çalışabilirlik ve UiAutomator ile birlikte çalışabilirlik başlıklı makalelere bakın.
Compose'u mevcut uygulama mimarinizle entegre etme
Tek Yönlü Veri Akışı (UDF) mimarisi desenleri, Compose ile sorunsuz bir şekilde çalışır. Uygulama bunun yerine Model View Presenter (MVP) gibi başka mimari kalıplar kullanıyorsa Compose'u kullanmaya başlamadan önce veya kullanırken kullanıcı arayüzünün bu bölümünü UDF'ye taşımanızı öneririz.
Yazma işleminde ViewModel kullanma
Architecture Components
ViewModel kitaplığını kullanıyorsanız Compose ve diğer kitaplıklar bölümünde açıklandığı gibi viewModel() işlevini çağırarak herhangi bir composable'dan ViewModel öğesine erişebilirsiniz.
Compose'u kullanırken ViewModel öğeleri View yaşam döngüsü kapsamlarını izlediğinden farklı composable'larda aynı ViewModel türünü kullanmamaya dikkat edin. Kapsam, Navigation kitaplığı kullanılıyorsa ana makine etkinliği, parça veya gezinme grafiği olur.
Örneğin, composable'lar bir etkinlikte barındırılıyorsa viewModel() her zaman aynı örneği döndürür. Bu örnek yalnızca etkinlik tamamlandığında temizlenir.
Aşağıdaki örnekte, aynı kullanıcı ("user1") iki kez karşılanıyor. Bunun nedeni, ana makine etkinliği altındaki tüm composable'larda aynı GreetingViewModel örneğinin yeniden kullanılmasıdır. Oluşturulan ilk ViewModel örneği diğer composable'larda yeniden kullanılır.
class GreetingActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MaterialTheme { Column { GreetingScreen("user1") GreetingScreen("user2") } } } } } @Composable fun GreetingScreen( userId: String, viewModel: GreetingViewModel = viewModel( factory = GreetingViewModelFactory(userId) ) ) { val messageUser by viewModel.message.observeAsState("") Text(messageUser) } class GreetingViewModel(private val userId: String) : ViewModel() { private val _message = MutableLiveData("Hi $userId") val message: LiveData<String> = _message } class GreetingViewModelFactory(private val userId: String) : ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create(modelClass: Class<T>): T { return GreetingViewModel(userId) as T } }
Navigasyon grafikleri ViewModel öğelerini de kapsadığından, navigasyon grafiğinde hedef olan composable'lar farklı bir ViewModel örneğine sahiptir.
Bu durumda, ViewModel, hedefin yaşam döngüsüyle sınırlanır ve hedef, geri yığından kaldırıldığında temizlenir. Aşağıdaki örnekte, kullanıcı Profil ekranına gittiğinde GreetingViewModel öğesinin yeni bir örneği oluşturulur.
@Composable fun MyApp() { NavHost(rememberNavController(), startDestination = "profile/{userId}") { /* ... */ composable("profile/{userId}") { backStackEntry -> GreetingScreen(backStackEntry.arguments?.getString("userId") ?: "") } } }
Doğru durum kaynağı
Compose'u kullanıcı arayüzünün bir bölümünde kullandığınızda Compose ve View sistemi kodunun verileri paylaşması gerekebilir. Mümkün olduğunda, bu paylaşılan durumu her iki platform tarafından kullanılan UDF en iyi uygulamalarına uyan başka bir sınıfta kapsamanızı öneririz. Örneğin, veri güncellemelerini yayınlamak için paylaşılan verilerin akışını gösteren bir ViewModel içinde.
Ancak paylaşılacak veriler değiştirilebiliyorsa veya bir kullanıcı arayüzü öğesine sıkı bir şekilde bağlıysa bu her zaman mümkün olmayabilir. Bu durumda, bir sistem doğruluk kaynağı olmalı ve bu sistem, diğer sistemle tüm veri güncellemelerini paylaşmalıdır. Genel bir kural olarak, doğruluk kaynağı, kullanıcı arayüzü hiyerarşisinin köküne daha yakın olan öğeye ait olmalıdır.
Doğru bilgi kaynağı olarak Compose
Compose durumunu Compose olmayan koda yayınlamak için
SideEffect
composable'ı kullanın. Bu durumda, doğruluk kaynağı, durum güncellemeleri gönderen bir composable içinde tutulur.
Örneğin, analiz kitaplığınız, sonraki tüm analiz etkinliklerine özel meta veriler (bu örnekte kullanıcı özellikleri) ekleyerek kullanıcı popülasyonunuzu segmentlere ayırmanıza olanak tanıyabilir. Mevcut kullanıcının kullanıcı türünü analiz kitaplığınıza iletmek için SideEffect kullanarak değerini güncelleyin.
@Composable fun rememberFirebaseAnalytics(user: User): FirebaseAnalytics { val analytics: FirebaseAnalytics = remember { FirebaseAnalytics() } // On every successful composition, update FirebaseAnalytics with // the userType from the current User, ensuring that future analytics // events have this metadata attached SideEffect { analytics.setUserProperty("userType", user.userType) } return analytics }
Daha fazla bilgi için Compose'daki yan etkiler başlıklı makaleyi inceleyin.
Sistemi doğru bilgi kaynağı olarak görme
Durum, View sistemi tarafından sahiplenilip Compose ile paylaşılıyorsa Compose'da iş parçacığı güvenli hale getirmek için durumu mutableStateOf nesnelerine sarmalamanızı öneririz. Bu yaklaşımı kullanırsanız composable işlevler basitleştirilir. Çünkü artık tek bir doğru kaynağa sahip değildirler. Ancak View sisteminin, değiştirilebilir durumu ve bu durumu kullanan View'ları güncellemesi gerekir.
Aşağıdaki örnekte, CustomViewGroup içinde TextView ve TextField composable'ı olan bir ComposeView yer alıyor. TextView, kullanıcının TextField alanına yazdığı içeriği göstermelidir.
class CustomViewGroup @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : LinearLayout(context, attrs, defStyle) { // Source of truth in the View system as mutableStateOf // to make it thread-safe for Compose private var text by mutableStateOf("") private val textView: TextView init { orientation = VERTICAL textView = TextView(context) val composeView = ComposeView(context).apply { setContent { MaterialTheme { TextField(value = text, onValueChange = { updateState(it) }) } } } addView(textView) addView(composeView) } // Update both the source of truth and the TextView private fun updateState(newValue: String) { text = newValue textView.text = newValue } }
Paylaşılan kullanıcı arayüzünü taşıma
Compose'a kademeli olarak geçiş yapıyorsanız hem Compose'da hem de View sisteminde paylaşılan kullanıcı arayüzü öğelerini kullanmanız gerekebilir. Örneğin, uygulamanızda özel bir CallToActionButton bileşen varsa hem Compose hem de View tabanlı ekranlarda bu bileşeni kullanmanız gerekebilir.
Compose'da paylaşılan kullanıcı arayüzü öğeleri, XML kullanılarak stil verilen veya özel görünüm olan öğeden bağımsız olarak uygulama genelinde yeniden kullanılabilen composable'lara dönüşür. Örneğin, CallToActionButton harekete geçirici mesaj bileşeniniz için composable oluşturursunuz Button.
View tabanlı ekranlarda composable'ı kullanmak için AbstractComposeView öğesinden türeyen özel bir görünüm sarmalayıcısı oluşturun. Geçersiz kılınan Content composable'ında, aşağıdaki örnekte gösterildiği gibi, oluşturduğunuz composable'ı Compose temanıza sarılmış şekilde yerleştirin:
@Composable fun CallToActionButton( text: String, onClick: () -> Unit, modifier: Modifier = Modifier, ) { Button( colors = ButtonDefaults.buttonColors( containerColor = MaterialTheme.colorScheme.secondary ), onClick = onClick, modifier = modifier, ) { Text(text) } } class CallToActionViewButton @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyle: Int = 0 ) : AbstractComposeView(context, attrs, defStyle) { var text by mutableStateOf("") var onClick by mutableStateOf({}) @Composable override fun Content() { YourAppTheme { CallToActionButton(text, onClick) } } }
Birleştirilebilir parametrelerin, özel görünümde değiştirilebilir değişkenler haline geldiğini unutmayın. Bu sayede, özel CallToActionViewButton görünümü şişirilebilir ve geleneksel bir görünüm gibi kullanılabilir. View Binding ile ilgili örneği aşağıda görebilirsiniz:
class ViewBindingActivity : ComponentActivity() { private lateinit var binding: ActivityExampleBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityExampleBinding.inflate(layoutInflater) setContentView(binding.root) binding.callToAction.apply { text = getString(R.string.greeting) onClick = { /* Do something */ } } } }
Özel bileşen değiştirilebilir durum içeriyorsa Doğruluğun durum kaynağı bölümüne bakın.
Durumu sunudan ayırmaya öncelik verin
Geleneksel olarak View durum bilgisi içerir. View, nasıl gösterileceğinin yanı sıra ne gösterileceğini açıklayan alanları yönetir. Bir View öğesini Compose'a dönüştürdüğünüzde, state hoisting bölümünde daha ayrıntılı olarak açıklandığı gibi tek yönlü bir veri akışı elde etmek için oluşturulan verileri ayırmaya çalışın.
Örneğin, bir View, görünür, görünmez veya kayıp olup olmadığını açıklayan bir visibility özelliğine sahiptir. Bu, View'nın doğasında olan bir özelliktir. Diğer kod parçaları View görünürlüğünü değiştirebilir ancak mevcut görünürlüğün ne olduğunu yalnızca View bilir. View görünürlüğünü sağlama mantığı hataya açık olabilir ve genellikle View ile bağlantılıdır.
Bunun aksine, Compose, Kotlin'deki koşullu mantığı kullanarak tamamen farklı composable'ları görüntülemeyi kolaylaştırır:
@Composable fun MyComposable(showCautionIcon: Boolean) { if (showCautionIcon) { CautionIcon(/* ... */) } }
CautionIcon, tasarım gereği neden gösterildiğini bilmek veya önemsemek zorunda değildir ve visibility kavramı yoktur: ya kompozisyonda yer alır ya da yer almaz.
Durum yönetimi ile sunum mantığını net bir şekilde ayırarak, içeriği durumdan kullanıcı arayüzüne dönüştürürken nasıl göstereceğinizi daha özgürce değiştirebilirsiniz. Gerekli olduğunda durumu yükseltebilmek, durum sahipliği daha esnek olduğundan composable'ları daha fazla yeniden kullanılabilir hale getirir.
Kapsüllenmiş ve yeniden kullanılabilir bileşenleri tanıtın
View öğeleri genellikle nerede bulundukları hakkında bir fikre sahiptir: Activity, Dialog, Fragment veya başka bir View hiyerarşisinin içinde bir yerde. Genellikle statik düzen dosyalarından şişirildikleri için View'nın genel yapısı çok katı olma eğilimindedir. Bu durum, daha sıkı bir bağlantıya yol açar ve View öğesinin değiştirilmesini veya yeniden kullanılmasını zorlaştırır.
Örneğin, özel bir View, belirli bir kimliğe sahip belirli türde bir alt görünümü olduğunu varsayabilir ve bazı işlemlerin sonucunda özelliklerini doğrudan değiştirebilir. Bu durum, söz konusu View öğelerini birbirine sıkı bir şekilde bağlar: Alt öğeyi bulamazsa özel View kilitlenebilir veya bozulabilir. Alt öğe de özel View üst öğesi olmadan yeniden kullanılamaz.
Yeniden kullanılabilir composable'lar içeren Compose'da bu sorun daha az görülür. Ebeveynler durumu ve geri çağırmaları kolayca belirleyebilir. Böylece, kullanılacakları yeri tam olarak bilmenize gerek kalmadan yeniden kullanılabilir composable'lar yazabilirsiniz.
@Composable fun AScreen() { var isEnabled by rememberSaveable { mutableStateOf(false) } Column { ImageWithEnabledOverlay(isEnabled) ControlPanelWithToggle( isEnabled = isEnabled, onEnabledChanged = { isEnabled = it } ) } }
Yukarıdaki örnekte, üç bölümün tamamı daha fazla kapsüllenmiş ve daha az bağlıdır:
ImageWithEnabledOverlayyalnızca mevcutisEnableddurumunun ne olduğunu bilmesi gerekir.ControlPanelWithToggle'nin var olduğunu veya nasıl kontrol edilebileceğini bilmesi gerekmez.ControlPanelWithToggle,ImageWithEnabledOverlayadlı yerin varlığından haberdar değil.isEnabledöğesinin gösterilme şekli sıfır, bir veya daha fazla olabilir veControlPanelWithToggleöğesinin değişmesi gerekmez.Üst öğe için
ImageWithEnabledOverlayveyaControlPanelWithToggleöğelerinin ne kadar iç içe yerleştirildiği önemli değildir. Bu çocuklar değişiklikleri canlandırabilir, içerikleri değiştirebilir veya diğer çocuklara iletebilir.
Bu kalıp, kontrolün tersine çevrilmesi olarak bilinir. Bu konu hakkında daha fazla bilgiyi CompositionLocal dokümanlarında bulabilirsiniz.
Ekran boyutu değişikliklerini işleme
Farklı pencere boyutları için farklı kaynaklara sahip olmak, duyarlı View düzenler oluşturmanın temel yollarından biridir. Nitelikli kaynaklar, ekran düzeyindeki düzen kararları için hâlâ bir seçenek olsa da Compose, normal koşullu mantıkla düzenleri tamamen kodda değiştirmeyi çok daha kolay hale getirir. Daha fazla bilgi edinmek için Pencere boyutu sınıflarını kullanma başlıklı makaleyi inceleyin.
Ayrıca, Compose'un uyarlanabilir kullanıcı arayüzleri oluşturmak için sunduğu teknikler hakkında bilgi edinmek üzere Farklı ekran boyutlarını destekleme başlıklı makaleyi inceleyin.
Görünümlerle iç içe kaydırma
Kaydırılabilir View öğeleri ile her iki yönde de iç içe yerleştirilmiş kaydırılabilir composable'lar arasında iç içe kaydırma birlikte çalışabilirliğini etkinleştirme hakkında daha fazla bilgi için İç içe kaydırma birlikte çalışabilirliği başlıklı makaleyi inceleyin.
RecyclerView uygulamasında oluşturma
RecyclerView sürümü 1.3.0-alpha02'den itibaren RecyclerView içindeki composable'lar yüksek performanslıdır. Bu avantajlardan yararlanmak için RecyclerView uygulamasının en az 1.3.0-alpha02 sürümünü kullandığınızdan emin olun.
WindowInsets Görünümlerle birlikte çalışabilirlik
Ekranınızda aynı hiyerarşide hem Görünümler hem de Compose kodu varsa varsayılan iç boşlukları geçersiz kılmanız gerekebilir. Bu durumda, hangi öğenin iç kısımları kullanacağını ve hangisinin bunları yoksayacağını açıkça belirtmeniz gerekir.
Örneğin, en dıştaki düzeniniz bir Android View düzeniyse View sistemindeki iç kısımları kullanmanız ve Compose'da bunları yoksaymanız gerekir.
Alternatif olarak, en dıştaki düzeniniz composable ise Compose'daki yerleşimleri kullanmalı ve AndroidView composable'ları buna göre doldurmalısınız.
Varsayılan olarak her ComposeView, WindowInsetsCompat tüketim düzeyindeki tüm ekleri tüketir. Bu varsayılan davranışı değiştirmek için
ComposeView.consumeWindowInsets
değerini false olarak ayarlayın.
Daha fazla bilgi için Compose'da WindowInsets dokümanlarını okuyun.
Sizin için önerilenler
- Not: Bağlantı metni, JavaScript kapalıyken gösterilir.
- Görüntülenen emoji
- Compose'da Material Design 2
- Compose'da pencere iç kenarları