diff options
author | George Mount <mount@google.com> | 2017-11-16 08:42:43 -0800 |
---|---|---|
committer | Yigit Boyar <yboyar@google.com> | 2017-11-27 12:54:08 -0800 |
commit | f383e35e8a56958c89b92abe193fa2bad73d98d5 (patch) | |
tree | 0e8884ab5fea594d3e4a6287ce1627e5e5034852 /compiler/src/main/java/android/databinding | |
parent | 5a8588197dd14261e9010f326b62d15bbff0c23c (diff) | |
download | data-binding-f383e35e8a56958c89b92abe193fa2bad73d98d5.tar.gz |
Make LiveData observable by data binding.
Bug: 36122539
Data binding needed to support observing LiveData with a
LifecycleOwner. This CL adds listening to LiveData change
events.
If no LifecycleOwner exists, the LiveData will not be observed.
Test: LiveDataTest
Change-Id: I76b0a4d38a9107a0fd7f6e03594aea49be9d645b
Diffstat (limited to 'compiler/src/main/java/android/databinding')
8 files changed, 99 insertions, 46 deletions
diff --git a/compiler/src/main/java/android/databinding/tool/Binding.java b/compiler/src/main/java/android/databinding/tool/Binding.java index 529926bf..467a4cad 100644 --- a/compiler/src/main/java/android/databinding/tool/Binding.java +++ b/compiler/src/main/java/android/databinding/tool/Binding.java @@ -138,27 +138,19 @@ public class Binding implements LocationScopeProvider { } } else { ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance(); - boolean warn = false; - if (mExpr.getResolvedType().isObservableField()) { + if (mExpr.getResolvedType().getObservableGetterName() != null) { // If it is an ObservableField, try with the contents of it first. Expr expr = mExpr.unwrapObservableField(); mSetterCall = SetterStore.get(modelAnalyzer).getSetterCall(mName, viewType, expr.getResolvedType(), mExpr.getModel().getImports()); if (mSetterCall != null) { mExpr = expr; - } else { - // No setter for the contents of the ObservableField. - // If we find one for the ObservableField, we should warn. - warn = true; } } if (mSetterCall == null) { // Now try with the value object directly mSetterCall = SetterStore.get(modelAnalyzer).getSetterCall(mName, viewType, mExpr.getResolvedType(), mExpr.getModel().getImports()); - if (warn && mSetterCall != null) { - L.w(ErrorMessages.OBSERVABLE_FIELD_USE, mSetterCall.getDescription()); - } } } } diff --git a/compiler/src/main/java/android/databinding/tool/BindingTarget.java b/compiler/src/main/java/android/databinding/tool/BindingTarget.java index d24cb671..83c28b08 100644 --- a/compiler/src/main/java/android/databinding/tool/BindingTarget.java +++ b/compiler/src/main/java/android/databinding/tool/BindingTarget.java @@ -32,7 +32,6 @@ import android.databinding.tool.util.L; import android.databinding.tool.util.Preconditions; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -207,7 +206,7 @@ public class BindingTarget implements LocationScopeProvider { Scope.enter(binding); attributes[i] = binding.getName(); final ModelClass type = binding.getExpr().getResolvedType(); - if (type.isObservableField()) { + if (type.getObservableGetterName() != null) { hasObservableFields = true; types[i] = binding.getExpr().unwrapObservableField().getResolvedType(); } else { @@ -239,8 +238,6 @@ public class BindingTarget implements LocationScopeProvider { getMultiAttributeSetterCalls(attributes, getResolvedType(), types); List<MergedBinding> observableFieldCalls = createMultiSetters(multiAttributeSetterCalls, false); - observableFieldCalls.forEach(call -> L.w(ErrorMessages.OBSERVABLE_FIELD_USE, - call.getMultiAttributeSetter().getDescription())); merged.addAll(observableFieldCalls); } diff --git a/compiler/src/main/java/android/databinding/tool/expr/Expr.java b/compiler/src/main/java/android/databinding/tool/expr/Expr.java index f05c950d..4a3dd324 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/Expr.java +++ b/compiler/src/main/java/android/databinding/tool/expr/Expr.java @@ -185,6 +185,16 @@ abstract public class Expr implements VersionProvider, LocationScopeProvider { return getResolvedType().isObservable(); } + public String getUpdateRegistrationCall() { + if (!isObservable()) { + L.e("The expression isn't observable!"); + } + if (getResolvedType().isLiveData()) { + return "updateLiveDataRegistration"; + } + return "updateRegistration"; + } + public void setUnwrapObservableFields(boolean unwrapObservableFields) { mUnwrapObservableFields = unwrapObservableFields; } @@ -855,8 +865,9 @@ abstract public class Expr implements VersionProvider, LocationScopeProvider { public Expr unwrapObservableField() { Expr expr = this; - while (expr.getResolvedType().isObservableField()) { - Expr unwrapped = mModel.methodCall(expr, "get", Collections.EMPTY_LIST); + String simpleGetterName; + while ((simpleGetterName = expr.getResolvedType().getObservableGetterName()) != null) { + Expr unwrapped = mModel.methodCall(expr, simpleGetterName, Collections.EMPTY_LIST); mModel.bindingExpr(unwrapped); unwrapped.setUnwrapObservableFields(false); expr = unwrapped; @@ -884,10 +895,11 @@ abstract public class Expr implements VersionProvider, LocationScopeProvider { final Expr child = mChildren.get(childIndex); Expr unwrapped = null; Expr expr = child; - while (expr.getResolvedType().isObservableField() + String simpleGetterName; + while ((simpleGetterName = expr.getResolvedType().getObservableGetterName()) != null && (type == null || (!type.isAssignableFrom(expr.getResolvedType()) && !ModelMethod.isImplicitConversion(expr.getResolvedType(), type)))) { - unwrapped = mModel.methodCall(expr, "get", Collections.EMPTY_LIST); + unwrapped = mModel.methodCall(expr, simpleGetterName, Collections.EMPTY_LIST); if (unwrapped == this) { L.w(ErrorMessages.OBSERVABLE_FIELD_GET, this); return; // This was already unwrapped! diff --git a/compiler/src/main/java/android/databinding/tool/expr/MethodCallExpr.java b/compiler/src/main/java/android/databinding/tool/expr/MethodCallExpr.java index 4012f249..371925c2 100644 --- a/compiler/src/main/java/android/databinding/tool/expr/MethodCallExpr.java +++ b/compiler/src/main/java/android/databinding/tool/expr/MethodCallExpr.java @@ -31,7 +31,6 @@ import com.google.common.collect.Lists; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import static android.databinding.tool.reflection.Callable.DYNAMIC; @@ -273,8 +272,9 @@ public class MethodCallExpr extends Expr { if (mMethod == null) { return "Could not find the method " + mName + " to inverse for two-way binding"; } - if (mName.equals("get") && getTarget().getResolvedType().isObservableField() && - getArgs().isEmpty()) { + final ModelClass targetResolvedType = getTarget().getResolvedType(); + if (mName.equals(targetResolvedType.getObservableGetterName()) + && targetResolvedType.getObservableSetterName() != null && getArgs().isEmpty()) { return null; } String inverse = setterStore.getInverseMethod(mMethod); @@ -289,11 +289,13 @@ public class MethodCallExpr extends Expr { @Override public Expr generateInverse(ExprModel model, Expr value, String bindingClassName) { getResolvedType(); // ensure mMethod has been resolved. - if (mName.equals("get") && getTarget().getResolvedType().isObservableField() && - getArgs().isEmpty()) { + ModelClass targetResolvedType = getTarget().getResolvedType(); + if (mName.equals(targetResolvedType.getObservableGetterName()) + && getArgs().isEmpty()) { Expr castExpr = model.castExpr(getResolvedType().toJavaCode(), value); Expr target = getTarget().cloneToModel(model); - Expr inverse = model.methodCall(target, "set", Lists.newArrayList(castExpr)); + String simpleSetterName = targetResolvedType.getObservableSetterName(); + Expr inverse = model.methodCall(target, simpleSetterName, Lists.newArrayList(castExpr)); inverse.setUnwrapObservableFields(false); return inverse; } diff --git a/compiler/src/main/java/android/databinding/tool/reflection/ModelAnalyzer.java b/compiler/src/main/java/android/databinding/tool/reflection/ModelAnalyzer.java index ec0764fe..a6ecc5b7 100644 --- a/compiler/src/main/java/android/databinding/tool/reflection/ModelAnalyzer.java +++ b/compiler/src/main/java/android/databinding/tool/reflection/ModelAnalyzer.java @@ -18,7 +18,6 @@ package android.databinding.tool.reflection; import android.databinding.tool.reflection.annotation.AnnotationAnalyzer; import android.databinding.tool.util.L; import android.databinding.tool.util.Preconditions; -import android.databinding.tool.util.StringUtils; import java.util.HashMap; import java.util.Map; @@ -54,6 +53,11 @@ public abstract class ModelAnalyzer { public static final String OBSERVABLE_MAP_CLASS_NAME = "android.databinding.ObservableMap"; + public static final String LIVE_DATA_CLASS_NAME = "android.arch.lifecycle.LiveData"; + + public static final String MUTABLE_LIVE_DATA_CLASS_NAME = + "android.arch.lifecycle.MutableLiveData"; + public static final String[] OBSERVABLE_FIELDS = { "android.databinding.ObservableBoolean", "android.databinding.ObservableByte", @@ -79,6 +83,8 @@ public abstract class ModelAnalyzer { private ModelClass mObservableType; private ModelClass mObservableListType; private ModelClass mObservableMapType; + private ModelClass mLiveDataType; + private ModelClass mMutableLiveDataType; private ModelClass[] mObservableFieldTypes; private ModelClass mViewBindingType; private ModelClass mViewStubType; @@ -300,6 +306,20 @@ public abstract class ModelAnalyzer { return mObservableMapType; } + ModelClass getLiveDataType() { + if (mLiveDataType == null) { + mLiveDataType = loadClassErasure(LIVE_DATA_CLASS_NAME); + } + return mLiveDataType; + } + + ModelClass getMutableLiveDataType() { + if (mMutableLiveDataType == null) { + mMutableLiveDataType = loadClassErasure(MUTABLE_LIVE_DATA_CLASS_NAME); + } + return mMutableLiveDataType; + } + ModelClass getViewDataBindingType() { if (mViewBindingType == null) { mViewBindingType = findClass(VIEW_DATA_BINDING, null); diff --git a/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java b/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java index 47cd3c43..e4351222 100644 --- a/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java +++ b/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java @@ -203,10 +203,11 @@ public abstract class ModelClass { */ public boolean isObservable() { ModelAnalyzer modelAnalyzer = ModelAnalyzer.getInstance(); - return modelAnalyzer.getObservableType().isAssignableFrom(this) || - modelAnalyzer.getObservableListType().isAssignableFrom(this) || - modelAnalyzer.getObservableMapType().isAssignableFrom(this); - + return modelAnalyzer.getObservableType().isAssignableFrom(this) + || modelAnalyzer.getObservableListType().isAssignableFrom(this) + || modelAnalyzer.getObservableMapType().isAssignableFrom(this) + || (modelAnalyzer.getLiveDataType() != null + && modelAnalyzer.getLiveDataType().isAssignableFrom(this)); } /** @@ -224,6 +225,50 @@ public abstract class ModelClass { } /** + * @return whether or not this is a LiveData + */ + public boolean isLiveData() { + ModelClass liveDataType = ModelAnalyzer.getInstance().getLiveDataType(); + return liveDataType != null && liveDataType.isAssignableFrom(erasure()); + } + + /** + * @return whether or not this is a MutableLiveData + */ + public boolean isMutableLiveData() { + ModelClass mutableLiveDataType = ModelAnalyzer.getInstance().getMutableLiveDataType(); + return mutableLiveDataType != null && mutableLiveDataType.isAssignableFrom(erasure()); + } + + /** + * @return the name of the simple getter method when this is an ObservableField or LiveData or + * {@code null} for any other type + */ + public String getObservableGetterName() { + if (isObservableField()) { + return "get"; + } else if (isLiveData()) { + return "getValue"; + } else { + return null; + } + } + + /** + * @return the name of the simple setter method when this is an ObservableField or + * MutableLiveData or {@code null} for any other type. + */ + public String getObservableSetterName() { + if (isObservableField()) { + return "set"; + } else if (isMutableLiveData()) { + return "setValue"; + } else { + return null; + } + } + + /** * @return whether or not this ModelClass represents a void */ public abstract boolean isVoid(); diff --git a/compiler/src/main/java/android/databinding/tool/reflection/ModelMethod.java b/compiler/src/main/java/android/databinding/tool/reflection/ModelMethod.java index a7109277..b83ad214 100644 --- a/compiler/src/main/java/android/databinding/tool/reflection/ModelMethod.java +++ b/compiler/src/main/java/android/databinding/tool/reflection/ModelMethod.java @@ -99,8 +99,9 @@ public abstract class ModelMethod { parametersMatch = false; if (unwrapObservableFields) { // try unwrapping an observable field argument, if possible - while (arg.isObservableField()) { - arg = arg.getMethod("get", Collections.EMPTY_LIST, + String simpleGetterName; + while ((simpleGetterName = arg.getObservableGetterName()) != null) { + arg = arg.getMethod(simpleGetterName, Collections.EMPTY_LIST, false, false, false).getReturnType(); if (parameterType.isAssignableFrom(arg) || isImplicitConversion(arg, parameterType)) { diff --git a/compiler/src/main/java/android/databinding/tool/store/SetterStore.java b/compiler/src/main/java/android/databinding/tool/store/SetterStore.java index 22220427..8c8fd4af 100644 --- a/compiler/src/main/java/android/databinding/tool/store/SetterStore.java +++ b/compiler/src/main/java/android/databinding/tool/store/SetterStore.java @@ -256,12 +256,6 @@ public class SetterStore { } adapters.put(key, new MethodDescription(bindingMethod, 1, takesComponent)); - - if (mClassAnalyzer.findClass(value, null).isObservableField()) { - ExecutableType executableType = (ExecutableType) bindingMethod.asType(); - L.w(bindingMethod, ErrorMessages.OBSERVABLE_FIELD_USE, - AnnotationTypeUtil.getInstance().toJava(bindingMethod, executableType)); - } } public void addInverseAdapter(ProcessingEnvironment processingEnv, String attribute, @@ -358,16 +352,6 @@ public class SetterStore { MethodDescription methodDescription = new MethodDescription(bindingMethod, attributes.length, takesComponent); mStore.multiValueAdapters.put(key, methodDescription); - - final long numObservableFields = Arrays.stream(key.parameterTypes) - .map(type -> mClassAnalyzer.findClass(type, null)) - .filter(type -> type.isObservableField()) - .count(); - if (numObservableFields > 0) { - ExecutableType executableType = (ExecutableType) bindingMethod.asType(); - L.w(bindingMethod, ErrorMessages.OBSERVABLE_FIELD_USE, - AnnotationTypeUtil.getInstance().toJava(bindingMethod, executableType)); - } } private static void testRepeatedAttributes(MultiValueAdapterKey key, ExecutableElement method) { |