Görünümler'den Compose'a geçiş tamamen kullanıcı arayüzüyle ilgili olsa da güvenli ve aşamalı bir geçiş gerçekleştirmek 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
Materyal Tasarım, Android uygulamalarına tema uygulamak için önerilen tasarım sistemidir.
Görünüm tabanlı uygulamalar için üç Material sürümü mevcuttur:
- AppCompat kitaplığını kullanan Materyal Tasarım 1 (ör.
Theme.AppCompat.*) - MDC-Android kitaplığını kullanan Materyal Tasarım 2 (ör.
Theme.MaterialComponents.*) - MDC-Android kitaplığını (ör.
Theme.Material3.*) kullanan Materyal Tasarım 3
Compose uygulamaları için iki Material sürümü mevcuttur:
- Compose Material kitaplığını kullanan Materyal Tasarım 2 (ör.
androidx.compose.material.MaterialTheme) - Compose Material 3 kitaplığını kullanan Materyal Tasarım 3 (ör.
androidx.compose.material3.MaterialTheme)
Uygulamanızın tasarım sistemi uygunsa en yeni sürümü (Material 3) kullanmanızı öneririz. Hem Görünümler hem de Compose için geçiş rehberleri mevcuttur:
- Görünümlerde Materyal 1'den Materyal 2'ye geçiş
- 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 Materyal Tasarım sürümünden bağımsız olarak, Compose Material kitaplıklarından kullanıcı arayüzü oluşturan tüm composable'lardan önce bir MaterialTheme uyguladığınızdan emin olun. Materyal bileşenleri (Button, Text vb.) bir MaterialTheme öğesinin mevcut olmasına bağlıdır ve bu öğe olmadan davranışları tanımlanmaz.
Tüm Jetpack Compose örnekleri, MaterialTheme üzerine oluşturulmuş ö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.
Gezinme
Uygulamanızda Navigation bileşenini kullanıyorsanız daha fazla bilgi için Parça tabanlı bir uygulamada Compose'dan gezinme ve Jetpack Navigation'ı Navigation Compose'a taşıma başlıklı makaleleri inceleyin.
Karma Compose/Views 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 yapmak ç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 etmenize olanak tanıyan 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ü test çerçeveleriyle birlikte çalışabilirlik için Espresso ile birlikte çalışabilirlik ve UiAutomator ile birlikte çalışabilirlik bölümlerine bakın.
Compose'u mevcut uygulama mimarinizle entegre etme
Tek Yönlü Veri Akışı (UDF) mimari kalıpları, Compose ile sorunsuz bir şekilde çalışır. Uygulama, Model View Presenter (MVP) gibi başka mimari kalıplar kullanıyorsa kullanıcı arayüzünün bu bölümünü Compose'u kullanmaya başlamadan önce veya kullanmaya başlarken UDF'ye taşımanızı öneririz.
Oluşturma'da 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 kullanmaya başlarken farklı composable'larda aynı ViewModel türünü kullanmaya dikkat edin. ViewModel öğeleri, görünüm yaşam döngüsü kapsamlarına uyar. Kapsam; ana etkinlik, parça veya Gezinme kitaplığı kullanılıyorsa gezinme grafiği olur.
Örneğin, composable'lar bir etkinlikte barındırılıyorsa viewModel() her zaman yalnızca etkinlik bittiğinde temizlenen aynı örneği döndürür.
Aşağıdaki örnekte, aynı kullanıcı ("user1") iki kez karşılanır çünkü aynı GreetingViewModel örneği, ana makine etkinliğinin altındaki tüm composable'larda yeniden kullanılı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 } }
Gezinme grafikleri ViewModel öğelerini de kapsadığından, gezinme grafiğinde hedef olan composable'lar farklı bir ViewModel örneğine sahiptir.
Bu durumda ViewModel, hedefin yaşam döngüsü kapsamındadır ve hedef, eski yığından kaldırıldığında temizlenir. Aşağıdaki örnekte, kullanıcı Profil ekranına gittiğinde yeni bir GreetingViewModel örneği oluşturulur.
@Composable fun MyApp() { NavHost(rememberNavController(), startDestination = "profile/{userId}") { /* ... */ composable("profile/{userId}") { backStackEntry -> GreetingScreen(backStackEntry.arguments?.getString("userId") ?: "") } } }
Eyalet veri kaynağı
Kullanıcı arayüzünün bir bölümünde Compose'u kullandığınızda Compose ve View sistemi kodunun veri paylaşması gerekebilir. Mümkün olduğunda, bu paylaşılan durumu her iki platform tarafından kullanılan UDF en iyi uygulamalarını izleyen başka bir sınıfta kapsüllemenizi ö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, sistemlerden biri doğru bilgi kaynağı olmalı ve veri güncellemelerini diğer sistemle 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 dışı koda yayınlamak için SideEffect composable'ını kullanın. Bu durumda, doğru kaynak, durum güncellemeleri gönderen bir composable'da tutulur.
Örneğin, analiz kitaplığınız, sonraki tüm analiz etkinliklerine özel meta veriler (bu örnekte kullanıcı özellikleri) ekleyerek kullanıcı kitlenizi 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'da yan etkiler başlıklı makaleyi inceleyin.
Sistemi doğru bilgi kaynağı olarak görme
Durum, görünüm sistemine aitse ve Compose ile paylaşılıyorsa Compose için iş parçacığı açısından güvenli hale getirmek amacıyla durumu mutableStateOf nesnelerine sarmalamanızı öneririz. Bu yaklaşımı kullanırsanız composable işlevler basitleştirilir çünkü artık doğru kaynak onlarda değildir. Ancak Görünüm sisteminin, değiştirilebilir durumu ve bu durumu kullanan Görünümleri güncellemesi gerekir.
Aşağıdaki örnekte, CustomViewGroup bir TextView ve içinde TextField composable'ı olan bir ComposeView içerir. 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 görünüm sisteminde paylaşılan kullanıcı arayüzü öğelerini kullanmanız gerekebilir. Örneğin, uygulamanızda özel bir CallToActionButton bileşeni varsa bunu hem Oluşturma hem de Görünüm tabanlı ekranlarda kullanmanız gerekebilir.
Compose'da, paylaşılan kullanıcı arayüzü öğeleri, öğenin XML kullanılarak stillendirilmesine veya özel bir görünüm olmasına bakılmaksızın uygulama genelinde yeniden kullanılabilen composable'lar haline gelir. Örneğin, özel harekete geçirici mesaj Button bileşeniniz için bir CallToActionButton composable oluşturursunuz.
Oluşturulabilir öğeyi görünüm tabanlı ekranlarda kullanmak için AbstractComposeView'dan genişletilen özel bir görünüm sarmalayıcı oluşturun. Geçersiz kılınan Content composable'ında, oluşturduğunuz composable'ı aşağıdaki örnekte gösterildiği gibi Compose temanıza sarmalanmış ş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) } } }
Oluşturulabilir parametrelerin, özel görünümde değiştirilebilir değişkenlere dönüştüğüne dikkat edin. Bu sayede özel CallToActionViewButton görünümü, geleneksel bir görünüm gibi şişirilebilir ve kullanılabilir hale gelir. Görünüm bağlama ile ilgili bir ö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 Durum için doğru kaynak bölümüne bakın.
Durumu sunudan ayırmaya öncelik verme
Geleneksel olarak View durum bilgisi içerir. View, nasıl gösterileceğinin yanı sıra neyin gösterileceğini açıklayan alanları yönetir. Bir View öğesini Compose'a dönüştürürken, 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 dikkat edin.
Örneğin, bir View, görünür, görünmez veya kaybolmuş olup olmadığını açıklayan bir visibility özelliğine sahiptir. Bu, View modelinin doğal bir özelliğidir. Diğer kod parçaları bir View öğesinin görünürlüğünü değiştirebilse de yalnızca View öğesinin kendisi mevcut görünürlüğünün ne olduğunu gerçekten bilir. Bir View öğesinin görünür olmasını sağlama mantığı hataya açık olabilir ve genellikle View öğesinin kendisine bağlıdır.
Buna karşılık Compose, Kotlin'de koşullu mantık kullanarak tamamen farklı composable'ları görüntülemeyi kolaylaştırır:
@Composable fun MyComposable(showCautionIcon: Boolean) { if (showCautionIcon) { CautionIcon(/* ... */) } }
Tasarımı gereği CautionIcon, neden gösterildiğini bilmek veya önemsemek zorunda değildir ve visibility kavramı yoktur: Ya oluşturmada yer alır ya da yer almaz.
Durum yönetimi ve sunum mantığını net bir şekilde ayırarak içeriği kullanıcı arayüzüne dönüştürme şeklinizi daha özgürce değiştirebilirsiniz. Gerektiğinde durumu yukarı çekebilmek, durum sahipliği daha esnek olduğundan composable'ları daha yeniden kullanılabilir hale getirir.
Kapsüllenmiş ve yeniden kullanılabilir bileşenleri teşvik etme
View öğeleri genellikle nerede bulundukları hakkında bir fikre sahiptir: Activity, Dialog, Fragment veya başka bir View hiyerarşisinin içinde. Genellikle statik düzen dosyalarından şişirildikleri için View'nin genel yapısı çok katı olma eğilimindedir. Bu durum, daha sıkı bağlantıya neden olur ve View değiştirilmesini veya yeniden kullanılmasını zorlaştırır.
Örneğin, özel bir View, belirli bir kimliğe sahip belirli bir türde çocuk ekranına sahip olduğunu varsayabilir ve bazı işlemlere yanıt olarak özelliklerini doğrudan değiştirebilir. Bu durum, View öğelerini birbirine sıkıca bağlar: Özel View, alt öğeyi bulamazsa kilitlenebilir veya bozulabilir ve alt öğe, özel View üst öğesi olmadan yeniden kullanılamayabilir.
Yeniden kullanılabilir bileşenler sayesinde Compose'da bu sorun daha az görülür. Ebeveynler, durumu ve geri çağırmaları kolayca belirtebilir. Böylece, tam olarak nerede kullanılacaklarını 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 de daha kapsüllenmiş ve daha az bağlıdır:
ImageWithEnabledOverlay, yalnızca mevcutisEnableddurumunun ne olduğunu bilmelidir.ControlPanelWithToggle'nin var olduğunu veya nasıl kontrol edilebileceğini bilmesi gerekmez.ControlPanelWithToggle,ImageWithEnabledOverlayadlı bir dosyanın varlığından haberdar değil.isEnabled, sıfır, bir veya daha fazla şekilde gösterilebilir veControlPanelWithToggle'in değişmesi gerekmez.Üst öğe için
ImageWithEnabledOverlayveyaControlPanelWithToggleöğelerinin ne kadar iç içe yerleştirildiği önemli değildir. Bu alt öğeler değişiklikleri canlandırıyor, içerikleri değiştiriyor veya içerikleri diğer alt öğelere aktarıyor olabilir.
Bu kalıp, denetimin tersine çevrilmesi olarak bilinir. Bu konu hakkında daha fazla bilgiyi CompositionLocal belgelerinde bulabilirsiniz.
Ekran boyutu değişikliklerini işleme
Farklı pencere boyutları için farklı kaynaklar kullanmak, duyarlı View düzenleri oluşturmanın temel yollarından biridir. Ekran düzeyinde düzen kararları için nitelikli kaynaklar 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 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 görünüm öğeleri ile kaydırılabilir composable'lar arasında her iki yönde iç içe yerleştirilmiş kaydırma birlikte çalışabilirliğini nasıl etkinleştireceğiniz hakkında daha fazla bilgi için İç içe yerleştirilmiş kaydırma birlikte çalışabilirliği başlıklı makaleyi inceleyin.
RecyclerView uygulamasında oluşturun
RecyclerView içindeki composable'lar, RecyclerView 1.3.0-alpha02 sürümünden itibaren yüksek performanslıdır. Bu avantajlardan yararlanmak için RecyclerView kitaplığının en az 1.3.0-alpha02 sürümünü kullandığınızdan emin olun.
WindowInsets, Görünümler ile birlikte çalışabilir
Ekranınızda aynı hiyerarşide hem Görünüm hem de Oluşturma kodu varsa varsayılan iç boşlukları geçersiz kılmanız gerekebilir. Bu durumda, hangisinin içe yerleştirmeleri kullanması, hangisinin yoksayması gerektiğini açıkça belirtmeniz gerekir.
Örneğin, en dıştaki düzeniniz bir Android View düzeniyse iç boşlukları View sisteminde kullanmanız ve Oluşturma için yoksaymanız gerekir.
Alternatif olarak, en dıştaki düzeniniz bir composable ise Compose'da iç boşlukları kullanmanız ve AndroidView composable'ları buna göre doldurmanız gerekir.
Varsayılan olarak her ComposeView, WindowInsetsCompat tüketim düzeyindeki tüm iç kısımları 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: JavaScript kapalıyken bağlantı metni gösterilir.
- Emoji göster
- Compose'da Materyal Tasarım 2
- Compose'da pencere iç boşlukları