Tổng quan về ViewModel   Một phần của Android Jetpack.

Lớp ViewModel được thiết kế nhằm lưu trữ và quản lý dữ liệu liên quan đến giao diện người dùng theo cách nhận biết vòng đời. Lớp ViewModel duy trì dữ liệu sau các thay đổi về cấu hình, chẳng hạn như thao tác xoay màn hình.

Khung Android quản lý vòng đời của các bộ điều khiển giao diện người dùng, chẳng hạn như các hoạt động và mảnh (fragment). Khung này có thể quyết định việc huỷ hoặc tạo lại một bộ điều khiển giao diện người dùng để phản hồi một số thao tác nhất định của người dùng hoặc các sự kiện hoàn toàn nằm ngoài tầm kiểm soát của bạn trên thiết bị.

Nếu hệ thống huỷ hoặc tạo lại bộ điều khiển giao diện người dùng, thì mọi dữ liệu tạm thời liên quan đến giao diện người dùng mà bạn lưu trữ trong đó sẽ bị mất. Ví dụ: ứng dụng của bạn có thể chứa một danh sách những người dùng thuộc một trong những hoạt động của ứng dụng đó. Khi hoạt động được tạo lại để thay đổi cấu hình, hoạt động mới phải tìm nạp lại danh sách người dùng. Đối với dữ liệu đơn giản, hoạt động có thể sử dụng phương thức onSaveInstanceState() và khôi phục dữ liệu trong gói của onCreate(), tuy nhiên phương pháp này chỉ phù hợp với lượng dữ liệu nhỏ có thể được tuần tự hoá và sau đó giải tuần tự hoá, không phù hợp với lượng dữ liệu có thể có kích thước lớn như danh sách người dùng hoặc bitmap.

Một vấn đề khác là những lệnh gọi không đồng bộ do bộ điều khiển giao diện người dùng cần thực hiện thường xuyên có thể mất một chút thời gian để trả về. Bộ điều khiển giao diện người dùng cần quản lý các lệnh gọi này và đảm bảo việc hệ thống dọn sạch chúng sau khi huỷ bộ điều khiển để tránh việc rò rỉ bộ nhớ tiềm ẩn. Việc quản lý này đòi hỏi nhiều thời gian bảo trì. Trong trường hợp đối tượng được tạo lại để thay đổi cấu hình, bạn sẽ lãng phí tài nguyên vì đối tượng có thể phải phát hành lại các lệnh gọi đã thực hiện trước đó.

Các bộ điều khiển giao diện người dùng như các hoạt động và mảnh chủ yếu dùng để hiển thị dữ liệu giao diện người dùng, phản ứng với các thao tác của người dùng hoặc xử lý nội dung giao tiếp của hệ điều hành, chẳng hạn như các yêu cầu cấp quyền. Lớp sẽ bị quá tải nếu bạn yêu cầu bộ điều khiển giao diện người dùng chịu thêm trách nhiệm tải dữ liệu từ cơ sở dữ liệu hoặc mạng. Việc chỉ định quá nhiều trách nhiệm cho bộ điều khiển giao diện người dùng có thể dẫn đến việc một lớp cố gắng tự xử lý tất cả công việc của một ứng dụng, thay vì uỷ quyền cho các lớp khác. Điều này cũng khiến cho việc kiểm thử trở nên khó khăn hơn nhiều.

Sẽ dễ dàng và hiệu quả hơn khi bạn tách biệt chế độ xem quyền sở hữu dữ liệu khỏi logic bộ điều khiển giao diện người dùng.

Triển khai một ViewModel

Bộ thành phần cấu trúc ViewModel cung cấp lớp trợ giúp cho bộ điều khiển giao diện người dùng chịu trách nhiệm chuẩn bị dữ liệu cho giao diện người dùng. Các đối tượng ViewModel tự động được giữ lại khi có thay đổi về cấu hình. Nhờ đó, thực thể tiếp theo của hoạt động hoặc mảnh sẽ có thể sử dụng ngay dữ liệu mà các đối tượng này lưu trữ. Ví dụ: nếu bạn cần hiển thị danh sách người dùng trong ứng dụng, hãy nhớ chỉ định trách nhiệm thu nạp và giữ danh sách người dùng cho một ViewModel, thay vì một hoạt động hoặc mảnh, được minh hoạ bằng mã mẫu sau đây:

