Compose kullanıcı arayüzünüzün mimarisini oluşturma

Compose'da kullanıcı arayüzü sabittir. Çizildikten sonra güncellemenin bir yolu yoktur. Kullanıcı arayüzünüzün durumunu kontrol edebilirsiniz. Compose, kullanıcı arayüzünün durumu her değiştiğinde kullanıcı arayüzü ağacının değişen kısımlarını yeniden oluşturur. Composable, durumu kabul edebilir ve etkinlikleri gösterebilir. Örneğin, TextField bir değeri kabul eder ve geri çağırma işleyicinin değeri değiştirmesini isteyen bir geri çağırma onValueChange sunar.

var name by remember { mutableStateOf("") }
OutlinedTextField(
    value = name,
    onValueChange = { name = it },
    label = { Text("Name") }
)

composable'lar durumu kabul edip etkinlikleri ortaya çıkardığından tek yönlü veri akışı kalıbı Jetpack Compose'a uygundur. Bu kılavuzda, Compose'da tek yönlü veri akışı kalıbının nasıl uygulanacağı, etkinliklerin ve durum sahiplerinin nasıl uygulanacağı ve ViewModel'leriyle Compose'da nasıl çalışılacağı ele alınmaktadır.

Tek yönlü veri akışı

Tek yönlü veri akışı (UDF), durumun aşağı indiği ve etkinliklerin yukarı çıktığı bir tasarım kalıbıdır. Tek yönlü veri akışını uygulayarak kullanıcı arayüzünde durum görüntüleyen composable'ları, uygulamanızın durumunu depolayan ve değiştiren bölümlerinden ayırabilirsiniz.

