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

Oluşturma bölümünde kullanıcı arayüzü değiştirilemez. Çizildikten sonra güncellenmesi mümkün değildir. Kontrol edebildiğiniz şey, kullanıcı arayüzünüzün durumudur. Kullanıcı arayüzünün durumu her değiştiğinde Oluştur, kullanıcı arayüzü ağacının değişen bölümlerini yeniden oluşturur. Kompozitler durum kabul edebilir ve etkinlikleri gösterebilir. Örneğin, bir TextField bir değeri kabul eder ve geri çağırma işleyicisinden değeri değiştirmesini isteyen bir geri çağırma onValueChange gösterir.

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

Kompozitler durumu kabul ettiği ve etkinlikleri gösterdiği için tek yönlü veri akışı kalıbı Jetpack Compose ile iyi uyum sağlar. Bu kılavuzda, tek yönlü veri akışı kalıbının Compose'da nasıl uygulanacağı, etkinliklerin ve durum tutucuların nasıl uygulanacağı ve Compose'da ViewModel'lerle nasıl çalışılacağı ele alınmaktadır.

Tek yönlü veri akışı

Tek yönlü veri akışı (UDF), durumun aşağı ve etkinliklerin yukarı aktığı bir tasarım modelidir. Tek yönlü veri akışını izleyerek, kullanıcı arayüzünde durumu gösteren bileşenleri, durumu depolayan ve değiştiren uygulamanızın bölümlerinden ayırabilirsiniz.

