Android Architecture Components - Live Data

Tiếp theo các phần trước, lần này chúng ta sẽ tìm hiểu về lớp Live Data của Android Architecture Components.

LiveData là một lớp dữ liệu dạng observable. Không giống như observable thường lệ, LiveData nhận biết vòng đời, có nghĩa là nó tôn trọng vòng đời của các thành phần ứng dụng khác, chẳng hạn như các Activity, Fragment hay Service. Nhận thức này đảm bảo LiveData chỉ cập nhật các thành phần ứng dụng đang trong trạng thái hoạt động.

Lưu ý: Để import các thành phần LiveData vào dự án Android của bạn, hãy xem Thêm các thành phần vào dự án của bạn .

LiveData xem xét một lớp quan sát, được đại diện bởi lớp Observer , đang ở trong trạng thái hoạt động nếu vòng đời của nó ở trạng thái STARTED hoặc RESUMED . LiveData chỉ thông báo cho các observer về các cập nhật. Những observer không hoạt động dù đã đăng ký để theo dõi các đối tượng LiveData sẽ không được thông báo về những thay đổi.

Bạn có thể đăng ký một observer được ghép nối với một đối tượng thực thi giao diện LifecycleOwner . Mối quan hệ này cho phép observer được xóa khi trạng thái của đối tượng Lifecycle tương ứng thay đổi thành DESTROYED . Điều này đặc biệt hữu ích cho các Activity và các Fragment vì chúng có thể quan sát các đối tượng LiveData một cách an toàn và không phải lo lắng về sự rò rỉ - Activity và Fragment ngay lập tức bị hủy đăng ký khi vòng đời của chúng bị hủy.

Lợi thế của việc sử dụng LiveData

Sử dụng LiveData cung cấp những lợi ích sau:

Đảm bảo UI phù hợp với trạng thái dữ liệu của bạn LiveData tuân theo pattern Observer. LiveData thông báo cho Observer đối tượng khi thay đổi trạng thái của vòng đời. Bạn có thể hợp nhất mã của mình để cập nhật UI trong các đối tượng Observer này. Thay vì cập nhật giao diện người dùng mỗi khi dữ liệu ứng dụng thay đổi, observer của bạn có thể cập nhật UI mỗi khi có sự thay đổi.

Không có rò rỉ bộ nhớ Các Observer bị ràng buộc với các object với Lifecycle và tự dọn dẹp sau khi chu trình sống của chúng bị hủy.

Không có sự cố do các hoạt động bị ngừng Nếu vòng đời của observer không hoạt động, chẳng hạn như trong trường hợp Activity ở ngăn xếp phía sau, thì nó không nhận được bất kỳ sự kiện LiveData nào.

Không phải xử lý vòng đời bằng tay Các thành phần UI chỉ quan sát dữ liệu có liên quan và không dừng lại hoặc tiếp tục quan sát. LiveData tự động quản lý tất cả những điều này vì nó nhận thức được sự thay đổi trạng thái chu kỳ liên quan trong khi quan sát.

Luôn cập nhật dữ liệu Nếu vòng đời của observer không hoạt động, nó sẽ nhận được dữ liệu mới nhất khi hoạt động trở lại. Ví dụ, một Activity ở phía sau nhận được dữ liệu mới nhất ngay sau khi nó quay trở lại foreground.

Thay đổi cấu hình đúng Nếu một Activity hoặc Fragment được tạo lại do thay đổi cấu hình, như xoay thiết bị, nó sẽ nhận được dữ liệu mới nhất hiện có.

Chia sẻ tài nguyên Bạn có thể mở rộng đối tượng LiveData bằng cách sử dụng mẫu singleton để gói các dịch vụ hệ thống để chúng có thể được chia sẻ trong ứng dụng của bạn. Đối tượng LiveData kết nối với dịch vụ hệ thống một lần và sau đó bất kỳ người quan sát nào cần nguồn này chỉ có thể xem đối tượng LiveData . Để biết thêm thông tin, hãy xem phần Mở rộng LiveData .

Làm việc với đối tượng LiveData

