Jetpack學(xué)習(xí)9-ViewModel

[TOC]

LiveData

LiveData概述

LiveData是一個可觀察的數(shù)據(jù)持有者類。與常規(guī)observable不同,LiveData是生命周期感知的,這意味著它尊重其他應(yīng)用程序組件的生命周期,例如 activities, fragments, or services.。此感知確保LiveData僅更新處于活動生命周期狀態(tài)的應(yīng)用程序組件觀察者。

注意:要將LiveData組件導(dǎo)入您的Android項目,請參見向項目中添加組件。

LiveData認為,如果Observer類所表示的觀察者的生命周期處于STARTEDRESUMED狀態(tài),則該觀察者處于活動狀態(tài)。LiveData僅通知活動觀察者有關(guān)更新的信息。注冊觀察LiveData對象的非活動觀察者 不會收到有關(guān)更改的通知。

您可以注冊一個觀察者和實現(xiàn)該LifecycleOwner 接口的對象配對。這種關(guān)系允許在相應(yīng)Lifecycle對象的狀態(tài)更改為DESTROYED時刪除觀察者。這對于activities和fragments特別有用,因為它們可以安全地觀察LiveData對象而不用擔(dān)心泄漏--當(dāng)活動和片段的生命周期被破壞時,它們會立即被取消訂閱。

有關(guān)如何使用LiveData的詳細信息,請參閱使用LiveData對象。

使用LiveData的優(yōu)點

使用LiveData具有以下優(yōu)勢:

確保UI與數(shù)據(jù)狀態(tài)匹配

? LiveData遵循觀察者模式。當(dāng)生命周期的狀態(tài)改變時,LiveData會通知Observer對象。你可以整合代碼使用Observer對象來更新UI。與每次應(yīng)用數(shù)據(jù)更改時更新UI不同,觀察者可以生命周期的狀態(tài)改變時更新UI。

沒有內(nèi)存泄漏

? 觀察者綁定 Lifecycle對象并在其相關(guān)生命周期被破壞后自行清理。

沒有因stopped activities而崩潰

? 如果觀察者的生命周期處于非活動狀態(tài)(例如,在后臺堆棧中的activity的情況下),則它不會接收任何LiveData事件。

不再需要手動生命周期處理

? UI組件只觀察相關(guān)數(shù)據(jù),不停止或恢復(fù)觀察。LiveData會自動管理所有這些,因為它在觀察的同時會知道相關(guān)的生命周期狀態(tài)變化。

始終保持最新數(shù)據(jù)

? 如果生命周期變?yōu)榉腔顒訝顟B(tài),它將在再次變?yōu)榛顒訝顟B(tài)時接收最新數(shù)據(jù)。例如,后臺activity在返回前臺后立即接收最新數(shù)據(jù)。

適當(dāng)?shù)呐渲酶?/strong>

? 如果由于配置更改(例如設(shè)備輪換)而重新創(chuàng)建activity或fragment,則會立即接收最新的可用數(shù)據(jù)。

共享資源

? 您可以使用單例模式擴展LiveData對象來包裝系統(tǒng)服務(wù),以便在應(yīng)用程序中共享它們。LiveData對象連接到系統(tǒng)服務(wù)一次,然后任何需要該資源的觀察者只能觀察該LiveData 對象。有關(guān)更多信息,請參閱擴展LiveData

使用LiveData對象

按照以下步驟使用LiveData對象:

  1. 創(chuàng)建一個LiveData用于保存特定類型數(shù)據(jù)的實例。這通常在您的ViewModel類內(nèi)完成 。
  2. 創(chuàng)建一個定義onChanged()方法的Observer對象,控制當(dāng)LiveData對象持有的數(shù)據(jù)發(fā)生更改是的情況。您通常在UI控制器中創(chuàng)建一個Observer對象,例如activity或fragment。
  3. 使用observe()方法將Observer對象附加到LiveData對象。observer()方法接受LifecycleOwner對象。這將Observer對象訂閱到LiveData對象,以便通知它更改。通常將Observer對象附加到UI控制器中,例如activity或fragment。

注意:可以使用observeForever(Observer)方法注冊一個沒有關(guān)聯(lián)LifecycleOwner對象的觀察者。在這種情況下,觀察者被認為總是處于活動狀態(tài),因此總是會收到關(guān)于修改的通知。可以調(diào)用removeObserver(Observer)方法刪除這些觀察者。

當(dāng)您更新存儲在LiveData對象中的值時,只要附加的LifecycleOwner處于活動狀態(tài),它就會觸發(fā)所有注冊的觀察者。

LiveData允許UI控制器觀察者訂閱更新。當(dāng)LiveData對象保存的數(shù)據(jù)發(fā)生更改時,UI會自動響應(yīng)更新。

創(chuàng)建LiveData對象

