LiveData 總覽 Android Jetpack 的一部分。

LiveData 是可觀測的資料擁有者類別。和一般觀測作業不同,LiveData 具備生命週期感知功能,代表會遵循其他應用程式元件 (例如活動、片段或服務) 的生命週期。此感知功能可確保 LiveData 只會更新處於活動生命週期狀態的應用程式元件觀測器。

如果生命週期成為 STARTEDRESUMED 狀態,LiveData 會認為 Observer 類別表示的觀測器處於活動狀態。LiveData 只會將更新通知運作中的觀測器。註冊用以觀測 LiveData 物件的觀測器處於閒置狀態時,系統不會通知任何變更。

您可以註冊與導入 LifecycleOwner 介面物件配對的觀測器。對應的 Lifecycle 物件狀態變更為 DESTROYED 時,此關係可移除觀測器。這個做法在活動和片段中特別實用,因為可以安全地觀測 LiveData 物件,而且不必擔心資料外洩 — 在生命週期刪除時,系統會立即取消訂閱活動和片段。

如要進一步瞭解如何使用 LiveData,請參閱「使用 LiveData 物件作業」。

使用 LiveData 的優點

使用 LiveData 提供下列優點:

確保使用者介面與資料狀態相符
LiveData 遵循觀測器模式。資料變更時,LiveData 會通知 Observer 物件。您可以藉由合併程式碼來更新這些 Observer 物件的使用者介面。這樣一來,您就無需在每次應用程式資料變更時更新使用者介面,因為觀測器會為您代勞。
無記憶體流失
觀測器會繫結至 Lifecycle 物件,並在相關聯的生命週期遭到刪除時自行清理。
因停止活動而沒有當機
如果觀測器的生命週期為閒置 (例如返回堆疊中的活動),就不會接收任何 LiveData 事件。
再也不必手動處理生命週期
使用者介面元件只會觀測相關資料,無法停止或繼續觀測作業。LiveData 會在觀測到相關生命週期狀態變更時,自動管理上述所有事務。
隨時保持最新資料
如果生命週期變為閒置狀態,會在生命週期再次變為活動狀態時收到最新的資料。舉例來說,背景執行的活動會在返回前景後立即收到最新的資料。
正確的設定變更
如果活動或片段因設定變更 (例如裝置旋轉) 而重新建立,則會立即接收最新的可用資料。
共用資源
您可以使用單例模式來擴充 LiveData 物件,以便包裝系統服務,使其能在應用程式中共用這些服務。LiveData 物件會連線至系統服務一次,而需要該資源的任何觀測器只能觀測 LiveData 物件。詳情請參閱「擴充 LiveData」。

使用 LiveData 物件作業

遵循下列步驟以使用 LiveData 物件作業:

  1. 建立 LiveData 的執行個體以存放特定類型的資料。這項作業通常會在 ViewModel 類別中完成,
  2. 建立可定義 onChanged() 方式的 Observer 物件,藉此控管 LiveData 物件的留存資料變更狀況。您通常會在使用者介面控制器中建立 Observer 物件,例如活動或片段。
  3. 使用 observe() 方式將 Observer 物件附加至 LiveData 物件。observe() 方式採用 LifecycleOwner 物件。此會將 Observer 物件訂閱至 LiveData 物件,以便於變更時收到通知。通常您必須在使用者介面控制器中附加 Observer 物件,例如活動或片段。

當您更新 LiveData 物件中儲存的值時,只要附加的 LifecycleOwner 處於活動狀態,就會觸發所有已註冊的觀測器。

LiveData 可讓使用者介面控管觀測器訂閱更新項目。當 LiveData 物件保存的資料有所變更時,使用者介面也會因應回應自動更新。

建立 LiveData 物件

LiveData 為包裝函式,可與任何資料搭配使用,包括導入 Collections 的物件 (例如 List)。LiveData 物件通常儲存在 ViewModel 物件中,並可透過 getter 方式存取,如下列範例所示:

Kotlin

class NameViewModel : ViewModel() {

    // Create a LiveData with a String
    val currentName: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }

    // Rest of the ViewModel...
}

Java

public class NameViewModel extends ViewModel {

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

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

// Rest of the ViewModel...
}

最初不會設定 LiveData 物件中的資料。

如要進一步瞭解 ViewModel 類別的好處和使用方式,請參閱 ViewModel 指南

觀測 LiveData 物件

