Dikkat edilmesi gereken diğer noktalar

Görünüm'den Compose'a geçiş tamamen kullanıcı arayüzüyle ilgili olsa da güvenli ve artımlı bir taşıma gerçekleştirmek için göz önünde bulundurulması gereken birçok nokta vardır. Bu sayfada, View tabanlı uygulamanızı Compose'a taşırken dikkat etmeniz gereken bazı noktalar yer almaktadı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ünüm tabanlı uygulamalar için üç Materyal sürümü vardır:

  • AppCompat kitaplığını (ör. Theme.AppCompat.*) kullanan Materyal Tasarım 1
  • MDC-Android kitaplığının (ör. Theme.MaterialComponents.*) kullanıldığı Materyal Tasarım 2
  • MDC-Android kitaplığının (ör. Theme.Material3.*) kullanıldığı Materyal Tasarım 3

Oluşturma uygulamalarında, Materyal'in iki sürümü vardır:

  • Oluşturma Materyali kitaplığının kullanıldığı Materyal Tasarım 2 (ör. androidx.compose.material.MaterialTheme)
  • Compose Material 3 kitaplığını kullanarak Materyal Tasarım 3 (ör. androidx.compose.material3.MaterialTheme)

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

Compose'da yeni ekranlar oluştururken Material Design'ın hangi sürümünü kullandığınızdan bağımsız olarak, Compose Material kitaplıklarından kullanıcı arayüzü yayınlayan tüm composable'lardan önce MaterialTheme uyguladığınızdan emin olun. Materyal bileşenler (Button, Text vb.) bir MaterialTheme öğesinin mevcut olmasına bağlıdır ve bu bileşen olmadan davranışları tanımlanamaz.

Tüm Jetpack Compose örneklerinde MaterialTheme üzerine inşa edilmiş özel bir Oluştur teması kullanılır.

Daha fazla bilgi edinmek için Oluşturma bölümünde sistem tasarlama ve XML temalarını Oluşturma'ya taşıma başlıklı makalelere göz atın.

Uygulamanızda Gezinme bileşenini kullanıyorsanız daha fazla bilgi için Compose ile Gezinme - Birlikte Çalışabilirlik ve Jetpack Gezinmesini Navigation Compose'a 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 herhangi bir şeyi bozmadığınızdan emin olmak için test yapmak çok önemlidir.

Bir etkinlik veya parça, Oluştur işlevini kullandığında ActivityScenarioRule yerine createAndroidComposeRule kullanmanız gerekir. createAndroidComposeRule, Oluşturma ve Kodu görüntüleme özelliklerini aynı anda test etmenizi sağlayan ComposeTestRule ile ActivityScenarioRule entegre eder.

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 etme hakkında daha fazla bilgi edinmek için Oluşturma düzeninizi test etme başlıklı makaleye bakın. Kullanıcı arayüzü test çerçeveleriyle birlikte çalışabilme için Espresso ile birlikte çalışabilirlik ve UiAutomator ile birlikte çalışabilirlik sayfalarına göz atın.

Compose'u mevcut uygulama mimarinizle entegre etme

Tek Yönlü Veri Akışı (UDF) mimari kalıpları, Compose ile sorunsuz şekilde çalışır. Uygulama, bunun yerine Model Görüntüleme Sunucusu (MVP) gibi başka mimari kalıpları kullanıyorsa Compose'u kullanmaya başlamadan önce veya kullanırken kullanıcı arayüzünün ilgili bölümünü UDF'ye taşımanızı öneririz.

Compose'da ViewModel kullanma

Architecture 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 composable'dan bir ViewModel dosyasına erişebilirsiniz.

ViewModel öğeleri Görüntüleme yaşam döngüsü kapsamlarını takip ettiğinden, Compose'u kullanırken aynı ViewModel türünü farklı composable'larda kullanma konusunda dikkatli olun. Gezinme kitaplığı kullanılırsa kapsam; ana makine etkinliği, parça veya gezinme grafiği olacaktır.

Örneğin, composable'lar bir etkinlikte barındırılıyorsa viewModel(), her zaman yalnızca etkinlik bittiğinde temizlenen örneği döndürür. Aşağıdaki örnekte, aynı GreetingViewModel örneği ana makine etkinliği altındaki tüm composable'larda yeniden kullanıldığından aynı kullanıcı ("user1") iki kez karşılanır. Oluşturulan ilk ViewModel örnek diğer composable'larda yeniden kullanılıyor.

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 de ViewModel öğelerini kapsadığından, bir gezinme grafiğinde hedef olan composable'lar farklı bir ViewModel örneğine sahiptir. Bu durumda, ViewModel hedefin yaşam döngüsüne dahil edilir ve hedef geri 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") ?: "")
        }
    }
}