Tek yönlü veri akışı kullanan bir uygulamanın kullanıcı arayüzü güncelleme döngüsü aşağıdaki gibidir:

  1. Etkinlik: Kullanıcı arayüzünün bir kısmı bir etkinlik oluşturur ve bunu yukarıya iletir (ör. ViewModel'e işlenmek üzere iletilen bir düğme tıklaması). Alternatif olarak, uygulamanızın diğer katmanlarından bir etkinlik iletilebilir (ör. kullanıcı oturumunun süresinin dolduğunu belirten bir etkinlik).
  2. Durumu güncelleme: Bir etkinlik işleyici durumu değiştirebilir.
  3. Durum görüntüleme: Durum tutucusu durumu iletir ve kullanıcı arayüzü durumu gösterir.

Etkinlikler kullanıcı arayüzünden bir durum tutucuya, durum ise durum tutucudan kullanıcı arayüzüne akar.
Şekil 1. Tek yönlü veri akışı.

Jetpack Compose'u kullanırken bu kalıbı uygulamak çeşitli avantajlar sağlar:

  • Test edilebilirlik: Durumu gösteren kullanıcı arayüzünden ayırmak, her ikisini de ayrı ayrı test etmeyi kolaylaştırır.
  • Durum kapsüllemesi: Durum yalnızca tek bir yerden güncellenebildiğinden ve bir bileşenin durumu için yalnızca bir doğru kaynak olduğundan tutarsız durumlar nedeniyle hata oluşturma olasılığınız daha düşüktür.
  • Kullanıcı arayüzü tutarlılığı: StateFlow veya LiveData gibi gözlemlenebilir durum tutucular kullanılarak tüm durum güncellemeleri kullanıcı arayüzüne hemen yansıtılır.

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

Kompozitler 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ğırma işlevi (değerin yeni bir değerle değiştirilmesini isteyen bir etkinlik) gösterir. Oluştur, State nesnesini bir değer tutucusu olarak tanımlar ve durum değerinde yapılan değişiklikler yeniden oluşturmayı tetikler. Değeri ne kadar süre boyunca hatırlamanız gerektiğine bağlı olarak durumu bir remember { mutableStateOf(value) } veya rememberSaveable { mutableStateOf(value) içinde tutabilirsiniz.

TextField bileşeninin değerinin türü String olduğundan bu değer herhangi bir yerden (kodda sabitlenmiş bir değerden, ViewModel'den veya üst bileşenden) alınabilir. Değeri bir State nesnesinde tutmanız gerekmez ancak onValueChange çağrıldığında değeri güncellemeniz gerekir.

Birleştirilebilir parametreleri tanımlama

Bir bileşenin durum parametrelerini tanımlarken aşağıdaki soruları göz önünde bulundurmanız gerekir:

  • Kompozit ne kadar yeniden kullanılabilir veya esnek?
  • Durum parametreleri bu bileşiğin performansını nasıl etkiler?

Ayrıştırmayı ve yeniden kullanımı teşvik etmek için her bir bileşen mümkün olan en az miktarda bilgi içermelidir. Örneğin, bir haber makalesinin başlığını barındıracak bir bileşen oluştururken haber makalesinin tamamı yerine yalnızca gösterilmesi gereken bilgileri iletin:

@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 parametreleri tek tek kullanmak da performansı artırır. Örneğin, News yalnızca title ve subtitle'den daha fazla bilgi içeriyorsa News'nin yeni bir örneği Header(news)'a her iletildiğinde, title ve subtitle değişmemiş olsa bile derlenebilir öğe yeniden derlenir.

İlettiğiniz parametre sayısını dikkatlice düşünün. Çok fazla parametre içeren bir işlev, işlevin ergonomisini azaltır. Bu nedenle, bu durumda parametreleri bir sınıfta gruplandırmak tercih edilir.

Oluşturma bölümündeki etkinlikler

Uygulamanıza yapılan her giriş bir etkinlik olarak temsil edilmelidir: Dokunuşlar, metin değişiklikleri ve hatta zamanlayıcılar veya diğer güncellemeler. Bu etkinlikler kullanıcı arayüzünüzün durumunu değiştirdiğinden, bunları işleyen ve kullanıcı arayüzü durumunu güncelleyen ViewModel olmalıdır.

Kullanıcı arayüzü katmanı, uygulamanızda tutarsızlıklara ve hatalara neden olabileceğinden hiçbir zaman bir etkinlik işleyicinin dışında durum değiştirmemelidir.

Durum ve etkinlik işleyici lambda'ları için değişmez değerler geçirmeyi tercih edin. Bu yaklaşımın avantajları şunlardır:

  • Yeniden kullanılabilirliği iyileştirirsiniz.
  • Kullanıcı arayüzünüzün, durumun değerini doğrudan değiştirmediğinden emin olun.
  • Durum bilgisinin başka bir iş parçacığında değiştirilmediğinden emin olduğunuz için eşzamanlılık sorunlarını önlersiniz.
  • Genellikle kod karmaşıklığını azaltırsınız.

Örneğin, parametre olarak String ve lambda kabul eden bir birleştirilebilir, birçok bağlamda çağrılabilir ve yüksek oranda yeniden kullanılabilir. Uygulamanızdaki üst uygulama çubuğunun her zaman metin gösterdiğini ve bir geri düğmesi olduğunu varsayalım. Metni ve geri düğmesi işaretçisini parametre olarak alan daha genel bir MyAppTopAppBar bileşeni 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.AutoMirrored.Filled.ArrowBack,
                    contentDescription = localizedString
                )
            }
        },
        // ...
    )
}

ViewModel'ler, durumlar ve etkinlikler: örnek

Aşağıdakilerden biri geçerliyse ViewModel ve mutableStateOf'ü kullanarak uygulamanızda tek yönlü veri akışı da kullanabilirsiniz:

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

Örneğin, oturum açma ekranı uygularken Oturum aç düğmesine dokunduğunuzda uygulamanızın bir ilerleme çubuğu ve ağ çağrısı göstermesi gerekir. Giriş başarılı olursa uygulamanız farklı bir ekrana gider. Hata durumunda ise uygulama bir Snackbar gösterir. Ekran durumunu ve etkinliği nasıl modelleyeceğiniz aşağıda açıklanmıştır:

Ekranın dört durumu vardır:

  • Oturum kapalı: Kullanıcı henüz oturum açmamışsa.
  • Devam ediyor: Uygulamanız şu anda ağ çağrısı yaparak kullanıcının oturum açmasını deniyor.
  • Hata: Oturum açılırken hata oluştuğunda.
  • Oturum açmış: Kullanıcı oturum açtığında.

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

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 dinleyici olarak kaydolup değeri durum olarak temsil etmek üzere eklentiler 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 göz atın:

Örnekler