有關(guān)Databinding與MVVM的一些事

DataBinding

說(shuō)到DataBinding,大家就會(huì)想到雙向綁定。那究竟什么是雙向綁定,其實(shí)對(duì)于剛接觸的人來(lái)說(shuō)是需要去理解一下的。

MVVM中,ViewModel是互相隔離的。假設(shè)有以下的EditText布局:

android:text="@{echo.text}"

那么顯而易見(jiàn),當(dāng)echo.text發(fā)生變化時(shí),我們希望EditText中的android:text屬性可以自動(dòng)變化。這是Model層->View層的綁定。

反之,當(dāng)用戶通過(guò)View層在EditText中進(jìn)行輸入時(shí),我們希望echo.text字段也可以同步更新到最新的輸入值。這是ViewModel層的綁定。這一點(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)聽器就被注冊(cè)了,這個(gè)監(jiān)聽器會(huì)在每次text字段被更新后去更新視圖。

先來(lái)一段小小的源碼分析,每個(gè)layout生成的xxxBinding都是關(guān)鍵的類,里面有一個(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è)變量的fieldIdmodel.str這個(gè)Observable綁定在一起,同時(shí)使用前面的addOnPropertyChangedCallbackViewBinding類作為回調(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里我這么寫: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里這么寫:

 <ImageView  
    android:layout_width="100dp"  
    android:layout_height="100dp"  
    app:imageUrl="@{user.url}" />  

這就每次更新user.url時(shí)就會(huì)自動(dòng)重新設(shè)置圖片。

DataBindingset屬性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方法名稱。

@BindingMethods({@BindingMethod(type = NestedScrollView.class, attribute = "custom", method = "setMyCustomAttr")})

注解在類上。

個(gè)人覺(jué)得他與BindingAdapter的區(qū)別在于:

  • 參數(shù),BindingAdapter可以拿到view引用
  • component

監(jiān)聽屬性變更

databinding讓我們省去了各種監(jiān)聽函數(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è)自定義屬性雙向綁定,寫上@=時(shí):你需要寫以下函數(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)

1.png

現(xiàn)在先讓我們忘記前面說(shuō)的一切有關(guān)雙向綁定的事情。。。來(lái)看看google推出的架構(gòu)LiveData&ViewModel

我們先看平時(shí)開發(fā)時(shí)會(huì)有哪些問(wèn)題:

通常Android系統(tǒng)來(lái)管理UI controllers(如Activity、Fragment)的生命周期,由系統(tǒng)響應(yīng)用戶交互或者重建組件,用戶無(wú)法操控。當(dāng)組件被銷毀并重建后,原來(lái)組件相關(guān)的數(shù)據(jù)也會(huì)丟失,如果數(shù)據(jù)類型比較簡(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組件已銷毀,而請(qǐng)求還未返回的情況,因此UI controllers需要做額外的工作以防止內(nèi)存泄露。
當(dāng)Activity因?yàn)榕渲米兓N毀重建時(shí),一般數(shù)據(jù)會(huì)重新請(qǐng)求,其實(shí)這是一種浪費(fèi),最好就是能夠保留上次的數(shù)據(jù)。

解決fragmentfragment之間通信的問(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)聽

ViewModel

[站外圖片上傳中...(image-93bfcd-1526989876647)]

說(shuō)實(shí)話一開始看到這個(gè)我總以為跟MVVMViewModel有什么異曲同工之妙,事實(shí)證明我想多了。此ViewModel是用來(lái)存儲(chǔ)和管理UI相關(guān)的數(shù)據(jù)。

ViewModel是生存在整個(gè)生命周期內(nèi)的,所以在這個(gè)類中不能存在android.content.Context; 簡(jiǎn)單來(lái)說(shuō)不能有viewcontext的引用,所以一般都會(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è)fragmentsetRetain(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í)需要用到大量LiveDataobserve函數(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

一些額外的技巧

image

image

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

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

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