前人未踏の領域へ アプリ開発編

Android, iOSアプリ開発に関する調査メモ置き場。ほとんどAndroid。古い記事にはアプリ以外も含まれます。

Android constraintlayout.widget.GroupのメンバーViewに個別のvisibilityが効かない

課題

Viewの表示・非表示を一括で管理するために androidx.constraintlayout.widget.Group でVIewをまとめたところ、そのGroup内のViewに対して 個別のVisibilityを設定しても反映されなくなった。

対応

Groupを使うのを諦め、ViewGroupで囲んでしまうのが無難か。

ただし本件はバグとして登録されていて、ConstraintLayout 2.0 beta 6(リリース済み)で解消されているらしい。

androidstudio.googleblog.com

https://issuetracker.google.com/issues?q=73186245

beta 6まで来てるならそろそろConstraintLayout 2.0がリリースされても良さそうだな。

Android: ViewBindingでViewHolderの記述をシンプルにする

課題

これまでButterKnifeを使ってFragment及びViewHolderのView操作を記述してきたが、ViewBinding登場に伴いButterKnifeはdeprecatedになった。 ViewHolderをViewBindingを使って書き換えたいが、どうすればよいか。

対応

user.xmlとかいうユーザー一覧のアイテムを表すlayout.xmlがあったとする。

class UsersAdapter(val context: Context, val items: ArrayList<*>) : 
     RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
        RecyclerView.ViewHolder {
        when (viewType) {
            TYPE_USER -> return UserViewHolder (
                UserBinding.inflate(inflater, parent, false)
            )
       }
    }

    inner class UserViewHolder(val binding: UserBinding) :
        RecyclerView.ViewHolder(binding.root)
}

bindingがViewの情報を全部持ってるのでViewHolderがほとんど不要になってる。 めっちゃ楽になりましたわ。

Android: ViewModelのSavedStateHandleについて

課題

ViewModelのSavedStateHandleの特性について知りたい

結論

  • SavedStateHandleはプロセスがKillされた場合に状態が保持される
  • アプリを正常終了した場合は状態がクリアされる
  • SharedPreferenceの代替とはならない

調査

ViewModelの作成

まずViewModelを作る。 SavedStateHandleAndroidViewModelの場合に効力を発揮するため、AndroidViewModelを継承する。

class UserViewModel(application: Application) : AndroidViewModel(application) {
}

次にSavedStateHandleをコンストラクタで受け取るために追加する

class UserViewModel(application: Application, 
        private val state: SavedStateHandle) : AndroidViewModel(application) {
}

name プロパティをLiveDataで追加。この際、state から値を取得、セットする。

class UserViewModel(
    application: Application,
    private val state: SavedStateHandle
) : AndroidViewModel(application) {

    val name: MutableLiveData<String> by lazy {
        state.getLiveData("NAME", "")
    }

    fun saveName(name: String) {
        state.set("NAME", name)
    }
}

Fragmentから値をセットする

お試しなので、ある画面のonResumeで値をセットしてみよう。

class HogeFragment : Fragment() {

    private val userViewModel: UserViewModel by activityViewModels()

    override fun onResume() {
        super.onResume()
         userViewModel.saveName("SUCCESS!")
    }
}

Fragmentから値を取得する

そしてまたある画面のonResume()で値を取得し、ログに出力してみる

class UserFragment : Fragment() {

    private val userViewModel: UserViewModel by activityViewModels()

    override fun onResume() {
        super.onResume()
        Log.d("SavedStateHandleテスト", "出力:${userViewModel.name.value}")
    }
}

実験

最初にUserFragmentにアクセスすると以下が表示される。値は空だ。

D/SavedStateHandleテスト: 出力:

次に HogeFragmentにアクセス、UserFragmentに戻ると以下が表示される

D/SavedStateHandleテスト: 出力:SUCCESS!

ここで一度ホームボタンを押してホームに戻り、AndroidStudioのLogCatビューからアプリを強制終了(Terminate Application)し、プロセスをKillする。 そしてアプリを再度立ち上げ、UserFragmentに遷移すると値が保持されていることが分かる。

D/SavedStateHandleテスト: 出力:SUCCESS!

次にアプリをバックボタンや最近使ったアプリ上からスワイプするなどして終了させてから アプリを立ち上げて確認すると.....

D/SavedStateHandleテスト: 出力:

値がクリアされている。
ということでonSavedInstanceStateと同じようなイメージで使用しよう。
といいうつつ普段savedInstanceStateほとんど使ってないんだけど...

ログイン情報などSharedPreferenceに持たせていた情報はこれまで通りSharedPreferenceに持ちつつViewModelと組み合わせて使うのが良さそう。