Androidアプリ開発において、複数のFragment間でViewModelを共有することは、一貫した状態管理とデータフローを維持するために非常に重要です。
このポストでは、ViewModelのインスタンスを管理し、データを共有する方法について記述しました。
ViewModelインスタンスの作成と共有
ViewModel
- ViewModelを使用してデータを管理する際、Fragmentが同じActivityに属していればViewModelインスタンスを共有することができます。
これにより、Fragment間でデータの同期と状態の共有が可能です。
📌 ViewModelインスタンスの共有例
ViewModel
class InViewModel : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data
private val _data2 = MutableLiveData<String>()
val data2: LiveData<String> = _data2
fun updateData(newData: String) {
_data.value = newData
}
fun function1(text: String) {
_data2.value = text
Log.d("sdsd", "function1: ${_data2.value}")
}
fun function2(text: String) {
_data2.value = text
Log.d("sdsd", "function2: ${_data2.value}")
}
}
- 現在このビューモデルはデータを管理する中央保存庫の役割を担っており、MutableLiveDataを使用してデータを保存し
このデータはLiveDataを通じて他のコンポーネントと共有されます。
1. インスタンスの共有
- 直接フラグメントからテキストを入力して、ビューモデルを通じて他のフラグメントに値を伝えようとしています。
値を伝える前の例
値を伝える例
- 入力を行ってデータを伝えるフラグメント
class HomeFragment : BaseFragment<FragmentHomeBinding>(R.layout.fragment_home) {
private lateinit var viewModel: InViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(requireActivity()).get(InViewModel::class.java)
binding.button.setOnClickListener {
val data = binding.editText.text.toString()
viewModel.updateData(data)
}
binding.button2.setOnClickListener {
findNavController().navigate(R.id.action_nav_home_to_nav_sam)
}
}
}
HomeFragmentはユーザーからの入力を受けてViewModelのデータを更新する役割を持っています。
ユーザーはEditTextにデータを入力し、ボタンをクリックしてこのデータをViewModelに渡すことができます。
-
ViewModelProvider
を通じてActivity 範囲の ViewModel インスタンス
を取得します。 - ユーザーがボタンをクリックすると、EditTextからテキストを読み取り、ViewModelのupdateDataメソッドを呼び出します。
- その後、ボタンをクリックすると、別のフラグメントに移動します。
2.HomeFragmentからデータを受け取って表示します。
class SamFragment : BaseFragment<FragmentSamBinding>(R.layout.fragment_sam) {
private lateinit var viewModel: InViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(requireActivity()).get(InViewModel::class.java)
viewModel.data.observe(viewLifecycleOwner) { data ->
binding.textView.text = data
}
}
}
SamFragmentはHomeFragmentから更新されたデータを観察し、それを画面に表示します。 LiveDataを使用してデータの変更を観察し、データが変更されると自動的にUIを更新します。
- ViewModelのLiveDataであるdataを観察し、変更があるたびにTextViewを更新します。
ViewModelProvider(requireActivity())
-
フラグメント間でデータを伝達するために
ViewModelProvider
にrequireActivity
を使用し、
同じViewModelインスタンスを共有します。これにより、HomeFragmentで設定されたデータをSamFragmentでも観察することができます。 -
この例を通じて、LiveDataを使用してフラグメント間でデータを共有し、変更をリアルタイムで観察することができることを確認できます。重要なのは同じ
ViewModelインスタンス
を共有することです。
2. 個別インスタンス
- 今回は、アクティビティ範囲のViewModelインスタンスではなく、該当フラグメントに対するViewModelインスタンスを使用してデータ共有を確認します。
値の伝達失敗
- データを入力して伝達するフラグメント
class HomeFragment2 : BaseFragment<FragmentHome2Binding>(R.layout.fragment_home2) {
private lateinit var viewModel: InViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(InViewModel::class.java)
binding.button.setOnClickListener {
val data = binding.editText.text.toString()
viewModel.updateData(data)
}
binding.button2.setOnClickListener {
findNavController().navigate(R.id.action_nav_home2_to_nav_another)
}
}
}
HomeFragmentはユーザーからの入力を受けてViewModelのデータを更新する役割を持っています。
ViewModelProvider(this)を通じて自分専用のViewModelインスタンスを生成し、管理しています。
- フラグメント内で新しいViewModelインスタンスを生成します。
2.HomeFragment2からデータを受け取って表示しようとする別のビュー
class AnotherFragment : BaseFragment<FragmentAnotherBinding>(R.layout.fragment_another) {
private lateinit var viewModel: InViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(InViewModel::class.java)
viewModel.data.observe(viewLifecycleOwner) { data ->
binding.textView.text = data
}
}
}
AnotherFragmentはViewModelのdata LiveDataを観察し、このデータが更新されるたびにtextViewのテキストを更新します。
このフラグメントはViewModelProvider(this)を使用して自分専用のViewModelインスタンスを生成します。
ViewModelProvider(this).get(InViewModel::class.java)
-
ViewModelProvider(this)を使用すると、該当フラグメントに対するViewModelインスタンスが生成されるため、データを伝達する際には
アクティビティ内のすべてのフラグメントで共有が不可能になり、フラグメント間でデータを共有または通信するためには別のメカニズムが必要になります。 -
このように生成されたViewModelインスタンスは、該当フラグメントのライフサイクルに従って管理されます。つまり、フラグメントが破棄されるときにViewModelも一緒に破棄されます。
3. 共通と個別インスタンス
- 今回は、データを送信するフラグメントにデータバインディングオブジェクトとビューモデルの接続を行い、requireActivity()を宣言して、データを受け取るフラグメントのインスタンスを宣言して確認します。
- データを入力して伝達するフラグメント
class HomeFragment3 : BaseFragment<FragmentHome3Binding>(R.layout.fragment_home3) {
private lateinit var viewModel: InViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(requireActivity()).get(InViewModel::class.java)
binding.viewModel = viewModel
binding.lifecycleOwner = this
binding.button.setOnClickListener {
viewModel.function1(binding.editText.text.toString())
}
binding.button2.setOnClickListener {
findNavController().navigate(R.id.action_nav_home3_to_nav_other)
}
}
}
HomeFragment3はデータバインディングオブジェクトをビューモデルに接続し、ビューモデルのデータをUIと直接バインドし、イベントベースでビューモデルの関数を呼び出す方法です。
2.HomeFragment3からデータを受け取って表示しようとする別のビュー
class OtherFragment : BaseFragment<FragmentOtherBinding>(R.layout.fragment_other) {
private lateinit var viewModel: InViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(InViewModel::class.java)
viewModel.data2.observe(viewLifecycleOwner) { text ->
binding.textView.text = text
}
binding.button.setOnClickListener {
val text = binding.textView.text.toString()
viewModel.function2(text)
}
}
}
ViewModelProvider(this)を使用してフラグメント自体にビューモデルインスタンスを生成し、
ユーザーがボタンをクリックすると、現在のtextViewに表示されたテキストを読み取り、ViewModelのfunction2メソッドを呼び出してdata2を更新します。
動作確認
class InViewModel : ViewModel() {
private val _data = MutableLiveData<String>()
val data: LiveData<String> = _data
private val _data2 = MutableLiveData<String>()
val data2: LiveData<String> = _data2
fun updateData(newData: String) {
_data.value = newData
}
fun function1(text: String) {
_data2.value = text
Log.d("sdsd", "function1: ${_data2.value}")
}
fun function2(text: String) {
_data2.value = text
Log.d("sdsd", "function2: ${_data2.value}")
}
}
- ビューモデルに宣言された関数function1とfunction2を見ると、HomeFragment3がfunction1にデータを送り、_data2の値をテキストで設定した値に更新します。
- ログを確認すると、ライブデータに値が設定されていることがわかります。
2._data2の値が設定されたことを確認した後、function2をOtherFragmentで実行して取得してみます。
現在のコードでは、HomeFragment3とOtherFragmentがそれぞれ独立したビューモデルインスタンスを使用しています。
つまり、HomeFragment3で設定したdata2の値はOtherFragmentでアクセスできず、ビューモデルの関数のログを通じて直感的に確認できました。これはViewModelProvider(this)を使用してフラグメントごとに別のビューモデルインスタンスを生成するためです。
📌 まとめ
それでは、最初から快適にアクティビティ範囲のビューモデルインスタンスを全て宣言していた方が便利ではないかと思うかもしれませんが、選択は状況により異なります。フラグメント間でデータを共有する必要がなく、各フラグメントが独立したデータを扱う場合には、ViewModelProvider(this)
を使用するのが適切であり、コードの一貫性と可読性のために全ての場所でrequireActivity()
を使用するよりも、実際に必要な場合にのみ使用してコードの意図を明確にし、不要なアクティビティ範囲のビューモデルの生成を避けることができます。
また、同じインスタンスを使用することで、Androidアプリでのデータの一貫性と反応性を保証し、複数のFragmentで同じビューモデルインスタンスを共有することで複雑なデータフローと状態管理を簡単に処理することができます。このアプローチはアプリの保守性を高め、開発プロセスを効率化することが再確認されました。
ViewModelProvider
を使用せずにby viewModels()
を使用すると、ViewModelProvider
を直接使用することなく、ViewModelインスタンス
を自動的に生成し管理することができます。特定の範囲でViewModel
を共有したい場合は、by activityViewModels()
を使用して宣言する方法も一つの選択肢です。
GitHub : https://github.com/GEUN-TAE-KIM/ViewModelInstanceStudy_Sample.git