読者です 読者をやめる 読者になる 読者になる

今更遅れてDataBinding事始め(2)

android gradle data_binding

前回のお話

環境構築とかは下記のエントリを参照してください

と呟かれてしまうぐらい大変。

とりあえずこのエントリでは単方向Bindの話をまとめます。


自分も

  • 1画面が半日〜1日

かかってしまって白目向いている状態だったりも・・。

慣れれば効率が上がるんだろうか・・・(汗

スモールスタートが良いかもしれない。

でもちょっとの記述エラーで全く動かなくなるのはJavaScriptに通じるものがあるかも、、

自分の開発スタイル

  1. layout.xml を 戻しやすい形にして修正
  2. data-binding の形式のレイアウトに直す
    1. 動かなくなったら戻す *1
  3. Gradle Sync / Build のみを行って正常に変換されるか確認する

    1. エラーはMessageに拾われるはずだが、偶に拾われないでBuildが成功になることがある
    2. この場合はapk転送しようとした時にdebugビルドでエラーになる
      1. 生成される位置に XXXBinding.java のコードが生成されていてもエラーになることも
      2. Taskの内部でsourceSet にapt生成先のパスが追加されないので Class Not Found エラー*2
      3. この段階なら、コマンドラインでビルドすると コンソールにエラーが出ていることがあるのでそれを確認する
  4. 生成される位置

app/build/generated/source/apt/debug
    XXXXBinding.java

APTの生成コードといえば

  • app/build.gradle
android {
    sourceSets {
        main {
            java.srcDirs = ['src/main/java', 'build/generated/source/apt/debug']
        }
    }
}

等でパス通せば

  • Gradle Sync でプロジェクト的には見えるようになる
  • assembleDebug で二重エントリでビルドエラーになる

な状況なんだけど DataBinding絡みで APTを組み込む仕組みとか内部で取り込まれているがための挙動なんだろうな。。と

だからクラス候補には出るけどsouceJumpとか存在確認が何気に現在大変かと思う。開発環境的には退化した?

だからDataBindingのコードも別名生成できるようですが、それをしないほうが無難かと思われます


例)生成クラス名

  • JPPの場合
  • DataBindingの場合
    • main_map_acvitiy.xml => MainMapActivityBinding.java
    • main_map_acvitiy_2.xml => MainMapActivity2Binding.java

実際の作業手順をメモってみる

layout.xml を 戻しやすい形にして修正

  • layout/activity_main.xml(元)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"  
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello Wold" />
</RelativeLayout>
  • layout/activity_main.xml(変更後)
    • 外側に mergeタグを追加して、xmlnsタグを移動する
<?xml version="1.0" encoding="utf-8"?>
<merge
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello Wold" />
</RelativeLayout>

</merge>

data-binding の形式のレイアウトに直す

  • layout/activity_main.xml(変更前)
<?xml version="1.0" encoding="utf-8"?>
<merge
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello Wold" />
</RelativeLayout>

</merge>
  • layout/activity_main.xml(変更後)
    • エラーが出て戻す場合は以下の作業をする
      • layout => merge タグへ
      • dataタグ、利用箇所をコメント
<?xml version="1.0" encoding="utf-8"?>
<layout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

<data>
    <variable name="activity" type="hoge.fuga.MainActivity" />
</data>


<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:onClick="@{activity.onClick}"
        android:text="@{activity.message}" />
</RelativeLayout>

</layout>
  • 単にviewにマッピングする単方向Bind であれば、
    • わざわざ別クラスを作る必要もない
    • activityの参照と、publicフィールド/public関数名*3

で事足りてしまう。。

layout.xml 触っててハマった注意点

  • importタグ はワイルドカードとかは使えません
  • importタグ でクラスをimport後、variable のtype宣言 で省略して書くという書き方 が有りますが、
    • 実はこれ誤動作することがあるらしく、基本的にtype宣言は フルパスで書いたほうが良いとのこと

レイアウト編集をする上での所感的な感想

  • layout.xml に記述する判定式は、単純なboolean判定の処理程度にすべき。
    • それ以上の場合はエスケープシーケンス地獄でレイアウトエラーの沼間にハマる
  • 複雑な判定をしたい場合は、判定用のUtilクラスを作成して、importして使うほうが遥かに楽
   <data>
        <import type="android.text.TextUtils" />
        <variable name="activity" type="hoge.fuga.MainActivity" />
   </data>

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@{TextUtils.isEmpty(activity.message) ? "empty" : activity.message" />
</RelativeLayout>

Gradle Sync を行う

このタイミングでAPTの処理が走り、XXXXBinding のソースがでて、初めて利用できる形になる

  • DataBindingの場合
    • main_map_acvitiy.xml => MainMapActivityBinding.java
    • main_map_acvitiy_2.xml => MainMapActivity2Binding.java

なイメージ

ソース上で 単方向のBind処理を行う

双方向の話は次回以降で。

Activityの場合

DataBindingUtil.setContentView で bindする

  • app/MainActivity
public class MainActivity extends AppCompatActivity {

    public String message = "Hello World!";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);//★

        // DataBindingUtilクラスを使ってレイアウトを初期化する
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);//◎
        // ここでデータをバインド
        binding.setActivity(this);
    }
    
    public void onClick(View view){
    
    }
    
}

★に関してですが、コメントしても、あっても動作します。

ネットでググると、★の箇所を消しているサンプルもあったり。

どっちが推奨記述なんでしょうね??*5

Fragmentの場合

XXXXBinding.bind を使う //★のタイミングで生成されたものを使う

  • app/MainActivityFragment.java
public class MainActivityFragment extends Fragment {
    FragmentMainBinding fragmentMainBinding;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment_main, container, false);//★
    }

    @Override
    public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        fragmentMainBinding = FragmentMainBinding.bind(view);//◎
        fragmentMainBinding.setFragment(this);
    }
}

Adapter の場合

DataBindingUtil.inflate を使う

   @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        HogeItemBinding binding;
        if (convertView == null)
        {
            // リストのアイテムにレイアウト適用と Data Binding を適用する
            binding = DataBindingUtil.inflate(
                    LayoutInflater.from(getContext()),
                    R.layout.hoge_item,
                    parent,
                    false
                );//◎
            // レイアウトを適用したアイテムの View を取得
            convertView = binding.getRoot();
        }
        // 2 周目ではアイテムの View が作成されているので
        else
        {
            // Data Binding オブジェクトを View から取得
            binding = DataBindingUtil.getBinding(convertView);//◎
        }

        // Data Binding するために View と layout.xml上の item変数に 紐つける
        binding.setItem(getItem(position));
        
        return super.getView(position, convertView, parent);
    }

*1:レイアウト不正状態になるのでプロジェクトが 真っ赤だな〜真っ赤だな〜 になるので気分が落ち込みやすい><

*2:最初これ何起きてるか凄く悩んだ。。。

*3:public void onClick(View view) が期待値になる

*4:判定用Utilクラスのほうにログを仕込むとかぐらい?

*5:G様のドキュメントレスさは酷すぎですね。。