Android - LiveData

Xin chào các bạn, chào mừng các bạn tới bài viết tiếp theo của mình về chủ đề Android Architecture Components.

Ở bài viết lần trước https://viblo.asia/p/android-architecture-components-viewmodel-xu-ly-configuration-changes-chua-bao-gio-don-gian-den-the-ByEZk3A4ZQ0 chúng ta đã bàn luận về ViewModel một Components cực kì mạnh mẽ của Google cung cấp và lưu trữ data cho UI như activities hoặc fragments. Nếu bạn chưa đọc bài viết đó mình rất khuyến khích các bạn nên xem nhé.

Hôm nay chúng ta sẽ tiếp tục với chủ đề LiveData - lifecycle-aware observable data holder

I Giới thiệu

Chúng ta cùng xem lại ví dụ ở bài viết lần trước của mình về ViewModel nhé Chúng ta có một ViewModel class với 2 biến là Điểm của đội A mScoreTeamA và Điểm của đội B mScoreTeamB

public class CountNumberViewModel extends AndroidViewModel {
    private MutableLiveData<Integer> mScoreTeamA = new MutableLiveData<>();
    private MutableLiveData<Integer> mScoreTeamB = new MutableLiveData<>();

    public CountNumberViewModel(@NonNull Application application) {
        super(application);
        mScoreTeamA.setValue(0);
        mScoreTeamB.setValue(0);
    }

    public MutableLiveData<Integer> getScoreTeamA() {
        return mScoreTeamA;
    }

    public MutableLiveData<Integer> getScoreTeamB() {
        return mScoreTeamB;
    }

    public void increaseScroeTeamA(int score) {
        mScoreTeamA.setValue(mScoreTeamA.getValue() + score);
    }

    public void increaseScroeTeamB(int score) {
        mScoreTeamB.setValue(mScoreTeamB.getValue() + score);
    }
}

LiveData is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services. This awareness ensures LiveData only updates app component observers that are in an active lifecycle state.

Đoạn này mình cũng đang hơi bí từ để giải thích nên hơi bị lủng củng, các bạn đọc thông cảm nha.

LiveData là một lớp nắm giữ dữ liệu và cho phép dữ liệu đó có thể quan sát được. (đoạn này hơi bị khó hiểu một chút nhưng nếu bạn nào đã tìm hiều về Observer Pattern hoặc RxJava, RxAndroid thì có lẽ sẽ dễ hiểu hơn). Không giống các kiểu dữ liệu có thể quan sát được khác, LiveData có thể nhận biết được vòng đời của các bên quan sát nó như activies, fragments, services để đảm bảo rằng LiveData chỉ gọi update các bên quan sát khi mà các bên đó còn hoạt động

II Cách hoạt động của LiveData

Khi chúng ta có một LiveData object (ở ví dụ trên là mScoreTeamB) chúng ta có thể thêm Observer (đối tượng quan sát sự thay đổi của LiveData) là LifecycleOwner (cói thể là Activity hoặc Fragment)

mViewModel.getScoreTeamA().observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(@Nullable Integer integer) {
                mTextScoreTeamA.setText(String.valueOf(integer));
            }
        });

Khi đó

  • Activity hoặc fragment sẽ được thông báo thông qua menthod onChanged mỗi khi data có sự thay đổi vì vậy UI luôn luôn được tự động update (ở đây TextView mTextScoreTeamA sẽ luôn luôn hiển thị điểm mới nhất cuả đội A)
  • Tất cả đều được giới hạn mởi lifecycle, vì vậy tất cả những thay đổi chỉ có thể được update nếu activity hoặc fragment quan sát đang ở trạng thái STARTED hoặc RESUMED vì vậy sẽ không còn memory leaks hay NullPointerExceptions nữa.

*Lưu ý: * Các bạn có thể lắng nghe LiveData mà không cần LifecycleOwner thông qua menthod observeForever, khi đó LifecyclerOwner được coi là luôn active lưu ý các bạn nhớ gọi removeObserver khi không muốn lắng nghe nữa nhé.

III Làm việc với LiveData

1. Khởi tạo LiveData

LiveData có thể sử dụng với tất cả các kiểu dữ liệu kể cả các kiểu dữ liệu implement Collections như List.

ví dụ: LiveData<Integer>, LiveData<String>, LiveData<Object>... Thông thường một đối tượng LiveData tường được khởi tạo và lưu trữ với một ViewModel và được truy xuất thông qua getter giống như mình đã đặt ra ở trên

public class CountNumberViewModel extends AndroidViewModel {
    private MutableLiveData<Integer> mScoreTeamA = new MutableLiveData<>();
    private MutableLiveData<Integer> mScoreTeamB = new MutableLiveData<>();

    public CountNumberViewModel(@NonNull Application application) {
        super(application);
        mScoreTeamA.setValue(0);
        mScoreTeamB.setValue(0);
    }

