DataBinding
說(shuō)到DataBinding
,大家就會(huì)想到雙向綁定蛹稍。那究竟什么是雙向綁定,其實(shí)對(duì)于剛接觸的人來(lái)說(shuō)是需要去理解一下的长赞。
在MVVM
中,View
和Model
是互相隔離的姆另。假設(shè)有以下的EditText
布局:
android:text="@{echo.text}"
那么顯而易見(jiàn),當(dāng)echo.text
發(fā)生變化時(shí),我們希望EditText
中的android:text
屬性可以自動(dòng)變化忙芒。這是Model
層->View
層的綁定凰棉。
反之,當(dāng)用戶(hù)通過(guò)View
層在EditText
中進(jìn)行輸入時(shí),我們希望echo.text
字段也可以同步更新到最新的輸入值膀哲。這是View
到Model
層的綁定往产。這一點(diǎn)很多人都會(huì)忽略被碗。
那么首先我們看是Model
層->View
層的綁定是怎么實(shí)現(xiàn)的,在DataBinding
中大家都知道有以下兩種方式:
-
Model
繼承BaseObservable
,get
帶上@Bindable
注解 - 相應(yīng)字段使用
Observablexxx
變量,如下text
字段(其實(shí)Observablexxx
就是繼承BaseObservable
)
可以簡(jiǎn)單看下BaseObservable
的源碼,后面會(huì)用到:
public class BaseObservable implements Observable {
@Override
public void addOnPropertyChangedCallback(@NonNullOnPropertyChangedCallback callback) {
...
mCallbacks.add(callback);
}
public void notifyPropertyChanged(int fieldId) {
...
mCallbacks.notifyCallbacks(this, fieldId, null);
}
就是一個(gè)回調(diào)模式。
public class Echo {
public ObservableField<String> text = new ObservableField<>();
}
當(dāng)使用ObservableField
后,就真正使用了觀察者的模式仿村。也就是說(shuō)當(dāng)調(diào)用setEcho
方法后,一個(gè)監(jiān)聽(tīng)器就被注冊(cè)了,這個(gè)監(jiān)聽(tīng)器會(huì)在每次text
字段被更新后去更新視圖蛮放。
先來(lái)一段小小的源碼分析,每個(gè)layout
生成的xxxBinding
都是關(guān)鍵的類(lèi),里面有一個(gè)executeBinding
方法。
我們來(lái)看,一個(gè)簡(jiǎn)單的
android:text="@{model.str}"
會(huì)生成什么模板代碼奠宜?
@Override
protected void executeBindings() {
long dirtyFlags = 0;
synchronized(this) {
dirtyFlags = mDirtyFlags;
mDirtyFlags = 0;
}
java.lang.String modelStr = null;
me.lizz0y.myapplication.VM model = mModel;
if ((dirtyFlags & 0x3L) != 0) {
if (model != null) {
// read model.str
modelStr = model.str;
}
}
// batch finished
if ((dirtyFlags & 0x3L) != 0) {
// api target 1
android.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, modelStr);
}
}
關(guān)鍵代碼:
modelStr = model.str;
android.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, modelStr);//這個(gè)`xxxAdapter`后面再說(shuō),這里就看出簡(jiǎn)單的賦值就好了
如果變成
public ObservableField<String> str = new ObservableField<String>("sss");
我們會(huì)發(fā)現(xiàn)包颁,在executeBinding
中多了一句:
updateRegistration(0, modelStr);//localFieldId
將這個(gè)變量的fieldId
與model.str
這個(gè)Observable
綁定在一起,同時(shí)使用前面的addOnPropertyChangedCallback
把ViewBinding
類(lèi)作為回調(diào)傳進(jìn)去。
最終,當(dāng)我們對(duì)mode.str
進(jìn)行set
操作時(shí),一系列回調(diào)最終走到ViewDataBinding
的
@Override
protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
switch (localFieldId) {
case 0 :
return onChangeModelStr((android.databinding.ObservableField<java.lang.String>) object, fieldId);
}
return false;
}
其實(shí)就是對(duì)str
這個(gè)變量賦予臟位,讓下次屏幕刷新時(shí)更新這個(gè)變量對(duì)應(yīng)的View
压真。
介紹一些常用的運(yùn)算符:
運(yùn)算符
@BindingConversion
如果在xml
里我這么寫(xiě):android:background="@{@color/blue}"
會(huì)報(bào)錯(cuò),因?yàn)?code>background應(yīng)該是drawable
娩嚼。所以要進(jìn)行自動(dòng)轉(zhuǎn)化,所以需要進(jìn)行如下定義:
//轉(zhuǎn)化@color/blue為drawable
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
return new ColorDrawable(color);
}
經(jīng)過(guò)前面分析也很簡(jiǎn)單:
android.databinding.adapters.ViewBindingAdapter.setBackground(
this.mboundView0,me.lizz0y.myapplication.VM.convertColorToDrawable(
mboundView0.getResources().getColor(R.color.blue)));
當(dāng)然這里顯然有人會(huì)問(wèn),假設(shè)我定義了多個(gè)怎么破,結(jié)論就是后面的覆蓋前面的。滴肿。岳悟。
@BindAdapter
舉個(gè)栗子就明白了:
@BindingAdapter({"imageUrl"})
public static void loadImage(ImageView view, String u) {
RequestOptions options = new RequestOptions()
.centerCrop()
.placeholder(R.mipmap.ic_launcher_round)
.error(R.mipmap.ic_launcher)
.priority(Priority.HIGH)
.diskCacheStrategy(DiskCacheStrategy.NONE);
Glide.with(view.getContext()).applyDefaultRequestOptions(options).load(u).transition(new DrawableTransitionOptions().crossFade(1000)).into(view);
}
xml
里這么寫(xiě):
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
app:imageUrl="@{user.url}" />
這就每次更新user.url
時(shí)就會(huì)自動(dòng)重新設(shè)置圖片。
DataBinding
在set
屬性attr
時(shí)會(huì)先看View
有沒(méi)有setXXX
方法泼差。如果沒(méi)有就去找有沒(méi)有BindingAdapter
注解設(shè)置對(duì)應(yīng)的方法贵少。
前面分析源碼的時(shí)候提到過(guò)
if ((dirtyFlags & 0x3L) != 0) {
// api target 1
android.databinding.adapters.TextViewBindingAdapter.setText(this.mboundView1, modelStr);
}
我們看這里的BindingAdapter
:
@BindingAdapter("android:text")
public static void setText(TextView view, CharSequence text) {
final CharSequence oldText = view.getText();
if (text == oldText || (text == null && oldText.length() == 0)) {
return;
}
if (text instanceof Spanned) {
if (text.equals(oldText)) {
return; // No change in the spans, so don't set anything.
}
} else if (!haveContentsChanged(text, oldText)) {
return; // No content changes, so don't set anything.
}
view.setText(text);
}
可以看到會(huì)比較oldText&newText
,以防無(wú)限循環(huán)。
Component
我們可以定義多個(gè)BindingAdapter
,但究竟想要哪個(gè)發(fā)揮作用呢堆缘? 就可以使用這個(gè)Component
BindingMethod
該注解可以幫助我們重新命名view
屬性對(duì)應(yīng)的setter
方法名稱(chēng)滔灶。
@BindingMethods({@BindingMethod(type = NestedScrollView.class, attribute = "custom", method = "setMyCustomAttr")})
注解在類(lèi)上。
個(gè)人覺(jué)得他與BindingAdapter
的區(qū)別在于:
- 參數(shù),
BindingAdapter
可以拿到view
引用 component
監(jiān)聽(tīng)屬性變更
databinding
讓我們省去了各種監(jiān)聽(tīng)函數(shù),但有的時(shí)候我們需要在屬性變化時(shí)做一些額外的事情:
mModel.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(Observable observable, int i) {
if (i == BR.name) {
Toast.makeText(TwoWayActivity.this, "name changed",
Toast.LENGTH_SHORT).show();
} else if (i == BR.password) {
Toast.makeText(TwoWayActivity.this, "password changed",
Toast.LENGTH_SHORT).show();
}
}
});
雙重綁定
下面我們來(lái)說(shuō)說(shuō)如果從View
->Model
的綁定吼肥。我們看在前面已有的基礎(chǔ)上,我們?nèi)绾巫约簩?shí)現(xiàn)雙重綁定
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Text 1"
android:text="@{echo.text}"
android:addTextChangedListener="@{echo.watcher}"/>
假設(shè)有兩個(gè)EditText
都使用了android:text
,可以看到一個(gè)改變并不能連帶帶動(dòng)另一個(gè)EditText
,因?yàn)?code>EditText的輸入并沒(méi)有手動(dòng)去調(diào)用setField
方法录平。所以顯而易見(jiàn)需要再使用DataBinding
賦予textChangedListener
public TextWatcher watcher = new TextWatcherAdapter() {
@Override public void afterTextChanged(Editable s) {
if (!Objects.equals(text.get(), s.toString())) { //防止無(wú)限循環(huán)
text.set(s.toString());
}
}
};
customBinding
public class BindableString extends BaseObservable {
private String value;
public String get() {
return value != null ? value : “”;
}
public void set(String value) {
if (!Objects.equals(this.value, value)) {
this.value = value;
notifyChange();
}
}
public boolean isEmpty() {
return value == null || value.isEmpty();
}
}
@BindingConversion
public static String convertBindableToString(
BindableString bindableString) {
return bindableString.get();
}
當(dāng)主動(dòng)調(diào)用BindableString.set
時(shí)會(huì)通過(guò)notifyChange
去觸發(fā)UI更新,UI更新時(shí)調(diào)用convertBindableToString
取出string
綁定
或者BindAdapter
:
@BindingAdapter({“app:binding”})
public static void bindEditText(EditText view,
final BindableString bindableString) {
Pair<BindableString, TextWatcherAdapter> pair =
(Pair) view.getTag(R.id.bound_observable);
if (pair == null || pair.first != bindableString) {
if (pair != null) {
view.removeTextChangedListener(pair.second);
}
TextWatcherAdapter watcher = new TextWatcherAdapter() {
public void onTextChanged(CharSequence s,
int start, int before, int count) {
bindableString.set(s.toString());
}
};
view.setTag(R.id.bound_observable,
new Pair<>(bindableString, watcher));
view.addTextChangedListener(watcher);
}
String newValue = bindableString.get();
if (!view.getText().toString().equals(newValue)) {
view.setText(newValue);
}
}
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Text 1"
app:binding="@{echo.text}"/>
或者可以使用BindAdapter
:
@BindingAdapter({“app:binding”})
public static void bindEditText(EditText view,
final BindableString bindableString) {
Pair<BindableString, TextWatcherAdapter> pair =
(Pair) view.getTag(R.id.bound_observable);
if (pair == null || pair.first != bindableString) {
if (pair != null) {
view.removeTextChangedListener(pair.second);
}
TextWatcherAdapter watcher = new TextWatcherAdapter() {
public void onTextChanged(CharSequence s,
int start, int before, int count) {
bindableString.set(s.toString());
}
};
view.setTag(R.id.bound_observable,
new Pair<>(bindableString, watcher));
view.addTextChangedListener(watcher);
}
String newValue = bindableString.get();
if (!view.getText().toString().equals(newValue)) {
view.setText(newValue);
}
}
當(dāng)然,google
不可能真的這么蠢。缀皱。讓我們自己去實(shí)現(xiàn)這一套,它在xml
里給我們提供了很簡(jiǎn)單的運(yùn)算符@=
@=
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textNoSuggestions"
android:text="@={model.name}"/>
這么搞完當(dāng)EditText
更新時(shí)就自動(dòng)更新model.name
字段斗这。當(dāng)然,肯定很好奇背后的實(shí)現(xiàn)(這一part看了我半天。啤斗。) 它的實(shí)現(xiàn)是由多個(gè)注解完成的,先看其中兩個(gè):
- @InverseBindingAdapter
- @InverseBindingListener
TextViewBindingAdapter.java:
@BindingAdapter(value = {"android:beforeTextChanged", "android:onTextChanged",
"android:afterTextChanged", "android:textAttrChanged"}, requireAll = false)
public static void setTextWatcher(TextView view, final BeforeTextChanged before,
final OnTextChanged on, final AfterTextChanged after,
final InverseBindingListener textAttrChanged) {
final TextWatcher newValue;
if (before == null && after == null && on == null && textAttrChanged == null) {
newValue = null;
} else {
newValue = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
if (before != null) {
before.beforeTextChanged(s, start, count, after);
}
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (on != null) {
on.onTextChanged(s, start, before, count);
}
if (textAttrChanged != null) {
textAttrChanged.onChange();
}
}
@Override
public void afterTextChanged(Editable s) {
if (after != null) {
after.afterTextChanged(s);
}
}
};
}
final TextWatcher oldValue = ListenerUtil.trackListener(view, newValue, R.id.textWatcher);
if (oldValue != null) {
view.removeTextChangedListener(oldValue);
}
if (newValue != null) {
view.addTextChangedListener(newValue);
}
}
@InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged")
public static String getTextString(TextView view) {
return view.getText().toString();
}
xxxViewBinding.java
private android.databinding.InverseBindingListener mboundView2androidTextAttrChanged = new android.databinding.InverseBindingListener() {
@Override
public void onChange() {
// Inverse of model.str.get()
// is model.str.set((java.lang.String) callbackArg_0)
java.lang.String callbackArg_0 = android.databinding.adapters.TextViewBindingAdapter.getTextString(mboundView2);
// localize variables for thread safety
// model
me.lizz0y.myapplication.VM model = mModel;
// model.str != null
boolean modelStrJavaLangObjectNull = false;
// model != null
boolean modelJavaLangObjectNull = false;
// model.str.get()
java.lang.String modelStrGet = null;
// model.str
android.databinding.ObservableField<java.lang.String> modelStr = null;
modelJavaLangObjectNull = (model) != (null);
if (modelJavaLangObjectNull) {
modelStr = model.str;
modelStrJavaLangObjectNull = (modelStr) != (null);
if (modelStrJavaLangObjectNull) {
modelStr.set(((java.lang.String) (callbackArg_0)));
}
}
}
};
也很簡(jiǎn)單,當(dāng)text
發(fā)生變化時(shí),觸發(fā)onTextChange
,然后調(diào)用mboundView2androidTextAttrChanged.onChange
,里面調(diào)用了由@InverseBindingAdapter
注解的getTextString
去獲取值賦給model
總結(jié)一下,假設(shè)你要給一個(gè)自定義屬性雙向綁定,寫(xiě)上@=
時(shí):你需要寫(xiě)以下函數(shù):
@InverseBindingAdapter(attribute = "refreshing", event = "refreshingAttrChanged")
public static boolean getRefreshing(PhilView view) { //賦值時(shí)來(lái)這里取
return isRefreshing;
}
@BindingAdapter(value = {"refreshingAttrChanged"}, requireAll = false)
public static void setRefreshingAttrChanged(PhilView view, final InverseBindingListener inverseBindingListener) {
Log.d(TAG, "setRefreshingAttrChanged");
if (inverseBindingListener == null) {
view.setRefreshingListener(null);
} else {
mInverseBindingListener = inverseBindingListener;
view.setRefreshingListener(mOnRefreshingListener);
}
}
@InverseMethod & @InverseBindingMethod[s]
只是簡(jiǎn)化了一下@InverseBindingAdapter
的注解表箭。
與RecyclerView
public class MyViewHolder extends RecyclerView.ViewHolder {
private final ItemBinding binding;
public MyViewHolder(ItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
public void bind(Item item) {
binding.setItem(item);
binding.executePendingBindings();
}
}
強(qiáng)刷executePendingBinding
強(qiáng)制綁定操作馬上執(zhí)行,而不是推遲到下一幀刷新時(shí)钮莲。RecyclerView 會(huì)在 onBindViewHolder 之后立即測(cè)量 View免钻。如果因?yàn)榻壎ㄍ七t到下一幀繪制時(shí)導(dǎo)致錯(cuò)誤的數(shù)據(jù)被綁定到 View 中, View 會(huì)被不正確地測(cè)量,因此這個(gè) executePendingBindings() 方法非常重要臂痕!
todoApp結(jié)構(gòu)
現(xiàn)在先讓我們忘記前面說(shuō)的一切有關(guān)雙向綁定的事情伯襟。猿涨。握童。來(lái)看看google
推出的架構(gòu)LiveData
&ViewModel
我們先看平時(shí)開(kāi)發(fā)時(shí)會(huì)有哪些問(wèn)題:
通常Android系統(tǒng)來(lái)管理UI controllers(如Activity、Fragment)的生命周期叛赚,由系統(tǒng)響應(yīng)用戶(hù)交互或者重建組件澡绩,用戶(hù)無(wú)法操控稽揭。當(dāng)組件被銷(xiāo)毀并重建后,原來(lái)組件相關(guān)的數(shù)據(jù)也會(huì)丟失肥卡,如果數(shù)據(jù)類(lèi)型比較簡(jiǎn)單溪掀,同時(shí)數(shù)據(jù)量也不大,可以通過(guò)onSaveInstanceState()存儲(chǔ)數(shù)據(jù)步鉴,組件重建之后通過(guò)onCreate()揪胃,從中讀取Bundle恢復(fù)數(shù)據(jù)。但如果是大量數(shù)據(jù)氛琢,不方便序列化及反序列化喊递,則上述方法將不適用。
UI controllers經(jīng)常會(huì)發(fā)送很多異步請(qǐng)求阳似,有可能會(huì)出現(xiàn)UI組件已銷(xiāo)毀骚勘,而請(qǐng)求還未返回的情況,因此UI controllers需要做額外的工作以防止內(nèi)存泄露撮奏。
當(dāng)Activity因?yàn)榕渲米兓N(xiāo)毀重建時(shí)俏讹,一般數(shù)據(jù)會(huì)重新請(qǐng)求,其實(shí)這是一種浪費(fèi)畜吊,最好就是能夠保留上次的數(shù)據(jù)泽疆。
解決
fragment
和fragment
之間通信的問(wèn)題
LiveData
LiveData
,顧名思義和生命周期綁定,解決上面的第二個(gè)異步問(wèn)題:
能夠感知組件(Fragment、Activity玲献、Service)的生命周期于微;
只有在組件出于激活狀態(tài)(STARTED、RESUMED)才會(huì)通知觀察者有數(shù)據(jù)更新青自;
public class NameViewModel extends ViewModel{
// Create a LiveData with a String
private MutableLiveData<String> mCurrentName;
// Create a LiveData with a String list
private MutableLiveData<List<String>> mNameListData;
public MutableLiveData<String> getCurrentName() {
if (mCurrentName == null) {
mCurrentName = new MutableLiveData<>();
}
return mCurrentName;
}
public MutableLiveData<List<String>> getNameList(){
if (mNameListData == null) {
mNameListData = new MutableLiveData<>();
}
return mNameListData;
}
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mNameViewModel = ViewModelProviders.of(this).get(NameViewModel.class);
mNameViewModel.getCurrentName().observe(this,(String name) -> {
mTvName.setText(name);
Log.d(TAG, "currentName: " + name);
}); // 訂閱LiveData中當(dāng)前Name數(shù)據(jù)變化株依,以lambda形式定義Observer
mNameViewModel.getNameList().observe(this, (List<String> nameList) -> {
for (String item : nameList) {
Log.d(TAG, "name: " + item);
}
}); // 訂閱LiveData中Name列表數(shù)據(jù)變化,以lambda形式定義Observer
}
當(dāng)組件處于激活狀態(tài)延窜,并且mCurrentName
變量發(fā)生變化時(shí),fragment
觀察者就會(huì)收到監(jiān)聽(tīng)
ViewModel
[站外圖片上傳中...(image-93bfcd-1526989876647)]
說(shuō)實(shí)話一開(kāi)始看到這個(gè)我總以為跟MVVM
的ViewModel
有什么異曲同工之妙,事實(shí)證明我想多了恋腕。此ViewModel
是用來(lái)存儲(chǔ)和管理UI相關(guān)的數(shù)據(jù)。
ViewModel
是生存在整個(gè)生命周期內(nèi)的,所以在這個(gè)類(lèi)中不能存在android.content.Context;
簡(jiǎn)單來(lái)說(shuō)不能有view
或context
的引用,所以一般都會(huì)傳ApplicationContext
進(jìn)去逆瑞。
用法很簡(jiǎn)單:
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
// Create a ViewModel the first time the system calls an activity's onCreate() method.
// Re-created activities receive the same MyViewModel instance created by the first activity.
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
// update UI
});
}
}
public class MyViewModel extends ViewModel {
private MutableLiveData<List<User>> users;
public LiveData<List<User>> getUsers() {
if (users == null) {
users = new MutableLiveData<List<Users>>();
loadUsers();
}
return users;
}
private void loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
很容易看出它為什么可以解決以上問(wèn)題, ViewModelProviders.of(this).get(MyViewModel.class);
調(diào)用這個(gè)時(shí),內(nèi)部代碼會(huì)幫我們做存儲(chǔ)荠藤。至于怎么做存儲(chǔ),也很常見(jiàn),加了一個(gè)fragment
并setRetain(true)
就可以了。這樣重建恢復(fù)后拿的還是同一個(gè)ViewModel
,因此顯然數(shù)據(jù)也都還在获高。同時(shí),一個(gè)Activity
對(duì)應(yīng)的兩個(gè)fragemt
也可以通信:
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> getSelected() {
return selected;
}
}
public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, { item ->
// Update the UI.
});
}
}
使用getActivity
拿到同一份ViewModel
,就可以拿到同一份數(shù)據(jù)哈肖。也很容易看到,其實(shí)LiveData
是需要與ViewModel
結(jié)合在一起用的
todoApp-mvvm-live
從todoApp
可以看出最明顯的區(qū)別:
@NonNull
public static TaskDetailViewModel obtainViewModel(FragmentActivity activity) {
// Use a Factory to inject dependencies into the ViewModel
ViewModelFactory factory = ViewModelFactory.getInstance(activity.getApplication());
return ViewModelProviders.of(activity, factory).get(TaskDetailViewModel.class);
}
其次,其實(shí)這個(gè)例子還是跟dataBinding
搞在一起了,否則拿回?cái)?shù)據(jù)更新UI時(shí)需要用到大量LiveData
的observe
函數(shù)。
最后所以用了LiveData
的作用在哪,我們看一個(gè)例子,點(diǎn)擊某個(gè)按鈕后跳轉(zhuǎn)Activity
:
before
public class TasksActivity extends AppCompatActivity implements TaskItemNavigator, TasksNavigator {
....
}
@Nullable
private WeakReference<TaskItemNavigator> mNavigator;
在ViewModel
中:
public void taskClicked() {
String taskId = getTaskId();
if (taskId == null) {
// Click happened before task was loaded, no-op.
return;
}
if (mNavigator != null && mNavigator.get() != null) { //煩躁
mNavigator.get().openTaskDetails(taskId);
}
}
after
TasksActivity.java
// Subscribe to "open task" event
mViewModel.getOpenTaskEvent().observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String taskId) {
if (taskId != null) {
openTaskDetails(taskId);
}
}
});
// mTasksViewModel.getOpenTaskEvent().setValue(task.getId());
利用liveData
的生命周期特性,就不用管activity
是否已經(jīng)消失念秧。
調(diào)試
Databinding
想調(diào)試自動(dòng)生成的代碼,需要在setting
里選擇Reference code generated by the compiler