Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

Suyama
October 10, 2021

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

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

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

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

Suyama

October 10, 2021
Tweet

More Decks by Suyama

Other Decks in Programming

Transcript

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

    • ViewModel, LiveData, Coroutines導入済み データ管理はViewModelで管理できている →全画面を画面回転できるようにするだけ?
  2. マルチモジュールを採用しているので、モジュール単位で開発を担当 • GitHubのプロジェクトボードを利用 • モジュール単位でカード作成 • プロジェクトボードのカラムでカードのステータス管理 a. 待機中、開発中、完了 b.

    テンプレートでステータス変更を自動でできる 参考資料 :https://docs.github.com/ja/issues/organizing-your-work-with-project-boards/managing-project-boards/about-proj ect-boards タブレット対応の方針と導入実績
  3. Activity再生成 1. Activityを再生成させない • AndroidManifest.xmlに “android:configChanges” 属性を追加 • システムによってActivityが再生成されない •

    公式で最後の手段として検討する手法と紹介されている 参考資料:https://developer.android.com/guide/topics/resources/runtime-changes
  4. Activity再生成時のデータ管理 class SampleActivity : AppCompatActivity() { // Activity再生成で0に初期化 private var

    mCount: Int = 0 override fun onCreate(state: Bundle?) { class SampleViewModel : ViewModel() { // Activity再生成で初期化されない var count: Int = 0
  5. 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() }
  6. 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() }
  7. Activity再生成時のDialogFragmentのリスナー class SampleDialogFragment : DialogFragment() { interface SampleListener { fun

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

    ViewModelFactory<SampleViewModel> private val viewModel by viewModels<SampleViewModel> { factory } fun clickButton() { // ViewModel経由で結果反映 viewModel.onClick() }
  9. 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) { // 次画面に遷移 }
  10. 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 } }
  11. 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 // ダイアログ表示 }
  12. Activity再生成時のネットワーク通信 class SampleActivity : AppCompatActivity() { private fun fetchData() {

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

    { runCatching { // ネットワーク通信中にActivity再生成でキャンセル }.fold( onSuccess = {}, onFailure = { // ここに入らない showDialog() } ) } Activity再生成時のネットワーク通信
  14. 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)
  15. 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) }
  16. 補足 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) // ロジックなどの処理 }
  17. 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
  18. 対応項目一覧 Activity再生成 データ管理 AlertDialog DialogFragmentのリスナー LiveDataのobserve Analytics送信 ネットワーク通信 ViewModelの初期化 画面サイズの違い

    TabLayoutのタブ幅 GridLayoutのspanCount 横向き表示でのレイアウト 横向き表示でViewが隠れる 入力デバイスの違い キーボードでの操作