Làm theo các bước sau để làm việc với các đối tượng LiveData :

  1. Tạo một thể hiện của LiveData để giữ một loại dữ liệu nhất định. Điều này thường được thực hiện trong lớp ViewModel của bạn.
  2. Tạo một đối tượng Observer xác định phương thức onChanged() , điều khiển điều gì sẽ xảy ra khi dữ liệu LiveData thay đổi. Bạn thường tạo một đối tượng Observer trong bộ điều khiển giao diện người dùng, chẳng hạn như một Activity hoặc một Fragment.
  3. Gắn đối tượng Observer với đối tượng LiveData sử dụng phương thức LiveData observe() . Phương thức observe() lấy đối tượng LifecycleOwner . Điều này đăng ký đối tượng Observer đối tượng LiveData để nó được thông báo về các thay đổi. Bạn thường đính kèm đối tượng Observer trong bộ điều khiển giao diện người dùng, chẳng hạn như Activity hoặc một Fragment.

Lưu ý: Bạn vẫn có thể đăng ký một người quan sát mà không có một đối tượng LifecycleOwner liên quan sử dụng phương thức observeForever(Observer) . Trong trường hợp này, người quan sát được coi là luôn hoạt động và do đó luôn được thông báo về các sửa đổi. Bạn có thể loại bỏ các observer này bằng cách gọi phương thức removeObserver(Observer) .

Khi bạn cập nhật giá trị được lưu trữ trong đối tượng LiveData , nó sẽ kích hoạt tất cả các quan sát viên đã đăng ký miễn là LifecycleOwner thuộc trạng thái đang hoạt động.

LiveData cho phép các nhà quan sát điều khiển UI đăng ký nhận update. Khi dữ liệu được giữ bởi đối tượng LiveData thay đổi, giao diện người dùng sẽ tự động cập nhật phản hồi.

Tạo đối tượng LiveData

LiveData là một wrapper có thể được sử dụng với bất kỳ dữ liệu, bao gồm các đối tượng thực hiện Collections , chẳng hạn như List . Đối tượng LiveData thường được lưu trữ trong một đối tượng ViewModel và được truy cập thông qua một phương thức getter, như thể hiện trong ví dụ sau:

 public class NameViewModel extends ViewModel {

// Create a LiveData with a String
private MutableLiveData<String> mCurrentName;

    public MutableLiveData<String> getCurrentName() {
        if (mCurrentName == null) {
            mCurrentName = new MutableLiveData<String>();
        }
        return mCurrentName;
    }

// Rest of the ViewModel...
}

Ban đầu, dữ liệu trong đối tượng LiveData không được đặt.

Lưu ý: Đảm bảo lưu trữ các đối tượng LiveData cập nhật giao diện người dùng trong các đối tượng ViewModel , không phải trong Activity hoặc Fragment, vì những lý do sau:

  • Để tránh các Activity và Fragment. Bây giờ các bộ điều khiển UI có trách nhiệm hiển thị dữ liệu nhưng không giữ trạng thái dữ liệu.
  • Để tách riêng các thể hiện LiveData khỏi các Activity hoặc các Fragment cụ thể và cho phép các đối tượng LiveData tồn tại trong trường hợp thay đổi cấu hình. Bạn có thể đọc thêm về các lợi ích và cách sử dụng của lớp ViewModel trong hướng dẫn ViewModel.

Quan sát đối tượng LiveData

Trong hầu hết các trường hợp, phương thức onCreate() của thành phần ứng dụng là nơi thích hợp để bắt đầu quan sát đối tượng LiveData vì những lý do sau:

  • Để đảm bảo hệ thống không thực hiện các cuộc gọi dự phòng từ một Activity hoặc Fragment trong onResume() .
  • Để đảm bảo rằng Activity hoặc Fragment có dữ liệu mà nó có thể hiển thị ngay khi nó hoạt động. Ngay khi một thành phần ứng dụng ở trạng thái STARTED , nó sẽ nhận được giá trị gần đây nhất từ các đối tượng LiveData mà nó đang quan sát. Điều này chỉ xảy ra nếu đối tượng LiveData được quan sát đã được thiết lập. Nói chung, LiveData cung cấp cập nhật chỉ khi dữ liệu thay đổi, và chỉ cho các nhà quan sát tích cực. Một ngoại lệ đối với hành vi này là các nhà quan sát cũng nhận được một cập nhật khi họ thay đổi từ một trạng thái không hoạt động sang trạng thái hoạt động. Hơn nữa, nếu người quan sát thay đổi từ không hoạt động sang hoạt động lần thứ hai, nó chỉ nhận được cập nhật nếu giá trị đã thay đổi kể từ lần cuối cùng nó trở nên hoạt động.