Bilgi veri kaynağı

Kullanıcı arayüzünün bir bölümünde Oluştur'u seçtiğinizde, Compose ve View (Sistem) 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ını takip eden başka bir sınıfa (örneğin, veri güncellemeleri yayınlamak için paylaşılan veri akışını açığa çıkaran bir ViewModel içinde) kullanmanızı öneririz.

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

Bilgi kaynağı olarak oluşturun

Oluşturma durumunu Oluştur dışı kodlara yayınlamak için SideEffect composable'ı kullanın. Bu durumda bilginin kaynağı, durum güncellemeleri gönderen bir composable'da saklanır.

Örnek olarak, 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 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 edinmek için Oluşturma işleminde yan efektler başlıklı makaleyi inceleyin.

Sistemi doğru kaynak olarak görüntüle

Durum, View sistemine aitse ve Compose ile paylaşılıyorsa Oluşturma için iş parçacığı güvenli hale getirilmesi amacıyla durumu mutableStateOf nesnelerinde sarmalamanızı öneririz. Bu yaklaşımı kullanırsanız composable işlevler artık bilgi kaynağına sahip olmadıkları için basitleştirilmiştir. Ancak View sisteminin değişken durumu ve bu durumu kullanan Görünümler'i güncellemesi gerekir.

Aşağıdaki örnekte, bir CustomViewGroup içinde bir TextView ve içinde bir TextField composable bulunan ComposeView bulunuyor. TextView, kullanıcının TextField içinde yazdığı içeriğin içeriğini 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

Kademeli olarak Oluştur'a geçiyorsanız hem Oluştur hem de Görünüm sisteminde paylaşılan kullanıcı arayüzü öğeleri kullanmanız gerekebilir. Örneğin, uygulamanızda özel CallToActionButton bileşeni varsa bu bileşeni hem Oluşturma hem de Görüntüleme tabanlı ekranlarda kullanmanız gerekebilir.

Compose'da paylaşılan kullanıcı arayüzü öğeleri, öğenin XML kullanılarak biçimlendirilmesine veya özel görünüm olmasına bakılmaksızın uygulama genelinde yeniden kullanılabilecek composable'lar haline gelir. Örneğin, özel harekete geçirici mesaj Button bileşeniniz için bir CallToActionButton composable'ı oluşturabilirsiniz.

Görünüme dayalı ekranlarda composable'ı kullanmak için AbstractComposeView çerçevesini kapsayan özel bir görünüm sarmalayıcı oluşturun. Oluşturduğunuz composable, geçersiz kılınmış Content composable öğesinin içine aşağıdaki örnekte gösterildiği gibi Compose temanıza sarmalanmış olarak yerleştirilir:

@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)
        }
    }
}

composable parametrelerin, özel görünüm içinde değişken değişkenlere dönüştüğüne 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ğıdaki Görünüm Bağlama ile bunun bir örneğini 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şken durum içeriyorsa Durum bilgi kaynağı bölümüne bakın.

Sunudan bölme durumunu önceliklendirin

Geleneksel olarak View durum bilgili olur. View, nasıl gösterileceğinin yanı sıra nenin gösterileceğini açıklayan alanları da yönetir. Bir View öğesini Compose'a dönüştürürken eyalet yükseltme bölümünde ayrıntılı olarak açıklandığı gibi, oluşturulan verileri tek yönlü veri akışı sağlayacak şekilde ayırmaya çalışın.

Örneğin View, görünür olup olmadığını veya kaybolup olmadığını açıklayan visibility özelliğine sahiptir. Bu, View öğesinin doğal bir özelliğidir. Diğer kod parçaları bir View öğesinin görünürlüğünü değiştirebilir ancak yalnızca View, mevcut görünürlüğünün gerçekten ne olduğunu bilir. Bir View öğesinin görünür olmasını sağlamanın mantığı hataya açık olabilir ve genellikle View ile bağlantılıdır.

Buna karşın Compose ise 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örüntülendiğini bilmesine veya önemsemesine gerek yoktur ve visibility kavramı yoktur: Hem Bestede yer alır hem de değildir.