在多數情況下,應用程式元件的 onCreate() 方式是開始觀測 LiveData 物件的正確位置,原因如下所示:

  • 確保系統不會透過活動或片段的 onResume() 方式發出多餘的呼叫。
  • 為了確保該活動或片段包含資料,一旦啟用後就會立即顯示。應用程式元件處於 STARTED 狀態後,便會從觀測到的 LiveData 物件接收最新的值。只有在要觀測的 LiveData 物件已設定時,才會發生這種情形。

一般來說,LiveData 只會在資料變更時提供更新,且只會更新至運作中的伺服器。但若觀測器從閒置狀態變更為活動狀態,也會收到更新。此外,如果觀測器第二次從閒置狀態變更為活動狀態,則從自上次啟用後曾變更的值,才會收到更新。

下列程式碼範例說明如何開始觀測 LiveData 物件:

Kotlin

class NameActivity : AppCompatActivity() {

    // Use the 'by viewModels()' Kotlin property delegate
    // from the activity-ktx artifact
    private val model: NameViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Other code to setup the activity...

        // Create the observer which updates the UI.
        val nameObserver = Observer<String> { newName ->
            // Update the UI, in this case, a TextView.
            nameTextView.text = newName
        }

        // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
        model.currentName.observe(this, nameObserver)
    }
}

Java

public class NameActivity extends AppCompatActivity {

    private NameViewModel model;

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

        // Other code to setup the activity...

        // Get the ViewModel.
        model = new ViewModelProvider(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.
                nameTextView.setText(newName);
            }
        };

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

在呼叫以 nameObserver 做為參數傳送的 observe() 之後,系統會立即叫用 onChanged(),並提供最近儲存在 mCurrentName 的值。如果 LiveData 物件並未在 mCurrentName 中設定值,系統不會呼叫 onChanged()

更新 LiveData 物件

LiveData 沒有公開更新儲存資料的方式。該 MutableLiveData 類別會公開 setValue(T) 以及 postValue(T) 方式,若您需要編輯儲存在 LiveData 物件的值,則必須使用這些方式。ViewModel 通常會使用 MutableLiveData,而 ViewModel 只會向觀測器公開不可變更的 LiveData 物件。

設定好觀測器關係後,您就可以更新 LiveData 物件的值,只要使用者輕觸按鈕,就會觸發所有觀測器,如下列範例所示:

Kotlin

button.setOnClickListener {
    val anotherName = "John Doe"
    model.currentName.setValue(anotherName)
}

Java

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

在範例中呼叫 setValue(T) 會導致觀測器呼叫其值為 John DoeonChanged() 方式。此範例會顯示按鈕按下,但出於各種原因 (包括回應網路要求或資料庫載入完成),可呼叫 setValue()postValue() 來更新 mName;在任何情況下,呼叫 setValue()postValue() 都會觸發觀測器並更新使用者介面。

透過會議室使用 LiveData

Room 持續性程式庫支援可觀測的查詢,這些查詢會回傳 LiveData 物件。可觀測的查詢會寫入資料庫存取物件 (DAO) 的一部分。

並在資料庫更新時,會議室會產生更新 LiveData 物件的所有必要程式碼。產生的程式碼會視需要在背景執行緒上非同步執行查詢。此模式可用於讓使用者介面中顯示的資料與資料庫中儲存的資料保持同步。如要進一步瞭解會議室和 DAO,請參閱「會議室持續性程式庫指南」。

將協同程式與 LiveData 搭配使用

LiveData 包括支援 Kotlin 協同程式。詳情請參閱「Android 架構元件與 Kotlin 協同程式搭配使用」。

應用程式架構中的 LiveData

LiveData 具備生命週期感知,遵循活動和片段等實體的生命週期。使用 LiveData,在生命週期擁有者和其他不同壽命的物件 (例如 ViewModel 物件) 之間進行通訊。ViewModel 的主要職責是載入及管理使用者介面相關資料,因此是保留 LiveData 物件的絕佳選擇。在 ViewModel 中建立 LiveData 物件,並使用這些物件向使用者介面層公開狀態。

活動和片段不應存放 LiveData 執行個體,因為其角色為顯示資料,而非處於保留狀態。此外,保留活動和片段時,請勿保留資料,可使其更輕鬆撰寫單元測試。

您可能打算在資料層類別中使用 LiveData 物件,但 LiveData 的設計不能用來處理非同步的資料串流。雖然您可以使用 LiveData 轉換和 MediatorLiveData 來完成這項工作,但這種方法有下列缺點:能夠合併資料串流的功能相當有限,並且所有 LiveData 物件 (包括透過轉換建立的物件) 都會出現在主執行緒上。下列程式碼是在 Repository 中保留 LiveData 來封鎖主要執行緒的範例:

Kotlin

class UserRepository {