Số lượt xem

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.
    }
}

Số lượt xem

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.
    }
}

Sau đó, bạn có thể truy cập danh sách trong một hoạt động như sau:

Số lượt xem

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
        })
    }
}

Số lượt xem

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
        });
    }
}

Nếu được tạo lại thì hoạt động sẽ nhận được cùng một thực thể MyViewModel do hoạt động đầu tiên tạo ra. Khi hoạt động chủ hoàn tất, khung sẽ gọi phương thức onCleared()của đối tượng ViewModel để phương thức có thể dọn dẹp tài nguyên.

Các đối tượng ViewModel được thiết kế để kéo dài hơn bản sao của các chế độ xem cụ thể hoặc LifecycleOwners. Cách thiết kế này cũng cho phép bạn có thể dễ dàng viết các kiểm thử để kiểm tra một ViewModel vì lớp đó không biết về chế độ xem và các đối tượng Lifecycle. Các đối tượng ViewModel có thể chứa LifecycleObservers, chẳng hạn như các đối tượng LiveData. Tuy nhiên, các đối tượng ViewModel không được phép quan sát những thay đổi của các đối tượng nhận biết vòng đời quan sát được, chẳng hạn như các đối tượng LiveData. Nếu ViewModel cần ngữ cảnh Application, chẳng hạn như để tìm một dịch vụ hệ thống, thì đối tượng có thể mở rộng lớp AndroidViewModel và có một hàm khởi tạo nhận được Application trong hàm khởi tạo, vì lớp Application mở rộng Context.

Tạo ViewModel có các phần phụ thuộc

Làm theo các phương pháp hay nhất về thao tác chèn phần phụ thuộc, ViewModel có thể lấy các phần phụ thuộc làm tham số trong hàm dựng. Đây chủ yếu là các loại từ lớp miền hoặc lớp dữ liệu. Vì khung cung cấp ViewModel, nên bạn cần có một cơ chế đặc biệt để tạo các thực thể của khung đó. Cơ chế đó là giao diện ViewModelProvider.Factory. Chỉ các cách triển khai giao diện này mới có thể tạo bản sao các ViewModel trong phạm vi phù hợp.

Nếu một lớp ViewModel nhận phần phụ thuộc trong hàm khởi tạo của lớp đó, hãy cung cấp nhà máy triển khai giao diện ViewModelProvider.Factory. Ghi đè hàm create(Class<T>, CreationExtras) để cung cấp bản sao mới của ViewModel.

CreationExtras cho phép bạn truy cập thông tin liên quan giúp tạo bản sao của ViewModel. Dưới đây là danh sách các khoá có thể truy cập từ lớp bổ sung:

Khoá Chức năng
ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY Key này cung cấp quyền truy cập vào khoá tuỳ chỉnh mà bạn đã chuyển tới ViewModelProvider.get().
ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY Cung cấp quyền truy cập vào thực thể của lớp Application.
SavedStateHandleSupport.DEFAULT_ARGS_KEY Cung cấp quyền truy cập vào Gói đối số mà bạn nên sử dụng để tạo SavedStateHandle.
SavedStateHandleSupport.SAVED_STATE_REGISTRY_OWNER_KEY Cung cấp quyền truy cập vào SavedStateRegistryOwner đang được dùng để tạo ViewModel.
SavedStateHandleSupport.VIEW_MODEL_STORE_OWNER_KEY Cung cấp quyền truy cập vào ViewModelStoreOwner đang được dùng để tạo ViewModel.

Để tạo một phiên bản mới của SavedStateHandle, hãy sử dụng hàm CreationExtras.createSavedStateHandle().createSavedStateHandle()) và truyền hàm đó vào ViewModel.

Sau đây là ví dụ về cách cung cấp một bản sao của ViewModel lấy kho lưu trữ thuộc lớp ApplicationSavedStateHandle làm phần phụ thuộc:

Số lượt xem

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
            }
        }
    }
}

Số lượt xem

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);
        }
    );
}

Sau đó, bạn có thể sử dụng nhà máy (trạng thái ban đầu) này khi truy xuất một phiên bản của ViewModel:

Số lượt xem

import androidx.activity.viewModels

class MyActivity : AppCompatActivity() {

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

    // Rest of Activity code
}

Số lượt xem

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)
) {
    // ...
}

Ngoài ra, hãy sử dụng DSL nhà máy của ViewModel để tạo các nhà máy bằng cách sử dụng API Kotlin đúng cách hơn:

Số lượt xem

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
                )
            }
        }
    }
}

Nhà máy (trạng thái ban đầu) dành cho phiên bản ViewModel trước phiên bản 2.5.0

Nếu đang sử dụng phiên bản ViewModel trước phiên bản 2.5.0, bạn cần cung cấp các nhà máy (trạng thái ban đầu) từ một tập hợp con các lớp mở rộngViewModelProvider.Factory và triển khai hàm create(Class<T>). Tuỳ thuộc vào phần phụ thuộc mà ViewModel cần, bạn cần mở rộng một lớp khác từ:

Nếu không cần Application hoặc SavedStateHandle, bạn chỉ cần mở rộng từ ViewModelProvider.Factory.

Ví dụ sau đây sử dụng một AbstractSavedStateViewModelFactory cho ViewModel có kho lưu trữ và loại SavedStateHandle làm phần phụ thuộc:

Số lượt xem

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
                }
            }
    }
}

Số lượt xem

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);
    }
}

Sau đó, bạn có thể sử dụng nhà máy (trạng thái ban đầu) để truy xuất ViewModel:

Số lượt xem

import androidx.activity.viewModels

class MyActivity : AppCompatActivity() {

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

    // Rest of Activity code
}

Số lượt xem

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
        )
    )
) {
    // ...
}

Vòng đời của ViewModel

Các đối tượng ViewModel nằm trong phạm vi của Lifecycle của ViewModelStoreOwner đã chuyển vào ViewModelProvider khi nhận được ViewModel. ViewModel sẽ vẫn lưu trong bộ nhớ cho đến khi ViewModelStoreOwnerViewModel nằm trong đó biến mất vĩnh viễn:

  • Trong trường hợp của một hoạt động, đó là khi hoạt động kết thúc.
  • Trong trường hợp của mảnh, đó là khi mảnh được tách ra.
  • Trong trường hợp một mục nhập Điều hướng, đó là khi mục bị xoá khỏi ngăn xếp lui.

Điều này khiến ViewModel trở thành một giải pháp tuyệt vời để lưu trữ dữ liệu vẫn tồn tại sau khi thay đổi cấu hình.

Hình 1 minh hoạ các trạng thái vòng đời của một hoạt động khi hoạt động đó xoay vòng và sau đó kết thúc. Hình minh hoạ cũng cho thấy toàn bộ thời gian hoạt động của ViewModel bên cạnh vòng đời hoạt động được liên kết. Sơ đồ cụ thể này minh hoạ các trạng thái của một hoạt động. Các trạng thái cơ bản tương tự áp dụng cho vòng đời của một mảnh.

Hình minh hoạ vòng đời của một ViewModel khi một hoạt động thay đổi trạng thái.

Bạn thường yêu cầu một ViewModel trong lần đầu tiên hệ thống gọi phương thức onCreate() của đối tượng hoạt động. Hệ thống có thể gọi onCreate() nhiều lần trong suốt thời gian của một hoạt động, chẳng hạn như khi bạn xoay màn hình thiết bị. ViewModel bắt đầu tồn tại từ lần đầu khi bạn yêu cầu một ViewModel cho đến khi hoạt động kết thúc và bị huỷ.

API ViewModel

Phương thức ViewModelProvider.get() cho phép bạn có được bản sao của một ViewModel thuộc phạm vi của ViewModelStoreOwner bất kỳ. Người dùng Kotlin có các hàm mở rộng khác nhau dành cho trường hợp sử dụng phổ biến nhất. Tất cả các phương thức triển khai hàm mở rộng Kotlin đều sử dụng API ViewModelProvider.