Durum yönetimi ve sunum mantığını net bir şekilde ayırarak, durumu kullanıcı arayüzüne dönüştürerek içeriği görüntüleme şeklinizi daha özgürce değiştirebilirsiniz. Gerektiğinde eski durumu kaldırabilme imkanı, composable'ları yeniden kullanılabilir kılmaktadır, çünkü devlet sahipliği daha esnektir.

Kapsüllenmiş ve yeniden kullanılabilir bileşenleri teşvik edin

View öğeleri genellikle nerede bulunduklarına dair bir fikre sahiptir: Activity, Dialog, Fragment veya başka bir View hiyerarşisinin içindeki herhangi bir yer. Bunlar genellikle statik düzen dosyalarından şişirildiği için View'nin genel yapısı çok sabit olur. Bu, daha sıkı bir bağlantı oluşturur ve View öğesinin değiştirilmesini veya yeniden kullanılmasını zorlaştırır.

Örneğin, bir özel View, belirli bir türde, belirli bir kimliğe sahip alt görünüme sahip olduğunu varsayabilir ve bir işleme yanıt olarak özelliklerini doğrudan değiştirebilir. Bu işlem, bu View öğelerini birbiriyle sıkı şekilde birleştirir: Alt öğeyi bulamazsa özel View kilitlenebilir veya bozulabilir ve alt öğe, özel View üst öğesi olmadan muhtemelen yeniden kullanılamaz.

Bu, Compose'da yeniden kullanılabilir composable'larla ilgili bir sorun teşkil etmez. Ebeveynler durum ve geri çağırmaları kolayca belirtebilir, böylece tam olarak nerede kullanılacağını bilmek zorunda 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, her üç bölüm de daha kapsamlı ve daha az kuyrukludur:

  • ImageWithEnabledOverlay yalnızca mevcut isEnabled durumunun ne olduğunu bilmelidir. ControlPanelWithToggle ifadesinin var olduğunu, hatta nasıl kontrol edilebilir olduğunu bile bilmesine gerek yoktur.

  • ControlPanelWithToggle, ImageWithEnabledOverlay uygulamasının mevcut olduğunu bilmiyor. isEnabled sıfır, bir veya daha fazla şekilde gösterilirken ControlPanelWithToggle için de değişiklik yapılması gerekmez.

  • Üst yayıncı için ImageWithEnabledOverlay veya ControlPanelWithToggle öğelerinin ne kadar derin bir şekilde iç içe geçmiş olduğu önemli değildir. Bu çocuklar animasyon yapıyor, içerikleri değiştiriyor veya diğer çocuklara içerik aktarıyor olabilir.

Bu kalıp, kontrolün ters çevirmesi 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ı kaynaklara sahip olmak, duyarlı View düzenleri oluşturmanın ana yollarından biridir. Nitelikli kaynaklar ekran düzeyinde düzen kararları için hâlâ bir seçenek olsa da Oluştur, düzenleri normal koşullu mantıkla tamamen kod halinde değiştirmeyi çok daha kolay hale getirir. Daha fazla bilgi edinmek için Farklı ekran boyutlarını destekleme bölümüne bakın.

Ayrıca, Compose'un uyarlanabilir kullanıcı arayüzleri oluşturmak için sunduğu teknikleri öğrenmek için Uyarlanabilir düzenler oluşturma 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ışabilirlik özelliğinin nasıl etkinleştirileceği hakkında daha fazla bilgi için İç içe kaydırma birlikte çalışabilirlik bölümünü okuyun.

RecyclerView uygulamasında oluştur

RecyclerView ürünündeki composable'lar RecyclerView 1.3.0-alpha02 sürümünden itibaren iyi performans gösteriyor. Bu avantajları görmek için RecyclerView uygulamasının en az 1.3.0-alpha02 sürümünü kullandığınızdan emin olun.

WindowInsets ile View'lar birlikte çalışabilir

Ekranınızda aynı hiyerarşide hem Görünümler hem de Oluşturma kodu olduğunda varsayılan ek öğeleri geçersiz kılmanız gerekebilir. Bu durumda, verinin hangi veride kullanıldığını, hangisinin hangi veriyi göz ardı etmesi gerektiğini net olarak belirlemeniz gerekir.

Örneğin, en dıştaki düzeniniz bir Android Görünümü düzeniyse ek öğeleri Görünüm sisteminde kullanmalı ve Oluştur için yoksaymalısınız. Alternatif olarak, en dıştaki düzeniniz composable ise Compose'da içe aktarılan öğeleri kullanıp AndroidView composable'ları buna uygun şekilde 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 Oluşturma bölümünde WindowInsets dokümanlarını okuyun.