    // DON'T DO THIS! LiveData objects should not live in the repository.
    fun getUsers(): LiveData<List<User>> {
        ...
    }

    fun getNewPremiumUsers(): LiveData<List<User>> {
        return getUsers().map { users ->
            // This is an expensive call being made on the main thread and may
            // cause noticeable jank in the UI!
            users
                .filter { user ->
                  user.isPremium
                }
          .filter { user ->
              val lastSyncedTime = dao.getLastSyncedTime()
              user.timeCreated > lastSyncedTime
                }
    }
}

Java

class UserRepository {

    // DON'T DO THIS! LiveData objects should not live in the repository.
    LiveData<List<User>> getUsers() {
        ...
    }

    LiveData<List<User>> getNewPremiumUsers() {
    return Transformations.map(getUsers(),
        // This is an expensive call being made on the main thread and may cause
        // noticeable jank in the UI!
        users -> users.stream()
            .filter(User::isPremium)
            .filter(user ->
                user.getTimeCreated() > dao.getLastSyncedTime())
            .collect(Collectors.toList()));
    }
}

如果需要在應用程式其他層使用資料串流,請考慮使用「Kotlin 流程」,然後轉換成 LiveDataViewModel 使用 asLiveData()。歡迎前往程式碼研究室,進一步瞭解如何將 Kotlin FlowLiveData 搭配使用。如果是以 Java 建構的程式碼集,請考慮將 Executors 與回呼或 RxJava 搭配使用。

擴充 LiveData

如果觀測器的生命週期為 STARTEDRESUMED 狀態,LiveData 則考慮使觀測器處於活動狀態。下列程式碼範例說明如何擴充 LiveData 類別:

Kotlin

class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
    private val stockManager = StockManager(symbol)

    private val listener = { price: BigDecimal ->
        value = price
    }

    override fun onActive() {
        stockManager.requestPriceUpdates(listener)
    }

    override fun onInactive() {
        stockManager.removeUpdates(listener)
    }
}

Java

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

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

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

    @Override
    protected void onActive() {
        stockManager.requestPriceUpdates(listener);
    }

    @Override
    protected void onInactive() {
        stockManager.removeUpdates(listener);
    }
}

本範例中的價格事件監聽器導入項目包含下列重要方式:

  • LiveData 物件包含運作中的觀測器時,系統會呼叫 onActive() 方式。這表示您需要開始透過這個方式觀測股價更新。
  • LiveData 物件沒有任何運作中的觀測器時,系統會呼叫 onInactive() 方式。由於沒有監聽的觀測器,因此目前沒有理由與 StockManager 服務保持連線。
  • setValue(T) 方式會更新 LiveData 執行個體的值,並通知任何運作中的觀測器變更。

您可以使用 StockLiveData 類別,如下所示:

Kotlin

public class MyFragment : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val myPriceListener: LiveData<BigDecimal> = ...
        myPriceListener.observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? ->
            // Update the UI.
        })
    }
}

Java

public class MyFragment extends Fragment {
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        LiveData<BigDecimal> myPriceListener = ...;
        myPriceListener.observe(getViewLifecycleOwner(), price -> {
            // Update the UI.
        });
    }
}

observe() 方式會傳遞與片段檢視畫面相關聯的 LifecycleOwner 做為第一個引數。也就是說,此觀測器會繫結至與擁有者相關聯的 Lifecycle 物件,這表示:

  • 如果 Lifecycle 物件未處於活動狀態,即使值變更,也不會呼叫觀測器。
  • 刪除 Lifecycle 物件後,系統會自動移除觀測器。

LiveData 物件為生命週期感知,也就是說,您可以在多個活動、片段和服務之間共用這些物件。如要簡化範例,您可以將 LiveData 類別作為單例模式來導入,如下所示:

Kotlin

class StockLiveData(symbol: String) : LiveData<BigDecimal>() {
    private val stockManager: StockManager = StockManager(symbol)

    private val listener = { price: BigDecimal ->
        value = price
    }

    override fun onActive() {
        stockManager.requestPriceUpdates(listener)
    }

    override fun onInactive() {
        stockManager.removeUpdates(listener)
    }

    companion object {
        private lateinit var sInstance: StockLiveData

        @MainThread
        fun get(symbol: String): StockLiveData {
            sInstance = if (::sInstance.isInitialized) sInstance else StockLiveData(symbol)
            return sInstance
        }
    }
}

Java

public class StockLiveData extends LiveData<BigDecimal> {
    private static StockLiveData sInstance;
    private StockManager stockManager;

    private SimplePriceListener listener = 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) {
        stockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive() {
        stockManager.requestPriceUpdates(listener);
    }

    @Override
    protected void onInactive() {
        stockManager.removeUpdates(listener);
    }
}