Tek yönlü veri akışı kullanan bir uygulama için kullanıcı arayüzü güncelleme döngüsü şöyle görünür:

  • Etkinlik: Kullanıcı arayüzünün bir bölümü bir etkinlik oluşturur ve bunu yukarı doğru aktarır (ör. işlemesi için ViewModel'e iletilen bir düğme tıklaması veya uygulamanızın diğer katmanlarından bir etkinlik (örneğin, kullanıcı oturumunun sona erdiğini belirten) iletilir).
  • Güncelleme durumu: Bir olay işleyici, durumu değiştirebilir.
  • Görüntüleme durumu: Durum tutucu durumu iletir ve kullanıcı arayüzü bunu görüntüler.

Şekil 1. Tek yönlü veri akışı.

Jetpack Compose'u kullanırken bu kalıbı izlemek birçok avantaj sağlar:

  • Test edilebilirlik: Durumun gösterildiği kullanıcı arayüzünden ayrılması, her iki durumun da yalıtımda test edilmesini kolaylaştırır.
  • Durum kapsüllemesi: Durum yalnızca tek bir yerde güncellenebileceğinden ve bir composable'ın durumu için tek bir doğruluk kaynağı bulunduğundan tutarsız durumlar nedeniyle hata oluşturma olasılığınız daha düşüktür.
  • Kullanıcı arayüzü tutarlılığı: Tüm durum güncellemeleri StateFlow veya LiveData gibi gözlemlenebilir durum sahipleri kullanılarak kullanıcı arayüzüne hemen yansıtılır.

Jetpack Compose'da tek yönlü veri akışı

Composable'lar duruma ve etkinliklere göre çalışır. Örneğin, bir TextField yalnızca value parametresi güncellendiğinde güncellenir ve bir onValueChange geri çağırması (değerin yeni değerle değiştirilmesini isteyen bir etkinlik) sunar. Compose, State nesnesini değer sahibi olarak tanımlar ve durum değerinde yapılan değişiklikler yeniden oluşturma işlemini tetikler. Değerini ne kadar süreyle hatırlamanız gerektiğine bağlı olarak durumu remember { mutableStateOf(value) } veya rememberSaveable { mutableStateOf(value) içinde tutabilirsiniz.

TextField composable'ın değerinin türü String olduğundan bu değer; sabit kodlu bir değerden, ViewModel'den veya üst composable'dan iletilen herhangi bir yerden gelebilir. Değeri bir State nesnesinde tutmanız gerekmez, ancak onValueChange çağrıldığında değeri güncellemeniz gerekir.

Oluşturulabilir parametreleri tanımlama

Bir composable'ın durum parametrelerini tanımlarken aşağıdaki soruları göz önünde bulundurmalısınız:

  • Ne kadar yeniden kullanılabilir veya esnek bir composable?
  • Durum parametreleri bu composable'ın performansını nasıl etkiler?

Ayrışmayı ve yeniden kullanmayı teşvik etmek için her composable mümkün olan en az miktarda bilgi içermelidir. Örneğin, bir haber makalesinin başlığını içerecek bir composable oluştururken haber makalesinin tamamı yerine yalnızca görüntülenmesi gereken bilgileri aktarmayı tercih edin:

@Composable
fun Header(title: String, subtitle: String) {
    // Recomposes when title or subtitle have changed.
}

@Composable
fun Header(news: News) {
    // Recomposes when a new instance of News is passed in.
}

Bazen bağımsız parametrelerin kullanılması performansı da artırır. Örneğin, News yalnızca title ve subtitle dışında daha fazla bilgi içeriyorsa Header(news) içine yeni bir News örneği aktarıldığında title ve subtitle değişmemiş olsa bile composable yeniden oluşturulur.

İlettiğiniz parametre sayısını dikkatlice düşünün. Bir işlevin çok fazla parametreye sahip olması, işlevin ergonomisini azalttığından, bu durumda bunları bir sınıfta gruplandırmak tercih edilir.

Oluşturulan etkinlikler

Uygulamanıza yapılan her giriş bir etkinlik olarak gösterilmelidir: Dokunmalar, metin değişiklikleri, hatta zamanlayıcılar ve diğer güncellemeler. Bu etkinlikler kullanıcı arayüzünüzün durumunu değiştirdiğinden, bunları işleyecek ve kullanıcı arayüzü durumunu güncelleyecek olan kişi ViewModel olmalıdır.

Kullanıcı arayüzü katmanının durumu hiçbir zaman etkinlik işleyici dışında değişmemelidir. Aksi takdirde, uygulamanızda tutarsızlıklar ve hatalar oluşabilir.

Durum ve etkinlik işleyici lambdaları için sabit değerlerin geçirilmesini tercih edin. Bu yaklaşım aşağıdaki avantajları sağlar:

  • Yeniden kullanılabilirliği iyileştirirsiniz.
  • Kullanıcı arayüzünüzün, durumun değerini doğrudan değiştirmemesini sağlarsınız.
  • Durumun başka bir iş parçacığından dönüştürülmediğinden emin olduğunuz için eşzamanlılık sorunlarından kaçınırsınız.
  • Çoğunlukla kod karmaşıklığını azaltırsınız.

Örneğin, parametre olarak String ve lambda kabul eden bir composable, birçok bağlamdan çağrılabilir ve yüksek oranda yeniden kullanılabilir. Uygulamanızın üst tarafındaki uygulama çubuğunda her zaman metin görüntülendiğini ve geri düğmesi olduğunu varsayalım. Metni ve geri düğmesi tutma yerini parametre olarak alan daha genel bir MyAppTopAppBar composable tanımlayabilirsiniz:

@Composable
fun MyAppTopAppBar(topAppBarText: String, onBackPressed: () -> Unit) {
    TopAppBar(
        title = {
            Text(
                text = topAppBarText,
                textAlign = TextAlign.Center,
                modifier = Modifier
                    .fillMaxSize()
                    .wrapContentSize(Alignment.Center)
            )
        },
        navigationIcon = {
            IconButton(onClick = onBackPressed) {
                Icon(
                    Icons.Filled.ArrowBack,
                    contentDescription = localizedString
                )
            }
        },
        // ...
    )
}

ViewModel'ler, durumlar ve etkinlikler: örnek

ViewModel ve mutableStateOf kullandığınızda, aşağıdakilerden biri geçerliyse uygulamanızda tek yönlü veri akışı da sağlayabilirsiniz:

  • Kullanıcı arayüzünüzün durumu, StateFlow veya LiveData gibi gözlemlenebilir durum sahipleri aracılığıyla gösterilir.
  • ViewModel, kullanıcı arayüzünden veya uygulamanızın diğer katmanlarından gelen etkinlikleri işler ve etkinliklere göre durum sahibini günceller.

Örneğin, bir oturum açma ekranı uygularken Oturum aç düğmesine dokunduğunuzda, uygulamanızda bir ilerleme durumu döner simgesi ve bir ağ çağrısı görüntülenir. Giriş başarılı olursa uygulamanız farklı bir ekrana gider. Hata durumunda uygulama bir Snackbar gösterir. Ekran durumu ve etkinliği şu şekilde modelleyebilirsiniz:

Ekranda dört durum vardır:

  • Oturum kapalıyken: Kullanıcı henüz oturum açmadığında.
  • Devam ediyor: Uygulamanız o sırada bir ağ çağrısı gerçekleştirerek kullanıcının oturum açmasını sağlamaya çalışıyordur.
  • Hata: Oturum açılırken bir hata oluştuğunda.
  • Oturum açma: Kullanıcı oturum açtığında.

Bu durumları kapalı sınıf olarak modelleyebilirsiniz. ViewModel, durumu State olarak gösterir, başlangıç durumunu ayarlar ve durumu gerektiği gibi günceller. ViewModel, bir onSignIn() yöntemini göstererek oturum açma etkinliğini de işler.

class MyViewModel : ViewModel() {
    private val _uiState = mutableStateOf<UiState>(UiState.SignedOut)
    val uiState: State<UiState>
        get() = _uiState

    // ...
}

Compose, mutableStateOf API'ye ek olarak LiveData, Flow ve Observable için işleyici olarak kaydolmak ve değeri durum olarak temsil etmek üzere uzantılar sağlar.

class MyViewModel : ViewModel() {
    private val _uiState = MutableLiveData<UiState>(UiState.SignedOut)
    val uiState: LiveData<UiState>
        get() = _uiState

    // ...
}

@Composable
fun MyComposable(viewModel: MyViewModel) {
    val uiState = viewModel.uiState.observeAsState()
    // ...
}

Daha fazla bilgi

Jetpack Compose'daki mimari hakkında daha fazla bilgi edinmek için aşağıdaki kaynaklara bakın:

Numuneler