summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeorge Mount <mount@google.com>2016-09-28 19:21:41 +0000
committerGeorge Mount <mount@google.com>2016-09-28 12:48:34 -0700
commite468b817509c5c7dd657a0cbc997452e1106aed8 (patch)
treeaa194fa6a5d607d7eda7d4e11dfc2578a7eec05b
parent83bcfb874866a2f6f74f7700fe2c67f0af2a3c20 (diff)
downloaddata-binding-e468b817509c5c7dd657a0cbc997452e1106aed8.tar.gz
Re-adds "Add support for dependent bindable properties."
This reverts commit 729298e98a96730a9fbcf8c1d2575a8f0da7cc70. This also fixes the build break caused by the added abstract methods in ModelMethod and ModelField. Change-Id: I4d7d5085a1a2e02ede0662591d8ccd79770c5fb3
-rw-r--r--baseLibrary/src/main/java/android/databinding/Bindable.java21
-rw-r--r--compilationTests/src/test/java/android/databinding/compilationTest/SimpleCompilationTest.java80
-rw-r--r--compilationTests/src/test/resources/android/databinding/compilationTest/badJava/ObservableFieldDependent.java28
-rw-r--r--compilationTests/src/test/resources/android/databinding/compilationTest/badJava/ObservableNoDependent.java27
-rw-r--r--compilationTests/src/test/resources/android/databinding/compilationTest/badJava/ObservableNotBindableDependent.java31
-rw-r--r--compilationTests/src/test/resources/layout/layout_with_dependency.xml25
-rw-r--r--compiler/src/main/java/android/databinding/tool/Binding.java2
-rw-r--r--compiler/src/main/java/android/databinding/tool/expr/FieldAccessExpr.java69
-rw-r--r--compiler/src/main/java/android/databinding/tool/expr/MethodCallExpr.java2
-rw-r--r--compiler/src/main/java/android/databinding/tool/reflection/Callable.java7
-rw-r--r--compiler/src/main/java/android/databinding/tool/reflection/InjectedField.java2
-rw-r--r--compiler/src/main/java/android/databinding/tool/reflection/InjectedMethod.java2
-rw-r--r--compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java13
-rw-r--r--compiler/src/main/java/android/databinding/tool/reflection/ModelField.java9
-rw-r--r--compiler/src/main/java/android/databinding/tool/reflection/ModelMethod.java7
-rw-r--r--compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationField.java5
-rw-r--r--compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationMethod.java5
-rw-r--r--compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt5
-rw-r--r--compiler/src/test/java/android/databinding/tool/reflection/java/JavaField.java5
-rw-r--r--compiler/src/test/java/android/databinding/tool/reflection/java/JavaMethod.java5
-rw-r--r--compilerCommon/src/main/java/android/databinding/tool/util/StringUtils.java17
-rw-r--r--integration-tests/MultiModuleTestApp/testlibrary/build.gradle2
-rw-r--r--integration-tests/TestApp/app/src/androidTest/AndroidManifest.xml4
-rw-r--r--integration-tests/TestApp/app/src/androidTestApi9/java/android/databinding/testapp/BindingReliesOnTest.java81
-rw-r--r--integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/BasicObject.java26
-rw-r--r--integration-tests/TestApp/app/src/main/res/layout/relies_on.xml50
26 files changed, 517 insertions, 13 deletions
diff --git a/baseLibrary/src/main/java/android/databinding/Bindable.java b/baseLibrary/src/main/java/android/databinding/Bindable.java
index 45436d6c..2efd2824 100644
--- a/baseLibrary/src/main/java/android/databinding/Bindable.java
+++ b/baseLibrary/src/main/java/android/databinding/Bindable.java
@@ -26,10 +26,31 @@ import java.lang.annotation.Target;
* The Bindable annotation should be applied to any getter accessor method of an
* {@link Observable} class. Bindable will generate a field in the BR class to identify
* the field that has changed.
+ * <p>
+ * When applied to an accessor method, the Bindable annotation takes an optional
+ * list of property names that it depends on. If there is a change notification of any of the
+ * listed properties, this value will also be considered dirty and be refreshed. For example:
+ * <p><pre>
+ * <code>&commat;Bindable
+ * public void getFirstName() { return this.firstName; }
+ *
+ * &commat;Bindable
+ * public void getLastName() { return this.lastName; }
+ *
+ * &commat;Bindable({"firstName", "lastName"}}
+ * public void getName() { return this.firstName + ' ' + this.lastName; }
+ * </code></pre>
+ * <p>
+ * Whenever either {@code firstName} or {@code lastName} has a change notification, {@code name}
+ * will also be considered dirty. This does not mean that
+ * {@link OnPropertyChangedCallback#onPropertyChanged(Observable, int)} will be notified for
+ * {@code BR.name}, only that binding expressions containing {@code name} will be dirtied and
+ * refreshed.
*
* @see OnPropertyChangedCallback#onPropertyChanged(Observable, int)
*/
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME) // this is necessary for java analyzer to work
public @interface Bindable {
+ String[] value() default {};
}
diff --git a/compilationTests/src/test/java/android/databinding/compilationTest/SimpleCompilationTest.java b/compilationTests/src/test/java/android/databinding/compilationTest/SimpleCompilationTest.java
index 4b8ec9f1..516452ad 100644
--- a/compilationTests/src/test/java/android/databinding/compilationTest/SimpleCompilationTest.java
+++ b/compilationTests/src/test/java/android/databinding/compilationTest/SimpleCompilationTest.java
@@ -392,4 +392,84 @@ public class SimpleCompilationTest extends BaseCompilationTest {
assertNotNull("Method " + method + " not found", modelMethod);
}
}
+
+ @Test
+ public void testDependantDoesNotExist() throws Throwable {
+ prepareProject();
+ copyResourceTo("/layout/layout_with_dependency.xml",
+ "/app/src/main/res/layout/layout_with_dependency.xml");
+ copyResourceTo(
+ "/android/databinding/compilationTest/badJava/ObservableNoDependent.java",
+ "/app/src/main/java/android/databinding/compilationTest/badJava/MyObservable.java");
+
+ CompilationResult result = runGradle("assembleDebug");
+ assertNotEquals(0, result.resultCode);
+ List<ScopedException> errors = ScopedException.extractErrors(result.error);
+ assertEquals(result.error, 1, errors.size());
+ final ScopedException ex = errors.get(0);
+ final ScopedErrorReport report = ex.getScopedErrorReport();
+ final File errorFile = new File(report.getFilePath());
+ assertTrue(errorFile.exists());
+ assertEquals(new File(testFolder,
+ "/app/src/main/res/layout/layout_with_dependency.xml")
+ .getCanonicalFile(),
+ errorFile.getCanonicalFile());
+ assertEquals("Could not find dependent property 'notExist' referenced in " +
+ "@Bindable annotation on " +
+ "android.databinding.compilationTest.badJava.MyObservable.getField",
+ ex.getBareMessage());
+ }
+
+ @Test
+ public void testDependantNotBindable() throws Throwable {
+ prepareProject();
+ copyResourceTo("/layout/layout_with_dependency.xml",
+ "/app/src/main/res/layout/layout_with_dependency.xml");
+ copyResourceTo(
+ "/android/databinding/compilationTest/badJava/ObservableNotBindableDependent.java",
+ "/app/src/main/java/android/databinding/compilationTest/badJava/MyObservable.java");
+
+ CompilationResult result = runGradle("assembleDebug");
+ assertNotEquals(0, result.resultCode);
+ List<ScopedException> errors = ScopedException.extractErrors(result.error);
+ assertEquals(result.error, 1, errors.size());
+ final ScopedException ex = errors.get(0);
+ final ScopedErrorReport report = ex.getScopedErrorReport();
+ final File errorFile = new File(report.getFilePath());
+ assertTrue(errorFile.exists());
+ assertEquals(new File(testFolder,
+ "/app/src/main/res/layout/layout_with_dependency.xml")
+ .getCanonicalFile(),
+ errorFile.getCanonicalFile());
+ assertEquals("The dependent property 'otherField' referenced in " +
+ "@Bindable annotation on " +
+ "android.databinding.compilationTest.badJava.MyObservable.getField " +
+ "must be annotated with @Bindable", ex.getBareMessage());
+ }
+
+ @Test
+ public void testDependantField() throws Throwable {
+ prepareProject();
+ copyResourceTo("/layout/layout_with_dependency.xml",
+ "/app/src/main/res/layout/layout_with_dependency.xml");
+ copyResourceTo(
+ "/android/databinding/compilationTest/badJava/ObservableFieldDependent.java",
+ "/app/src/main/java/android/databinding/compilationTest/badJava/MyObservable.java");
+
+ CompilationResult result = runGradle("assembleDebug");
+ assertNotEquals(0, result.resultCode);
+ List<ScopedException> errors = ScopedException.extractErrors(result.error);
+ assertEquals(result.error, 1, errors.size());
+ final ScopedException ex = errors.get(0);
+ final ScopedErrorReport report = ex.getScopedErrorReport();
+ final File errorFile = new File(report.getFilePath());
+ assertTrue(errorFile.exists());
+ assertEquals(new File(testFolder,
+ "/app/src/main/res/layout/layout_with_dependency.xml")
+ .getCanonicalFile(),
+ errorFile.getCanonicalFile());
+ assertEquals("Bindable annotation with property names is only supported on methods. " +
+ "Field 'android.databinding.compilationTest.badJava.MyObservable.field' has " +
+ "@Bindable(\"otherField\")", ex.getBareMessage());
+ }
}
diff --git a/compilationTests/src/test/resources/android/databinding/compilationTest/badJava/ObservableFieldDependent.java b/compilationTests/src/test/resources/android/databinding/compilationTest/badJava/ObservableFieldDependent.java
new file mode 100644
index 00000000..78c19659
--- /dev/null
+++ b/compilationTests/src/test/resources/android/databinding/compilationTest/badJava/ObservableFieldDependent.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.compilationTest.badJava;
+
+import android.databinding.BaseObservable;
+import android.databinding.Bindable;
+
+public class MyObservable extends BaseObservable {
+ @Bindable
+ public String otherField;
+
+ @Bindable("otherField")
+ public String field;
+}
diff --git a/compilationTests/src/test/resources/android/databinding/compilationTest/badJava/ObservableNoDependent.java b/compilationTests/src/test/resources/android/databinding/compilationTest/badJava/ObservableNoDependent.java
new file mode 100644
index 00000000..5138b92c
--- /dev/null
+++ b/compilationTests/src/test/resources/android/databinding/compilationTest/badJava/ObservableNoDependent.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.compilationTest.badJava;
+
+import android.databinding.BaseObservable;
+import android.databinding.Bindable;
+
+public class MyObservable extends BaseObservable {
+ @Bindable("notExist")
+ public String getField() {
+ return "Hello";
+ }
+}
diff --git a/compilationTests/src/test/resources/android/databinding/compilationTest/badJava/ObservableNotBindableDependent.java b/compilationTests/src/test/resources/android/databinding/compilationTest/badJava/ObservableNotBindableDependent.java
new file mode 100644
index 00000000..7992fe3e
--- /dev/null
+++ b/compilationTests/src/test/resources/android/databinding/compilationTest/badJava/ObservableNotBindableDependent.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.databinding.compilationTest.badJava;
+
+import android.databinding.BaseObservable;
+import android.databinding.Bindable;
+
+public class MyObservable extends BaseObservable {
+ @Bindable("otherField")
+ public String getField() {
+ return "Hello";
+ }
+
+ public String otherField() {
+ return "World";
+ }
+}
diff --git a/compilationTests/src/test/resources/layout/layout_with_dependency.xml b/compilationTests/src/test/resources/layout/layout_with_dependency.xml
new file mode 100644
index 00000000..4c13e40b
--- /dev/null
+++ b/compilationTests/src/test/resources/layout/layout_with_dependency.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+ <data>
+ <variable name="obj" type="android.databinding.compilationTest.badJava.MyObservable"/>
+ </data>
+ <TextView android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@{obj.field}"/>
+</layout> \ No newline at end of file
diff --git a/compiler/src/main/java/android/databinding/tool/Binding.java b/compiler/src/main/java/android/databinding/tool/Binding.java
index 2395f23a..5f193a3e 100644
--- a/compiler/src/main/java/android/databinding/tool/Binding.java
+++ b/compiler/src/main/java/android/databinding/tool/Binding.java
@@ -156,7 +156,7 @@ public class Binding implements LocationScopeProvider {
// Now try with the value object directly
mSetterCall = SetterStore.get(modelAnalyzer).getSetterCall(mName,
viewType, mExpr.getResolvedType(), mExpr.getModel().getImports());
- if (warn) {
+ if (warn && mSetterCall != null) {
L.w(ErrorMessages.OBSERVABLE_FIELD_USE, mSetterCall.getDescription());
}
}
diff --git a/compiler/src/main/java/android/databinding/tool/expr/FieldAccessExpr.java b/compiler/src/main/java/android/databinding/tool/expr/FieldAccessExpr.java
index be48cd4a..d4a54de0 100644
--- a/compiler/src/main/java/android/databinding/tool/expr/FieldAccessExpr.java
+++ b/compiler/src/main/java/android/databinding/tool/expr/FieldAccessExpr.java
@@ -16,6 +16,7 @@
package android.databinding.tool.expr;
+import android.databinding.Bindable;
import android.databinding.tool.Binding;
import android.databinding.tool.BindingTarget;
import android.databinding.tool.InverseBinding;
@@ -26,16 +27,20 @@ import android.databinding.tool.reflection.Callable;
import android.databinding.tool.reflection.Callable.Type;
import android.databinding.tool.reflection.ModelAnalyzer;
import android.databinding.tool.reflection.ModelClass;
+import android.databinding.tool.reflection.ModelField;
import android.databinding.tool.store.SetterStore;
import android.databinding.tool.store.SetterStore.BindingGetterCall;
import android.databinding.tool.util.BrNameUtil;
import android.databinding.tool.util.L;
import android.databinding.tool.util.Preconditions;
+import android.databinding.tool.util.StringUtils;
import android.databinding.tool.writer.KCode;
import com.google.common.collect.Lists;
+import java.util.Arrays;
import java.util.List;
+import java.util.stream.Collectors;
public class FieldAccessExpr extends MethodBaseExpr {
// notification name for the field. Important when we map this to a method w/ different name
@@ -157,6 +162,70 @@ public class FieldAccessExpr extends MethodBaseExpr {
}
}
+ /**
+ * @return The list of all properties that dirty this expression. This will also contain the
+ * the BR identifier for this property if there is one. If the property is not bindable,
+ * it will contain an empty list.
+ */
+ public String[] getDirtyingProperties() {
+ String[] names = null;
+ if (mGetter != null && mGetter.canBeInvalidated()) {
+ if (mGetter.type == Type.FIELD) {
+ if (mGetter.bindableAnnotation != null &&
+ mGetter.bindableAnnotation.value().length != 0) {
+ L.e("Bindable annotation with property names is only supported on methods. " +
+ "Field '%s.%s' has @Bindable(\"%s\")",
+ getTarget().getResolvedType().toJavaCode(), mGetter.name,
+ StringUtils.join(mGetter.bindableAnnotation.value(), "\", \""));
+ }
+ } else if (mGetter.method != null && mGetter.canBeInvalidated() &&
+ mGetter.bindableAnnotation != null) {
+ String[] dependencies = mGetter.bindableAnnotation.value();
+ validateDependencies(dependencies);
+ names = new String[dependencies.length + 1];
+ for (int i = 0; i < dependencies.length; i++) {
+ names[i] = "BR." + dependencies[i];
+ }
+ names[dependencies.length] = getBrName();
+ }
+ }
+ if (names == null) {
+ String br = getBrName();
+ if (br == null) {
+ names = new String[0];
+ } else {
+ names = new String[] { br };
+ }
+ }
+ return names;
+ }
+
+ /**
+ * Validates the dependent properties -- they must exist and be Bindable.
+ */
+ private void validateDependencies(String[] dependencies) {
+ try {
+ Scope.enter(this);
+ Arrays.stream(dependencies).forEach(field -> {
+ ModelClass resolvedType = getTarget().getResolvedType();
+ Callable getter = resolvedType.findGetterOrField(field, mGetter.isStatic());
+ if (getter == null) {
+ L.e("Could not find dependent property '%s' referenced in " +
+ "@Bindable annotation on %s.%s", field,
+ mGetter.method.getDeclaringClass().toJavaCode(),
+ mGetter.method.getName());
+ } else if (!getter.canBeInvalidated()) {
+ L.e("The dependent property '%s' referenced in @Bindable annotation on " +
+ "%s.%s must be annotated with @Bindable", field,
+ mGetter.method.getDeclaringClass().toJavaCode(),
+ mGetter.method.getName());
+ }
+ });
+ } finally {
+ Scope.exit();
+ }
+ }
+
@Override
protected ModelClass resolveType(ModelAnalyzer modelAnalyzer) {
if (mIsListener) {
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 dfeec181..dea19925 100644
--- a/compiler/src/main/java/android/databinding/tool/expr/MethodCallExpr.java
+++ b/compiler/src/main/java/android/databinding/tool/expr/MethodCallExpr.java
@@ -186,7 +186,7 @@ public class MethodCallExpr extends Expr {
flags |= STATIC;
}
mGetter = new Callable(Type.METHOD, mMethod.getName(), null, mMethod.getReturnType(args),
- mMethod.getParameterTypes().length, flags, mMethod);
+ mMethod.getParameterTypes().length, flags, mMethod, null);
}
return mGetter.resolvedType;
}
diff --git a/compiler/src/main/java/android/databinding/tool/reflection/Callable.java b/compiler/src/main/java/android/databinding/tool/reflection/Callable.java
index 5088523d..54db94e3 100644
--- a/compiler/src/main/java/android/databinding/tool/reflection/Callable.java
+++ b/compiler/src/main/java/android/databinding/tool/reflection/Callable.java
@@ -15,6 +15,8 @@
*/
package android.databinding.tool.reflection;
+import android.databinding.Bindable;
+
import org.jetbrains.annotations.Nullable;
public class Callable {
@@ -42,8 +44,10 @@ public class Callable {
private final int mParameterCount;
+ public final Bindable bindableAnnotation;
+
public Callable(Type type, String name, String setterName, ModelClass resolvedType,
- int parameterCount, int flags, ModelMethod method) {
+ int parameterCount, int flags, ModelMethod method, Bindable bindable) {
this.type = type;
this.name = name;
this.resolvedType = resolvedType;
@@ -51,6 +55,7 @@ public class Callable {
this.setterName = setterName;
mFlags = flags;
this.method = method;
+ this.bindableAnnotation = bindable;
}
public String getTypeCodeName() {
diff --git a/compiler/src/main/java/android/databinding/tool/reflection/InjectedField.java b/compiler/src/main/java/android/databinding/tool/reflection/InjectedField.java
index 85719f1c..82db70b1 100644
--- a/compiler/src/main/java/android/databinding/tool/reflection/InjectedField.java
+++ b/compiler/src/main/java/android/databinding/tool/reflection/InjectedField.java
@@ -16,6 +16,8 @@
package android.databinding.tool.reflection;
+import android.databinding.Bindable;
+
import java.util.Map;
/**
diff --git a/compiler/src/main/java/android/databinding/tool/reflection/InjectedMethod.java b/compiler/src/main/java/android/databinding/tool/reflection/InjectedMethod.java
index f47442b7..a91ae109 100644
--- a/compiler/src/main/java/android/databinding/tool/reflection/InjectedMethod.java
+++ b/compiler/src/main/java/android/databinding/tool/reflection/InjectedMethod.java
@@ -16,6 +16,8 @@
package android.databinding.tool.reflection;
+import android.databinding.Bindable;
+
import java.util.List;
import java.util.Map;
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 813b72e1..d408405e 100644
--- a/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java
+++ b/compiler/src/main/java/android/databinding/tool/reflection/ModelClass.java
@@ -15,6 +15,7 @@
*/
package android.databinding.tool.reflection;
+import android.databinding.Bindable;
import android.databinding.tool.reflection.Callable.Type;
import android.databinding.tool.util.L;
import android.databinding.tool.util.StringUtils;
@@ -417,7 +418,7 @@ public abstract class ModelClass {
public Callable findGetterOrField(String name, boolean staticOnly) {
if ("length".equals(name) && isArray()) {
return new Callable(Type.FIELD, name, null,
- ModelAnalyzer.getInstance().loadPrimitive("int"), 0, 0, null);
+ ModelAnalyzer.getInstance().loadPrimitive("int"), 0, 0, null, null);
}
String capitalized = StringUtils.capitalize(name);
String[] methodNames = {
@@ -435,8 +436,10 @@ public abstract class ModelClass {
if (method.isStatic()) {
flags |= STATIC;
}
+ final Bindable bindable;
if (method.isBindable()) {
flags |= CAN_BE_INVALIDATED;
+ bindable = method.getBindableAnnotation();
} else {
// if method is not bindable, look for a backing field
final ModelField backingField = getField(name, true, method.isStatic());
@@ -444,13 +447,16 @@ public abstract class ModelClass {
backingField == null ? "NOT FOUND" : backingField.getName());
if (backingField != null && backingField.isBindable()) {
flags |= CAN_BE_INVALIDATED;
+ bindable = backingField.getBindableAnnotation();
+ } else {
+ bindable = null;
}
}
final ModelMethod setterMethod = findSetter(method, name);
final String setterName = setterMethod == null ? null : setterMethod.getName();
final Callable result = new Callable(Callable.Type.METHOD, methodName,
setterName, method.getReturnType(null), method.getParameterTypes().length,
- flags, method);
+ flags, method, bindable);
return result;
}
}
@@ -484,7 +490,8 @@ public abstract class ModelClass {
if (publicField.isBindable()) {
flags |= CAN_BE_INVALIDATED;
}
- return new Callable(Callable.Type.FIELD, name, setterFieldName, fieldType, 0, flags, null);
+ return new Callable(Callable.Type.FIELD, name, setterFieldName, fieldType, 0, flags, null,
+ publicField.getBindableAnnotation());
}
public ModelMethod findInstanceGetter(String name) {
diff --git a/compiler/src/main/java/android/databinding/tool/reflection/ModelField.java b/compiler/src/main/java/android/databinding/tool/reflection/ModelField.java
index 0cde85b0..2baef1e8 100644
--- a/compiler/src/main/java/android/databinding/tool/reflection/ModelField.java
+++ b/compiler/src/main/java/android/databinding/tool/reflection/ModelField.java
@@ -15,6 +15,8 @@
*/
package android.databinding.tool.reflection;
+import android.databinding.Bindable;
+
public abstract class ModelField {
/**
@@ -46,4 +48,11 @@ public abstract class ModelField {
* @return The declared type of the field variable.
*/
public abstract ModelClass getFieldType();
+
+ /**
+ * @return the Bindable annotation on the field or null if there isn't one.
+ */
+ public Bindable getBindableAnnotation() {
+ return null;
+ }
}
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 01500706..5605245a 100644
--- a/compiler/src/main/java/android/databinding/tool/reflection/ModelMethod.java
+++ b/compiler/src/main/java/android/databinding/tool/reflection/ModelMethod.java
@@ -44,6 +44,13 @@ public abstract class ModelMethod {
public abstract boolean isBindable();
/**
+ * @return the Bindable annotation on the method or null if it doesn't exist.
+ */
+ public Bindable getBindableAnnotation() {
+ return null;
+ }
+
+ /**
* Since when this method is available. Important for Binding expressions so that we don't
* call non-existing APIs when setting UI.
*
diff --git a/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationField.java b/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationField.java
index 9373c6c9..be860570 100644
--- a/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationField.java
+++ b/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationField.java
@@ -70,6 +70,11 @@ class AnnotationField extends ModelField {
}
@Override
+ public Bindable getBindableAnnotation() {
+ return mField.getAnnotation(Bindable.class);
+ }
+
+ @Override
public boolean equals(Object obj) {
if (obj instanceof AnnotationField) {
AnnotationField that = (AnnotationField) obj;
diff --git a/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationMethod.java b/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationMethod.java
index 66c1dbc5..839aa282 100644
--- a/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationMethod.java
+++ b/compiler/src/main/java/android/databinding/tool/reflection/annotation/AnnotationMethod.java
@@ -147,6 +147,11 @@ class AnnotationMethod extends ModelMethod {
}
@Override
+ public Bindable getBindableAnnotation() {
+ return mExecutableElement.getAnnotation(Bindable.class);
+ }
+
+ @Override
public int getMinApi() {
if (mApiLevel == -1) {
mApiLevel = SdkUtil.getMinApi(this);
diff --git a/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt b/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt
index 3820084d..2da9367d 100644
--- a/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt
+++ b/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt
@@ -749,14 +749,15 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) {
block("switch (fieldId)", {
val accessedFields: List<FieldAccessExpr> = it.parents.filterIsInstance(FieldAccessExpr::class.java)
accessedFields.filter { it.isUsed && it.hasBindableAnnotations() }
- .groupBy { it.brName }
+ .flatMap { expr -> expr.dirtyingProperties.map { Pair(it, expr)} }
+ .groupBy { it.first }
.forEach {
// If two expressions look different but resolve to the same method,
// we are not yet able to merge them. This is why we merge their
// flags below.
block("case ${it.key}:") {
block("synchronized(this)") {
- val flagSet = it.value.foldRight(FlagSet()) { l, r -> l.invalidateFlagSet.or(r) }
+ val flagSet = it.value.foldRight(FlagSet()) { l, r -> l.second.invalidateFlagSet.or(r) }
mDirtyFlags.mapOr(flagSet) { suffix, index ->
tab("${mDirtyFlags.localValue(index)} |= ${flagSet.localValue(index)};")
diff --git a/compiler/src/test/java/android/databinding/tool/reflection/java/JavaField.java b/compiler/src/test/java/android/databinding/tool/reflection/java/JavaField.java
index 6821f168..41f68488 100644
--- a/compiler/src/test/java/android/databinding/tool/reflection/java/JavaField.java
+++ b/compiler/src/test/java/android/databinding/tool/reflection/java/JavaField.java
@@ -56,4 +56,9 @@ public class JavaField extends ModelField {
public ModelClass getFieldType() {
return new JavaClass(mField.getType());
}
+
+ @Override
+ public Bindable getBindableAnnotation() {
+ return mField.getAnnotation(Bindable.class);
+ }
}
diff --git a/compiler/src/test/java/android/databinding/tool/reflection/java/JavaMethod.java b/compiler/src/test/java/android/databinding/tool/reflection/java/JavaMethod.java
index b7b626c1..46f7bf3a 100644
--- a/compiler/src/test/java/android/databinding/tool/reflection/java/JavaMethod.java
+++ b/compiler/src/test/java/android/databinding/tool/reflection/java/JavaMethod.java
@@ -88,6 +88,11 @@ public class JavaMethod extends ModelMethod {
}
@Override
+ public Bindable getBindableAnnotation() {
+ return mMethod.getAnnotation(Bindable.class);
+ }
+
+ @Override
public int getMinApi() {
return SdkUtil.getMinApi(this);
}
diff --git a/compilerCommon/src/main/java/android/databinding/tool/util/StringUtils.java b/compilerCommon/src/main/java/android/databinding/tool/util/StringUtils.java
index 5774183a..12b61d32 100644
--- a/compilerCommon/src/main/java/android/databinding/tool/util/StringUtils.java
+++ b/compilerCommon/src/main/java/android/databinding/tool/util/StringUtils.java
@@ -19,6 +19,9 @@ package android.databinding.tool.util;
import com.google.common.base.StandardSystemProperty;
import com.google.common.base.Strings;
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
public class StringUtils {
public static final String LINE_SEPARATOR = StandardSystemProperty.LINE_SEPARATOR.value();
@@ -74,7 +77,19 @@ public class StringUtils {
.replace(LFEED_ENTITY, "\n");
}
- private StringUtils() {
+ /**
+ * Merges the array of strings together using a separator string.
+ * @param strings The array of strings to merge into one string.
+ * @param sep The separator between the string values.
+ * @return The merge of all string values, separated by sep or null if strings is null
+ */
+ public static <T extends CharSequence> String join(T[] strings, String sep) {
+ if (strings == null) {
+ return null;
+ }
+ return Arrays.stream(strings).collect(Collectors.joining(sep));
}
+ private StringUtils() {
+ }
}
diff --git a/integration-tests/MultiModuleTestApp/testlibrary/build.gradle b/integration-tests/MultiModuleTestApp/testlibrary/build.gradle
index 38bf3a6c..62f97fb8 100644
--- a/integration-tests/MultiModuleTestApp/testlibrary/build.gradle
+++ b/integration-tests/MultiModuleTestApp/testlibrary/build.gradle
@@ -21,7 +21,7 @@ android {
buildToolsVersion dataBindingConfig.buildToolsVersion
defaultConfig {
- minSdkVersion 7
+ minSdkVersion 9
targetSdkVersion dataBindingConfig.targetSdkVersion
versionCode 1
versionName "1.0"
diff --git a/integration-tests/TestApp/app/src/androidTest/AndroidManifest.xml b/integration-tests/TestApp/app/src/androidTest/AndroidManifest.xml
index 2acf5dc4..b8bb7cc4 100644
--- a/integration-tests/TestApp/app/src/androidTest/AndroidManifest.xml
+++ b/integration-tests/TestApp/app/src/androidTest/AndroidManifest.xml
@@ -13,7 +13,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="android.databinding.testapp">
- <uses-sdk android:minSdkVersion="7" tools:overrideLibrary="android.support.test,
+ <uses-sdk android:minSdkVersion="9" tools:overrideLibrary="android.support.test,
android.app, android.support.test.rule, android.support.test.espresso,
android.support.test.espresso.idling"/>
<application android:allowBackup="true"
@@ -25,4 +25,4 @@
android:screenOrientation="landscape"/>
</application>
-</manifest> \ No newline at end of file
+</manifest>
diff --git a/integration-tests/TestApp/app/src/androidTestApi9/java/android/databinding/testapp/BindingReliesOnTest.java b/integration-tests/TestApp/app/src/androidTestApi9/java/android/databinding/testapp/BindingReliesOnTest.java
new file mode 100644
index 00000000..78e4cffb
--- /dev/null
+++ b/integration-tests/TestApp/app/src/androidTestApi9/java/android/databinding/testapp/BindingReliesOnTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.databinding.testapp;
+
+import android.databinding.testapp.databinding.ReliesOnBinding;
+import android.databinding.testapp.BR;
+import android.databinding.testapp.vo.BasicObject;
+import android.test.UiThreadTest;
+
+public class BindingReliesOnTest extends BaseDataBinderTest<ReliesOnBinding> {
+
+
+ public BindingReliesOnTest() {
+ super(ReliesOnBinding.class);
+ }
+
+ protected void setUp() throws Exception {
+ initBinder(null);
+ }
+
+ @UiThreadTest
+ public void testReliesOn() {
+ BasicObject obj = new BasicObject();
+ obj.setField1("Hello");
+ obj.setField2("World");
+ obj.field3 = "1";
+ mBinder.setObj(obj);
+ mBinder.executePendingBindings();
+
+ assertEquals("Hello", mBinder.field1.getText().toString());
+ assertEquals("World", mBinder.field2.getText().toString());
+ assertEquals("Hello World 1", mBinder.combo.getText().toString());
+ assertEquals("+1", mBinder.field4.getText().toString());
+
+ obj.setField1("Goodbye");
+ mBinder.executePendingBindings();
+
+ assertEquals("Goodbye", mBinder.field1.getText().toString());
+ assertEquals("World", mBinder.field2.getText().toString());
+ assertEquals("Goodbye World 1", mBinder.combo.getText().toString());
+ assertEquals("+1", mBinder.field4.getText().toString());
+
+ obj.setField2("Cruel");
+ mBinder.executePendingBindings();
+
+ assertEquals("Goodbye", mBinder.field1.getText().toString());
+ assertEquals("Cruel", mBinder.field2.getText().toString());
+ assertEquals("Goodbye Cruel 1", mBinder.combo.getText().toString());
+ assertEquals("+1", mBinder.field4.getText().toString());
+
+ obj.field3 = "World";
+ obj.notifyPropertyChanged(BR.field3);
+ mBinder.executePendingBindings();
+
+ assertEquals("Goodbye", mBinder.field1.getText().toString());
+ assertEquals("Cruel", mBinder.field2.getText().toString());
+ assertEquals("Goodbye Cruel World", mBinder.combo.getText().toString());
+ assertEquals("+World", mBinder.field4.getText().toString());
+
+ obj.setJoin("-");
+ mBinder.executePendingBindings();
+
+ assertEquals("Goodbye", mBinder.field1.getText().toString());
+ assertEquals("Cruel", mBinder.field2.getText().toString());
+ assertEquals("Goodbye-Cruel-World", mBinder.combo.getText().toString());
+ assertEquals("-World", mBinder.field4.getText().toString());
+ }
+}
diff --git a/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/BasicObject.java b/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/BasicObject.java
index 5f332ef6..f52faefc 100644
--- a/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/BasicObject.java
+++ b/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/BasicObject.java
@@ -25,6 +25,14 @@ public class BasicObject extends BaseObservable {
@Bindable
private String mField2;
+ @Bindable
+ public String field3;
+
+ @Bindable("field3")
+ private String mField4 = "+";
+
+ private String mJoin = " ";
+
public String getField1() {
return mField1;
}
@@ -40,7 +48,23 @@ public class BasicObject extends BaseObservable {
public void setField2(String field2) {
this.mField2 = field2;
- notifyPropertyChanged(BR.field1);
+ notifyPropertyChanged(BR.field2);
+ }
+
+ @Bindable({"field1", "field2", "field3"})
+ public String getCombo() {
+ return mField1 + mJoin + mField2 + mJoin + field3;
+ }
+
+ public void setJoin(String join) {
+ mJoin = join;
+ mField4 = join;
+ notifyPropertyChanged(BR.combo);
+ notifyPropertyChanged(BR.field4);
+ }
+
+ public String getField4() {
+ return mField4 + field3;
}
@Bindable
diff --git a/integration-tests/TestApp/app/src/main/res/layout/relies_on.xml b/integration-tests/TestApp/app/src/main/res/layout/relies_on.xml
new file mode 100644
index 00000000..e28b1b71
--- /dev/null
+++ b/integration-tests/TestApp/app/src/main/res/layout/relies_on.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<layout xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <data>
+ <variable
+ name="obj"
+ type="android.databinding.testapp.vo.BasicObject" />
+ </data>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/field1"
+ android:text="@{obj.field1}"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@+id/field2"
+ android:text="@{obj.field2}"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@+id/combo"
+ android:text="@{obj.combo}"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@+id/field4"
+ android:text="@{obj.field4}"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+</layout> \ No newline at end of file