LiveData 總覽 Android Jetpack 的一部分。
LiveData
是可觀測的資料擁有者類別。和一般觀測作業不同,LiveData 具備生命週期感知功能,代表會遵循其他應用程式元件 (例如活動、片段或服務) 的生命週期。此感知功能可確保 LiveData 只會更新處於活動生命週期狀態的應用程式元件觀測器。
如果生命週期成為 STARTED
或 RESUMED
狀態,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
物件作業:
- 建立
LiveData
的執行個體以存放特定類型的資料。這項作業通常會在ViewModel
類別中完成, - 建立可定義
onChanged()
方式的Observer
物件,藉此控管LiveData
物件的留存資料變更狀況。您通常會在使用者介面控制器中建立Observer
物件,例如活動或片段。 使用
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 Doe
的 onChanged()
方式。此範例會顯示按鈕按下,但出於各種原因 (包括回應網路要求或資料庫載入完成),可呼叫 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 流程」,然後轉換成 LiveData
在 ViewModel
使用 asLiveData()
。歡迎前往程式碼研究室,進一步瞭解如何將 Kotlin Flow
和 LiveData
搭配使用。以 Java 建構的程式碼集,請考慮使用 Executors 來與回呼或 RxJava
搭配使用。
擴充 LiveData
如果觀測器的生命週期為 STARTED
或 RESUMED
狀態,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 來源
MediatorLiveData
是 LiveData
的子類別,可讓您合併多個 LiveData 來源。每當任何原始的 DataData 來源物件變更時,就會觸發 MediatorLiveData
物件的觀測器。
舉例來說,假設您的使用者介面中有可從本機資料庫或網路更新的 LiveData
物件,您可以將下列來源新增至 MediatorLiveData
物件:
- 與儲存在資料庫中的資料相關聯的
LiveData
物件。 - 與從網路存取的資料相關聯的
LiveData
物件。
您的活動只需要觀測 MediatorLiveData
物件,才能接收來自兩個來源的更新。如需詳細範例,請參閱「應用程式架構指南」的「附錄:公開網路狀態」一節。
其他資源
如要進一步瞭解 LiveData
類別,請參閱下列資源。
範例
- Sunflower:示範架構元件最佳做法的試用版應用程式
程式碼研究室
- 含有檢視畫面的 Android 會議室 (Java) (Kotlin)
- 透過 Kotlin 流程和 LiveData 學習進階協同程式
網誌
- ViewModels 和 LiveData:模式 + 反模式
- 在 ViewModel 之外的 LiveData - 使用 Transformations 和 MediatorLiveData 的反應模式
- 含有 Snackbar、導航和其他事件的 LiveData (SingleLiveEvent 案例)
影片
為您推薦
- 注意:系統會在 JavaScript 關閉時顯示連結文字
- 搭配生命週期感知元件使用 Kotlin 協同程式
- 使用生命週期感知元件處理生命週期
- 測試分頁實作