Studyplus Engineering Blog

スタディプラスの開発者が発信するブログ

Android 13への対応の注意事項

こんにちは、クライアントグループの隅山です。 しばらくFlutter開発していましたが、Android 13への対応が必要になったため直近はAndroidの更新作業を行なっていました。 今回はAndroid 13への対応項目と対応時に注意すべき点について紹介します。

Android 13への対応項目

まずは、Android 13への対応項目として下記の公式ドキュメントを参考にしました。

developer.android.com

developer.android.com

弊社で対応した項目をピックアップしていきます。

通知に関する実行時の権限

Android 13で通知に関する権限が追加されました。 Android 13を搭載した端末でAndroid 13未満をターゲットにしているアプリを起動した場合、アプリ起動時に通知の権限ダイアログが表示されるようになります。 しかし、Android 13以上をターゲットにしているアプリの場合は権限ダイアログの表示を実装する必要があります。

対応方法は下記を参考にしました。

developer.android.com

広告IDに必要な権限

Google Play開発サービスの広告IDを使用し、Android 13以上をターゲットにしているアプリでは、マニフェストに広告IDの必要な権限を宣言する必要があります。 ただし、利用ライブラリのマニフェストで広告IDに必要な権限が宣言されている場合は統合されるため、改めて権限をマニフェストに追加する必要はありません。

マニフェストの統合についてや統合されたマニフェストの確認方法は下記にありますのでご参考にしてください。 弊社では統合されたマニフェストに宣言されていたため、広告IDについては特に対応しませんでした。

developer.android.com

注意事項

上記の公式ドキュメントに沿って対応した後に動作確認したら、ある1画面でクラッシュしていました。 デバッグしてみるとlateinit property binding has not been initializedが発生していました。 エラー内容としてはlateinit変数が初期化される前に参照されているというものですが、公式ドキュメントにも該当しそうな内容の記載がなかったため調査してみました。

Android 13での変更箇所

調査してみると画面生成時に呼ばれるメソッドの順番が変わっていることがわかりました。 Android 13をターゲットにする以前はActivity生成時にonCreateが呼ばれ、その後にメニューを再描画させるinvalidateOptionsMenuが呼ばれていました。 しかし、Android 13をターゲットにすると呼ばれる順番が変わり、Activity生成時にonCreateが呼ばれる前にinvalidateOptionsMenuが呼ばれるようになっていました。

更に深く調査するとComponentActivityに変更が入っていることがわかりました。 下記が該当箇所の変更です。

android.googlesource.com

ComponentActivityのPrivateフィールドにMenuHostHelperが追加され、そのインスタンス生成時にinvalidateMenuが呼ばれるように変更されていました。 この変更により弊社のアプリのある1画面でクラッシュが起きるようになっていました。

クラッシュの原因

上記の変更がクラッシュを引き起こす致命的な修正かといえばそうではなく、弊社の実装に問題がありました。

弊社ではDataBindingを利用しておりonCreateでBindingオブジェクトを作成していました。 そして、クラッシュが起きていた画面ではinvalidateOptionsMenuでBindingオブジェクトのViewの表示非表示を制御するコードが書かれていました。

private lateinit var binding: MainBinding

override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)

  binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
}

override fun invalidateOptionsMenu() {
  super.invalidateOptionsMenu()

  // 参照データによって画面のボタンの表示非表示を制御
  binding.button.isVisible = true
}

何故invalidateOptionsMenuでBindingオブジェクトを呼び出しているのかと調査しているときに思ったが、上記の箇所がAndroid 13対応時にクラッシュしていました。

対応内容

そもそもinvalidateOptionsMenuでBindingオブジェクトを呼び出しているのが問題なのでViewModel経由でViewの表示非表示を制御する形に対応しました。 本来はinvalidateOptionsMenuでメニュー以外のViewの制御することがあまりよくないのですが、暫定対応として下記のような形で対応しました。

private lateinit var binding: MainBinding
private val viewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)

  binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

  viewModel.isVisibleButton.observe(this) {
    binding.button.isVisible = it
  }
}

override fun invalidateOptionsMenu() {
  super.invalidateOptionsMenu()

  // 参照データによって画面のボタンの表示非表示を制御
  viewModel.isVisibleButton.value = true
}

まとめ

今回はAndroid 13への対応で行ったことを紹介しました。 注意事項は実装に問題があるときに起きることですが、大きいアプリでリファクタリングが間に合っていない場合は起こり得る事象なのかなと思います。 今回の教訓としては画面制御は適切な箇所で行い、できていない場合はリファクタリングを早めに行うべきだと学びました。 今後Android 13への対応が必須になりますが、ドキュメントに書かれている以外にも注意すべきことがあり、この記事が役に立てばいいなと思います。