SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

一歩進んだAndroidアプリ開発ができる「Android Jetpack」入門

画面部品へのアクセスを効率化、ビューバインディングとデータバインディングを解説

一歩進んだAndroidアプリ開発ができる「Android Jetpack」入門 第6回

  • X ポスト
  • このエントリーをはてなブックマークに追加

 本連載は、「Android Studio2で始めるアプリ開発入門」連載、および『Androidアプリ開発の教科書』の続編にあたる内容として、Jetpackを取り上げていきます。前回は、LiveDataを自動生成できるRoomの機能と、そのLiveDataと同等の機能を提供できるKotlin専用のFlowも紹介しました。今回から3回にわたって紹介する内容は、画面へのデータ反映処理を自動化できるデータバインディングです。今回は、データバインディングの前編として、ビューバインディングから話を始め、データバインディングの基本的な利用方法を紹介します。

  • X ポスト
  • このエントリーをはてなブックマークに追加

画面部品の取得を自動化できるビューバインディング

 今回のテーマは、画面へのデータ反映処理を自動化できるデータバインディングです。そのデータバインディングを紹介する前に、画面へデータを反映させる従来の方法から話を始め、ビューバインディングを紹介します。

 なお、今回のサンプルデータは、GitHubから参照できます。

画面部品の従来の扱い方

 例えば、図1のようなシンプルなアプリを考えてみます。このアプリは、ボタンをタップすると、乱数を発生させて、それを表示させるものです。

図1:乱数を表示させるアプリの画面
図1:乱数を表示させるアプリの画面

 乱数の発生や発生した乱数値の保持を第1回で紹介したViewModelに任せるとしても、そのデータを表示させるのはアクティビティの役割であり、従来は、Javaではリスト1の、Kotlinではリスト2の(1)と(2)のコードを記述していました。また、ボタンのリスナ登録も、(3)と(4)のようなコードを記述していました。

リスト1:画面部品を扱う従来のコード(Java版)
TextView tvRand = findViewById(R.id.tvRand);  // (1)
tvRand.setText(_mainViewModel.getRandNumStr());  // (2)
Button btnRand = findViewById(R.id.btnRand);  // (3)
btnRand.setOnClickListener(new ButtonClickListener());  // (4)
リスト2:画面部品を扱う従来のコード(Kotlin版)
val tvRand = findViewById<TextView>(R.id.tvRand)  // (1)
tvRand.text = _mainViewModel.getRandNumStr()  // (2)
val btnRand = findViewById<Button>(R.id.btnRand)  // (3)
btnRand.setOnClickListener(ButtonClickListener())  // (4)

 ここで注目したいのは、(1)と(3)です。画面部品に対して、データを表示したりリスナを設定したりなど、何か処理を行う場合は、まず、画面部品のidのR値を引数として、findViewById()メソッドで該当画面部品を取得する必要があります。

 このfindViewById()を利用したコードは、画面部品を利用するたびに記述する必要があり、そのぶん、アクティビティ内のコードが膨らむ傾向があります。さらに、コード量の問題だけではなく、nullとデータ型の問題を孕んでいます。まず、idのR値として、現在表示させている画面とは別のレイアウトxmlに記述されたidを指定すると、findViewById()の戻り値はnullとなり、結果的にその後のコード(例えば、(2)や(4))でNullPointerExceptionが発生します。また、戻り値を格納する変数のデータ型(Kotlinコードならばジェネリクスで指定するデータ型)として違うものを記述すると、ClassCastExceptioinが発生します。つまり、findViewById()はnull安全でもなく、型安全でもない、ということです。

ビューバインディングとその利用設定

 このような問題を解決するために、Jetpackではビューバインディングという仕組みが導入されています。このビューバインディングでは、レイアウトxmlファイルの記述を元に、その画面に含まれる画面部品が格納されたオブジェクトを自動生成させる仕組みです。

 このビューバインディングを利用する場合、まず、プロジェクトに設定が必要です。build.gradle.kts(Module: app)に、リスト3の太字の追記を行います。ファイル直下に既に存在しているandroidブロックにbuildFeaturesブロックを追加し、viewBindingプロパティをtrueとします。

