Save 37% off PRO during our Black Friday Sale! »

マルチデバイス対応で考慮すべきポイント

4c0c2a5a353b06c9d0238373872822bb?s=47 Suyama
October 10, 2021

 マルチデバイス対応で考慮すべきポイント

本発表では、マルチデバイス対応で何を準備すべきかやどこから始めればいいかを実際にタブレット対応した事例を元に紹介していきます。

Androidは、タブレットやChromeBookに加えて最近ではフォルダブル端末も発表されるなど、様々な種類の端末が販売されてきました。
2021年の年末頃にPixel初のフォルダブル端末がリリースされる可能性も示唆されており、Androidアプリの開発は、それらの様々な端末上で正常に動作することが求められるようになっています。

すぐに全ての端末に対応することは非常に難しいため、マルチデバイス対応を始めるための知識と準備段階から、第一段階としてのタブレット対応と画面回転対応について発表していきます。特に、画面回転対応はマルチデバイス対応に関わらずスマホのみでの利用でも必要な要素のため、画面回転対応でどこを気をつけたほうがいいかわからない方も本発表をご参考にしてみてください。

4c0c2a5a353b06c9d0238373872822bb?s=128

Suyama

October 10, 2021
Tweet

Transcript

  1. マルチデバイス対応で考 慮すべきポイント Junichiro Suyama

  2. 自己紹介 名前:Junichiro Suyama 会社:Studyplus Inc. DroidKaigi 2020でも採択 「Navigation Componentを実践導入した際の感 動、便利さ、そしてつまづき」

  3. Agenda 1. マルチデバイス対応とは 2. Studyplusの開発状況 3. タブレット対応の方針と導入実績 4. タブレット対応が必要な項目 a.

    Activityの再生成 b. 画面サイズの違い c. 入力デバイスの違い
  4. Agenda 1. マルチデバイス対応とは 2. Studyplusの開発状況 3. タブレット対応の方針と導入実績 4. タブレット対応が必要な項目 a.

    Activityの再生成 b. 画面サイズの違い c. 入力デバイスの違い
  5. マルチデバイス対応とは 様々な種類の端末でAndroid OSが動作 • スマホ、フォルダブルスマホ • タブレット、ChromeBook • Android Wear、スマートテレビ

    それぞれの端末上でアプリが正常に動作することが求められる
  6. マルチデバイス対応とは 特にAndroidタブレットの需要が急増 • コロナ禍での自宅利用 • 学校でタブレット支給 マルチデバイス対応の第一段階として、タブレット対応を行った

  7. Agenda 1. マルチデバイス対応とは 2. Studyplusの開発状況 3. タブレット対応の方針と導入実績 4. タブレット対応が必要な項目 a.

    Activityの再生成 b. 画面サイズの違い c. 入力デバイスの違い
  8. 会社紹介

  9. Studyplusの開発状況 MADスコア(Modern Android Development) • 自身のAndroid開発がどれだけ最新であるかをスコア化できるツール • Kotlin, Jetpack, Android

    Studio, Android App Bundleなど 参考資料:https://developer.android.com/modern-android-development/scorecard
  10. Studyplusの開発状況

  11. Studyplusの開発状況

  12. Studyplusの開発状況

  13. Studyplusの開発状況

  14. Studyplusの開発状況

  15. Studyplusの開発状況 タブレット対応前 • 130のActivity + 90のFragment = 約220クラス • 全画面を縦方向表示で固定

    • ViewModel, LiveData, Coroutines導入済み
  16. Studyplusの開発状況 タブレット対応前 • 130のActivity + 90のFragment = 約220クラス • 全画面を縦方向表示で固定

    • ViewModel, LiveData, Coroutines導入済み データ管理はViewModelで管理できている →全画面を画面回転できるようにするだけ?
  17. Studyplusの開発状況 タブレット対応前 • 130のActivity + 90のFragment = 約220クラス • 全画面を縦方向表示で固定

    • ViewModel, LiveData, Coroutines導入済み 意外と対応することが多かった...
  18. Agenda 1. マルチデバイス対応とは 2. Studyplusの開発状況 3. タブレット対応の方針と導入実績 4. タブレット対応が必要な項目 a.

    Activityの再生成 b. 画面サイズの違い c. 入力デバイスの違い
  19. • Android開発者:2名 • 開発体制:フルリモート • 開発期間:4ヶ月 必須の開発案件以外はストップ タブレット対応の方針と導入実績

  20. マルチモジュールを採用しているので、モジュール単位で開発を担当 • GitHubのプロジェクトボードを利用 • モジュール単位でカード作成 • プロジェクトボードのカラムでカードのステータス管理 a. 待機中、開発中、完了 b.

    テンプレートでステータス変更を自動でできる 参考資料 :https://docs.github.com/ja/issues/organizing-your-work-with-project-boards/managing-project-boards/about-proj ect-boards タブレット対応の方針と導入実績
  21. 開発手順 1. 大まかな対応内容を確認するため、1つのモジュールでペアプロ 2. 開発方針の認識共有できたら各モジュールを対応 3. モジュール対応終わったら開発者が検証 4. 検証で動作に問題なかったらプルリク作成 5.

    全モジュール完了後、全員で全画面検証 タブレット対応の方針と導入実績
  22. タブレット対応の方針と導入実績 検証について • 端末種類:スマホ、タブレット • 端末状態:縦方向表示、横方向表示 • 画面数:約220クラス 約880通りの検証をする必要がある

  23. タブレット対応の方針と導入実績 検証について 1. 各モジュール修正後に動作確認 2. 全画面修正完了後に動作確認 3. iOSチーム含めた7名で最終動作確認

  24. タブレット対応の方針と導入実績 リリースについて 1. 各モジュールの対応完了後、毎週リリースに組み込む 2. 全モジュールの対応完了後、画面回転できる対応を入れ最終リリース

  25. タブレット対応の方針と導入実績 最終リリース後について • スケジュール通りの4ヶ月で完了 • 大きな不具合は0件、軽微な不具合は数件(対応漏れ) ※QAは端末種類や端末状態が増えるため、事前に調整する

  26. 1. マルチデバイス対応とは 2. Studyplusの開発状況 3. タブレット対応の方針と導入実績 4. タブレット対応が必要な項目 a. Activityの再生成

    b. 画面サイズの違い c. 入力デバイスの違い Agenda
  27. Activity再生成 下記の条件でActivityの再生成が起こる • 画面の向き変更 • マルチウィンドウのサイズ変更 • 言語設定、Light/Darkの切り替えなど タブレットでの利用でActivityの再生成がよく起きる

  28. Activity再生成 Activity再生成の対応方法 1. Activityを再生成させない(非推奨) 2. Jetpack Composeの導入 3. Activity再生成に対応

  29. Activity再生成 1. Activityを再生成させない • AndroidManifest.xmlに “android:configChanges” 属性を追加 • システムによってActivityが再生成されない •

    公式で最後の手段として検討する手法と紹介されている 参考資料:https://developer.android.com/guide/topics/resources/runtime-changes
  30. Activity再生成 2. Jetpack Composeの導入 • Jetpack Composeを画面レベルで導入 • 状態管理が新しくなり、Activity再生成にうまく対応できる •

    View全体を書き換えるため、導入コストがとても高い
  31. Activity再生成 3. Activity再生成に対応 • ViewModel, SavedInstanceStateを用いて状態を管理 • Activity/Fragmentの初期化処理を見直す必要がある • AAC導入しているなら書き換えコストが低め

  32. Activity再生成 Studyplusが採用した方式 1. Activityを再生成させない(非推奨) 2. Jetpack Composeの導入 3. Activity再生成に対応

  33. Activity再生成 Activity再生成の対応観点 • Activity/Fragmentの初期化処理の見直し • ViewModel, LiveDataの使い方を見直し

  34. Activity再生成時のデータ管理 class SampleActivity : AppCompatActivity() { // Activity再生成で0に初期化 private var

    mCount: Int = 0 override fun onCreate(state: Bundle?) { class SampleViewModel : ViewModel() { // Activity再生成で初期化されない var count: Int = 0
  35. Activity再生成時のAlertDialog class SampleActivity : AppCompatActivity() { fun showDialog() { //

    Activity再生成時にDialogが消える MaterialAlertDialogBuilder(this) .setMessage(R.string.error_network) .setPositiveButton(android.R.string.ok) { _, _ -> finish() } .setCancelable(false) .show() }
  36. Activity再生成時のAlertDialog // Activity再生成時も消えないダイアログ class InitNetworkErrorDialog : DialogFragment() { override fun

    onCreateDialog(state: Bundle?): Dialog { isCancelable = false return MaterialAlertDialogBuilder(requireActivity()) .setMessage(R.string.error_network) .setPositiveButton(android.R.string.ok) { _, _ -> activity?.finish() } .setCancelable(false) .create() }
  37. Activity再生成時のDialogFragmentのリスナー class SampleDialogFragment : DialogFragment() { interface SampleListener { fun

    onClick() } fun click() { // Activity再生成で前のインスタンスに通知 listener.onClick() }
  38. Activity再生成時のDialogFragmentのリスナー class SampleDialogFragment : DaggerDialogFragment() { @Inject lateinit var factory:

    ViewModelFactory<SampleViewModel> private val viewModel by viewModels<SampleViewModel> { factory } fun clickButton() { // ViewModel経由で結果反映 viewModel.onClick() }
  39. 補足 MaterialTimePicker/MaterialDatePickerのリスナー ViewModel経由もできない →Activity再生成時にはダイアログが消える仕様にしている 参考資料:https://github.com/material-components/material-components-android/issues/1688

  40. Activity再生成時のLiveDataのobserve class SampleViewModel() : ViewModel() { val showDialog = MutableLiveData<Boolean>()

    val navigateNext = MutableLiveData<Boolean>() class SampleActivity : AppCompatActivity() { override fun onCreate(state: Bundle?) { viewModel.showDialog.observe(this) { // ダイアログ表示 } viewModel.navigateNext.observe(this) { // 次画面に遷移 }
  41. Activity再生成時のLiveDataのobserve 参考資料 :https://medium.com/androiddevelopers/livedata-with-snackbar-navigation-and-other-events-the-singleliveevent-ca se-ac2622673150 open class Event<out T>(private val content:

    T) { var hasBeenHandled = false private set fun getContentIfNotHandled(): T? { return if (hasBeenHandled) { null } else { hasBeenHandled = true content } }
  42. Activity再生成時のLiveDataのobserve class SampleViewModel() : ViewModel() { val showDialog = MutableLiveData<Event<Boolean>>()

    class SampleActivity : AppCompatActivity() { override fun onCreate(state: Bundle?) { viewModel.showDialog.observe(this) { event -> val isShow = event. getContentIfNotHandled() // Activity再生成時はnullでアーリーリターン if (isShow == null) return // ダイアログ表示 }
  43. Activity再生成時のAnalytics送信 画面の初期化処理でAnalytics送信 Activity再生成のたびにFirebaseAnalyticsが送信される →複数回送信されないようにViewModelのinitかEventで制御

  44. Activity再生成時のネットワーク通信 画面の初期化処理でネットワーク通信 Activity再生成のたびにネットワーク通信される →不要なネットワーク通信しないようにViewModelのinitかEventで制御

  45. Activity再生成時のネットワーク通信 class SampleActivity : AppCompatActivity() { private fun fetchData() {

    lifecycleScope.launch { runCatching { // ネットワーク通信中にActivity再生成でJobCancelException }.fold( onSuccess = {}, onFailure = { // UI操作でクラッシュ showDialog() } ) }
  46. class SampleActivity : AppCompatActivity() { private fun fetchData() { lifecycleScope.launchWhenResumed

    { runCatching { // ネットワーク通信中にActivity再生成でキャンセル }.fold( onSuccess = {}, onFailure = { // ここに入らない showDialog() } ) } Activity再生成時のネットワーク通信
  47. Activity再生成時のViewModel初期化 class SampleViewModel() : ViewModel() { private var mCount: Int

    fun init(count: Int) mCount = count } class SampleActivity : AppCompatActivity() { @Inject lateinit var factory: ViewModelFactory<SampleViewModel> private val viewModel: SampleViewModel by viewModels { factory } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel.init(count)
  48. Activity再生成時のViewModel初期化 参考資料:https://star-zero.medium.com/viewmodel%E3%81%A7assistedinject%E3%82%92%E4%BD%BF%E3%81%86-1bb375f6cd48 class SampleViewModel @AssistedInject constructor( @Assisted private val count:

    Int, ) : ViewModel() { @AssistedFactory interface Factory { fun create(count: Int): SampleViewModel } class SampleActivity : AppCompatActivity() { @Inject lateinit var factory: SampleViewModel.Factory private val viewModel: SampleViewModel by viewModels { factory.create(count) }
  49. 補足 FragmentのonCreateViewと onViewCreatedの使い分け • onCreateView ◦ レイアウトのinflateするのみ • onViewCreated ◦

    ロジックなどの処理 参考資料 :https://developer.android.com/reference/kotlin/android x/fragment/app/Fragment#oncreateview class SampleFragment : Fragment(R.layout.fragment_sample) { override fun onViewCreated(view: View, state: Bundle?) { super.onViewCreated(view, savedInstanceState) // ロジックなどの処理 }
  50. 1. マルチデバイス対応とは 2. Studyplusの開発状況 3. タブレット対応の方針と導入実績 4. タブレット対応が必要な項目 a. Activityの再生成

    b. 画面サイズの違い c. 入力デバイスの違い Agenda
  51. TabLayoutのタブ幅 <com.google.android.material.tabs.TabLayout … app:tabGravity="fill" app:tabMaxWidth="0dp" />

  52. GridLayoutのspanCount <androidx.recyclerview.widget.RecyclerView android:layout_width="match_parent" android:layout_height="wrap_parent" app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" app:spanCount="4" />

  53. GridLayoutのspanCount // values/integers.xml <integer name="grid_span_size">4</integer> // values-w600dp/integers.xml <integer name="grid_span_size">8</integer> //

    values-w905dp/integers.xml <integer name="grid_span_size">12</integer> <androidx.recyclerview.widget.RecyclerView android:layout_width="match_parent" android:layout_height="wrap_parent" app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" app:spanCount="@integer/grid_span_size" /> 参考資料:https://material.io/design/layout/responsive-layout-grid.html#breakpoints
  54. 横向き表示でレイアウト調整 画像サイズや他のViewの大きさバランスが崩れる →デザイナーと相談しながらレイアウト調整

  55. 横向き表示でViewが隠れる 操作ボタンが隠れてユーザーが操作できない →ScrollViewを各画面に導入

  56. 補足 Material DesignのBottomNavigationで画面サイズに よって位置を変更させることが提案されている 360dp-599dp:下部のナビゲーションバー 600dp-1239dp:ナビゲーションレール 1240dp+:ナビゲーションドロワー 参考資料:https://material.io/components/bottom-navigation#behavior

  57. 1. マルチデバイス対応とは 2. Studyplusの開発状況 3. タブレット対応の方針と導入実績 4. タブレット対応が必要な項目 a. Activityの再生成

    b. 画面サイズの違い c. 入力デバイスの違い Agenda
  58. キーボードでの操作 キーボード操作でも動作に問題ないか確認 • Tabボタンで入力フォームの移動など

  59. 対応項目一覧 Activity再生成 データ管理 AlertDialog DialogFragmentのリスナー LiveDataのobserve Analytics送信 ネットワーク通信 ViewModelの初期化 画面サイズの違い

    TabLayoutのタブ幅 GridLayoutのspanCount 横向き表示でのレイアウト 横向き表示でViewが隠れる 入力デバイスの違い キーボードでの操作
  60. まとめ • タブレット対応で対応すべき項目が意外と多かった • 非エンジニアとの調整もあるので早めに行動するのも大事 • AACやデータ管理がきちんと導入できれば品質向上にもつながる

  61. まとめ • タブレット対応で対応すべき項目が意外と多かった • 非エンジニアとの調整もあるので早めに行動するのも大事 • AACやデータ管理がきちんと導入できれば品質向上にもつながる マルチデバイス対応を進めていきましょう!