Dikkat edilmesi gereken diğer noktalar

Görünümler'den Oluştur'a geçiş tamamen kullanıcı arayüzüyle ilgili olsa da güvenli ve artımlı bir taşıma işlemi gerçekleştirmek için dikkate alınması gereken birçok nokta vardır. Bu sayfada, görüntülemeye dayalı uygulamanızı Compose'a taşırken dikkate almanız gereken bazı noktalar açıklanmaktadır.

Uygulamanızın temasını taşıma

Android uygulamalarında tema oluşturmak için önerilen tasarım sistemi Materyal Tasarım'dır.

Görüntü tabanlı uygulamalar için üç Material sürümü mevcuttur:

  • AppCompat kitaplığını kullanan Material Design 1 (ör. Theme.AppCompat.*)
  • MDC-Android kitaplığını (ör. Theme.MaterialComponents.*) kullanan Material Design 2
  • MDC-Android kitaplığını (ör. Theme.Material3.*) kullanan Material Design 3

Oluşturma uygulamaları için iki Material sürümü mevcuttur:

  • Compose Material kitaplığını (ör. androidx.compose.material.MaterialTheme) kullanan Material Design 2
  • Compose Material 3 kitaplığını (ör. androidx.compose.material3.MaterialTheme) kullanan Material Design 3

Uygulamanızın tasarım sistemi bunu yapabilecek durumdaysa en son sürümü (Material 3) kullanmanızı öneririz. Hem Görünümler hem de Oluştur için taşıma kılavuzları mevcuttur:

Compose'da yeni ekranlar oluştururken, Materyal Tasarım'ın hangi sürümünü kullanıyor olursanız olun, Compose Material kitaplıklarından kullanıcı arayüzü yayınlayan tüm composable'lardan önce MaterialTheme eklediğinizden emin olun. Materyal bileşenleri (Button, Text vb.), MaterialTheme'nin mevcut olmasına bağlıdır ve bu bileşen olmadan davranışları tanımlanmaz.

Tüm Jetpack Compose örnekleri, MaterialTheme üzerine inşa edilmiş özel bir Compose teması kullanır.

Daha fazla bilgi edinmek için Oluştur'da tasarım sistemleri ve XML temalarını Oluştur'a taşıma başlıklı makaleleri inceleyin.

Uygulamanızda Gezinme bileşenini kullanıyorsanız daha fazla bilgi için Compose ile Gezinme - Birlikte Çalışabilirlik ve Jetpack Gezinmeyi Gezinme Oluşturmaya Taşıma konularına bakın.

Karma Oluşturma/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 test yapmak, hiçbir şeyi bozmadığınızdan emin olmak için kritik öneme sahiptir.

Bir etkinlik veya parçada Oluştur kullanılıyorsa ActivityScenarioRule yerine createAndroidComposeRule kullanmanız gerekir. createAndroidComposeRule, Oluşturma ve Görüntüleme kodunu aynı anda test etmenize olanak tanıyan bir ComposeTestRule ile ActivityScenarioRule entegre edilir.

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 başlıklı makalelere bakın.

Compose'u mevcut uygulama mimarinizle entegre etme

Tek Yönlü Veri Akışı (UDF) mimari kalıpları Oluşturma ile sorunsuz şekilde çalışır. Uygulamada bunun yerine Model View Presenter (MVP) gibi başka mimari kalıpları kullanılıyorsa Compose'u kullanmaya başlamadan önce veya bu işlemi yaparken kullanıcı arayüzünün bu bölümünü UDF'ye taşımanızı öneririz.

Compose'da ViewModel kullanma

Mimari Bileşenleri ViewModel kitaplığını kullanıyorsanız Oluşturma ve diğer kitaplıklar bölümünde açıklandığı gibi viewModel() işlevini çağırarak herhangi bir derlenebilir öğeden ViewModel öğesine erişebilirsiniz.

Oluştur'u benimserken ViewModel öğelerinin görüntüleme yaşam döngüsü kapsamlarını izlemesi nedeniyle farklı composable'larda aynı ViewModel türünün kullanılmasına dikkat edin. Gezinme kitaplığı kullanılıyorsa bu kapsam; ana makine etkinliği, parça veya gezinme grafiği olur.

Örneğin, composable'lar bir etkinlik içinde barındırılıyorsa viewModel(), yalnızca etkinlik tamamlandığında silinen aynı örneği her zaman döndürür. Aşağıdaki örnekte, aynı GreetingViewModel örneği ana makine etkinliği altındaki tüm derlenebilir öğelerde yeniden kullanıldığı için aynı kullanıcı ("kullanıcı1") iki kez karşılanır. Oluşturulan ilk ViewModel örneği, diğer birleştirilebilir öğelerde 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 kapsama aldığından, bir navigasyon grafiğinde hedef olan derlenebilir öğeler ViewModel öğesinin farklı bir örneğine sahiptir. Bu durumda ViewModel, hedefin yaşam döngüsü kapsamına alınır ve hedef arka yığıntan 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") ?: "")
        }
    }
}

