Slide 1

Slide 1 text

DataBindingのコードを読む DroidKaigi 2018

Slide 2

Slide 2 text

About me • Kenji Abe • Android/iOS Developer • Twitter: STAR_ZERO • GitHub: STAR-ZERO

Slide 3

Slide 3 text

コントリビュートしました • https://android.googlesource.com/platform/frameworks/data- binding/+log/studio-master-dev

Slide 4

Slide 4 text

話すこと • DataBindingのコードから仕組みを解説 • Gradle Plugin • Annotation Processor • コード生成・実行 ※Android Gradle Plugin 3.0を対象してます

Slide 5

Slide 5 text

DataBindingのコード取得とデバッグ • ブログにまとめておきました • https://goo.gl/o68Ekt

Slide 6

Slide 6 text

Gradle Plugin

Slide 7

Slide 7 text

Gradle Plugin • https://android.googlesource.com/platform/ tools/base/+/gradle_3.0.0 • build-system/gradle • build-system/gradle-api • build-system/gradle-core

Slide 8

Slide 8 text

Gradle Plugin • 依存ライブラリの追加 • タスク追加 • レイアウトファイルのパース

Slide 9

Slide 9 text

Gradle Plugin • 依存ライブラリの追加 • タスク追加 • レイアウトファイルのパース

Slide 10

Slide 10 text

依存ライブラリの追加 • com.android.databinding:library • com.android.databinding:baseLibrary • com.android.databinding:adapters • com.android.databinding:compiler

Slide 11

Slide 11 text

com.android.databinding:library • DataBindingUtil • BaseObservable • ObservableField https://android.googlesource.com/platform/frameworks/ data-binding/+/gradle_3.0.0/extensions/library/

Slide 12

Slide 12 text

com.android.databinding:baseLibrary • @Bindable • @BindingAdapter • @InverseBindingAdapter https://android.googlesource.com/platform/frameworks/ data-binding/+/gradle_3.0.0/baseLibrary/

Slide 13

Slide 13 text

com.android.databinding:adapters • ViewBindingAdapter • TextViewBindingAdapter • CompoundButtonBindingAdapter https://android.googlesource.com/platform/frameworks/ data-binding/+/gradle_3.0.0/extensions/baseAdapters/

Slide 14

Slide 14 text

com.android.databinding:compiler • Annotation Processor • コード生成 https://android.googlesource.com/platform/frameworks/ data-binding/+/gradle_3.0.0/compiler/

Slide 15

Slide 15 text

• https://android.googlesource.com/platform/tools/base/+/gradle_3.0.0/build-system/gradle- core/src/main/java/com/android/build/gradle/internal/TaskManager.java 依存ライブラリの追加 public abstract class TaskManager { // ... public void addDataBindingDependenciesIfNecessary(DataBindingOptions options) { if (!options.isEnabled()) { return; } String version = MoreObjects.firstNonNull(options.getVersion(), dataBindingBuilder.getCompilerVersion()); project.getDependencies() .add( "api", SdkConstants.DATA_BINDING_LIB_ARTIFACT + ":" + dataBindingBuilder.getLibraryVersion(version)); project.getDependencies() .add( "api", SdkConstants.DATA_BINDING_BASELIB_ARTIFACT + ":" + dataBindingBuilder.getBaseLibraryVersion(version)); project.getDependencies() .add( "annotationProcessor", SdkConstants.DATA_BINDING_ANNOTATION_PROCESSOR_ARTIFACT + ":" + version); if (options.isEnabledForTests() || this instanceof LibraryTaskManager) { project.getDependencies().add("androidTestAnnotationProcessor",

Slide 16

Slide 16 text

Gradle Plugin • 依存ライブラリの追加 • タスク追加 • レイアウトファイルのパース

Slide 17

Slide 17 text

タスク追加 • dataBindingExportBuildInfoタスクが追加 • DataBindingInfo.javaを生成する

Slide 18

Slide 18 text

DataBindingInfo • 空のクラス • ここの@BindingBuildInfoが
 Annotation Processorで処理される @BindingBuildInfo(buildId="8c129f13-798b-4137-8327-f76a34f6a51f") public class DataBindingInfo {}

Slide 19

Slide 19 text