ViewModel thuộc phạm vi của ViewModelStoreOwner gần nhất

Bạn có thể thiết lập phạm vi của ViewModel trong một Hoạt động, Mảnh hoặc đích của biểu đồ Điều hướng. Với các hàm mở rộng viewModels() do thư viện Hoạt động, Mảnh và Điều hướng cung cấp cũng như hàm viewModel() trong Compose, bạn có thể xem một thực thể của ViewModel thuộc phạm vi của ViewModelStoreOwner gần nhất.

Số lượt xem

class MyActivity : AppCompatActivity() {

    // ViewModel API available in activity.activity-ktx
    // The ViewModel is scoped to `this` Activity
    val viewModel: MyViewModel by viewModels()
}

class MyFragment : Fragment() {

    // ViewModel API available in fragment.fragment-ktx
    // The ViewModel is scoped to `this` Fragment
    val viewModel: MyViewModel by viewModels()
}

Số lượt xem

public class MyActivity extends AppCompatActivity {

    // The ViewModel is scoped to `this` Activity
    MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class);
}

public class MyFragment extends Fragment {

    // The ViewModel is scoped to `this` Fragment
    MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class);
}

Compose

@Composable
fun MyScreen(
    modifier: Modifier = Modifier,
    // ViewModel API available in lifecycle.lifecycle-viewmodel-compose
    // The ViewModel is scoped to the closest ViewModelStoreOwner provided
    // via the LocalViewModelStoreOwner CompositionLocal. This could be the
    // host Activity or Fragment, or destination of the Navigation graph.
    viewModel: MyViewModel = viewModel()
) { /* ... */ }

ViewModel thuộc phạm vi của ViewModelStoreOwner bất kỳ

Hàm ComponentActivity.viewModels()Fragment.viewModels() trong hệ thống Khung hiển thị và hàm viewModel() trong Compose sẽ lấy một tham số ownerProducer (không bắt buộc) mà bạn có thể sử dụng để chỉ định ViewModelStoreOwner nào mà thực thể của ViewModel nằm trong đó. Mẫu sau đây cho biết cách lấy một thực thể của ViewModel thuộc phạm vi của mảnh mẹ:

Số lượt xem

class MyFragment : Fragment() {

    // ViewModel API available in fragment.fragment-ktx
    // The ViewModel is scoped to the parent of `this` Fragment
    val viewModel: SharedViewModel by viewModels(
        ownerProducer = { requireParentFragment() }
    )
}

Số lượt xem

public class MyFragment extends Fragment {

    SharedViewModel viewModel;

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        // The ViewModel is scoped to the parent of `this` Fragment
        viewModel = new ViewModelProvider(requireParentFragment())
            .get(SharedViewModel.class);
    }
}

Compose

@Composable
fun MyScreen(
    context: Context = LocalContext.current,
    // ViewModel API available in lifecycle.lifecycle-viewmodel-compose
    // The ViewModel is scoped to the parent of the host Fragment
    // where this composable function is called
    viewModel: SharedViewModel = viewModel(
        viewModelStoreOwner = (context as Fragment).requireParentFragment()
    )
) { /* ... */ }

Vì việc lấy ViewModel thuộc phạm vi của Hoạt động từ một Mảnh có thể là một trường hợp sử dụng phổ biến trong ứng dụng, nên bạn có thể sử dụng hàm mở rộng Khung hiển thị activityViewModels(). Nếu không dùng Khung hiển thị và Kotlin, bạn có thể sử dụng các API tương tự như trên và chuyển đúng chủ sở hữu.

Số lượt xem

class MyFragment : Fragment() {

    // ViewModel API available in fragment.fragment-ktx
    // The ViewModel is scoped to the host Activity
    val viewModel: SharedViewModel by activityViewModels()
}

Số lượt xem

public class MyFragment extends Fragment {

    SharedViewModel viewModel;

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        // The ViewModel is scoped to the host Activity
        viewModel = new ViewModelProvider(requireActivity())
            .get(SharedViewModel.class);
    }
}

Compose