Doğruluk kaynağı durumu

Kullanıcı arayüzünün bir bölümünde Oluştur'u kullandığınızda Oluştur ve Görüntüleme sistem 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ına uyan başka bir sınıfta kapsayabilirsiniz. Örneğin, veri güncellemelerini yayınlamak için paylaşılan verilerin akışını gösteren bir ViewModel sınıfında.

Ancak paylaşılacak veriler değiştirilebilirse veya bir kullanıcı arayüzü öğesine sıkıca bağlıysa bu her zaman mümkün olmayabilir. Bu durumda, bir sistem doğruluk kaynağı olmalı ve bu sistemin diğer sistemle veri güncellemelerini paylaşması gerekir. Genel kural olarak, doğruluk kaynağının sahibi kullanıcı arayüzü hiyerarşisinin köküne daha yakın olan öğe olmalıdır.

Doğru kaynak olarak oluşturma

Oluşturma durumunu Oluşturma dışı koda yayınlamak için SideEffect kodlanabilir öğeyi kullanın. Bu durumda, doğruluk kaynağı, durum güncellemeleri gönderen bir bileşende 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 değerini 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 Oluşturma'daki yan etkiler başlıklı makaleyi inceleyin.

Sistemi doğruluk kaynağı olarak görüntüleme

Görüntüleme sistemi durumun sahibiyse ve durumu Compose ile paylaşıyorsa durumu Compose için iş parçacığı açısından güvenli hale getirmek üzere mutableStateOf nesnelerine sarmalamanızı öneririz. Bu yaklaşımı kullanırsanız artık doğruluk kaynağına sahip olmadıkları için birleştirilebilir işlevler basitleştirilir ancak Görüntü sisteminin, değişken durumu ve bu durumu kullanan Görüntüleri güncellemesi gerekir.

Aşağıdaki örnekte, bir CustomViewGroup içinde bir TextView ve TextField bileşeni içeren bir ComposeView yer almaktadır. 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

Oluştur'a kademeli olarak geçiş yapıyorsanız hem Oluştur hem de Görüntüleme sisteminde ortak kullanıcı arayüzü öğelerini kullanmanız gerekebilir. Örneğin, uygulamanızın ö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, stilinin XML kullanılarak oluşturulması veya özel bir görünüm olması fark etmeksizin uygulama genelinde yeniden kullanılabilen composable'lar haline gelir. Örneğin, özel harekete geçirici mesaj Button bileşeni için bir CallToActionButton composable'ı oluşturursunuz.

Kompozitleri görünüm tabanlı ekranlarda kullanmak için AbstractComposeView'ten başlayan özel bir görünüm sarmalayıcısı oluşturun. Üzerine yazılmış Content bileşenine, oluşturduğunuz bileşeni aşağıdaki örnekte gösterildiği gibi Oluşturma temanıza sarmalanmış olarak 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üm içinde değiştirilebilir değişkenler haline geldiğine dikkat edin. Bu, özel CallToActionViewButton görünümünü geleneksel görünüm gibi şişirilebilir ve kullanılabilir hale getirir. Aşağıda, Görüntü Bağlantısı ile ilgili bir örneğe bakın:

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şende değişken durum varsa Doğruluk durumu kaynağı bölümüne bakın.

Sunudan bölme durumuna öncelik ver

Geleneksel olarak View durum bilgisine sahiptir. View, ne gösterileceğini ve nasıl gösterileceğini açıklayan alanları yönetir. Bir View öğesini Compose'a dönüştürdüğünüzde, durum kaldırma 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ırın.

Örneğin, bir View öğenin görünür olup olmadığını veya görünmediğini belirten bir visibility özelliğine sahiptir. Bu, View'ün doğal bir özelliğidir. Diğer kod parçaları bir View öğesinin görünürlüğünü değiştirebilir ancak mevcut görünürlüğünü gerçekten bilen tek öğe View'dir. Bir View öğesinin görünür olmasını sağlama mantığı hataya açık olabilir ve genellikle View öğesine bağlıdır.

Buna karşılık Compose, Kotlin'de koşullu mantık kullanarak tamamen farklı kompozisyonlar göstermeyi kolaylaştırır:

@Composable
fun MyComposable(showCautionIcon: Boolean) {
    if (showCautionIcon) {
        CautionIcon(/* ... */)
    }
}

CautionIcon öğesinin tasarımı gereği, neden gösterildiğini bilmesine veya önemsemesine gerek yoktur. Ayrıca visibility kavramı yoktur: Bestede yer alır ya da değildir.