タスク追加 • https://android.googlesource.com/platform/tools/base/+/gradle_3.0.0/build-system/gradle- core/src/main/java/com/android/build/gradle/internal/TaskManager.java public abstract class TaskManager { // ... protected void createDataBindingTasksIfNecessary(@NonNull TaskFactory tasks, @NonNull VariantScope scope) { if (!extension.getDataBinding().isEnabled()) { return; } VariantType type = scope.getVariantData().getType(); boolean isTest = type == VariantType.ANDROID_TEST || type == VariantType.UNIT_TEST; if (isTest && !extension.getDataBinding().isEnabledForTests()) { BaseVariantData testedVariantData = scope.getTestedVariantData(); if (testedVariantData.getType() != LIBRARY) { return; } } dataBindingBuilder.setDebugLogEnabled(getLogger().isDebugEnabled()); AndroidTask exportBuildInfo = androidTasks .create(tasks, new DataBindingExportBuildInfoTask.ConfigAction(scope)); exportBuildInfo.dependsOn(tasks, scope.getMergeResourcesTask()); exportBuildInfo.dependsOn(tasks, scope.getSourceGenTask()); scope.setDataBindingExportBuildInfoTask(exportBuildInfo); } }

Slide 20

Slide 20 text

タスク追加 • https://android.googlesource.com/platform/frameworks/data-binding/+/gradle_3.0.0/ compilerCommon/src/main/java/android/databinding/tool/LayoutXmlProcessor.java public class LayoutXmlProcessor { // ... public void writeEmptyInfoClass() { final Class annotation = BindingBuildInfo.class; String classString = "package " + RESOURCE_BUNDLE_PACKAGE + ";\n\n" + "import " + annotation.getCanonicalName() + ";\n\n" + "@" + annotation.getSimpleName() + "(buildId=\"" + mBuildId + "\")\n" + "public class " + CLASS_NAME + " {}\n"; mFileWriter.writeToFile(RESOURCE_BUNDLE_PACKAGE + "." + CLASS_NAME, classString); } }

Slide 21

Slide 21 text

Gradle Plugin • 依存ライブラリの追加 • タスク追加 • レイアウトファイルのパース

Slide 22

Slide 22 text

レイアウトファイルのパース • mergeDebugResourceタスクで実行される • レイアウトファイルの タグあるもの をパースして、新しくXMLファイルを生成する

Slide 23

Slide 23 text

レイアウトファイルのパース

Slide 24

Slide 24 text

レイアウトファイルのパース false 変数 View 式 Two-way binding

Slide 25

Slide 25 text

レイアウトファイルのパース • https://android.googlesource.com/platform/frameworks/data-binding/+/gradle_3.0.0/ compilerCommon/src/main/java/android/databinding/tool/store/LayoutFileParser.java public class LayoutFileParser { // ... private void parseExpressions(String newTag, final XMLParser.ElementContext rootView, final boolean isMerge, ResourceBundle.LayoutFileBundle bundle) { // ... for (XMLParser.ElementContext parent : bindingElements) { // ... for (XMLParser.AttributeContext attr : XmlEditor.expressionAttributes(parent)) { String value = escapeQuotes(attr.attrValue.getText(), true); final boolean isOneWay = value.startsWith("@{"); final boolean isTwoWay = value.startsWith("@={"); if (isOneWay || isTwoWay) { if (value.charAt(value.length() - 1) != '}') { L.e("Expecting '}' in expression '%s'", attr.attrValue.getText()); } final int startIndex = isTwoWay ? 3 : 2; final int endIndex = value.length() - 1; final String strippedValue = value.substring(startIndex, endIndex); Location attrLocation = new Location(attr); Location valueLocation = new Location(); // offset to 0 based valueLocation.startLine = attr.attrValue.getLine() - 1; valueLocation.startOffset = attr.attrValue.getCharPositionInLine() + attr.attrValue.getText().indexOf(strippedValue); valueLocation.endLine = attrLocation.endLine; valueLocation.endOffset = attrLocation.endOffset - 2; // account for: "} bindingTargetBundle.addBinding(escapeQuotes(attr.attrName.getText(), false),

Slide 26

Slide 26 text

