Olgun bir Android uygulamasının Compose'a geçişi genellikle hafta sonu yapılan yeniden yazım değildir. Eski View kodu yeni özellikleri sunarken Compose ekranları onun yanında yaşar. İki araç birlikte çalışacak şekilde tasarlanmıştır: View hiyerarşisine Compose eklenebilir, Compose içine eski View gömülebilir ve ortak design system korunarak ekran ekran geçilebilir. Uygulama geçiş boyunca çalışır kalır.
Strateji budur. İşin sonucu iki taraf arasındaki sınırların nasıl kurulduğuna bağlıdır.
Kısa çerçeve
- Interop iki yönlüdür:
ComposeView, View içine Compose;AndroidView, Compose içine View koyar. Aynı ekranda ikisi de gerekebilir. - Tek design system, iki renderer: XML ve Compose teması birbirinden koparsa uygulamanın yarısı farklı görünür. Token'lar ortak kaynak olmalıdır.
- State sınırda anlaşılır: state'i yukarı taşıyın ve Flow'ları lifecycle'a duyarlı toplayın; eski ve yeni yarı farklı şey göstermesin.
- Öngörülebilen tuzaklar: çift tema, nested scroll ve geri dönüştürülen container'lardaki composition ömrü.
İki yönlü interop
Geçişte iki dünya arasında yalnız tek kapı yoktur; iki yönü de kullanırız.
ComposeView: View dünyasının içinde Compose
ComposeView, composition barındıran sıradan Android View'ıdır. XML'de tanımlanıp Fragment veya Activity'den doldurulabilir. Önce başlığı, sonra listeyi, en sonunda ekranın tamamını taşımanın temel aracıdır.
<androidx.compose.ui.platform.ComposeView
android:id="@+id/compose_header"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
class ProfileFragment : Fragment() {
private var _binding: FragmentProfileBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentProfileBinding.inflate(inflater, container, false)
binding.composeHeader.apply {
// Tie the composition's lifetime to the fragment's view lifecycle.
setViewCompositionStrategy(
ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed
)
setContent {
AppTheme {
ProfileHeader()
}
}
}
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Buradaki binding yaşam döngüsü ve composition strategy önemlidir. Fragment'ın kendisi değil, view yaşam döngüsüyle uyum gerekir.
AndroidView: Compose içinde eski widget
Compose'a geçen ekran henüz yeniden yazılmamış chart, harita, imza alanı veya başka custom widget'a ihtiyaç duyabilir. AndroidView bunun için factory ve update lambda'larını sunar:
@Composable
fun LegacyChart(points: List<DataPoint>, modifier: Modifier = Modifier) {
AndroidView(
modifier = modifier.fillMaxWidth(),
factory = { context ->
// Runs once, on the UI thread: construct and do one-time setup.
LineChartView(context).apply {
axisStyle = AxisStyle.Minimal
}
},
update = { view ->
// Runs after factory, then on every recomposition that reads `points`.
view.submitPoints(points)
}
)
}
factory, View oluşturulurken UI thread'de sabit kurulumu yapar. update ilk kurulumdan sonra ve okuduğu state değiştiğinde yeniden çalışır. View'ı update içinde tekrar oluşturmayın; değişen state'i de yalnız factory içinde bırakmayın. Gerçekten yeni instance gereken durumda key(...) { } kullanılabilir.
Klasik factory/update overload'ı composition ömrü boyunca aynı View'ı değiştirir. Compose UI 1.5.0'dan itibaren onReset ve onRelease alan overload da vardır. Null olmayan onReset, LazyColumn/LazyRow gibi uygun yapılarda View'ın tekrar kullanılmasına izin verir. onReset önceki item state'ini temizler; yeni item için update uygulanır. Dolayısıyla pooling istediğinizde bu overload'a bilinçli olarak geçin. AndroidView API overload sınırlarını açıklar.
Tek design system, iki renderer
Geçiş uzun sürdüğünde renkler, tipografi ve butonlar hem XML/Material Components hem Compose MaterialTheme tarafından çizilir. Aradaki küçük fark bile aynı ekranda görünür.
Eski Mdc3Theme ve compose-theme-adapter ailesi deprecated olduğundan runtime köprüsü kurmak yerine ortak design token'ları esas alın. Material Theme Builder aynı girdiden XML ve Compose temaları üretebilir; elle yazılan AppTheme de XML değerleriyle eşleştirilebilir. Token'lar kaynak, iki tema çıktıdır. Her Compose girişini AppTheme ile sarın; aksi halde default Material renkleri devreye girebilir.
Bu sorumluluk ayrımı, eID SDK'sında okuma, doğrulama ve entegrasyonu ayırmakla aynı düşüncedir: bir taraf değişirken diğerinin varsayımları bozulmasın.
Fragment ve Navigation içinde yaşamak
Kademeli geçiş çoğu zaman mevcut Fragment + Navigation yapısında başlar. DisposeOnViewTreeLifecycleDestroyed, composition'ı view tree'nin LifecycleOwner'ı yok olduğunda bitirir; Fragment için bu, view yaşam döngüsüdür. Uygun olmayan strateji view ömründen daha uzun veya daha kısa composition ömrüne yol açabilir. Aynı XML'de birden fazla ComposeView varsa state restoration için her birine kararlı ve ayrı id verin.
Navigation uzun süre hibrit kalabilir. Fragment graph'ını tutup bazı destination Fragment'larının tüm içeriğini ComposeView yapmak mümkündür. Akışın yeterli kısmı taşındığında navigation-compose alt graph'ları eklenebilir. “Sonra nereye gidiyoruz?” kararını iki dünyaya dağıtmayın. Bir katman navigasyonu sahiplenir, diğeri yalnız niyet/olay iletir.
Geri tuşu da böyle bir sınırdır. Sistem geri hareketini Fragment mı composable mı karşılayacak, özellikle predictive back için bilinçli seçilmelidir. onBackPressed'den OnBackPressedDispatcher'a geçiş yazısındaki tek sahip ve doğru lifecycle ilkesi burada da geçerlidir.
State'i sınırda yukarı taşımak
Yarı taşınmış ekranın tutarlı kalması için ViewModel tek UI state akışı sunmalı; Compose ve kalan View tarafı onu okumalıdır.
@Composable
fun ProfileHeader(viewModel: ProfileViewModel = viewModel()) {
val state by viewModel.uiState.collectAsStateWithLifecycle()
ProfileHeaderContent(
state = state,
onEditClick = viewModel::onEditClicked
)
}
collectAsStateWithLifecycle, androidx.lifecycle:lifecycle-runtime-compose içindedir ve lifecycle etkinliğine göre toplamayı yönetir. Arkada repeatOnLifecycle kullanır; uygun state'in altında toplama durur, yeniden etkin olunca devam eder. RxJava'dan Coroutines'a geçişteki lifecycle-aware Flow yaklaşımının Compose karşılığıdır.
ProfileHeaderContent, state'i alan ve olayları yukarı ileten stateless composable'dır. Sınırda ince bir stateful wrapper, altta stateless içerik olması ekranı daha sonra tamamen Compose'a taşımayı kolaylaştırır. Ters yönde AndroidView.update, yukarı taşınan state'i okuyup eski View setter'larına uygular.
Gerçekten karşılaşılan tuzaklar
Çift tema
View/Compose sınırında ripple, elevation veya font ağırlığı değişiyorsa composition AppTheme ile sarılmamış olabilir veya temanın token'ları XML'le uyuşmuyordur. Ortak token kaynağı ve bütün giriş noktalarının sarılması gerekir.
Nested scrolling
Dışında collapsing toolbar barındıran CoordinatorLayout olan ekrana LazyColumn eklemek tek başına toolbar'ı kaydırmaz. Compose nested scroll sağlar; View parent da NestedScrollingParent3 uygulamalıdır. Bu durumda rememberNestedScrollInteropConnection() bağlantısını Modifier.nestedScroll'a verin:
setContent {
AppTheme {
val nestedScroll = rememberNestedScrollInteropConnection()
LazyColumn(
modifier = Modifier.nestedScroll(nestedScroll)
) {
items(feedItems, key = { it.id }) { item ->
FeedRow(item)
}
}
}
}
Parent gerçekten işbirliği yapıyorsa bağlantı işe yarar; CoordinatorLayout bu örnektir. RecyclerView veya ViewPager2, NestedScrollingParent3 sunmadığı için aynı bağlantı bunlara doğru kesintisiz kaydırmayı kendiliğinden çözmez. Scroll sahipliğini yeniden tasarlayın.
Geri dönüştürülen container'larda composition ömrü
RecyclerView satırında ComposeView kullanmak yaygın bir ara adımdır. Güncel varsayılan DisposeOnDetachedFromWindowOrReleasedFromPool, pooling container'lar için tasarlanmıştır: satır pool'da tekrar kullanılabilecekken composition korunur; container detach olduğunda veya item pool'dan atıldığında sonlandırılır. Her satır detach olduğunda hemen dispose eden stratejiye sonuçlarını ölçmeden dönmeyin. Android Compose-in-Views rehberi bu varsayılanı açıklar. Stratejiyi host'a eşleyin: Fragment'ta view lifecycle, recycler'da pooling lifecycle.
Endişelenmeden önce ölçmek
ComposeView kurulumunun maliyeti vardır. Eski layout içine onlarca çok küçük Compose adası serpmek, daha az sayıda büyük yüzeyden pahalı olabilir. Sınırı profiler ile değerlendirin; yalnız sezgiyle karar vermeyin.
Nereden başlamalı?
Bağımlılığı az, scroll davranışı basit bir yaprak ekran veya bileşen iyi başlangıçtır. İlk ekrandan önce ortak temayı kurun. State'i iki tarafın okuyabildiği ViewModel'de tutun ve navigasyonu ilk iş olarak taşımaya çalışmayın; çoğu zaman daha sonra ele alınması daha kolaydır.
Geçiş boyunca iki renderer tek ürün gibi davranmalıdır. Ortak token, state'in tek sahibi ve host'a uygun composition ömrü bu tutarlılığı sağlar. Böyle tasarlanınca geçiş büyük yeniden yazım yerine her aşaması yayınlanabilir ilerlemeye dönüşür.