heihei blog

Write once, recall anytime. 📝

"XXX is a boxed field but needs to be un-boxed to execute YYY. This may cause NPE so Data Binding will safely unbox it. You can change the expression and explicitly wrap XXX with safeUnbox() to prevent the warning."の警告を見て、Data Binding周りを調べたことについて

TL;DR

  • Data Bindingライブラリで<data>タグと<variable>タグを利用してXML上に変数定義をする際には、プリミティブ型で定義できる際には積極的にプリミティブ型を使っていこう
  • 参照型の変数を定義した際には、Data Bindingライブラリが内部でunboxingする
  • safeUnbox()を利用することもできる

詳細

本記事執筆時点のライブラリバージョンは下記です:

  • com.android.support:appcompat-v7: 27.0.1
  • com.android.databinding:compiler: 3.0.0

Data Bindingライブラリを使ってXML上に変数を定義してViewの見た目を変えるとします。下記は、isLoadingであればProgressBarを表示、そうでなければProgressBarを非表示(View.GONE指定)にする簡単なXMLの例です:

<data>
  <import type="android.view.View"/>
  <variable
    name="isLoading"
    type="Boolean"
  />
</data>
...
  <ProgressBar
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      app:layout_constraintTop_toTopOf="parent"
      app:layout_constraintLeft_toLeftOf="parent"
      app:layout_constraintRight_toRightOf="parent"
      app:layout_constraintBottom_toBottomOf="parent"
      android:visibility="@{isLoading ? View.VISIBLE : View.GONE}"
      />
...

一見なにも問題ないように見えますが、この状態でプロジェクトをビルドすると、タイトルのような警告が表示されます。

safeUnbox()

Booleanは参照型なので、値にnullが代入される可能性があります。nullが代入される可能性があるということは、NullPointerExceptionが発生する可能性が生まれるということです。

今回のコードを改善しようとするなら、2つ改善策がありそうです。

1つ目は、変数定義の際のtypeに、unboxする必要のない型を指定することです。ここではBoolean型(=参照型)の代わりにboolean型(=プリミティブ型)を指定すれば良さそうです。

<data>
  <import 
    type="android.view.View" />
  <variable
    name="isLoading"
    type="boolean"
  />
</data>
...

二つ目は、これはタイトルの警告文言にも記載されているのですが、

w: 警告: XXX is a boxed field but needs to be un-boxed to execute YYY. This may cause NPE so Data Binding will safely unbox it. You can change the expression and explicitly wrap XXX with safeUnbox() to prevent the warning.

safeUnbox()というData Bindingライブラリが用意してくれているstaticメソッドを利用することもできるようです。

<ProgressBar
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:visibility="@{safeUnbox(isLoading) ? View.VISIBLE : View.GONE}"
      app:layout_constraintTop_toTopOf="parent"
      app:layout_constraintLeft_toLeftOf="parent"
      app:layout_constraintRight_toRightOf="parent"
      app:layout_constraintBottom_toBottomOf="parent"
      />

このsafeUnbox()というメソッドはandroid.databindingパッケージに内包されているDynamicUtilクラスのstaticメソッドでした。

package android.databinding;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.databinding.BindingConversion;
@javax.annotation.Generated("Android Data Binding")
public class DynamicUtil {
    public static int safeUnbox(java.lang.Integer boxed) {
        return boxed == null ? 0 : (int)boxed;
    }
    public static long safeUnbox(java.lang.Long boxed) {
        return boxed == null ? 0L : (long)boxed;
    }
    public static short safeUnbox(java.lang.Short boxed) {
        return boxed == null ? 0 : (short)boxed;
    }
    public static byte safeUnbox(java.lang.Byte boxed) {
        return boxed == null ? 0 : (byte)boxed;
    }
    public static char safeUnbox(java.lang.Character boxed) {
        return boxed == null ? '\u0000' : (char)boxed;
    }
    public static double safeUnbox(java.lang.Double boxed) {
        return boxed == null ? 0.0 : (double)boxed;
    }
    public static float safeUnbox(java.lang.Float boxed) {
        return boxed == null ? 0f : (float)boxed;
    }
    public static boolean safeUnbox(java.lang.Boolean boxed) {
        return boxed == null ? false : (boolean)boxed;
    }
}

ただ、実際にはこのメソッド呼び出しをXML上で明示的に行わなくても、Data Bindingライブラリが生成するJavaソースコード(下記)内でこのメソッドを呼び出して安全にunboxしてくれているようなので、これに関しては書いても書かなくてもどちらでも良さそうです。(詳しいことはしっかりとは調べれていませんので、言い切る自信はないですが)

// read android.databinding.DynamicUtil.safeUnbox(isLoading)
androidDatabindingDynamicUtilSafeUnboxIsLoading = android.databinding.DynamicUtil.safeUnbox(isLoading);

DynamicUtilsafeUnbox()について今回初めて知ったのでメモとして記載した次第です!以上です!

参考リンク

https://stackoverflow.com/questions/42872201/data-binding-safeunbox-warning