レイアウトファイルのパース • https://android.googlesource.com/platform/frameworks/data-binding/+/gradle_3.0.0/ compilerCommon/src/main/java/android/databinding/tool/store/LayoutFileParser.java public class LayoutFileParser { // ... private void parseData(File xml, XMLParser.ElementContext data, ResourceBundle.LayoutFileBundle bundle) { if (data == null) { return; } for (XMLParser.ElementContext imp : filter(data, "import")) { final Map attrMap = attributeMap(imp); String type = attrMap.get("type"); String alias = attrMap.get("alias"); Preconditions.check(StringUtils.isNotBlank(type), "Type of an import cannot be empty." + " %s in %s", imp.toStringTree(), xml); if (Strings.isNullOrEmpty(alias)) { alias = type.substring(type.lastIndexOf('.') + 1); } bundle.addImport(alias, type, new Location(imp)); } for (XMLParser.ElementContext variable : filter(data, "variable")) { final Map attrMap = attributeMap(variable); String type = attrMap.get("type"); String name = attrMap.get("name"); Preconditions.checkNotNull(type, "variable must have a type definition %s in %s", variable.toStringTree(), xml); Preconditions.checkNotNull(name, "variable must have a name %s in %s", variable.toStringTree(), xml); bundle.addVariable(name, type, new Location(variable), true);

Slide 27

Slide 27 text

Gradle Plugin おまけ

Slide 28

Slide 28 text

ドキュメントに書いてないオプション dataBinding { enabled true version "2.3.3" addDefaultAdapters false enabledForTests true }

Slide 29

Slide 29 text

Annotation Processor

Slide 30

Slide 30 text

Annotation Processor • https://android.googlesource.com/platform/ frameworks/data-binding/+/gradle_3.0.0/ compiler/

Slide 31

Slide 31 text

Annotation Processor • Bindingクラス生成 • BRクラス生成 • DataBinderMapperクラス生成 kotlinが使われてる

Slide 32

Slide 32 text

Annotation Processor • https://android.googlesource.com/platform/frameworks/data-binding/+/gradle_3.0.0/ compiler/src/main/java/android/databinding/annotationprocessor/ProcessDataBinding.java @SupportedAnnotationTypes({ "android.databinding.BindingAdapter", "android.databinding.InverseBindingMethods", "android.databinding.InverseBindingAdapter", "android.databinding.InverseMethod", "android.databinding.Untaggable", "android.databinding.BindingMethods", "android.databinding.BindingConversion", "android.databinding.BindingBuildInfo"} ) /** * Parent annotation processor that dispatches sub steps to ensure execution order. * Use initProcessingSteps to add a new step. */ public class ProcessDataBinding extends AbstractProcessor { private List mProcessingSteps; private DataBindingCompilerArgs mCompilerArgs; @Override public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) { if (mProcessingSteps == null) { readArguments(); initProcessingSteps(); } if (mCompilerArgs == null) { return false; } DataBindingInfo のアノテーション

Slide 33

Slide 33 text

Annotation Processor • Bindingクラス生成 • BRクラス生成 • DataBinderMapperクラス生成

Slide 34

Slide 34 text

• Viewへの反映を行うクラス • レイアウトやアノテーションから生成される Bindingクラス生成

Slide 35

Slide 35 text

