$30 off During Our Annual Pro Sale. View Details »

DataBindingのコードを読む - DroidKaigi 2018

star_zero
February 06, 2018

DataBindingのコードを読む - DroidKaigi 2018

DroidKaigi 2018

star_zero

February 06, 2018
Tweet

More Decks by star_zero

Other Decks in Programming

Transcript

  1. DataBindingのコードを読む
    DroidKaigi 2018

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  6. Gradle Plugin

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  15. • 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",

    View Slide

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

    View Slide

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

    View Slide

  18. DataBindingInfo
    • 空のクラス
    • ここの@BindingBuildInfoが

    Annotation Processorで処理される
    @BindingBuildInfo(buildId="8c129f13-798b-4137-8327-f76a34f6a51f")
    public class DataBindingInfo {}

    View Slide

  19. タスク追加
    • 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);
    }
    }

    View Slide

  20. タスク追加
    • 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);
    }
    }

    View Slide

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

    View Slide

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

    xmlns:app="http://schemas.android.com/apk/res-auto">


    View Slide

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

    View Slide

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

    directory="layout"
    isMerge="false"
    layout="activity_main" modulePackage="com.star_zero.debugdatabinding">












    false











    変数
    View

    Two-way binding

    View Slide

  25. レイアウトファイルのパース
    • 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),

    View Slide

  26. レイアウトファイルのパース
    • 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);

    View Slide

  27. Gradle Plugin
    おまけ

    View Slide

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

    View Slide

  29. Annotation Processor

    View Slide

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

    View Slide

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

    View Slide

  32. 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
    のアノテーション

    View Slide

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

    View Slide

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

    View Slide

  35. 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クラス生成

    View Slide

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

    からBindingクラスを生成

    isMerge="false"
    layout="activity_main" modulePackage="com.star_zero.debugdatabinding">
    type="com.star_zero.debugdatabinding.ViewModel">



    view="android.support.constraint.ConstraintLayout">






    View Slide

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

    View Slide

  38. 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)

    View Slide

  39. 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

    View Slide

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

    View Slide

  41. 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;
    }

    View Slide

  42. 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()
    }
    }

    View Slide

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

    View Slide

  44. 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;
    }
    // …
    }

    View Slide

  45. 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("")

    View Slide

  46. コード生成・実行

    View Slide

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

    View Slide

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

    View Slide

  49. 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);
    }
    }

    View Slide

  50. • 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);
    // ...
    }
    }

    View Slide

  51. • 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
    通知できる状態をつくる

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  55. 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);
    }
    }
    // …
    }

    View Slide

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

    View Slide

  57. executeBindings
    • Choreographer.postFrameCallbackによって

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

    View Slide

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

    View Slide

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

    View Slide

  60. 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);
    }
    }
    }

    View Slide

  61. mDirtyFlags
    • longのビットフラグ
    • プロパティが64を超える場合は新しい変数が

    作られる
    • mDirtyFlags_1
    • mDirtyFlags_2
    • 1番左のビットはすべて変更フラグ
    • 変数が複数ある場合は最後の変数の1番左

    View Slide

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

    View Slide

  63. EventListener
    public class ViewModel {
    public void onClick(View view) {
    // ...
    }
    }
    android:id="@+id/button"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:onClick="@{viewModel::onClick}"
    android:text="Button" />

    View Slide

  64. 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 {
    }

    View Slide

  65. 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を定義

    View Slide

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

    View Slide

  67. • カスタムセッター
    • アノテーションで指定したメソッドが使われる
    @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で定義したメソッド

    View Slide

  68. @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));
    }
    }
    インスタンスメソッド
    android:id="@+id/text"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:dateValue="@{viewModel.date}"/>

    View Slide

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

    View Slide

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

    View Slide

  71. @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を指定

    View Slide

  72. • 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

    View Slide

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

    View Slide

  74. Two-way binding
    • @={} でTwo-way binding
    • TextViewのはTextViewBindingAdapterで定義
    • https://android.googlesource.com/platform/frameworks/data-
    binding/+/gradle_3.0.0/extensions/baseAdapters/
    android:id="@+id/editText"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@={viewModel.text}"/>

    View Slide

  75. • 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);
    }

    View Slide

  76. 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クラス

    View Slide

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

    View Slide

  78. 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に設定

    View Slide

  79. • 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

    View Slide

  80. 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")

    View Slide

  81. まとめ

    View Slide

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

    View Slide

  83. ありがとうございました

    View Slide