    public MutableLiveData<Integer> getScoreTeamA() {
        return mScoreTeamA;
    }

    public MutableLiveData<Integer> getScoreTeamB() {
        return mScoreTeamB;
    }
}

Lưu ý : Đảm bảo rằng lưu trữ LiveData object ở trong ViewModel object, tránh lưu trữ ở activities và fragments vì

  • Tránh phình to activities, fragments. Activity và Fragment chỉ đảm bảo nhiệm vụ hiển thị data chứ không lưu trữ data
  • Lưu trữ LiveData objects ở ViewModel đảm bảo LiveData objects tồn tại ngay cả khi có sự thay đổi về config.

2. Lắng nghe LiveData objects

Các bạn có thể bắt đầu lắng nghe LiveData ở nhièu nơi như onCreate(), onStart(), onResume() .. tuy nhiên Google khuyến khích bắt đầu lắng nghe LiveData ở onCreate()

  • Tránh tình trạng hệ thống gọi nhiều lần lắng nghe từ activity hoặc fragment thông qua hàm onResume()
  • Đảm bảo rằng data từ ViewModel được hiện thị lên giao diện càng sớm càng tốt, ngay sau khi activity hoặc fragment active.
 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_count);
        mViewModel = ViewModelProviders.of(this).get(CountNumberViewModel.class);
        registerLiveDataListenner();
        initViews();
        setTitle("Count with ViewModel");
    }
    
    public void registerLiveDataListenner() {
        mViewModel.getScoreTeamA().observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(@Nullable Integer integer) {
                mTextScoreTeamA.setText(String.valueOf(integer));
            }
        });
        mViewModel.getScoreTeamB().observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(@Nullable Integer integer) {
                mTextScoreTeamB.setText(String.valueOf(integer));
            }
        });
    }

Lưu ý: Hàm lắng nghe LiveData nên được viết ở UI Thread vì chỉ UI thread mới có thể update giao diện thôi các bạn nhé.

3 Update LiveData objects

LiveData không public menthod để có thể update data lưu trữ trong LiveData, tuy nhiên MutableLiveData thì có. MutableLiveData kế thừa từ LiveData và public thêm 2 menthod setValue(T) và postValue(T) Vì vậy muôn update LiveData objects các bạn nên sử dụng MutableLiveData nhé.

public class CountNumberViewModel extends AndroidViewModel {
 
    public void increaseScroeTeamA(int score) {
        mScoreTeamA.setValue(mScoreTeamA.getValue() + score);
    }

    public void increaseScroeTeamB(int score) {
        mScoreTeamB.setValue(mScoreTeamB.getValue() + score);
    }
}

Khi hàm increaseScoreTeamA() được gọi thì menthod onChanged() mà chúng ta đăng kí ở trên sẽ được gọi đến và lúc này data mới nhất về score sẽ được update lên màn hình.

Lưu ý: Bạn chỉ được gọi menthod setValue(T) để update LiveData object ở main thread, nếu menthod này được gọi ở worker thread bạn nên sử dụng menthod postValue

IV Mở rộng

Transform LiveData

Chúng ta mỗi thay đổi giá trị của data lưu trữ trong LiveData object trước khi gửi đi tới các observer, hoặc chúng ta muốn tạo ra một LiveData khác so với LiveData ban đầu chẳng hạn, lúc này là lúc chúng ta sử dụng Transformations Transformations cung cấp cho chúng ta 2 menthod vô cùng tiện lợi

1. Transformations.map()

Chúng ta sử dụng menthod này trên main thread để có thể chuyển đổi LiveData theo ý muốn của mình Giả sử chúng ta có 1 LiveData chứa một user, và chúng ta muốn hiển thị tên của user đó lên màn hình chúng ta có thể làm như sau

LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
    user.name + " " + user.lastName
});

2. Transformations.switchMap()

switchMap() dùng cũng tương tự như map() tuy nhiên switchMap() bắt buộc phải trả về một LiveData object. Ở trên của chúng ta là 1 String ( user.name + " " + user.lastName) ví dụ

private LiveData<User> getUser(String id) {
  ...;
}

LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );

V Tổng kết

Trên đây là bài hướng dẫn của mình về LiveData

  • Bài viết có tham khảo nguồn
  1. https://developer.android.com/topic/libraries/architecture/livedata.html
  2. https://medium.com/google-developers/viewmodels-and-livedata-patterns-antipatterns-21efaef74a54
  3. https://android.jlelse.eu/android-architecture-components-livedata-1ce4ab3c0466
  • Link project demo tại đây
  • Mình cũng làm một project về MVVM + Architecture Components + Dagger2 các bạn có thể theo dõi tại đây và feedback cho mình nhé

Cám ơn các bạn đã đón đọc và chúc các bạn học tốt!