diff options
10 files changed, 224 insertions, 58 deletions
diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/ExpressionVisitor.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/ExpressionVisitor.java index 879c439..a30f59b 100644 --- a/tools/data-binding/compiler/src/main/java/android/databinding/tool/ExpressionVisitor.java +++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/ExpressionVisitor.java @@ -93,7 +93,7 @@ public class ExpressionVisitor extends BindingExpressionBaseVisitor<Expr> { public Expr visitQuestionQuestionOp(@NotNull BindingExpressionParser.QuestionQuestionOpContext ctx) { final Expr left = ctx.left.accept(this); return mModel.ternary(mModel.comparison("==", left, mModel.symbol("null", Object.class)), - left, ctx.right.accept(this)); + ctx.right.accept(this), left); } @Override diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/Expr.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/Expr.java index a7cd412..09b96d8 100644 --- a/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/Expr.java +++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/Expr.java @@ -400,17 +400,12 @@ abstract public class Expr { return mRead; } - public boolean considerElevatingConditionals(Expr cond) { + public boolean considerElevatingConditionals(Expr justRead) { boolean elevated = false; for (Dependency dependency : mDependencies) { - if (dependency.isConditional() && dependency.getCondition() == cond) { + if (dependency.isConditional() && dependency.getCondition() == justRead) { dependency.elevate(); - // silent elevate because it is not necessary anymore (already calculated) - // but need to mark dependencies elevated so that we can decide to calculate - // this expression when all dependencies are elevated - if (!dependency.getOther().isRead()) { - elevated = true; - } + elevated = true; } } return elevated; @@ -433,7 +428,7 @@ abstract public class Expr { Predicate<Dependency> hasNestedCannotRead = new Predicate<Dependency>() { @Override public boolean apply(Dependency input) { - return input.getOther().hasNestedCannotRead(); + return input.isConditional() || input.getOther().hasNestedCannotRead(); } }; @@ -470,6 +465,9 @@ abstract public class Expr { } } + if (mRead) { + mShouldReadFlags = null; // if we've been marked as read, clear should read flags + } return mRead; } diff --git a/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/ExprModel.java b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/ExprModel.java index e3ec30a..0f8a935 100644 --- a/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/ExprModel.java +++ b/tools/data-binding/compiler/src/main/java/android/databinding/tool/expr/ExprModel.java @@ -26,6 +26,7 @@ import android.databinding.tool.util.L; import android.databinding.tool.writer.FlagSet; import java.util.ArrayList; +import java.util.Arrays; import java.util.BitSet; import java.util.HashMap; import java.util.List; @@ -437,12 +438,12 @@ public class ExprModel { return elevated; } - public Iterable<Expr> filterShouldRead(Iterable<Expr> exprs) { - return Iterables.filter(exprs, sShouldReadPred); + public static Iterable<Expr> filterShouldRead(Iterable<Expr> exprs) { + return toCollection(Iterables.filter(exprs, sShouldReadPred)); } - public Iterable<Expr> filterCanBeReadNow(Iterable<Expr> exprs) { - return Iterables.filter(exprs, sReadNowPred); + public static List<Expr> toCollection(Iterable<Expr> iterable) { + return Arrays.asList(Iterables.toArray(iterable, Expr.class)); } private static final Predicate<Expr> sShouldReadPred = new Predicate<Expr>() { diff --git a/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt b/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt index 228e759..763c94b 100644 --- a/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt +++ b/tools/data-binding/compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt @@ -679,7 +679,7 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) { } do { - val batch = model.filterShouldRead(model.getPendingExpressions()).toArrayList() + val batch = ExprModel.filterShouldRead(model.getPendingExpressions()).toArrayList() val mJustRead = arrayListOf<Expr>() while (!batch.none()) { val readNow = batch.filter { it.shouldReadNow(mJustRead) } @@ -758,8 +758,7 @@ class LayoutBinderWriter(val layoutBinder : LayoutBinder) { // create an if case for all dependencies that might be null val nullables = expr.getDependencies().filter { it.isMandatory() && it.getOther().getResolvedType().isNullable() - } - .map { it.getOther() } + }.map { it.getOther() } if (!expr.isEqualityCheck() && nullables.isNotEmpty()) { tab ("if ( ${nullables.map { "${it.localName} != null" }.joinToString(" && ")}) {") { tab("${expr.localName}").app(" = ", expr.toCode(true)).app(";") diff --git a/tools/data-binding/compiler/src/test/java/android/databinding/tool/expr/ExprModelTest.java b/tools/data-binding/compiler/src/test/java/android/databinding/tool/expr/ExprModelTest.java index ed9934d..7bccd6e 100644 --- a/tools/data-binding/compiler/src/test/java/android/databinding/tool/expr/ExprModelTest.java +++ b/tools/data-binding/compiler/src/test/java/android/databinding/tool/expr/ExprModelTest.java @@ -34,6 +34,8 @@ import android.databinding.tool.reflection.ModelClass; import android.databinding.tool.reflection.java.JavaAnalyzer; import android.databinding.tool.util.L; +import java.util.ArrayList; +import java.util.Arrays; import java.util.BitSet; import java.util.List; @@ -45,8 +47,11 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; public class ExprModelTest { + private static class DummyExpr extends Expr { + String mKey; + public DummyExpr(String key, DummyExpr... children) { super(children); mKey = key; @@ -76,7 +81,7 @@ public class ExprModelTest { protected void failed(Throwable e, Description description) { if (mExprModel != null && mExprModel.getFlagMapping() != null) { final String[] mapping = mExprModel.getFlagMapping(); - for (int i = 0; i < mapping.length; i ++) { + for (int i = 0; i < mapping.length; i++) { L.d("flag %d: %s", i, mapping[i]); } } @@ -96,6 +101,7 @@ public class ExprModelTest { assertSame(d, mExprModel.register(d)); assertEquals(1, mExprModel.mExprMap.size()); } + @Test public void testAddDupe1() { final DummyExpr d = new DummyExpr("a"); @@ -131,8 +137,8 @@ public class ExprModelTest { IdentifierExpr a = lb.addVariable("a", "java.lang.String"); IdentifierExpr b = lb.addVariable("b", "java.lang.String"); IdentifierExpr c = lb.addVariable("c", "java.lang.String"); - final Expr ternary = lb.parse("a == null ? b : c"); - final Expr equality = mExprModel.comparison("==", a, mExprModel.symbol("null", Object.class)); + lb.parse("a == null ? b : c"); + mExprModel.comparison("==", a, mExprModel.symbol("null", Object.class)); lb.getModel().seal(); Iterable<Expr> shouldRead = getShouldRead(); // a and a == null @@ -147,6 +153,59 @@ public class ExprModelTest { } @Test + public void testTernaryWithPlus() { + LayoutBinder lb = new MockLayoutBinder(); + mExprModel = lb.getModel(); + IdentifierExpr user = lb + .addVariable("user", "android.databinding.tool.expr.ExprModelTest.User"); + MathExpr parsed = parse(lb, "user.name + \" \" + (user.lastName ?? \"\")", MathExpr.class); + mExprModel.seal(); + Iterable<Expr> toRead = getShouldRead(); + Iterable<Expr> readNow = getReadFirst(toRead); + assertEquals(1, Iterables.size(readNow)); + assertSame(user, Iterables.getFirst(readNow, null)); + List<Expr> justRead = new ArrayList<Expr>(); + justRead.add(user); + readNow = filterOut(getReadFirst(toRead, justRead), justRead); + assertEquals(2, Iterables.size(readNow)); //user.name && user.lastName + Iterables.addAll(justRead, readNow); + // user.lastname (T, F), user.name + " " + readNow = filterOut(getReadFirst(toRead, justRead), justRead); + assertEquals(2, Iterables.size(readNow)); //user.name && user.lastName + Iterables.addAll(justRead, readNow); + readNow = filterOut(getReadFirst(toRead, justRead), justRead); + assertEquals(0, Iterables.size(readNow)); + mExprModel.markBitsRead(); + + toRead = getShouldRead(); + assertEquals(2, Iterables.size(toRead)); + justRead.clear(); + readNow = filterOut(getReadFirst(toRead, justRead), justRead); + assertEquals(1, Iterables.size(readNow)); + assertSame(parsed.getRight(), Iterables.getFirst(readNow, null)); + Iterables.addAll(justRead, readNow); + + readNow = filterOut(getReadFirst(toRead, justRead), justRead); + assertEquals(1, Iterables.size(readNow)); + assertSame(parsed, Iterables.getFirst(readNow, null)); + Iterables.addAll(justRead, readNow); + + readNow = filterOut(getReadFirst(toRead, justRead), justRead); + assertEquals(0, Iterables.size(readNow)); + mExprModel.markBitsRead(); + assertEquals(0, Iterables.size(getShouldRead())); + } + + private List<Expr> filterOut(Iterable itr, final Iterable exclude) { + return Arrays.asList(Iterables.toArray(Iterables.filter(itr, new Predicate() { + @Override + public boolean apply(Object input) { + return !Iterables.contains(exclude, input); + } + }), Expr.class)); + } + + @Test public void testTernaryInsideTernary() { LayoutBinder lb = new MockLayoutBinder(); mExprModel = lb.getModel(); @@ -202,10 +261,12 @@ public class ExprModelTest { IdentifierExpr e = lb.addVariable("e", "java.lang.String"); final Expr aTernary = lb.parse("a == null ? b == null ? c : d : e"); assertTrue(aTernary instanceof TernaryExpr); - final Expr bTernary = ((TernaryExpr)aTernary).getIfTrue(); + final Expr bTernary = ((TernaryExpr) aTernary).getIfTrue(); assertTrue(bTernary instanceof TernaryExpr); - final Expr aIsNull = mExprModel.comparison("==", a, mExprModel.symbol("null", Object.class)); - final Expr bIsNull = mExprModel.comparison("==", b, mExprModel.symbol("null", Object.class)); + final Expr aIsNull = mExprModel + .comparison("==", a, mExprModel.symbol("null", Object.class)); + final Expr bIsNull = mExprModel + .comparison("==", b, mExprModel.symbol("null", Object.class)); lb.getModel().seal(); Iterable<Expr> shouldRead = getShouldRead(); // a and a == null @@ -217,7 +278,6 @@ public class ExprModelTest { assertTrue(d.getShouldReadFlags().isEmpty()); assertTrue(e.getShouldReadFlags().isEmpty()); - Iterable<Expr> readFirst = getReadFirst(shouldRead, null); assertEquals(1, Iterables.size(readFirst)); final Expr first = Iterables.getFirst(readFirst, null); @@ -267,7 +327,6 @@ public class ExprModelTest { LayoutBinder lb = new MockLayoutBinder(); mExprModel = lb.getModel(); - IdentifierExpr u1 = lb.addVariable("u1", User.class.getCanonicalName()); IdentifierExpr u2 = lb.addVariable("u2", User.class.getCanonicalName()); IdentifierExpr a = lb.addVariable("a", int.class.getCanonicalName()); @@ -276,7 +335,8 @@ public class ExprModelTest { IdentifierExpr d = lb.addVariable("d", int.class.getCanonicalName()); IdentifierExpr e = lb.addVariable("e", int.class.getCanonicalName()); TernaryExpr abTernary = parse(lb, "a > b ? u1.name : u2.name", TernaryExpr.class); - TernaryExpr bcTernary = parse(lb, "b > c ? u1.getCond(d) ? u1.lastName : u2.lastName : `xx` + u2.getCond(e) ", TernaryExpr.class); + TernaryExpr bcTernary = parse(lb, "b > c ? u1.getCond(d) ? u1.lastName : u2.lastName : `xx`" + + " + u2.getCond(e) ", TernaryExpr.class); Expr abCmp = abTernary.getPred(); Expr bcCmp = bcTernary.getPred(); Expr u1GetCondD = ((TernaryExpr) bcTernary.getIfTrue()).getPred(); @@ -287,7 +347,6 @@ public class ExprModelTest { Expr u1LastName = ((TernaryExpr) bcTernary.getIfTrue()).getIfTrue(); Expr u2LastName = ((TernaryExpr) bcTernary.getIfTrue()).getIfFalse(); - mExprModel.seal(); Iterable<Expr> shouldRead = getShouldRead(); @@ -314,8 +373,10 @@ public class ExprModelTest { assertFlags(d, bcTernary.getRequirementFlagIndex(true)); assertFlags(e, bcTernary.getRequirementFlagIndex(false)); - assertFlags(u1, bcTernary.getRequirementFlagIndex(true), abTernary.getRequirementFlagIndex(true)); - assertFlags(u2, bcTernary.getRequirementFlagIndex(false), abTernary.getRequirementFlagIndex(false)); + assertFlags(u1, bcTernary.getRequirementFlagIndex(true), + abTernary.getRequirementFlagIndex(true)); + assertFlags(u2, bcTernary.getRequirementFlagIndex(false), + abTernary.getRequirementFlagIndex(false)); assertFlags(u1GetCondD, bcTernary.getRequirementFlagIndex(true)); assertFlags(u2GetCondE, bcTernary.getRequirementFlagIndex(false)); @@ -342,8 +403,6 @@ public class ExprModelTest { assertFlags(bcTernary, b, c, u1, u2, d, u1LastName, u2LastName, e); } - - @Test public void testCircularDependency() { LayoutBinder lb = new MockLayoutBinder(); @@ -442,11 +501,13 @@ public class ExprModelTest { int i = 0; log("list", iterable); for (Expr expr : exprs) { - assertTrue((i++) + ":must contain " + expr.getUniqueKey(), Iterables.contains(iterable, expr)); + assertTrue((i++) + ":must contain " + expr.getUniqueKey(), + Iterables.contains(iterable, expr)); } i = 0; for (Expr expr : iterable) { - assertTrue((i++) + ":must be expected " + expr.getUniqueKey(), ArrayUtils.contains(exprs, expr)); + assertTrue((i++) + ":must be expected " + expr.getUniqueKey(), + ArrayUtils.contains(exprs, expr)); } } @@ -459,18 +520,16 @@ public class ExprModelTest { private void log(String s, Iterable<Expr> iterable) { L.d(s); for (Expr e : iterable) { - L.d(": %s : %s allFlags: %s readSoFar: %s", e.getUniqueKey(), e.getShouldReadFlags(), e.getShouldReadFlagsWithConditionals(), e.getReadSoFar()); + L.d(": %s : %s allFlags: %s readSoFar: %s", e.getUniqueKey(), e.getShouldReadFlags(), + e.getShouldReadFlagsWithConditionals(), e.getReadSoFar()); } L.d("end of %s", s); } -// private Iterable<Expr> getReadFirst(Iterable<Expr> shouldRead) { -// return mExprModel.filterCanBeReadNow(shouldRead); -// } - private Iterable<Expr> getReadFirst(Iterable<Expr> shouldRead) { return getReadFirst(shouldRead, null); } + private Iterable<Expr> getReadFirst(Iterable<Expr> shouldRead, final Iterable<Expr> justRead) { return Iterables.filter(shouldRead, new Predicate<Expr>() { @Override @@ -485,7 +544,9 @@ public class ExprModelTest { } public static class User { + String name; + String lastName; public String getName() { diff --git a/tools/data-binding/grammarBuilder/src/test/java/android/databinding/BindingExpressionParserTest.java b/tools/data-binding/grammarBuilder/src/test/java/android/databinding/BindingExpressionParserTest.java index 31f041f..9197ccf 100644 --- a/tools/data-binding/grammarBuilder/src/test/java/android/databinding/BindingExpressionParserTest.java +++ b/tools/data-binding/grammarBuilder/src/test/java/android/databinding/BindingExpressionParserTest.java @@ -1,23 +1,25 @@ package android.databinding; -import android.databinding.BindingExpressionParser.AndOrOpContext; -import android.databinding.BindingExpressionParser.BinaryOpContext; -import android.databinding.BindingExpressionParser.BindingSyntaxContext; -import android.databinding.BindingExpressionParser.BitShiftOpContext; -import android.databinding.BindingExpressionParser.ComparisonOpContext; -import android.databinding.BindingExpressionParser.DefaultsContext; -import android.databinding.BindingExpressionParser.DotOpContext; -import android.databinding.BindingExpressionParser.ExpressionContext; -import android.databinding.BindingExpressionParser.GroupingContext; -import android.databinding.BindingExpressionParser.LiteralContext; -import android.databinding.BindingExpressionParser.MathOpContext; -import android.databinding.BindingExpressionParser.PrimaryContext; -import android.databinding.BindingExpressionParser.PrimitiveTypeContext; -import android.databinding.BindingExpressionParser.QuestionQuestionOpContext; -import android.databinding.BindingExpressionParser.ResourceContext; -import android.databinding.BindingExpressionParser.StringLiteralContext; -import android.databinding.BindingExpressionParser.TernaryOpContext; -import android.databinding.BindingExpressionParser.UnaryOpContext; +import android.databinding.parser.BindingExpressionLexer; +import android.databinding.parser.BindingExpressionParser; +import android.databinding.parser.BindingExpressionParser.AndOrOpContext; +import android.databinding.parser.BindingExpressionParser.BinaryOpContext; +import android.databinding.parser.BindingExpressionParser.BindingSyntaxContext; +import android.databinding.parser.BindingExpressionParser.BitShiftOpContext; +import android.databinding.parser.BindingExpressionParser.ComparisonOpContext; +import android.databinding.parser.BindingExpressionParser.DefaultsContext; +import android.databinding.parser.BindingExpressionParser.DotOpContext; +import android.databinding.parser.BindingExpressionParser.ExpressionContext; +import android.databinding.parser.BindingExpressionParser.GroupingContext; +import android.databinding.parser.BindingExpressionParser.LiteralContext; +import android.databinding.parser.BindingExpressionParser.MathOpContext; +import android.databinding.parser.BindingExpressionParser.PrimaryContext; +import android.databinding.parser.BindingExpressionParser.PrimitiveTypeContext; +import android.databinding.parser.BindingExpressionParser.QuestionQuestionOpContext; +import android.databinding.parser.BindingExpressionParser.ResourceContext; +import android.databinding.parser.BindingExpressionParser.StringLiteralContext; +import android.databinding.parser.BindingExpressionParser.TernaryOpContext; +import android.databinding.parser.BindingExpressionParser.UnaryOpContext; import org.antlr.v4.runtime.ANTLRInputStream; import org.antlr.v4.runtime.CommonTokenStream; diff --git a/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/InnerCannotReadDependencyTest.java b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/InnerCannotReadDependencyTest.java new file mode 100644 index 0000000..68cf06b --- /dev/null +++ b/tools/data-binding/integration-tests/TestApp/app/src/androidTest/java/android/databinding/testapp/InnerCannotReadDependencyTest.java @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2015 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.InnerCannotReadDependencyBinding; +import android.databinding.testapp.vo.BasicObject; +import android.os.Debug; +import android.test.UiThreadTest; + +import org.junit.Test; + +public class InnerCannotReadDependencyTest extends + BaseDataBinderTest<InnerCannotReadDependencyBinding> { + + public InnerCannotReadDependencyTest() { + super(InnerCannotReadDependencyBinding.class); + } + + @UiThreadTest + public void testBinding() { + BasicObject object = new BasicObject(); + object.setField1("a"); + mBinder.setObj(object); + mBinder.executePendingBindings(); + assertEquals("a ", mBinder.textView.getText().toString()); + object.setField1(null); + mBinder.executePendingBindings(); + assertEquals("null ", mBinder.textView.getText().toString()); + object.setField2("b"); + mBinder.executePendingBindings(); + assertEquals("null b", mBinder.textView.getText().toString()); + object.setField1("c"); + mBinder.executePendingBindings(); + assertEquals("c b", mBinder.textView.getText().toString()); + } +} diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/BasicObject.java b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/BasicObject.java new file mode 100644 index 0000000..450f7fb --- /dev/null +++ b/tools/data-binding/integration-tests/TestApp/app/src/main/java/android/databinding/testapp/vo/BasicObject.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2015 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.vo; +import android.databinding.BaseObservable; +import android.databinding.Bindable; +import android.databinding.testapp.BR; + +public class BasicObject extends BaseObservable { + @Bindable + private String mField1; + @Bindable + private String mField2; + + public String getField1() { + return mField1; + } + + public void setField1(String field1) { + this.mField1 = field1; + notifyPropertyChanged(BR.field1); + } + + public String getField2() { + return mField2; + } + + public void setField2(String field2) { + this.mField2 = field2; + notifyPropertyChanged(BR.field1); + } +} diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/find_method_test.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/find_method_test.xml index c65ee39..33a3746 100644 --- a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/find_method_test.xml +++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/find_method_test.xml @@ -15,11 +15,11 @@ <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@{obj.method(1.25f}"/> + android:text="@{obj.method(1.25f)}"/> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@{obj.method(`hello`}"/> + android:text="@{obj.method(`hello`)}"/> <TextView android:id="@+id/textView3" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/inner_cannot_read_dependency.xml b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/inner_cannot_read_dependency.xml new file mode 100644 index 0000000..2ad1980 --- /dev/null +++ b/tools/data-binding/integration-tests/TestApp/app/src/main/res/layout/inner_cannot_read_dependency.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" android:layout_width="match_parent" + android:layout_height="match_parent"> + <variable name="obj" type="android.databinding.testapp.vo.BasicObject"/> + <TextView + android:id="@+id/text_view" + android:text='@{obj.field1 + " " + (obj.field2 ?? "")}' + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> +</LinearLayout>
\ No newline at end of file |