ViewModel'e genel bakış   Android Jetpack'in bir parçasıdır.

Kotlin Multiplatform ile deneme
Kotlin Multiplatform, işletme mantığının diğer platformlarla paylaşılmasına olanak tanır. KMP'de ViewModel'i ayarlama ve ViewModel ile çalışma hakkında bilgi edinin.

ViewModel sınıfı, iş mantığı veya ekran düzeyinde durum tutucudur. Durumu kullanıcı arayüzüne sunar ve ilgili iş mantığını kapsar. Başlıca avantajı, durumu önbelleğe alması ve yapılandırma değişiklikleri boyunca kalıcı hale getirmesidir. Bu sayede, etkinlikler arasında gezinirken veya yapılandırma değişikliklerinden sonra (ör. ekran döndürüldüğünde) kullanıcı arayüzünüzün verileri tekrar getirmesi gerekmez.

Devlet görevlileri hakkında daha fazla bilgi için devlet görevlileri ile ilgili kılavuza bakın. Benzer şekilde, kullanıcı arayüzü katmanı hakkında daha fazla bilgi için Kullanıcı arayüzü katmanı ile ilgili yönergelere bakın.

ViewModel'in avantajları

ViewModel'e alternatif olarak, kullanıcı arayüzünüzde gösterdiğiniz verileri tutan düz bir sınıf kullanabilirsiniz. Bu durum, etkinlikler veya gezinme hedefleri arasında gezinirken sorun olabilir. Bu işlemi yaparsanız kayıtlı örnek durumu mekanizmasını kullanarak depolamadığınız veriler silinir. ViewModel, bu sorunu çözen veri kalıcılığı için uygun bir API sağlar.

ViewModel sınıfının temel avantajları esasen iki tanedir:

  • Kullanıcı arayüzü durumunu kalıcı hale getirmenizi sağlar.
  • İş mantığına erişim sağlar.

Kalıcılık

ViewModel, hem ViewModel'in tuttuğu durum hem de ViewModel'in tetiklediği işlemler aracılığıyla kalıcılığa olanak tanır. Bu önbelleğe alma işlemi sayesinde, ekran döndürme gibi yaygın yapılandırma değişiklikleri aracılığıyla verileri tekrar getirmeniz gerekmez.

Kapsam

ViewModel'i örneklediğinizde, ViewModelStoreOwner arayüzünü uygulayan bir nesne iletirsiniz. Bu, bir gezinme hedefi, gezinme grafiği, etkinlik, parça veya arayüzü uygulayan başka bir tür olabilir. ViewModel'ınız daha sonra ViewModelStoreOwner Lifecycle'ına göre kapsamlandırılır. ViewModelStoreOwner kalıcı olarak kaldırılana kadar bellekte kalır.

Bir dizi sınıf, ViewModelStoreOwner arayüzünün doğrudan veya dolaylı alt sınıflarıdır. Doğrudan alt sınıflar ComponentActivity, Fragment ve NavBackStackEntry'dır. Dolaylı alt sınıfların tam listesi için ViewModelStoreOwner referansına bakın.

ViewModel'in kapsamı belirlendiği parça veya etkinlik yok edildiğinde, ViewModel'de kapsamı belirlenen asenkron çalışma devam eder. Bu, kalıcılığın anahtarıdır.

Daha fazla bilgi için aşağıdaki ViewModel yaşam döngüsü bölümüne bakın.

SavedStateHandle

SavedStateHandle, verileri yalnızca yapılandırma değişiklikleri sırasında değil, süreç yeniden oluşturulurken de kalıcı hale getirmenize olanak tanır. Yani, kullanıcı uygulamayı kapatıp daha sonra açsa bile kullanıcı arayüzü durumunu korumanızı sağlar.

İş mantığına erişim

İş mantığının büyük çoğunluğu veri katmanında bulunsa da kullanıcı arayüzü katmanı da iş mantığı içerebilir. Bu durum, ekran kullanıcı arayüzü durumunu oluşturmak için birden fazla depodaki veriler birleştirildiğinde veya belirli bir veri türü için veri katmanı gerekmediğinde söz konusu olabilir.

ViewModel, kullanıcı arayüzü katmanındaki iş mantığını işlemek için doğru yerdir. ViewModel, etkinlikleri işlemeyle ve uygulama verilerini değiştirmek için iş mantığı uygulanması gerektiğinde bunları hiyerarşinin diğer katmanlarına devretmeyle de sorumludur.

Jetpack Compose

Jetpack Compose kullanırken ViewModel, ekran kullanıcı arayüzü durumunu composable'larınıza sunmanın birincil yoludur. Karma uygulamalarda etkinlikler ve parçalar, composable işlevlerinizi barındırır. Bu, geçmiş yaklaşımlardan farklıdır. Geçmişte, etkinlikler ve parçalarla yeniden kullanılabilir kullanıcı arayüzü parçaları oluşturmak bu kadar basit ve sezgisel değildi. Bu durum, etkinliklerin kullanıcı arayüzü denetleyicileri olarak çok daha aktif olmasına neden oluyordu.