リスト3:build.gradle.kts(Module: app)に追記するビューバインディングの設定コード
plugins {
  :
}
android {
  :
  buildFeatures {
    viewBinding = true
  }
}
dependencies {
  :
}

ビューバインディングの利用コード

 これで、プロジェクト内でビューバインディングが利用できるようになります。そして、そのビューバインディングを利用すると、リスト1の画面部品を扱うコードは、Javaではリスト4の(5)や(6)のようになります。

リスト4:ビューバインディングを扱うコード(Java版)
private ActivityMainBinding _activityMainBinding;  // (1)

@Override
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  _activityMainBinding = ActivityMainBinding.inflate(getLayoutInflater());  // (2)
  View contentView = _activityMainBinding.getRoot();  // (3)
  setContentView(contentView);  // (4)
  :
  _activityMainBinding.tvRand.setText(_mainViewModel.getRandNumStr());  // (5)
  _activityMainBinding.btnRand.setOnClickListener(new ButtonClickListener());  // (6)
}

 リスト3のビューバインディングの利用設定を行うと、先述のように、レイアウトxmlファイルの記述を元に、自動的にオブジェクトが作られます。このオブジェクトのクラス名は、レイアウトxmlファイル名をキャメル記法(パスカル記法)に変換した上でBindingをつけたものとなります。リスト4では、activity_main.xmlファイルを元にしているので、ActivityMainにBindingをつけ、ActivityMainBindingとなります。

 このオブジェクトを宣言したprivateフィールドが(1)であり、その取得、および、利用コードが(2)〜(4)です。こちらのコードに関しては後述するとして、このActivityMainBindingには、自動的にactivity_main.xmlに記述された画面部品がpublicフィールドとして定義されます(※)。

 そのため、データを表示するためのsetText()の実行も、(5)のようにそのフィールドに対して実行すれば問題なく反映されます。リスナの設定も、同様に、(6)のようにフィールドに対して設定すれば問題なく動作します。この仕組みにより、findViewById()を利用する必要がなくなり、id違いによるnullの問題や、データ型指定による型違いの問題も回避できます。

(※)より正確には、idが設定された画面部品のみが定義されます。ただし、ConstraintLayoutを利用した画面作成においてはidの設定は必須なので、全ての画面部品がフィールドとして定義されているといっても問題ないといえます。

バインディングオブジェクトの用意

 このように便利なバインディングクラス(リスト4ではActivityMainBindingクラス)は、再三説明してきているように、自動生成されています。ただし、利用するためには定型コードを記述する必要があります。それが、リスト4の(2)〜(4)です。

 まず、バインディングオブジェクト本体を取得するコードが(2)であり、これは、該当バインディングクラス(ActivityMainBinding)に自動生成されたstaticメソッドinflate()を実行するだけです。ただし、その際、LayoutInflaterオブジェクトを渡す必要があります。これは、アクティビティのgetLayoutInflater()メソッドの戻り値を渡します。

 その後、生成されたバインディングオブジェクトのgetRoot()メソッドを実行します。それが(3)です。このメソッドにより、レイアウトxmlファイルを元に生成(インフレート)された画面全体のインスタンスが返ってきます((3)ではcontentView)。そして、このcontentViewを引数としてsetContentView()を実行します。それが(4)です。

 アクティビティで表示画面を設定するsetContentView()の引数は、これまで、表示する画面のレイアウトxmlファイルのR値でした。その代わりに、バインディングオブジェクトによって生成された画面インスタンスを渡すことで、ビューバインディングが利用できるようになります。

 そして、このバインディングオブジェクトを、(1)のようにフィールドとして保持することで、アクティビティ内ではいつでも画面部品にアクセスすることができるようになり、結果、findViewById()コードをアクティビティから一掃することができます。

 なお、Kotlinコードでは、リスト5のようになり、リスト4を単にKotlinコードに置き換えたコードとなります。

リスト5:ビューバインディングを扱うコード(Kotlin版)
private lateinit var _activityMainBinding: ActivityMainBinding  // (1)