Durum yönetimini ve sunum mantığını net bir şekilde ayırarak, durumu kullanıcı arayüzüne dönüştürme işlemi olarak içeriği nasıl görüntülediğinizi daha özgürce değiştirebilirsiniz. Eyalet sahipliği daha esnek olduğundan, gerektiğinde kaldıraç duruma getirebilme olanağı, composable'ları yeniden kullanılabilir hale de getirir.

Kapsüllenmiş ve yeniden kullanılabilir bileşenleri tanıtın

View öğeleri genellikle nerede bulundukları hakkında bir fikir sahibidir: Activity, Dialog, Fragment veya başka bir View hiyerarşisinin içinde bir yerde. Genellikle statik düzen dosyalarından şişirildiği için View'lerin genel yapısı çok katı olma eğilimindedir. Bu durum, daha sıkı bir bağlantıya neden olur ve View'ün 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 bir alt görünüme sahip olduğunu varsayabilir ve özelliklerini doğrudan bir işleme yanıt olarak değiştirebilir. Bu, söz konusu View öğelerini birbirine sıkıca bağlar: Özel View, alt öğeyi bulamadığı takdirde kilitlenebilir veya bozulabilir. Alt öğe ise özel View üst öğesi olmadan büyük olasılıkla yeniden kullanılamaz.

Bu, yeniden kullanılabilir bileşenlerle Oluştur'da daha az sorun teşkil eder. Ebeveynler durumu ve geri çağırma işlevlerini kolayca belirtebilir. Böylece, kullanılacakları yeri tam olarak bilmek zorunda kalmadan yeniden kullanılabilir bileşenler yazabilirsiniz.

@Composable
fun AScreen() {
    var isEnabled by rememberSaveable { mutableStateOf(false) }

    Column {
        ImageWithEnabledOverlay(isEnabled)
        ControlPanelWithToggle(
            isEnabled = isEnabled,
            onEnabledChanged = { isEnabled = it }
        )
    }
}

Yukarıdaki örnekte, üç parça da daha kapsüllenmiş ve daha az ilişkilidir:

  • ImageWithEnabledOverlay'nin yalnızca mevcut isEnabled durumunu bilmesi gerekir. ControlPanelWithToggle'nin var olduğunu veya nasıl kontrol edilebileceğini bilmesi gerekmez.

  • ControlPanelWithToggle, ImageWithEnabledOverlay'un varlığını bilmiyor. isEnabled'ün sıfır, bir veya daha fazla şekilde gösterilmesi mümkündür ve ControlPanelWithToggle'ün değişmesi gerekmez.

  • Üst ağ için, ImageWithEnabledOverlay veya ControlPanelWithToggle öğesinin ne kadar derin bir şekilde iç içe yerleştirilmiş olduğu önemli değildir. Bu çocuklar, değişiklikleri animasyonlu olarak gösterebilir, içerik değiştirebilir veya içerikleri diğer çocuklara aktarabilir.

Bu kalıp, kontrolün tersine çevrilmesi olarak bilinir. Bu konuda 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üzenleri oluşturmanın ana yollarından biridir. Uygun kaynaklar, ekran düzeyinde 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 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 amacıyla sunduğu teknikleri öğrenmek için Farklı ekran boyutlarını destekleme bölümüne bakın.

Görünümler ile iç içe kaydırma

Kaydırılabilir View öğeleri ve her iki yönde iç içe yerleştirilmiş kaydırılabilir composable'lar arasında iç içe kaydırma birlikte çalışmasını etkinleştirme hakkında daha fazla bilgi için İç içe kaydırma birlikte çalışabilirliği makalesini okuyun.

RecyclerView uygulamasında oluşturma

RecyclerView'teki Composables, RecyclerView 1.3.0-alpha02 sürümünden beri performanslıdır. Bu avantajlardan yararlanmak için RecyclerView'ı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 Oluştur kodu varsa varsayılan iç içe eklemeleri geçersiz kılmanız gerekebilir. Bu durumda, hangi tarafın ekleri kullanması gerektiğini ve hangisinin bunları göz ardı etmesi gerektiğini açıkça belirtmeniz gerekir.

Örneğin, en dıştaki düzeniniz Android View düzeniyse Görünüm sistemindeki ek öğeleri kullanmalı ve Oluşturma için bunları yoksaymalısınız. Alternatif olarak, en dıştaki düzeniniz bir bileşense Oluştur'da iç içe eklemeleri kullanmanız ve AndroidView bileşenlerini uygun şekilde doldurmanız gerekir.

Varsayılan olarak her ComposeView, tüm iç metinleri WindowInsetsCompat tüketim düzeyinde tüketir. Bu varsayılan davranışı değiştirmek için ComposeView.consumeWindowInsets değerini false olarak ayarlayın.

Daha fazla bilgi için Oluşturma'daki WindowInsets dokümanlarını okuyun.