Đoạn mã dưới đây minh họa cách bắt đầu quan sát một đối tượng LiveData :

 public class NameActivity extends AppCompatActivity {

    private NameViewModel mModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Other code to setup the activity...

        // Get the ViewModel.
        mModel = ViewModelProviders.of(this).get(NameViewModel.class);

        // Create the observer which updates the UI.
        final Observer<String> nameObserver = new Observer<String>() {
            @Override
            public void onChanged(@Nullable final String newName) {
                // Update the UI, in this case, a TextView.
                mNameTextView.setText(newName);
            }
        };

        // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
        mModel.getCurrentName().observe(this, nameObserver);
    }
}

Sau khi observe() được gọi với nameObserver được truyền như là tham số, onChanged() được gọi ngay lập tức cung cấp giá trị gần đây nhất được lưu trữ trong mCurrentName . Nếu đối tượng LiveData chưa đặt giá trị trong mCurrentName , onChanged() không được gọi.

Cập nhật đối tượng LiveData

LiveData không có phương thức public để cập nhật dữ liệu được lưu trữ. Lớp MutableLiveData sẽ hiển thị các setValue(T) và postValue(T) công khai và bạn phải sử dụng chúng nếu bạn cần phải chỉnh sửa giá trị được lưu trữ trong một đối tượng LiveData . Thông thường MutableLiveData được sử dụng trong ViewModel và sau đó ViewModel chỉ hiển thị các đối tượng LiveData không thay đổi cho các nhà quan sát.

Sau khi thiết lập mối quan hệ quan sát viên, bạn có thể cập nhật giá trị của đối tượng LiveData , như được minh họa trong ví dụ sau, nó sẽ kích hoạt tất cả các quan sát viên khi người dùng nhấn một nút:

mButton.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        String anotherName = "John Doe";
        mModel.getCurrentName().setValue(anotherName);
    }
});

Gọi setValue(T) trong ví dụ trên khiến các nhà quan sát gọi phương thức onChanged() của họ với giá trị John Doe . Ví dụ cho thấy một nút bấm, nhưng setValue() hoặc postValue() có thể được gọi để cập nhật mName vì nhiều lý do khác nhau, bao gồm đáp ứng yêu cầu mạng hoặc hoàn thành tải cơ sở dữ liệu; trong mọi trường hợp, cuộc gọi đến setValue() hoặc postValue() kích hoạt các quan sát viên và cập nhật giao diện người dùng.

Lưu ý: Bạn phải gọi setValue(T) để cập nhật đối tượng LiveData từ Main Thread. Nếu mã được thực hiện trong một Worker Thread, bạn có thể sử dụng phương thức postValue(T) thay vì cập nhật đối tượng LiveData .

Sử dụng LiveData với Room

Thư viện Persistence của Room hỗ trợ các truy vấn có thể quan sát, trả về các đối tượng LiveData . Các truy vấn có thể quan sát được được viết như là một phần của một đối tượng truy cập cơ sở dữ liệu (DAO).

Room tạo ra tất cả các mã cần thiết để cập nhật đối tượng LiveData khi cơ sở dữ liệu được cập nhật. Mã đã tạo chạy truy vấn không đồng bộ trên một thread nền khi cần thiết. Mẫu này rất hữu ích để giữ dữ liệu hiển thị trong một giao diện người dùng đồng bộ với dữ liệu được lưu trữ trong cơ sở dữ liệu. Bạn có thể đọc thêm về Room và DAO trong hướng dẫn thư viện của Room .

Mở rộng LiveData