public class ActivityMainBinding extends ViewDataBinding { @Nullable private static final IncludedLayouts sIncludes; @Nullable private static final SparseIntArray sViewsWithIds; static { sIncludes = null; sViewsWithIds = null; } @NonNull private final LinearLayout mboundView0; @NonNull public final TextView textView1; @Nullable private ViewModel mViewModel; public ActivityMainBinding(@NonNull DataBindingComponent bindingComponent, @NonNull View root) { super(bindingComponent, root, 2); final Object[] bindings = mapBindings(bindingComponent, root, 2, sIncludes, sViewsWithIds); this.mboundView0 = (LinearLayout) bindings[0]; this.mboundView0.setTag(null); this.textView1 = (TextView) bindings[1]; this.textView1.setTag(null); setRootTag(root); // listeners invalidateAll(); } @Override public void invalidateAll() { synchronized(this) { mDirtyFlags = 0x4L; } Bindingクラス生成

Slide 36

Slide 36 text

Bindingクラス生成 • レイアウトファイルをパースしたXMLファイル
 からBindingクラスを生成

Slide 37

Slide 37 text

Bindingクラス生成 • import文の生成 • からプロパティ生成 • android:idがあるViewのフィールド作成 • @{}の式をパース • 式のパースにはANTLR v4が使われている • EventListenerの実装 • Viewに反映する処理 • BindingAdapterアノテーション • set + XMLの属性名 のメソッド

Slide 38

Slide 38 text

Bindingクラス生成 • https://android.googlesource.com/platform/frameworks/data-binding/+/gradle_3.0.0/ compiler/src/main/kotlin/android/databinding/tool/writer/LayoutBinderWriter.kt class LayoutBinderWriter(val layoutBinder : LayoutBinder) { // ... fun executePendingBindings() = kcode("") { nl("@Override") block("protected void executeBindings()") { val tmpDirtyFlags = FlagSet(mDirtyFlags.buckets) tmpDirtyFlags.localName = "dirtyFlags"; for (i in (0..mDirtyFlags.buckets.size - 1)) { nl("${tmpDirtyFlags.type} ${tmpDirtyFlags.localValue(i)} = 0;") } block("synchronized(this)") { for (i in (0..mDirtyFlags.buckets.size - 1)) { nl("${tmpDirtyFlags.localValue(i)} = ${mDirtyFlags.localValue(i)};") nl("${mDirtyFlags.localValue(i)} = 0;") } } model.pendingExpressions.filter { it.needsLocalField }.forEach { nl("${it.resolvedType.toJavaCode()} ${it.executePendingLocalName} = ${if (it.isVariable()) it.fieldName else it.defaultValue};") } L.d("writing executePendingBindings for %s", className) do { val batch = ExprModel.filterShouldRead(model.pendingExpressions) val justRead = arrayListOf() L.d("batch: %s", batch) while (!batch.none()) { val readNow = batch.filter { it.shouldReadNow(justRead) } if (readNow.isEmpty()) { throw IllegalStateException("do not know what I can read. bailing out $ {batch.joinToString("\n")}") } L.d("new read now. batch size: %d, readNow size: %d", batch.size, readNow.size)

Slide 39

Slide 39 text

Bindingクラス生成 • https://android.googlesource.com/platform/frameworks/data-binding/+/gradle_3.0.0/ compilerCommon/BindingExpression.g4 grammar BindingExpression; // ... expression : '(' expression ')' # Grouping // this isn't allowed yet. // | THIS # Primary | literal # Primary | VoidLiteral # Primary | identifier # Primary | classExtraction # Primary | resources # Resource // | typeArguments (explicitGenericInvocationSuffix | 'this' arguments) # GenericCall | expression '.' Identifier # DotOp | expression '::' Identifier # FunctionRef // | expression '.' 'this' # ThisReference // | expression '.' explicitGenericInvocation # ExplicitGenericInvocationOp | expression '[' expression ']' # BracketOp | target=expression '.' methodName=Identifier '(' args=expressionList? ')' # MethodInvocation | methodName=Identifier '(' args=expressionList? ')' # GlobalMethodInvocation | '(' type ')' expression # CastOp | op=('+'|'-') expression # UnaryOp | op=('~'|'!') expression # UnaryOp | left=expression op=('*'|'/'|'%') right=expression # MathOp | left=expression op=('+'|'-') right=expression # MathOp | left=expression op=('<<' | '>>>' | '>>') right=expression # BitShiftOp | left=expression op=('<=' | '>=' | '>' | '<') right=expression # ComparisonOp | expression 'instanceof' type # InstanceOfOp

Slide 40

Slide 40 text

Annotation Processor • Bindingクラス生成 • BRクラス生成 • DataBinderMapperクラス生成

Slide 41

Slide 41 text

BRクラス生成 • @BindableからBRクラスを生成する • notifyPropertyChangedで使う public class BR { public static final int _all = 0; public static final int text = 1; public static final int viewModel = 2; }

Slide 42

Slide 42 text

BRクラス生成 • https://android.googlesource.com/platform/frameworks/data-binding/+/gradle_3.0.0/ compiler/src/main/kotlin/android/databinding/tool/writer/BRWriter.kt class BRWriter(properties: Set, val useFinal : Boolean) { val indexedProps = properties.sorted().withIndex() fun write(pkg : String): String = "package $pkg;${StringUtils.LINE_SEPARATOR}$klass" val klass: String by lazy { kcode("") { val prefix = if (useFinal) "final " else ""; annotateWithGenerated() block("public class BR") { tab("public static ${prefix}int _all = 0;") indexedProps.forEach { tab ("public static ${prefix}int ${it.value} = ${it.index + 1};") } } }.generate() } }

Slide 43

Slide 43 text

Annotation Processor • Bindingクラス生成 • BRクラス生成 • DataBinderMapperクラス生成

Slide 44

Slide 44 text

DataBinderMapperクラス生成 • レイアウトとBindingクラスのマッピング class DataBinderMapper { final static int TARGET_MIN_SDK = 19; public DataBinderMapper() { } public ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View view, int layoutId) { switch(layoutId) { case R.layout.activity_main: return ActivityMainBinding.bind(view, bindingComponent); } return null; } // … }

Slide 45

Slide 45 text

DataBinderMapperクラス生成 • https://android.googlesource.com/platform/frameworks/data-binding/+/gradle_3.0.0/ compiler/src/main/kotlin/android/databinding/tool/writer/BindingMapperWriter.kt class BindingMapperWriter(var pkg : String, var className: String, val layoutBinders : List, val compilerArgs: DataBindingCompilerArgs) { // ... fun write(brWriter : BRWriter) = kcode("") { nl("package $pkg;") nl("import ${compilerArgs.modulePackage}.BR;") val extends = if (generateAsTest) "extends $appClassName" else "" annotateWithGenerated() block("class $className $extends") { nl("final static int TARGET_MIN_SDK = ${compilerArgs.minApi};") if (generateTestOverride) { nl("static $appClassName mTestOverride;") block("static") { block("try") { nl("mTestOverride = ($appClassName) $appClassName.class.getClassLoader().loadClass(\"$pkg.$testClassName\").newInstance();") } block("catch(Throwable ignored)") { nl("// ignore, we are not running in test mode") nl("mTestOverride = null;") } } } nl("")

Slide 46

Slide 46 text

コード生成・実行

Slide 47

Slide 47 text

コード生成・実行 • notifyPropertyChanged • executeBindings • mDirtyFlags • EventListener • @BindingAdapter • Two-way binding

Slide 48

Slide 48 text

コード生成・実行 • notifyPropertyChanged • executeBindings • mDirtyFlags • EventListener • @BindingAdapter • Two-way binding

Slide 49

Slide 49 text

notifyPropertyChanged • Viewへの変更通知を行う public class ViewModel extends BaseObservable { private String text; @Bindable public String getText() { return text; } public void setText(String text) { this.text = text; notifyPropertyChanged(BR.text); } }

Slide 50

Slide 50 text

• Activity notifyPropertyChanged public class MainActivity extends AppCompatActivity { private ActivityMainBinding binding; private ViewModel viewModel = new ViewModel(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = DataBindingUtil.setContentView( this, R.layout.activity_main); binding.setViewModel(viewModel); // ... } }

Slide 51

Slide 51 text

• Bindingクラス public class ActivityMainBinding extends ViewDataBinding { // ... public void setViewModel(@Nullable ViewModel ViewModel) { updateRegistration(2, ViewModel); this.mViewModel = ViewModel; synchronized(this) { mDirtyFlags |= 0x4L; } notifyPropertyChanged(BR.viewModel); super.requestRebind(); } } notifyPropertyChanged 通知できる状態をつくる

Slide 52

Slide 52 text

• setViewModelをした状態(簡易版) notifyPropertyChanged ActivityMainBinding(ViewDataBinding) WeakListener ViewModel 参照 参照

Slide 53

Slide 53 text

• notifyPropertyChangedを実行 notifyPropertyChanged ActivityMainBinding(ViewDataBinding) WeakListener ViewModel 参照 参照 notifyProperty Changed View更新 executeBindings

Slide 54

Slide 54 text

コード生成・実行 • notifyPropertyChanged • executeBindings • mDirtyFlags • EventListener • @BindingAdapter • Two-way binding

Slide 55

Slide 55 text

executeBindings public class ActivityMainBinding extends ViewDataBinding { // … @Override protected void executeBindings() { long dirtyFlags = 0; synchronized(this) { dirtyFlags = mDirtyFlags; mDirtyFlags = 0; } java.lang.String viewModelText = null; ViewModel viewModel = mViewModel; if ((dirtyFlags & 0x7L) != 0) { if (viewModel != null) { viewModelText = viewModel.getText(); } } if ((dirtyFlags & 0x7L) != 0) { TextViewBindingAdapter.setText(this.textView, viewModelText); } } // … }

Slide 56

Slide 56 text

executeBindings 1. プロパティ変更通知 2. mDirtyFlagsをセット 3. Choreographer.postFrameCallback実行 4. 3のコールバックからexecuteBindings実行

Slide 57

Slide 57 text

executeBindings • Choreographer.postFrameCallbackによって
 次フレームで実行される • https://developer.android.com/reference/android/view/ Choreographer.html#postFrameCallback(android.view.Choreographer.FrameCallback)

Slide 58

Slide 58 text

executeBindings • RecyclerView.ViewHolder • 次のフレームで実行されるので、間違った データでViewサイズの計算が行われる可能性 がある • これを防ぐには直接executeBindingsを呼んで 即時反映させる • https://medium.com/google-developers/android-data-binding- recyclerview-db7c40d9f0e4

Slide 59

Slide 59 text

コード生成・実行 • notifyPropertyChanged • executeBindings • mDirtyFlags • EventListener • @BindingAdapter • Two-way binding

Slide 60

Slide 60 text

mDirtyFlags • プロパティの変更ビットフラグ public class ActivityMainBinding extends ViewDataBinding { @Override public void invalidateAll() { synchronized(this) { mDirtyFlags = 0x10L; } requestRebind(); } private boolean onChangeViewModelText1(ObservableField ViewModelText1, int fieldId) { if (fieldId == BR._all) { synchronized(this) { mDirtyFlags |= 0x1L; } return true; } return false; } @Override protected void executeBindings() { // ... if ((dirtyFlags & 0x19L) != 0) { TextViewBindingAdapter.setText(this.textView1, viewModelText1Get); } } }

Slide 61

Slide 61 text

mDirtyFlags • longのビットフラグ • プロパティが64を超える場合は新しい変数が
 作られる • mDirtyFlags_1 • mDirtyFlags_2 • 1番左のビットはすべて変更フラグ • 変数が複数ある場合は最後の変数の1番左

Slide 62

Slide 62 text

コード生成・実行 • notifyPropertyChanged • executeBindings • mDirtyFlags • EventListener • @BindingAdapter • Two-way binding

Slide 63

Slide 63 text

EventListener public class ViewModel { public void onClick(View view) { // ... } }

Slide 64

Slide 64 text

EventListener • ViewBindingAdapterに定義 • https://android.googlesource.com/platform/frameworks/data- binding/+/gradle_3.0.0/extensions/baseAdapters/ @BindingMethods({ @BindingMethod(type = View.class, attribute = "android:onClick", method = "setOnClickListener"), }) public class ViewBindingAdapter { }

Slide 65

Slide 65 text

EventListener • Bindingクラス public class ActivityMainBinding extends ViewDataBinding { @Override protected void executeBindings() { OnClickListener listener = null; listener = new OnClickListenerImpl(); listener.setValue(viewModel); button.setOnClickListener(listener); } public static class OnClickListenerImpl implements OnClickListener{ private ViewModel value; public OnClickListenerImpl setValue(ViewModel value) { this.value = value; return value == null ? null : this; } @Override public void onClick(android.view.View arg0) { this.value.onClick(arg0); } } } ViewModelに定義したメソッド EventListenerをセット OnClickListenerを定義

Slide 66

Slide 66 text

コード生成・実行 • notifyPropertyChanged • executeBindings • mDirtyFlags • EventListener • @BindingAdapter • Two-way binding

Slide 67

Slide 67 text

• カスタムセッター • アノテーションで指定したメソッドが使われる @BindingAdapter public class CustomBinding { @BindingAdapter("intValue") public static void setIntValue(TextView view, int value) { view.setText(String.valueOf(value)); } } public class ActivityMainBinding extends ViewDataBinding { @Override protected void executeBindings() { // … CustomBinding.setIntValue(this.textView, viewModelValueGet); // ... } } @BindingAdapterで定義したメソッド

Slide 68

Slide 68 text

@BindingAdapter • インスタンスメソッドも使える public class SampleBindingAdapter { private SimpleDateFormat format; public SampleBindingAdapter(SimpleDateFormat format) { this.format = format; } @BindingAdapter("dateValue") public void setDate(TextView view, Date date) { view.setText(format.format(date)); } } インスタンスメソッド

Slide 69

Slide 69 text

@BindingAdapter • DataBindingComponentというinterafaceが生成 される package android.databinding; public interface DataBindingComponent { SampleBindingAdapter getSampleBindingAdapter(); }

Slide 70

Slide 70 text

@BindingAdapter • DataBindingComponentを実装する public class MyComponent implements DataBindingComponent { @Override public SampleBindingAdapter getSampleBindingAdapter() { SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); return new SampleBindingAdapter(format); } }

Slide 71

Slide 71 text

@BindingAdapter • DataBindingUtil.setContentViewに設定 • もしくはsetDefaultComponentでデフォルトを 設定 public class MainActivity extends AppCompatActivity { private ActivityMainBinding binding; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); binding = DataBindingUtil.setContentView( this, R.layout.activity_main, new MyComponent()); // デフォルトを指定したい場合 DataBindingUtil.setDefaultComponent(new MyComponent()); // ... } } 定義したComponentを指定

Slide 72

Slide 72 text

• Bindingクラス @BindingAdapter public class ActivityMainBinding extends ViewDataBinding { public ActivityMainBinding( @NonNull DataBindingComponent bindingComponent, @NonNull View root) { super(bindingComponent, root, 2); // ... } @Override protected void executeBindings() { // ... this.mBindingComponent.getSampleBindingAdapter() .setDate(this.textView, viewModelValueGet); } } setContentViewの 引数に渡したComponent

Slide 73

Slide 73 text

コード生成・実行 • notifyPropertyChanged • executeBindings • mDirtyFlags • EventListener • @BindingAdapter • Two-way binding

Slide 74

Slide 74 text

Two-way binding • @={} でTwo-way binding • TextViewのはTextViewBindingAdapterで定義 • https://android.googlesource.com/platform/frameworks/data- binding/+/gradle_3.0.0/extensions/baseAdapters/

Slide 75

Slide 75 text

• TextViewBindingAdapter Two-way binding @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; } // ... view.setText(text); }

Slide 76

Slide 76 text

Two-way binding public class ActivityMainBinding extends ViewDataBinding { private InverseBindingListener textAttrChanged = new InverseBindingListener() { @Override public void onChange() { String callbackArg_0 = TextViewBindingAdapter.getTextString(editText); // ... viewModel.setText(callbackArg_0)); } }; @Override protected void executeBindings() { // … TextViewBindingAdapter.setText(editText, viewModelText); TextViewBindingAdapter.setTextWatcher(editText, null, null, null, textAttrChanged); } } @BindingAdapter("android:text") • Bindingクラス

Slide 77

Slide 77 text

• TextViewBindingAdapter Two-way binding @InverseBindingAdapter(attribute = "android:text", event = "android:textAttrChanged") public static String getTextString(TextView view) { return view.getText().toString(); }

Slide 78

Slide 78 text

Two-way binding • Bindingクラス public class ActivityMainBinding extends ViewDataBinding { private InverseBindingListener textAttrChanged = new InverseBindingListener() { @Override public void onChange() { String callbackArg_0 = TextViewBindingAdapter.getTextString(editText); // ... viewModel.setText(callbackArg_0)); } }; @Override protected void executeBindings() { // … TextViewBindingAdapter.setText(editText, viewModelText); TextViewBindingAdapter.setTextWatcher(editText, null, null, null, textAttrChanged); } } @InverseBindingAdapter ViewModelに設定

Slide 79

Slide 79 text

• TextViewBindingAdapter Two-way binding @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) { // ... newValue = new TextWatcher() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (textAttrChanged != null) { textAttrChanged.onChange(); } } // ... }; // ... } @InverseBindingAdapter のevent InverseBindingListener 値を反映したいタイミングでonChange

Slide 80

Slide 80 text

Two-way binding • Bindingクラス public class ActivityMainBinding extends ViewDataBinding { private InverseBindingListener textAttrChanged = new InverseBindingListener() { @Override public void onChange() { String callbackArg_0 = TextViewBindingAdapter.getTextString(editText); // ... viewModel.setText(callbackArg_0)); } }; @Override protected void executeBindings() { // … TextViewBindingAdapter.setText(editText, viewModelText); TextViewBindingAdapter.setTextWatcher(editText, null, null, null, textAttrChanged); } } @BindingAdapter("android:text") @BindingAdapter("android:textAttrChanged")

Slide 81

Slide 81 text

まとめ

Slide 82

Slide 82 text

まとめ • コードを読むことで仕組みを深く知れる • ドキュメントに載ってないことも分かる • コード読むの大変だけどオススメ

Slide 83

Slide 83 text

ありがとうございました