而且可以在片段中使用,如下所示:

Kotlin

class MyFragment : Fragment() {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        StockLiveData.get(symbol).observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? ->
            // Update the UI.
        })

    }

Java

public class MyFragment extends Fragment {
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        StockLiveData.get(symbol).observe(getViewLifecycleOwner(), price -> {
            // Update the UI.
        });
    }
}

出現多個片段和活動時,您就能觀測 MyPriceListener 執行個體。只有當一或多個資料顯示且啟用時,LiveData 才會連線至系統服務。

轉換 LiveData

建議您先變更 LiveData 物件中儲存的值,再將其發送至觀測器,或視需要回傳以另一個值為基礎的不同 LiveData 執行個體。Lifecycle 套件提供 Transformations 類別,其中包含支援這些情境的輔助方式。

Transformations.map()
LiveData 物件中儲存的值套用函式,並於下游套用結果。

Kotlin

val userLiveData: LiveData<User> = UserLiveData()
val userName: LiveData<String> = userLiveData.map {
    user -> "${user.name} ${user.lastName}"
}

Java

LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
    user.name + " " + user.lastName
});
Transformations.switchMap()
類似於 map(),對 LiveData 物件中儲存的值套用函式,解開並將結果傳送至下游。傳送至 switchMap() 的函式必須回傳 LiveData 物件,如下列範例所示:

Kotlin

private fun getUser(id: String): LiveData<User> {
  ...
}
val userId: LiveData<String> = ...
val user = userId.switchMap { id -> getUser(id) }

Java

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

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

您可以使用轉換方式,將資訊儲存在觀測器的生命週期中。除非觀測器查看回傳的 LiveData 物件,否則不會計算轉換。轉換會延遲計算,所以與生命週期相關的行為會經由隱含的方式傳下來,不需要其他的明確呼叫或依附元件。

如果您認為 ViewModel 物件中需要 Lifecycle 物件,轉換可能是更好的解決方案。舉例來說,假設您有一個接受地址並回傳該地址郵遞區號的使用者介面元件。您可以為此元件導入簡單的 ViewModel,如下列程式碼範例所示:

Kotlin

class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() {

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

Java

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

接著,使用者介面元件必須取消註冊前一個 LiveData 物件,並在每次呼叫 getPostalCode() 時註冊新的執行個體。此外,如果重新建立使用者介面元件,將會觸發另一個呼叫 repository.getPostCode() 方式,而不是使用先前呼叫的結果。

您可以改為導入郵遞區號查詢做為地址輸入的轉換,如下列範例所示:

Kotlin

class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() {
    private val addressInput = MutableLiveData<String>()
    val postalCode: LiveData<String> = addressInput.switchMap {
            address -> repository.getPostCode(address) }


    private fun setInput(address: String) {
        addressInput.value = address
    }
}

Java

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

在此情況下,postalCode 欄位會定義為 addressInput 的轉換。只要應用程式具有與 postalCode 欄位相關聯的運行中觀測器,每當 addressInput 變更時,系統就會重新計算及擷取該欄位值。

此機制可讓應用程式的較低層級建立 LiveData 物件,並視需求延遲計算。ViewModel 物件可輕鬆取得 LiveData 物件的參考文件,並在其上定義轉換規則。

建立新轉換

有許多不同的特定轉換可能對您的應用程式有幫助,但其在預設情況下不提供。如要導入自己的轉換,可以使用 MediatorLiveData 類別,該類別會監聽其他 LiveData 物件和處理自身發出的事件。MediatorLiveData 會將狀態正確套用至來源 LiveData 物件。如要進一步瞭解此模式,請參閱 Transformations 類別的參考說明文件。

合併多個 LiveData 來源

MediatorLiveDataLiveData 的子類別,可讓您合併多個 LiveData 來源。每當任何原始的 DataData 來源物件變更時,就會觸發 MediatorLiveData 物件的觀測器。

舉例來說,假設您的使用者介面中有可從本機資料庫或網路更新的 LiveData 物件,您可以將下列來源新增至 MediatorLiveData 物件:

  • 與儲存在資料庫中的資料相關聯的 LiveData 物件。
  • 與從網路存取的資料相關聯的 LiveData 物件。

您的活動只需要觀測 MediatorLiveData 物件,才能接收來自兩個來源的更新。如需詳細範例,請參閱「應用程式架構指南」的「附錄:公開網路狀態」一節。

其他資源

如要進一步瞭解 LiveData 類別,請參閱下列資源。

範例

程式碼研究室

網誌

影片