LiveData是一個包裝器,可用于任何數(shù)據(jù),包括實現(xiàn)Collections(如List)的對象。LiveData對象通常存儲在ViewModel對象中,通過getter方法訪問,如下面的示例所示:

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

最初,沒有設(shè)置LiveData對象中的數(shù)據(jù)。

注意:請確保將更新UI的LiveData對象存儲在ViewModel對象中,而不是存儲在活動或片段中,原因如下:

  • 避免臃腫的activity和fragment。這些UI控制器負責(zé)顯示數(shù)據(jù)而不是保存數(shù)據(jù)狀態(tài)。
  • 將LiveData實例與特定的活動或片段實例解耦,并允許LiveData對象在配置更改后繼續(xù)存在。

您可以在ViewModel指南中閱讀更多關(guān)于ViewModel類的好處和用法的信息。

觀察LiveData對象

在大多數(shù)情況下,app組件的onCreate()方法是開始觀察LiveData對象的正確位置, 原因如下:

  • 確保系統(tǒng)不會對activity或fragment的onResume()方法進行多余的調(diào)用。
  • 確保activity或fragment在變?yōu)榛顒訝顟B(tài)時可以及時顯示數(shù)據(jù)。一旦app組件處于STARTED狀態(tài),它就會從它所觀察的LiveData對象接受最新的值。只有在這只了要觀察的LiveData對象時才會發(fā)生這種情況。

通常,LiveData僅在數(shù)據(jù)更改時才提供更新,并且僅在活動觀察者時提供更新。這種行為的一個例外是,觀察者在從非活動狀態(tài)更改為活動狀態(tài)時也會收到更新。此外,如果觀察者第二次從非活動狀態(tài)更改為活動狀態(tài),則只有在自上次活動狀態(tài)以來該值發(fā)生更改時才會收到更新。

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 = 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.
                nameTextView.setText(newName);
            }
        };

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

在用nameObserver作為參數(shù)傳遞調(diào)用observe()之后,立即調(diào)用onChanged(),提供存儲在mCurrentName中的最新值。如果LiveData對象沒有在mCurrentName中設(shè)置值,則不調(diào)用onChanged()。

更新LiveData對象

LiveData沒有公開的方法來更新存儲的數(shù)據(jù)。 MutableLiveData 類公開 setValue(T)postValue(T) 方法,如果需要編輯存儲在LiveData對象中的值,必須使用這些方法。通常在ViewModel中使用MutableLiveData,然后ViewModel只向觀察者公開不可變的LiveData對象。

在建立觀察者關(guān)系之后,您可以更新LiveData對象的值,如下面的例子所示,當(dāng)用戶點擊一個按鈕時,將觸發(fā)所有的觀察者:

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

在示例中調(diào)用setValue(T)會導(dǎo)致觀察者使用John Doe值調(diào)用其onChanged()方法。該示例展示了一個按鈕按下,(調(diào)用setValue()或postValue()來更新mName)。除此之外還有許多原因,可以調(diào)用setValue()或postValue()來更新mName,包括響應(yīng)網(wǎng)絡(luò)請求或數(shù)據(jù)庫加載完成;在所有情況下,調(diào)用setValue()postValue()都會觸發(fā)觀察者并更新UI。

注意:您必須在主線程中調(diào)用 setValue(T) 方法來更新LiveData對象。如果代碼在工作線程中執(zhí)行,則可以使用該 postValue(T) 方法來更新LiveData對象。

LiveData和Room一起使用

Room persistence庫支持可觀察的查詢,這些查詢返回LiveData對象。可觀察查詢是作為數(shù)據(jù)庫訪問對象(DAO)的一部分編寫的。

當(dāng)數(shù)據(jù)更新后,Room會生成更新LiveData對象所需的所有代碼。當(dāng)需要時,生成的代碼在后臺線程上異步查詢。此模式對于使UI中顯示的數(shù)據(jù)與存儲在數(shù)據(jù)庫中的數(shù)據(jù)保持同步非常有用。您可以在Room persistent library guide閱讀有關(guān)Room和DAO的更多信息。

擴展LiveData

如果觀察者的生命周期處于STARTEDRESUMED 狀態(tài),LiveData會將觀察者視為處于活動狀態(tài) 。以下示例代碼說明了如何擴展 LiveData類:

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

本例中price偵聽器的實現(xiàn)包括以下重要方法:

  • 當(dāng)LiveData對象有一個活躍的觀察者時,調(diào)用onActive()方法。這意味著您需要從這個方法開始觀察股票價格的更新。
  • 當(dāng)LiveData對象沒有任何活躍觀察者時,調(diào)用onInactive()方法。由于沒有觀察者在監(jiān)聽,因此沒有理由與StockManager服務(wù)保持聯(lián)系。
  • setValue(T) 方法更新LiveData實例的值,并通知任何活躍的觀察者有關(guān)更改的信息。