override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  _activityMainBinding = ActivityMainBinding.inflate(layoutInflater)  // (2)
  setContentView(_activityMainBinding.root)  // (3) & (4)
  :
  _activityMainBinding.tvRand.text = _mainViewModel.getRandNumStr()  // (5)
  _activityMainBinding.btnRand.setOnClickListener(ButtonClickListener())  // (6)
}

フラグメントでの利用(Java版)

 ここまでのコードは、アクティビティを参考に紹介してきました。このバインディングオブジェクトをフラグメントで利用する場合も、同様に考えることができます。ただし、フラグメントでは、画面を生成するライフサイクルコールバックメソッドがアクティビティとは違い、onCreateView()です。そこで、このメソッド内にリスト6の(1)〜(3)のようなバイディングオブジェクトの生成処理を記述します。

リスト6:フラグメントでビューバインディングを扱うコード(Java版)
private FragmentMainBinding _fragmentMainBinding;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
  _fragmentMainBinding = FragmentMainBinding.inflate(inflater, container, false);  // (1)
  View contentView = _fragmentMainBinding.getRoot();  // (2)
  return contentView;  // (3) 
}

@Override
public void onDestroyView() {
  super.onDestroyView();
  _fragmentMainBinding = null;  // (4)
}

 リスト6のコードのポイントは、バインディングオブジェクトを取得する際のinflate()メソッドとして、引数が3個のものを利用し、第1引数と第2引数にはonCreateView()メソッドの引数をそのまま渡します。そして、第3引数にはfalseを指定します。

 このようにして取得したバインディングオブジェクトから、レイアウトxmlファイルを元にインフレートされた画面全体のインスタンス(contentView)を取得するのは、アクティビティ同様に(2)のgetRoot()です。ただ、フラグメントのonCreateView()メソッドでは、(3)のように、このcontentViewを戻り値としてリターンします。

 また、このように取得したバインディングオブジェクトは、フラグメントの画面インスタンスの破棄と同時に破棄しておく必要があります。そのため、リスト6の(4)のように、onDestroyView()メソッドを実装し、フィールドで保持しているバインディングオブジェクトにnullを代入しておきます。

フラグメントでの利用(Kotlin版)

 これらのフラグメントでのビューバインディングの使い方は、Kotlinでも同様で、リスト6をKotlinの置き換えたようなリスト7のコードになります。

リスト7:フラグメントでビューバインディングを扱うコード(Kotlin版)
private var _fragmentMainBindingInit: FragmentMainBinding? = null  // (1)
private val _fragmentMainBinding get() = _fragmentMainBindingInit!!  // (2)

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
  _fragmentMainBindingInit = FragmentMainBinding.inflate(inflater, container, false)
  return _fragmentMainBinding.root  // (3)
}

override fun onDestroyView() {
  super.onDestroyView()
  _fragmentMainBindingInit = null;  // (4)
}

 ただし、onDestroyView()でnullを代入しやすいように、また、フラグメント中でプロパティとして保持したバインディングオブジェクトを使いやすいようなコードとしておく必要があります。そのため、バインディングオブジェクト本体を、(1)のように別名で、かつ、nullableとしておきます。

 さらに、(2)のように、(1)のバインディングオブジェクト本体をnullでなくしたバンディングオブジェクトのゲッタを用意します。これらのコードのおかげで、フラグメント内では、(3)のように、nullではないことを前提にバインディングオブジェクトの各プロパティやメソッドを利用できるようになる一方で、(4)のようにonDestroyView()ではその本体にnullを代入できるようになります。

会員登録無料すると、続きをお読みいただけます

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

次のページ
データ反映も自動化できるデータバインディング

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
一歩進んだAndroidアプリ開発ができる「Android Jetpack」入門連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 齊藤 新三(サイトウ シンゾウ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS Twitter: @yyamada(公式)、@yyamada/wings(メンバーリスト) Facebook<個人紹介>WINGSプロジェクト所属のテクニカルライター。Web系製作会社のシステム部門、SI会社を経てフリーランスとして独立。屋号はSarva(サルヴァ)。HAL大阪の非常勤講師を兼務。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/18582 2023/11/10 10:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング