Ringkasan ViewModel   Bagian dari Android Jetpack.

Class ViewModel didesain untuk menyimpan dan mengelola data terkait UI dengan cara yang berbasis siklus proses. Class ViewModel memungkinkan data bertahan saat terjadi perubahan konfigurasi seperti pada saat rotasi layar.

Framework Android mengelola siklus proses pengontrol UI, seperti aktivitas dan fragmen. Framework ini dapat memutuskan untuk menghancurkan atau membuat ulang pengontrol UI sebagai respons terhadap tindakan tertentu dari pengguna atau peristiwa perangkat yang benar-benar di luar kendali Anda.

Jika sistem menghancurkan atau membuat ulang pengontrol UI, setiap data terkait UI sementara yang Anda simpan di dalam pengontrol UI akan hilang. Misalnya, aplikasi Anda mungkin menyertakan daftar pengguna dalam salah satu aktivitasnya. Saat aktivitas dibuat ulang untuk perubahan konfigurasi, aktivitas baru tersebut harus menarik ulang daftar pengguna. Untuk data sederhana, aktivitas dapat menggunakan metode onSaveInstanceState() dan memulihkan datanya dari paket di onCreate(), tetapi pendekatan ini hanya cocok untuk data dalam jumlah sedikit yang dapat diserialisasi kemudian dideserialisasi, bukan untuk data yang berpotensi memiliki jumlah besar, seperti daftar pengguna atau bitmap.

Masalah lainnya yaitu pengontrol UI sering kali harus melakukan panggilan asinkron yang membutuhkan beberapa saat untuk ditampilkan. Pengontrol UI harus mengelola panggilan ini dan memastikan sistem akan membersihkan panggilan setelah panggilan dihancurkan untuk menghindari kemungkinan kebocoran memori. Pengelolaan ini memerlukan banyak pemeliharaan, dan ketika objek dibuat ulang untuk perubahan konfigurasi, pembuatan ulang objek membuat pemborosan resource karena objek mungkin harus melakukan panggilan ulang yang sebelumnya sudah dilakukan.

Pengontrol UI seperti aktivitas dan fragmen terutama ditujukan untuk menampilkan data UI, bereaksi pada tindakan pengguna, atau menangani komunikasi sistem operasi, misalnya permintaan izin. Mewajibkan pengontrol UI untuk juga bertanggung jawab atas pemuatan data dari database atau jaringan, akan menambahkan penggelembungan pada class. Menetapkan tanggung jawab berlebih pada pengontrol UI dapat mengakibatkan satu class yang mencoba menangani semua tugas aplikasi dengan sendirinya, bukan mendelegasikan tugas-tugas tersebut ke class lainnya. Menetapkan tanggung jawab berlebih pada pengontrol UI dengan cara seperti ini juga membuat pengujian menjadi jauh lebih sulit.

Akan lebih mudah dan lebih efisien untuk memisahkan kepemilikan data tampilan dari logika pengontrol UI.

Mengimplementasikan ViewModel

Komponen Arsitektur memberikan class helper ViewModel bagi pengontrol UI yang bertanggung jawab untuk menyediakan data bagi UI. Objek ViewModel otomatis disimpan pada saat perubahan konfigurasi sehingga data yang disimpan segera tersedia untuk instance aktivitas atau fragmen berikutnya. Misalnya, jika Anda perlu menampilkan daftar pengguna di aplikasi, pastikan untuk memberikan tanggung jawab perolehan dan penyimpanan daftar pengguna ke ViewModel, bukan pada aktivitas atau fragmen, seperti yang ditunjukkan oleh kode contoh berikut:

Views

class MyViewModel : ViewModel() {
    private val users: MutableLiveData<List<User>> by lazy {
        MutableLiveData<List<User>>().also {
            loadUsers()
        }
    }

    fun getUsers(): LiveData<List<User>> {
        return users
    }

    private fun loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}

Views

public class MyViewModel extends ViewModel {
    private MutableLiveData<List<User>> users;
    public LiveData<List<User>> getUsers() {
        if (users == null) {
            users = new MutableLiveData<List<User>>();
            loadUsers();
        }
        return users;
    }

    private void loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}

Anda kemudian dapat mengakses daftar tersebut dari aktivitas dengan cara sebagai berikut:

Views

class MyActivity : 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 MyViewModel instance created by the first activity.

        // Use the 'by viewModels()' Kotlin property delegate
        // from the activity-ktx artifact
        val model: MyViewModel by viewModels()
        model.getUsers().observe(this, Observer<List<User>>{ users ->
            // update UI
        })
    }
}

Views

public class MyActivity extends AppCompatActivity {
    public void onCreate(Bundle 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.

        MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class);
        model.getUsers().observe(this, users -> {
            // update UI
        });
    }
}

Jika aktivitas dibuat ulang, aktivitas akan menerima instance MyViewModel yang sama seperti yang dibuat oleh aktivitas pertama. Saat aktivitas pemilik selesai, framework memanggil metode onCleared() milik objek ViewModel agar metode tersebut dapat merapikan resource.

Objek ViewModel dirancang untuk aktif lebih lama dibandingkan pembuatan instance tampilan tertentu atau LifecycleOwners. Rancangan ini juga berarti bahwa Anda dapat menulis pengujian yang mencakup ViewModel dengan lebih mudah karena rancangan tidak mengetahui tampilan dan objek Lifecycle. Objek ViewModel dapat berisi LifecycleObservers, seperti objek LiveData. Namun, objek ViewModel tidak boleh mengamati perubahan pada observable berbasis siklus proses, seperti objek LiveData. Jika ViewModel membutuhkan konteks Application, misalnya untuk menemukan layanan sistem, ViewModel dapat memperluas class AndroidViewModel dan memiliki konstruktor yang menerima Application di dalam konstruktor, karena class Application memperluas Context.

Membuat ViewModel dengan dependensi

Mengikuti praktik terbaik injeksi dependensi, ViewModel dapat mengambil dependensi sebagai parameter dalam konstruktornya. Sebagian besar jenis ini berasal dari lapisan domain atau data. Karena framework ini menyediakan ViewModel, mekanisme khusus diperlukan untuk membuat instance darinya. Mekanisme tersebut adalah antarmuka ViewModelProvider.Factory. Hanya penerapan antarmuka ini yang dapat membuat instance ViewModel dalam cakupan yang tepat.

Jika class ViewModel menerima dependensi dalam konstruktornya, sediakan factory yang mengimplementasikan antarmuka ViewModelProvider.Factory. Ganti fungsi create(Class<T>, CreationExtras) untuk memberikan instance ViewModel baru.

CreationExtras memungkinkan Anda mengakses informasi relevan yang membantu membuat instance ViewModel. Berikut adalah daftar kunci yang dapat diakses dari tambahan:

Kunci Fungsi
ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY Key ini menyediakan akses ke kunci kustom yang Anda teruskan ke ViewModelProvider.get().
ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY Menyediakan akses ke instance class Application.
SavedStateHandleSupport.DEFAULT_ARGS_KEY Memberikan akses ke Paket argumen yang harus Anda gunakan untuk membuat SavedStateHandle.
SavedStateHandleSupport.SAVED_STATE_REGISTRY_OWNER_KEY Memberikan akses ke SavedStateRegistryOwner yang sedang digunakan untuk membuat ViewModel.
SavedStateHandleSupport.VIEW_MODEL_STORE_OWNER_KEY Memberikan akses ke ViewModelStoreOwner yang sedang digunakan untuk membuat ViewModel.

Untuk membuat instance SavedStateHandle baru, gunakan fungsi CreationExtras.createSavedStateHandle().createSavedStateHandle()) dan teruskan ke ViewModel.

Berikut adalah contoh cara menyediakan instance ViewModel yang mengambil repositori tercakup ke class Application dan SavedStateHandle sebagai dependensi:

Views

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewmodel.CreationExtras

class MyViewModel(
    private val myRepository: MyRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    // ViewModel logic
    // ...

    // Define ViewModel factory in a companion object
    companion object {

        val Factory: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
            @Suppress("UNCHECKED_CAST")
            override fun <T : ViewModel> create(
                modelClass: Class<T>,
                extras: CreationExtras
            ): T {
                // Get the Application object from extras
                val application = checkNotNull(extras[APPLICATION_KEY])
                // Create a SavedStateHandle for this ViewModel from extras
                val savedStateHandle = extras.createSavedStateHandle()

                return MyViewModel(
                    (application as MyApplication).myRepository,
                    savedStateHandle
                ) as T
            }
        }
    }
}

Views

import static androidx.lifecycle.SavedStateHandleSupport.createSavedStateHandle;
import static androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY;

import androidx.lifecycle.SavedStateHandle;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.viewmodel.ViewModelInitializer;

public class MyViewModel extends ViewModel {

    public MyViewModel(
        MyRepository myRepository,
        SavedStateHandle savedStateHandle
    ) { /* Init ViewModel here */ }

    static final ViewModelInitializer<MyViewModel> initializer = new ViewModelInitializer<>(
        MyViewModel.class,
        creationExtras -> {
            MyApplication app = (MyApplication) creationExtras.get(APPLICATION_KEY);
            assert app != null;
            SavedStateHandle savedStateHandle = createSavedStateHandle(creationExtras);

            return new MyViewModel(app.getMyRepository(), savedStateHandle);
        }
    );
}

Kemudian, Anda dapat menggunakan factory ini saat mengambil instance ViewModel:

Views

import androidx.activity.viewModels

class MyActivity : AppCompatActivity() {

    private val viewModel: MyViewModel by viewModels { MyViewModel.Factory }

    // Rest of Activity code
}

Views

import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;

public class MyActivity extends AppCompatActivity {

    MyViewModel myViewModel = new ViewModelProvider(
        this,
        ViewModelProvider.Factory.from(MyViewModel.initializer)
    ).get(MyViewModel.class);

    // Rest of Activity code
}

Compose

import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun MyScreen(
    modifier: Modifier = Modifier,
    viewModel: MyViewModel = viewModel(factory = MyViewModel.Factory)
) {
    // ...
}

Atau, gunakan DSL factory ViewModel untuk membuat factory menggunakan API Kotlin yang lebih idiomatis:

Views

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProvider.AndroidViewModelFactory.Companion.APPLICATION_KEY
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory

class MyViewModel(
    private val myRepository: MyRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    // ViewModel logic

    // Define ViewModel factory in a companion object
    companion object {
        val Factory: ViewModelProvider.Factory = viewModelFactory {
            initializer {
                val savedStateHandle = createSavedStateHandle()
                val myRepository = (this[APPLICATION_KEY] as MyApplication).myRepository
                MyViewModel(
                    myRepository = myRepository,
                    savedStateHandle = savedStateHandle
                )
            }
        }
    }
}

Factory untuk ViewModel versi yang lebih lama dari 2.5.0

Jika menggunakan versi ViewModel yang lebih lama dari 2.5.0, Anda harus menyediakan factory dari subset class yang memperluas ViewModelProvider.Factory dan menerapkan fungsi create(Class<T>). Bergantung pada dependensi yang diperlukan ViewModel, class yang berbeda harus diperluas dari:

Jika Application atau SavedStateHandle tidak diperlukan, cukup perluas dari ViewModelProvider.Factory.

Contoh berikut menggunakan AbstractSavedStateViewModelFactory untuk ViewModel yang menggunakan repositori dan jenis SavedStateHandle sebagai dependensi:

Views

class MyViewModel(
    private val myRepository: MyRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    // ViewModel logic ...

    // Define ViewModel factory in a companion object
    companion object {
        fun provideFactory(
            myRepository: MyRepository,
            owner: SavedStateRegistryOwner,
            defaultArgs: Bundle? = null,
        ): AbstractSavedStateViewModelFactory =
            object : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
                @Suppress("UNCHECKED_CAST")
                override fun <T : ViewModel> create(
                    key: String,
                    modelClass: Class<T>,
                    handle: SavedStateHandle
                ): T {
                    return MyViewModel(myRepository, handle) as T
                }
            }
    }
}

Views

import androidx.annotation.NonNull;
import androidx.lifecycle.AbstractSavedStateViewModelFactory;
import androidx.lifecycle.SavedStateHandle;
import androidx.lifecycle.ViewModel;

public class MyViewModel extends ViewModel {
    public MyViewModel(
        MyRepository myRepository,
        SavedStateHandle savedStateHandle
    ) { /* Init ViewModel here */ }
}

public class MyViewModelFactory extends AbstractSavedStateViewModelFactory {

    private final MyRepository myRepository;

    public MyViewModelFactory(
        MyRepository myRepository
    ) {
        this.myRepository = myRepository;
    }

    @SuppressWarnings("unchecked")
    @NonNull
    @Override
    protected <T extends ViewModel> T create(
        @NonNull String key, @NonNull Class<T> modelClass, @NonNull SavedStateHandle handle
    ) {
        return (T) new MyViewModel(myRepository, handle);
    }
}

Kemudian, Anda dapat menggunakan factory untuk mengambil ViewModel:

Views

import androidx.activity.viewModels

class MyActivity : AppCompatActivity() {

    private val viewModel: MyViewModel by viewModels {
        MyViewModel.provideFactory((application as MyApplication).myRepository, this)
    }

    // Rest of Activity code
}

Views

public class MyActivity extends AppCompatActivity {

    MyViewModel myViewModel = new ViewModelProvider(
        this,
        new MyViewModelFactory(((MyApplication) getApplication()).getMyRepository())
    ).get(MyViewModel.class);

    // Rest of Activity code
}

Compose

import androidx.lifecycle.viewmodel.compose.viewModel

@Composable
fun MyScreen(
    modifier: Modifier = Modifier,
    viewModel: MyViewModel = viewModel(
        factory = MyViewModel.provideFactory(
            (LocalContext.current.applicationContext as MyApplication).myRepository,
            owner = LocalSavedStateRegistryOwner.current
        )
    )
) {
    // ...
}

Siklus proses ViewModel

Objek ViewModel dicakupkan ke Lifecycle yang diteruskan ke ViewModelProvider saat menerima ViewModel. ViewModel tetap berada di memori sampai Lifecycle yang dicakupnya hilang secara permanen: dalam kasus aktivitas, yaitu saat aktivitas selesai, sementara dalam kasus fragmen, yaitu sampai fragmen terlepas.

Gambar 1 menunjukkan berbagai status siklus proses suatu aktivitas saat aktivitas tersebut mengalami rotasi dan kemudian selesai. Gambar ini juga menunjukkan masa aktif ViewModel di sebelah siklus proses aktivitas yang terkait. Diagram khusus ini menggambarkan status suatu aktivitas. Status dasar yang sama juga diterapkan untuk siklus proses suatu fragmen.

Menggambarkan siklus proses ViewModel saat suatu aktivitas berganti status.

Anda biasanya meminta ViewModel saat sistem pertama kali memanggil metode onCreate() milik objek aktivitas. Sistem mungkin memanggil onCreate() beberapa kali selama masa aktif aktivitas, seperti saat layar perangkat diputar. ViewModel tersedia sejak saat Anda pertama kali meminta ViewModel sampai aktivitas selesai dan dimusnahkan.

Berbagi data antar-fragmen

Sangatlah umum bagi dua fragmen atau lebih dalam suatu aktivitas untuk perlu saling berkomunikasi satu sama lain. Bayangkan kasus umum fragmen tampilan terpisah (list-detail), yakni saat Anda memiliki fragmen yang memungkinkan pengguna memilih item dari daftar dan fragmen lain yang menampilkan isi item yang dipilih. Kasus seperti ini tidaklah sepele karena kedua fragmen harus mendefinisikan beberapa deskripsi antarmuka, dan aktivitas pemilik harus mengikat keduanya secara bersamaan. Selain itu, kedua fragmen harus menangani skenario saat fragmen lain belum dibuat atau terlihat.

Titik permasalahan umum ini dapat diatasi menggunakan objek ViewModel. Fragmen ini dapat berbagi ViewModel menggunakan cakupan aktivitas untuk menangani komunikasi ini, seperti yang ditunjukkan dalam kode contoh berikut:

Views

class SharedViewModel : ViewModel() {
    val selected = MutableLiveData()

    fun select(item: Item) {
        selected.value = item
    }
}

class ListFragment : Fragment() {

    private lateinit var itemSelector: Selector

    // Use the 'by activityViewModels()' Kotlin property delegate
    // from the fragment-ktx artifact
    private val model: SharedViewModel by activityViewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        itemSelector.setOnClickListener { item ->
            // Update the UI
        }
    }
}

class DetailFragment : Fragment() {

    // Use the 'by activityViewModels()' Kotlin property delegate
    // from the fragment-ktx artifact
    private val model: SharedViewModel by activityViewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        model.selected.observe(viewLifecycleOwner, Observer { item ->
            // Update the UI
        })
    }
}

Views

public class SharedViewModel extends ViewModel {
    private final MutableLiveData<Item> selected = new MutableLiveData<Item>();

    public void select(Item item) {
        selected.setValue(item);
    }

    public LiveData getSelected() {
        return selected;
    }
}

public class ListFragment extends Fragment {
    private SharedViewModel model;

    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
        itemSelector.setOnClickListener(item -> {
            model.select(item);
        });
    }
}

public class DetailFragment extends Fragment {

    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        SharedViewModel model = new ViewModelProvider(requireActivity()).get(SharedViewModel.class);
        model.getSelected().observe(getViewLifecycleOwner(), item -> {
           // Update the UI.
        });
    }
}

Perhatikan bahwa kedua fragmen mengambil aktivitas yang memuatnya. Dengan begitu, saat setiap fragmen menerima ViewModelProvider, fragmen tersebut menerima instance SharedViewModel yang sama, yang dicakupkan ke aktivitas ini.

Pendekatan ini memberikan manfaat seperti berikut:

  • Aktivitas tidak perlu melakukan, atau mengetahui apa pun tentang komunikasi ini.
  • Fragmen tidak perlu mengenali satu sama lainnya selain kontrak SharedViewModel. Jika salah satu fragmen hilang, fragmen lainnya akan terus berfungsi seperti biasa.
  • Setiap fragmen memiliki siklus prosesnya sendiri, dan tidak terpengaruh oleh siklus proses fragmen lain. Jika satu fragmen menggantikan yang lainnya, UI akan terus berfungsi tanpa masalah.

Mengganti Loader dengan ViewModel

Class Loader seperti CursorLoader sering digunakan untuk menyimpan data di UI aplikasi yang disinkronkan dengan database. Anda dapat menggunakan ViewModel, dengan beberapa class lainnya, untuk menggantikan loader. Penggunaan ViewModel memisahkan pengontrol UI dari operasi pemuatan data, yang berarti Anda memiliki lebih sedikit referensi yang kuat antar-class.

Pada satu pendekatan umum dalam penggunaan loader, suatu aplikasi mungkin menggunakan CursorLoader untuk mengamati konten suatu database. Saat suatu nilai dalam database berubah, loader secara otomatis memicu pemuatan ulang data dan mengupdate UI:

Gambar 2. Pemuatan data dengan loader

ViewModel berfungsi dengan Room dan LiveData sebagai pengganti loader. ViewModel memastikan bahwa data bertahan saat terjadi perubahan konfigurasi perangkat. Room memberikan informasi ke LiveData saat database berubah, kemudian LiveData mengupdate UI dengan data baru.

Gambar 3. Pemuatan data menggunakan ViewModel

Menggunakan coroutine dengan ViewModel

ViewModel mencakup dukungan untuk coroutine Kotlin. Untuk informasi selengkapnya, lihat Menggunakan coroutine Kotlin dengan Komponen Arsitektur Android.

Informasi lebih lanjut

Ketika data Anda bertambah menjadi lebih kompleks, Anda mungkin memilih untuk memiliki class terpisah yang tugasnya hanya untuk memuat data. Tujuan dari ViewModel adalah untuk mengenkapsulasi data bagi pengontrol UI agar data dapat bertahan saat terjadi perubahan konfigurasi. Untuk informasi tentang cara memuat, mempertahankan, dan mengelola data di seluruh perubahan konfigurasi, lihat Menyimpan Status UI.

Panduan Arsitektur Aplikasi Android menyarankan pembuatan class repositori untuk penanganan fungsi-fungsi ini.

Referensi lainnya

Untuk informasi selengkapnya tentang class ViewModel, lihat referensi berikut.

Contoh

Codelab

Blog

Video