@Composable
fun MyScreen(
    context: Context = LocalContext.current,
    // ViewModel API available in lifecycle.lifecycle-viewmodel-compose
    // The ViewModel is scoped to the Activity of the host Fragment
    // where this composable function is called
    viewModel: SharedViewModel = viewModel(
        viewModelStoreOwner = (context as Fragment).requireActivity()
    )
) { /* ... */ }

ViewModel thuộc phạm vi của biểu đồ Điều hướng

Biểu đồ điều hướng cũng là chủ sở hữu cửa hàng ViewModel. Nếu bạn đang sử dụng Mảnh điều hướng hoặc Compose điều hướng, bạn có thể nhận thực thể của ViewModel trong phạm vi biểu đồ Điều hướng bằng hàm mở rộng Khung hiển thị navGraphViewModels(graphId).

Số lượt xem

class MyFragment : Fragment() {

    // ViewModel API available in navigation.navigation-fragment
    // The ViewModel is scoped to the `nav_graph` Navigation graph
    val viewModel: SharedViewModel by navGraphViewModels(R.id.nav_graph)

    // Equivalent navGraphViewModels code using the viewModels API
    val viewModel: SharedViewModel by viewModels(
        { findNavController().getBackStackEntry(R.id.nav_graph) }
    )
}

Số lượt xem

public class MyFragment extends Fragment {

    SharedViewModel viewModel;

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        NavController navController = NavHostFragment.findNavController(this);
        NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.my_graph);

        // The ViewModel is scoped to the `nav_graph` Navigation graph
        viewModel = new ViewModelProvider(backStackEntry).get(SharedViewModel.class);
    }
}

Compose

@Composable
fun MyAppNavHost() {
    // ...
    composable("myScreen") { backStackEntry ->
        val parentEntry = remember(backStackEntry) {
            navController.getBackStackEntry("parentNavigationRoute")
        }
        // The ViewModel is scoped to the `parentNavigationRoute` Navigation graph
        val parentViewModel: SharedViewModel = viewModel(parentEntry)
        // ...
    }
}

Nếu đang dùng Hilt cùng với Điều hướng Jetpack, bạn có thể sử dụng API hiltNavGraphViewModels(graphId) như sau.

Số lượt xem

class MyFragment : Fragment() {

    // ViewModel API available in hilt.hilt-navigation-fragment
    // The ViewModel is scoped to the `nav_graph` Navigation graph
    // and is provided using the Hilt-generated ViewModel factory
    val viewModel: SharedViewModel by hiltNavGraphViewModels(R.id.nav_graph)
}

Số lượt xem

public class MyFragment extends Fragment {

    SharedViewModel viewModel;

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        NavController navController = NavHostFragment.findNavController(this);
        NavBackStackEntry backStackEntry = navController.getBackStackEntry(R.id.my_graph);

        // The ViewModel is scoped to the `nav_graph` Navigation graph
        // and is provided using the Hilt-generated ViewModel factory
        viewModel = new ViewModelProvider(
            backStackEntry,
            HiltViewModelFactory.create(getContext(), backStackEntry)
        ).get(SharedViewModel.class);
    }
}

Compose

@Composable
fun MyAppNavHost() {
    // ...
    composable("myScreen") { backStackEntry ->
        val parentEntry = remember(backStackEntry) {
            navController.getBackStackEntry("parentNavigationRoute")
        }

        // ViewModel API available in hilt.hilt-navigation-compose
        // The ViewModel is scoped to the `parentNavigationRoute` Navigation graph
        // and is provided using the Hilt-generated ViewModel factory
        val parentViewModel: SharedViewModel = hiltViewModel(parentEntry)
        // ...
    }
}

Chia sẻ dữ liệu giữa các mảnh

Thông thường, hai hoặc nhiều mảnh trong một hoạt động cần giao tiếp với nhau. Hãy tưởng tượng một trường hợp phổ biến là mảnh chế độ xem phân tách (list-detail), khi bạn có một mảnh mà trong đó người dùng chọn một mục trong danh sách và một mảnh khác hiển thị nội dung của mục đã chọn. Trường hợp này luôn quan trọng vì cả hai mảnh đều cần xác định một số nội dung mô tả giao diện và hoạt động chủ phải liên kết cả hai với nhau. Ngoài ra, cả hai mảnh đều phải xử lý trường hợp mảnh khác chưa được tạo hoặc xuất hiện.