ViewModel'i Compose ile kullanırken dikkat etmeniz gereken en önemli nokta, ViewModel'in composable'a kapsamı dışında tutulamamasıdır. Bunun nedeni, composable'ın ViewModelStoreOwner olmamasıdır. Aynı composable'ın Composition'daki iki örneği veya aynı ViewModelStoreOwner altında aynı ViewModel türüne erişen iki farklı composable, ViewModel'in aynı örneğini alır. Bu durum genellikle beklenen davranış değildir.

Compose'da ViewModel'in avantajlarından yararlanmak için her ekranı bir Fragment veya Activity'de barındırın ya da Compose Navigation'ı kullanın ve ViewModel'leri Navigation hedefinin mümkün olduğunca yakınında composable işlevlerde kullanın. Bunun nedeni, ViewModel'i gezinme hedefleri, gezinme grafikleri, aktiviteler ve parçalarla sınırlayabilmenizdir.

Daha fazla bilgi için Jetpack Compose'da durum yükseltme ile ilgili kılavuza bakın.

ViewModel'i uygulama

Aşağıda, kullanıcının zar atmasına olanak tanıyan bir ekran için ViewModel'in örnek uygulaması verilmiştir.

Kotlin

data class DiceUiState(
    val firstDieValue: Int? = null,
    val secondDieValue: Int? = null,
    val numberOfRolls: Int = 0,
)

class DiceRollViewModel : ViewModel() {

    // Expose screen UI state
    private val _uiState = MutableStateFlow(DiceUiState())
    val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()

    // Handle business logic
    fun rollDice() {
        _uiState.update { currentState ->
            currentState.copy(
                firstDieValue = Random.nextInt(from = 1, until = 7),
                secondDieValue = Random.nextInt(from = 1, until = 7),
                numberOfRolls = currentState.numberOfRolls + 1,
            )
        }
    }
}

Java

public class DiceUiState {
    private final Integer firstDieValue;
    private final Integer secondDieValue;
    private final int numberOfRolls;

    // ...
}

public class DiceRollViewModel extends ViewModel {

    private final MutableLiveData<DiceUiState> uiState =
        new MutableLiveData(new DiceUiState(null, null, 0));
    public LiveData<DiceUiState> getUiState() {
        return uiState;
    }

    public void rollDice() {
        Random random = new Random();
        uiState.setValue(
            new DiceUiState(
                random.nextInt(7) + 1,
                random.nextInt(7) + 1,
                uiState.getValue().getNumberOfRolls() + 1
            )
        );
    }
}

Ardından, ViewModel'e bir etkinlikten aşağıdaki gibi erişebilirsiniz:

Kotlin

import androidx.activity.viewModels

class DiceRollActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same DiceRollViewModel instance created by the first activity.

        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        val viewModel: DiceRollViewModel by viewModels()
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Update UI elements
                }
            }
        }
    }
}

Java

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.
        DiceRollViewModel model = new ViewModelProvider(this).get(DiceRollViewModel.class);
        model.getUiState().observe(this, uiState -> {
            // update UI
        });
    }
}

Jetpack Compose

import androidx.lifecycle.viewmodel.compose.viewModel

// Use the 'viewModel()' function from the lifecycle-viewmodel-compose artifact
@Composable
fun DiceRollScreen(
    viewModel: DiceRollViewModel = viewModel()
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    // Update UI elements
}

ViewModel ile coroutine'leri kullanma

ViewModel, Kotlin eş yordamları için destek içerir. Asenkron çalışmayı, kullanıcı arayüzü durumunu kalıcı hale getirdiği şekilde kalıcı hale getirebilir.

Daha fazla bilgi için Android Architecture Components ile Kotlin coroutine'lerini kullanma başlıklı makaleyi inceleyin.

ViewModel'in yaşam döngüsü

Bir ViewModel yaşam döngüsü doğrudan kapsamına bağlıdır. Bir ViewModel, kapsamının belirlendiği ViewModelStoreOwner ortadan kaybolana kadar bellekte kalır. Bu durum aşağıdaki bağlamlarda ortaya çıkabilir:

  • Etkinliklerde ise etkinlik bittiğinde.
  • Parça söz konusu olduğunda, ayrıldığında.
  • Gezinme girişi, arka yığından kaldırıldığında

Bu nedenle, ViewModels, yapılandırma değişikliklerinden etkilenmeyen verileri depolamak için mükemmel bir çözümdür.

Şekil 1'de, bir etkinliğin rotasyona uğradıktan sonra tamamlanmasıyla ilgili çeşitli yaşam döngüsü durumları gösterilmektedir. Ayrıca, görselde ilişkili etkinlik yaşam döngüsünün yanında ViewModel'nin kullanım süresi de gösterilmektedir. Bu özel diyagram, bir etkinliğin durumlarını gösterir. Aynı temel durumlar, bir parçanın yaşam döngüsü için de geçerlidir.

Bir ViewModel&#39;in yaşam döngüsünü, etkinlik durumu değişirken gösterir.

Genellikle sistem bir etkinlik nesnesinin onCreate() yöntemini ilk kez çağırdığında ViewModel isteğinde bulunursunuz. Sistem, bir etkinliğin varlığı boyunca onCreate() işlevini birkaç kez çağırabilir. Örneğin, cihaz ekranı döndürüldüğünde bu işlev çağrılır. ViewModel, ilk kez ViewModel istediğiniz andan etkinlik tamamlanıp yok edilene kadar var olur.

ViewModel bağımlılıklarını temizleme

ViewModel, ViewModelStoreOwner yaşam döngüsü sırasında kendini yok ettiğinde onCleared yöntemini çağırır. Bu sayede, ViewModel'in yaşam döngüsünü takip eden tüm işleri veya bağımlılıkları temizleyebilirsiniz.

Aşağıdaki örnekte viewModelScope için alternatif bir yöntem gösterilmektedir. viewModelScope, ViewModel'in yaşam döngüsünü otomatik olarak takip eden yerleşik bir CoroutineScope'dir. ViewModel, işlemleri tetiklemek için bunu kullanır. Daha kolay test için viewModelScope yerine özel bir kapsam kullanmak istiyorsanız ViewModel, oluşturucusunda bağımlılık olarak CoroutineScope alabilir. ViewModelStoreOwner yaşam döngüsünün sonunda ViewModel'i temizlediğinde ViewModel de CoroutineScope'ı iptal eder.

class MyViewModel(
    private val coroutineScope: CoroutineScope =
        CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
) : ViewModel() {

    // Other ViewModel logic ...

    override fun onCleared() {
        coroutineScope.cancel()
    }
}

Yaşam döngüsü 2.5 sürümünden itibaren, CloseableViewModel örneği temizlendiğinde otomatik olarak kapanan ViewModel'in oluşturucusuna bir veya daha fazla nesne geçirebilirsiniz.

class CloseableCoroutineScope(
    context: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate
) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context
    override fun close() {
        coroutineContext.cancel()
   }
}

class MyViewModel(
    private val coroutineScope: CoroutineScope = CloseableCoroutineScope()
) : ViewModel(coroutineScope) {
    // Other ViewModel logic ...
}

En iyi uygulamalar

ViewModel'i uygularken izlemeniz gereken birkaç önemli en iyi uygulama aşağıda verilmiştir:

  • Kapsamları nedeniyle ViewModel'leri ekran düzeyinde bir durum tutucunun uygulama ayrıntıları olarak kullanın. Bunları, çip grupları veya formlar gibi yeniden kullanılabilir kullanıcı arayüzü bileşenlerinin durum tutucuları olarak kullanmayın. Aksi takdirde, çip başına açık bir görünüm modeli anahtarı kullanmadığınız sürece aynı ViewModelStoreOwner altındaki aynı kullanıcı arayüzü bileşeninin farklı kullanımlarında aynı ViewModel örneğini alırsınız.
  • ViewModel'ler, kullanıcı arayüzü uygulama ayrıntıları hakkında bilgi sahibi olmamalıdır. ViewModel API'nin sunduğu yöntemlerin ve kullanıcı arayüzü durumu alanlarının adlarını mümkün olduğunca genel tutun. Bu sayede ViewModel'iniz her türden kullanıcı arayüzünü (cep telefonu, katlanabilir telefon, tablet veya Chromebook) destekleyebilir.
  • ViewModel'ler, ViewModelStoreOwner'den daha uzun süre yaşayabileceğinden bellek sızıntılarını önlemek için Context veya Resources gibi yaşam döngüsüyle ilgili API'lerin referanslarını tutmamalıdır.
  • ViewModel'leri diğer sınıflara, işlevlere veya diğer kullanıcı arayüzü bileşenlerine aktarmayın. Platform bu dosyaları yönettiğinden, mümkün olduğunca platforma yakın tutmanız gerekir. Etkinliğinizin, parçanızın veya ekran düzeyindeki composable işlevinizin yakınında. Bu, alt düzey bileşenlerin ihtiyaç duyduklarından daha fazla veriye ve mantığa erişmesini engeller.

Daha fazla bilgi

Verileriniz karmaşıklaştıkça yalnızca verileri yüklemek için ayrı bir sınıf oluşturmayı tercih edebilirsiniz. ViewModel'ın amacı, verilerin yapılandırma değişikliklerinden etkilenmemesi için bir kullanıcı arayüzü denetleyicisine yönelik verileri kapsüllemektir. Yapılandırma değişiklikleri sırasında verilerin nasıl yükleneceği, kalıcı hale getirileceği ve yönetileceği hakkında bilgi edinmek için Kaydedilmiş Kullanıcı Arayüzü Durumları başlıklı makaleyi inceleyin.

Android Uygulama Mimarisi Rehberi, bu işlevleri yönetmek için bir depo sınıfı oluşturulmasını önerir.

Ek kaynaklar

ViewModel sınıfı hakkında daha fazla bilgi için aşağıdaki kaynakları inceleyin.

Belgeler

Örnekler