LiveData xem một người quan sát đang ở trong trạng thái hoạt động nếu vòng đời của người quan sát ở trong trạng thái STARTED hoặc RESUMED Đoạn mã dưới đây minh họa cách mở rộng lớp LiveData :

 public class StockLiveData extends LiveData<BigDecimal> {
    private StockManager mStockManager;

    private SimplePriceListener mListener = new SimplePriceListener() {
        @Override
        public void onPriceChanged(BigDecimal price) {
            setValue(price);
        }
    };

    public StockLiveData(String symbol) {
        mStockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive() {
        mStockManager.requestPriceUpdates(mListener);
    }

    @Override
    protected void onInactive() {
        mStockManager.removeUpdates(mListener);
    }
}

Việc thực hiện price listener trong ví dụ này bao gồm các phương thức quan trọng sau:

  • Phương thức onActive() được gọi khi đối tượng LiveData có người quan sát hoạt động. Điều này có nghĩa là bạn cần bắt đầu theo dõi các bản cập nhật price từ phương thức này.
  • Phương thức onInactive() được gọi khi đối tượng LiveData không có bất kỳ quan sát viên tích cực nào. Vì không có nhà quan sát nào lắng nghe, không có lý do gì để kết nối với dịch vụ StockManager .
  • Phương setValue(T) cập nhật giá trị của thể hiện LiveData và thông báo cho bất kỳ nhà quan sát nào về sự thay đổi.

Bạn có thể sử dụng lớp StockLiveData như sau:

public class MyFragment extends Fragment {
   @Override
   public void onActivityCreated(Bundle savedInstanceState) {
       super.onActivityCreated(savedInstanceState);
       LiveData<BigDecimal> myPriceListener = ...;
       myPriceListener.observe(this, price -> {
           // Update the UI.
       });
   }
}

Phương thức observe() truyền vào Fragment, 1 thực thi của LifecycleOwner , làm đối số đầu tiên. Làm như vậy cho thấy người quan sát này bị ràng buộc với đối tượng Lifecycle liên quan đến chủ sở hữu, có nghĩa là:

  • Nếu đối tượng Lifecycle không ở trạng thái hoạt động, thì người quan sát không được gọi ngay cả khi giá trị thay đổi.
  • Sau khi đối tượng Lifecycle bị phá hủy, người quan sát sẽ tự động được gỡ bỏ. Thực tế là các đối tượng LiveData sẽ ý thức về vòng đời có nghĩa là bạn có thể chia sẻ chúng giữa nhiều Activity, các Fragment và Service. Để giữ ví dụ đơn giản, bạn có thể thực hiện lớp LiveData như một singleton như sau:
public class StockLiveData extends LiveData<BigDecimal> {
   private static StockLiveData sInstance;
   private StockManager mStockManager;

   private SimplePriceListener mListener = new SimplePriceListener() {
       @Override
       public void onPriceChanged(BigDecimal price) {
           setValue(price);
       }
   };

   @MainThread
   public static StockLiveData get(String symbol) {
       if (sInstance == null) {
           sInstance = new StockLiveData(symbol);
       }
       return sInstance;
   }

   private StockLiveData(String symbol) {
       mStockManager = new StockManager(symbol);
   }

   @Override
   protected void onActive() {
       mStockManager.requestPriceUpdates(mListener);
   }

   @Override
   protected void onInactive() {
       mStockManager.removeUpdates(mListener);
   }
}

Và bạn có thể sử dụng nó trong Fragment như sau:

public class MyFragment extends Fragment {
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        StockLiveData.get(getActivity()).observe(this, price -> {
            // Update the UI.
        });
    }
}

Nhiều Fragment và Activity có thể quan sát lớp MyPriceListener . LiveData chỉ kết nối với dịch vụ hệ thống nếu một hoặc nhiều trong số đó có thể nhìn thấy và hoạt động.

Chuyển đổi LiveData

Bạn có thể muốn thay đổi giá trị được lưu trữ trong đối tượng LiveData trước khi gửi cho các nhà quan sát hoặc bạn cần phải trả lại một thể hiện LiveData khác dựa trên giá trị của một đối tượng khác. Gói Lifecycle cung cấp lớp Transformations bao gồm các phương thức trợ giúp hỗ trợ các kịch bản này.

  • Transformations.map() Áp dụng một hàm trên giá trị được lưu trữ trong đối tượng LiveData và truyền tải kết quả xuống.
LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
    user.name + " " + user.lastName
});

  • Transformations.switchMap() Tương tự như map() , áp dụng một hàm cho giá trị được lưu trữ trong đối tượng LiveData và không bao gồm và gửi xuống kết quả. Hàm được truyền vào switchMap() phải trả về một đối tượng LiveData , như được minh hoạ bởi ví dụ sau:
private LiveData<User> getUser(String id) {
  ...;
}

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

Bạn có thể sử dụng phương pháp chuyển đổi để mang thông tin qua vòng đời của người quan sát. Các phép biến đổi không được tính trừ khi người quan sát đang xem đối tượng LiveData trả lại. Bởi vì các phép biến đổi được tính toán lazy, hành vi liên quan đến vòng đời được ngầm chuyển xuống mà không yêu cầu thêm các cuộc gọi hoặc phụ thuộc rõ ràng.