您可以按如下方式使用StockLiveData類:

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

observe()方法將fragment(它是LifecycleOwner的一個實例)作為第一個參數(shù)傳遞。這樣做表示此觀察者綁定到與所有者關(guān)聯(lián)的Lifecycle對象,這意味著:

  • 如果Lifecycle對象未處于活躍狀態(tài),則即使值發(fā)生更改,也不會調(diào)用觀察者。
  • Lifecycle的對象被破壞之后,將自動刪除觀察者。

LiveData對象具有生命周期感知這一事實意味著您可以在多個activities,fragments和services之間共享它們。為了簡化示例,您可以將LiveData類實現(xiàn)為單例,如下所示:

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

您可以在fragment中使用它,如下所示:

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

多個fragments和activities可以觀察MyPriceListener實例。LiveData只在其中一個或多個是可見且活躍的情況下才連接到系統(tǒng)服務(wù)。

轉(zhuǎn)換LiveData

您可能希望在將LiveData對象分發(fā)給觀察者之前對其中存儲的值進行更改 ,或者您可能需要根據(jù)另一個LiveData實例的值返回不同的LiveData實例。 Lifecycle包提供了Transformations 類其中包含支持這些場景的幫助方法。

? Transformations.map()

? 將函數(shù)應(yīng)用于存儲在LiveData對象中值,并將結(jié)果傳播到下游。

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

? Transformations.switchMap()

? 類似于map(),將函數(shù)應(yīng)用于存儲在LiveData 對象中的值,并將結(jié)果解包并分派到下游。傳遞給的函數(shù)switchMap()必須返回一個LiveData對象,如下例所示:

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

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

您可以使用轉(zhuǎn)換方法在觀察者的生命周期中傳遞信息。除非觀察者正在觀察返回的LiveData對象,否則不會計算變換。由于轉(zhuǎn)換是延遲計算的,因此生命周期相關(guān)的行為會被隱式傳遞下去,而不需要額外的顯式調(diào)用或依賴項。

如果您認為在ViewModel對象中需要一個Lifecycle對象,那么轉(zhuǎn)換可能是更好的解決方案。例如,假設(shè)您有一個UI組件,它接受一個地址并返回該地址的郵政編碼。您可以為該組件實現(xiàn)ViewModel,如下面的示例代碼所示:

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

然后,UI組件需要從前面的LiveData對象注銷注冊,并在每次調(diào)用getPostalCode()時注冊到新實例。此外,如果重新創(chuàng)建UI組件,它將觸發(fā)對repository.getPostCode()方法的另一次調(diào)用,而不是使用前一個調(diào)用的結(jié)果。

相反,您可以將郵政編碼查找實現(xiàn)為地址輸入的轉(zhuǎn)換,如以下示例所示:

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轉(zhuǎn)換 。只要您的應(yīng)用程序有一個與postalCode字段相關(guān)聯(lián)的活動觀察者,該字段的值就會在addressInput發(fā)生更改時重新計算和檢索。

這種機制允許在app相對底層創(chuàng)建按需延遲計算的LiveData對象。ViewModel對象可以輕松獲取對LiveData對象的引用,然后在其(app相對上層)上定義轉(zhuǎn)換規(guī)則。

創(chuàng)建新的轉(zhuǎn)換

有十幾種不同的轉(zhuǎn)換可能對您的應(yīng)用有用,但默認情況下不提供。要實現(xiàn)自己的轉(zhuǎn)換,您可以使用MediatorLiveData 類,該類偵聽其他 LiveData對象并處理它們發(fā)出的事件。

MediatorLiveData正確地將其狀態(tài)傳播到源LiveData對象。要了解有關(guān)此模式的更多信息,請參閱Transformations 類的參考文檔 。

合并多個LiveData源

MediatorLiveDataLiveData的一個子類,允許您合并多個LiveData數(shù)據(jù)源。 只要任何原始LiveData源對象發(fā)生更改,就會觸發(fā)MediatorLiveData對象的觀察者。

例如,如果UI中有一個可以從本地數(shù)據(jù)庫或網(wǎng)絡(luò)更新的LiveData對象,那么可以向MediatorLiveData對象添加以下源:

  • 與數(shù)據(jù)庫中存儲的數(shù)據(jù)關(guān)聯(lián)的LiveData對象。
  • 與從網(wǎng)絡(luò)訪問的數(shù)據(jù)關(guān)聯(lián)的LiveData對象。

您的活動只需要觀察MediatorLiveData對象就可以從這兩個源接收更新。有關(guān)詳細示例,請參見應(yīng)用程序體系結(jié)構(gòu)指南的附錄:公開網(wǎng)絡(luò)狀態(tài)部分。

其他資源

要了解關(guān)于LiveData類的更多信息,請參考以下參考資料。

例子

Codelabs

Blogs

Videos

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容