Bạn có thể giải quyết vấn đề thường gặp này bằng cách sử dụng các đối tượng ViewModel. Những mảnh này có thể chia sẻ một ViewModel bằng cách sử dụng phạm vi hoạt động của chúng để xử lý quá trình giao tiếp, như minh hoạ trong mã mẫu sau:

Số lượt xem

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
        })
    }
}

Số lượt xem

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.
        });
    }
}

Xin lưu ý rằng cả hai mảnh sẽ truy xuất hoạt động có chứa chúng. Bằng cách đó, khi mỗi mảnh nhận được ViewModelProvider, chúng sẽ nhận được cùng một thực thể SharedViewModel thuộc phạm vi của hoạt động này.

Phương pháp này mang lại những lợi ích sau:

  • Hoạt động không cần phải làm gì hoặc không cần biết gì về quá trình giao tiếp này.
  • Các mảnh không cần phải biết về nhau ngoài hợp đồng SharedViewModel. Nếu một trong các mảnh biến mất, thì mảnh còn lại sẽ tiếp tục hoạt động như bình thường.
  • Mỗi mảnh có vòng đời riêng và không bị vòng đời của mảnh kia ảnh hưởng. Nếu một mảnh thay thế mảnh còn lại, thì giao diện người dùng sẽ tiếp tục hoạt động mà không gặp bất kỳ sự cố nào.

Thay thế Trình tải bằng ViewModel

Các lớp trình tải như CursorLoader thường được dùng để duy trì việc đồng bộ hoá dữ liệu trong giao diện người dùng của một ứng dụng với cơ sở dữ liệu. Bạn có thể dùng ViewModel cùng với một số lớp khác để thay thế trình tải. Việc sử dụng ViewModel sẽ tách bộ điều khiển giao diện người dùng khỏi hoạt động tải dữ liệu, đồng nghĩa với việc bạn sẽ có ít tệp tham chiếu mạnh giữa các lớp hơn.

Một phương pháp phổ biến để sử dụng trình tải là ứng dụng có thể sử dụng một CursorLoader để quan sát nội dung của cơ sở dữ liệu. Khi một giá trị trong cơ sở dữ liệu thay đổi, thì trình tải sẽ tự động kích hoạt quá trình tải lại dữ liệu và cập nhật giao diện người dùng:

Hình 2. Tải dữ liệu bằng trình tải

ViewModel hoạt động với RoomLiveData để thay thế trình tải. ViewModel đảm bảo rằng dữ liệu được duy trì sau khi thay đổi cấu hình thiết bị. Room thông báo về LiveData của bạn khi cơ sở dữ liệu thay đổi và LiveData sau đó sẽ cập nhật giao diện người dùng với dữ liệu đã sửa đổi.

Hình 3. Tải dữ liệu bằng ViewModel

Sử dụng coroutine với ViewModel

ViewModel có hỗ trợ coroutine Kotlin. Để biết thêm thông tin, hãy xem Sử dụng coroutine Kotlin với Bộ thành phần cấu trúc Android.

Thông tin khác

Khi dữ liệu của bạn ngày càng phức tạp, bạn có thể chọn một lớp riêng chỉ để tải dữ liệu. Mục đích của ViewModel là đóng gói dữ liệu cho một bộ điều khiển giao diện người dùng nhằm duy trì dữ liệu đó khi có sự thay đổi về cấu hình. Để biết thông tin về cách tải, duy trì và quản lý dữ liệu khi có nhiều thay đổi về cấu hình, hãy xem bài viết Lưu trạng thái giao diện người dùng.

Hướng dẫn về Cấu trúc ứng dụng Android đề xuất việc tạo một lớp lưu trữ để xử lý các chức năng này.

Tài nguyên khác

Để biết thêm thông tin về lớp ViewModel, hãy tham khảo các tài nguyên sau.

Mẫu

Lớp học lập trình

Blog

Video