Nếu bạn nghĩ rằng bạn cần một đối tượng Lifecycle bên trong một đối tượng ViewModel , sự chuyển đổi có lẽ là một giải pháp tốt hơn. Ví dụ: giả sử bạn có một thành phần UI chấp nhận một địa chỉ và trả về mã bưu điện cho địa chỉ đó. Bạn có thể thực hiện ViewModel cho thành phần này như được minh hoạ bởi mã mẫu sau:

class MyViewModel extends ViewModel {
    private final PostalCodeRepository repository;
    public MyViewModel(PostalCodeRepository repository) {
       this.repository = repository;
    }

    private LiveData<String> getPostalCode(String address) {
       // DON'T DO THIS
       return repository.getPostCode(address);
    }
}

Thành phần UI sau đó cần phải hủy đăng ký từ đối tượng LiveData trước đó và đăng ký với cá thể mới mỗi khi nó gọi getPostalCode() . Ngoài ra, nếu thành phần UI được tạo lại, nó sẽ kích hoạt một cuộc gọi khác vào phương thức repository.getPostCode() thay vì sử dụng kết quả cuộc gọi trước đó.

Thay vào đó, bạn có thể thực hiện tra cứu mã bưu chính như là một sự chuyển đổi của địa chỉ đầu vào, như thể hiện trong ví dụ sau:

class MyViewModel extends ViewModel {
    private final PostalCodeRepository repository;
    private final MutableLiveData<String> addressInput = new MutableLiveData();
    public final LiveData<String> postalCode =
            Transformations.switchMap(addressInput, (address) -> {
                return repository.getPostCode(address);
             });

  public MyViewModel(PostalCodeRepository repository) {
      this.repository = repository
  }

  private void setInput(String address) {
      addressInput.setValue(address);
  }
}

Trong trường hợp này, trường postalCode là public và final , bởi vì trường không bao giờ thay đổi. Trường postalCode được định nghĩa là sự chuyển đổi của addressInput , có nghĩa là phương thức repository.getPostCode() được gọi khi thay đổi addressInput . Điều này đúng nếu có người quan sát tích cực, nếu không có người quan sát tích cực vào repository.getPostCode() thời repository.getPostCode() được gọi, không tính toán được thực hiện cho đến khi người quan sát được thêm vào.

Cơ chế này cho phép các cấp thấp hơn của ứng dụng tạo các đối tượng LiveData được tính toán theo yêu cầu. Một đối tượng ViewModel có thể dễ dàng có được tham chiếu đến các đối tượng LiveData và sau đó xác định quy tắc chuyển đổi của họ.

Tạo các phép biến đổi mới

Có hàng chục chuyển đổi cụ thể khác nhau có thể hữu ích trong ứng dụng của bạn, nhưng chúng không được cung cấp theo mặc định. Để thực hiện chuyển đổi của riêng bạn, bạn có thể sử dụng lớp MediatorLiveData , nó lắng nghe các đối tượng LiveData khác và xử lý các sự kiện do chúng phát ra. MediatorLiveData truyền chính xác trạng thái của nó tới đối tượng LiveData nguồn. Để tìm hiểu thêm về mô hình này, xem tài liệu tham khảo của lớp Transformations .

Hợp nhất nhiều nguồn LiveData

MediatorLiveData là một phân lớp của LiveData cho phép bạn hợp nhất nhiều nguồn LiveData. Người quan sát đối tượng MediatorLiveData sau đó sẽ được kích hoạt bất cứ khi nào bất kỳ đối tượng nguồn LiveData ban đầu nào thay đổi.

Ví dụ: nếu bạn có đối tượng LiveData trong giao diện người dùng có thể được cập nhật từ cơ sở dữ liệu cục bộ hoặc mạng, sau đó bạn có thể thêm các nguồn sau vào đối tượng MediatorLiveData :

  • Đối tượng LiveData kết hợp với dữ liệu được lưu trữ trong cơ sở dữ liệu.
  • Đối tượng LiveData kết hợp với dữ liệu được truy cập từ mạng. Activity của bạn chỉ cần quan sát đối tượng MediatorLiveData để nhận các cập nhật từ cả hai nguồn. Để biết ví dụ chi tiết, hãy xem phần Phụ lục: hiển thị trạng thái mạng của Hướng dẫn về Kiến trúc ứng dụng .

Nguồn https://developer.android.com/topic/libraries/architecture/livedata.html