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 açıklanmaktadı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ığı (ör.
Theme.MaterialComponents.*
) kullanılarak Material Design 2 - MDC-Android kitaplığı (ör.
Theme.Material3.*
) kullanılarak Material Design 3
Compose uygulamaları için iki Material 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. Materyal ile 2. Materyal Arasındaki Fark
- Görünümlerde Malzeme 2'den Malzeme 3'e
- Oluşturma'da Materyal 2'den Materyal 3'e
Compose'da yeni ekranlar oluştururken, kullandığınız Material Design sürümü ne olursa olsun, Compose Material kitaplıklarından kullanıcı arayüzü yayan 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. UI 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) mimari kalıpları, 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
hedef 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 kaynağın durumu
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 uygun 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 oluşturma
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
, TextView
ve TextField
composable'ı içeren bir ComposeView
içeriyor. 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 öğe veya özel görünümden 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
.
Görünüme dayalı ekranlarda composable'ı kullanmak için AbstractComposeView
'dan 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ü geleneksel bir görünüm gibi şişirilebilir ve kullanılabilir hale gelir. 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ı bir View
öğesinin görünürlüğünü değiştirebilir ancak mevcut görünürlüğünün ne olduğunu gerçekten bilen tek şey View
öğesinin kendisidir. 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.
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. visibility
kavramı yoktur: ya Kompozisyon'dadır ya da değildir.
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
öğesinin 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 bir türde alt görünümü olduğunu varsayabilir ve bazı işlemlere yanıt olarak özelliklerini doğrudan değiştirebilir. Bu durum, söz konusu View
öğelerini birbirine sıkıca bağlar: Alt öğeyi bulamazsa özel View
kilitlenebilir veya bozulabilir. Alt öğe ise özel View
üst öğesi olmadan yeniden kullanılamaz.
Yeniden kullanılabilir composable'lar sayesinde 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:
ImageWithEnabledOverlay
yalnızca mevcutisEnabled
durumunun ne olduğunu bilmesi gerekir.ControlPanelWithToggle
öğesinin varlığını veya nasıl kontrol edilebileceğini bilmesi gerekmez.ControlPanelWithToggle
,ImageWithEnabledOverlay
'nin varlığından haberdar değildir.isEnabled
öğesinin gösterilme şekli sıfır, bir veya daha fazla olabilir veControlPanelWithToggle
öğesinin değişmesi gerekmez.Ebeveyn için
ImageWithEnabledOverlay
veyaControlPanelWithToggle
öğelerinin ne kadar iç içe yerleştirildiği önemli değildir. Bu çocuklar değişiklikleri animasyonla gösterebilir, 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 bunları Compose için yoksaymanız gerekir.
Alternatif olarak, en dıştaki düzeniniz composable ise Compose'daki yerleşimleri kullanmanız ve AndroidView
composable'ları buna göre doldurmanız gerekir.
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ümanını inceleyin.
Sizin için önerilenler
- Not: JavaScript kapalıyken bağlantı metni gösterilir.
- Emoji görüntüleme
- Compose'da Material Design 2
- Oluşturma penceresindeki pencere içleri