2018年4月12日木曜日

ViewPager + Fragment で AAC の ViewModel を使う

各ページが Fragment な ViewPager があるとします。
  1. class MainActivity : AppCompatActivity() {  
  2.   
  3.     override fun onCreate(savedInstanceState: Bundle?) {  
  4.         super.onCreate(savedInstanceState)  
  5.         setContentView(R.layout.activity_main)  
  6.   
  7.         val pager: ViewPager = findViewById(R.id.pager)  
  8.   
  9.         pager.adapter = MyPagerAdapter(supportFragmentManager)  
  10.     }  
  11.   
  12.     class MyPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm) {  
  13.         override fun getItem(position: Int): Fragment {  
  14.             return PageFragment.newInstance(position)  
  15.         }  
  16.   
  17.         override fun getCount(): Int {  
  18.             return 10  
  19.         }  
  20.     }  
  21. }  
FragmentStatePagerAdapter を使った場合、不要になったページの Fragment インスタンスは破棄されます。
(FragmentPagerAdapter では一度作った Fragment インスタンスは保持されます)

そのため各ページの Fragment 内で ViewModel を取得するときに、以下のように ViewModelProviders.of() にその Fragment のインスタンスを指定してしまうと、 再度そのページが必要になったときに ViewModel も新しく作り直されてしまいます。
  1. class PageFragment : Fragment() {  
  2.   
  3.     ...  
  4.   
  5.     override fun onActivityCreated(savedInstanceState: Bundle?) {  
  6.         super.onActivityCreated(savedInstanceState)  
  7.   
  8.         val viewModel = ViewModelProviders.of(this)  
  9.                 .get(PageViewModel::class.java)  
  10.   
  11.         val position = arguments!!.getInt("position")  
  12.   
  13.         Log.d("PageFragment""$position : $viewModel")  
  14.     }  
  15.   
  16.     companion object {  
  17.         fun newInstance(position: Int): PageFragment {  
  18.             return PageFragment().apply {  
  19.                 arguments = Bundle().apply {  
  20.                     putInt("position", position)  
  21.                 }  
  22.             }  
  23.         }  
  24.     }  
  25. }  
例えば最初に 1ページ目が表示され、そこから3ページ目に移動して1ページ目に戻ってくると、ViewModel のインスタンスが新しくなってしまい以前のデータを使えません。

D/PageFragment: 0 : net.yanzm.sample.PageViewModel@2dc8dc7
D/PageFragment: 1 : net.yanzm.sample.PageViewModel@389f31d
D/PageFragment: 2 : net.yanzm.sample.PageViewModel@60a3f63
D/PageFragment: 3 : net.yanzm.sample.PageViewModel@ad7a419
D/PageFragment: 0 : net.yanzm.sample.PageViewModel@8d76ebf ← 2dc8dc7 ではなく、別のインスタンスが来てしまう

そこで ViewModelProviders.of() に ViewPager を持つ Activity を指定すると、ViewModel のインスタンスが Activity にひも付くので、 ViewPager のページ移動で Fragment が作り直されても ViewModel は以前のインスタンスが取れるようになります。
  1. val viewModel = ViewModelProviders.of(activity)  
  2.         .get(PageViewModel::class.java)  
しかし別の問題が起こります。各ページが同じ PageFragment クラスなので、今度は全部のページで同じ ViewModel のインスタンスが来てしまいます。

D/PageFragment: 0 : net.yanzm.focussample.PageViewModel@a704306
D/PageFragment: 1 : net.yanzm.focussample.PageViewModel@a704306
D/PageFragment: 2 : net.yanzm.focussample.PageViewModel@a704306
D/PageFragment: 3 : net.yanzm.focussample.PageViewModel@a704306
D/PageFragment: 0 : net.yanzm.focussample.PageViewModel@a704306

各ページで別の ViewModel インスタンスを使いたいので、ViewModel のインスタンスを取得するときにキーを指定するようにします。
  1. val position = arguments!!.getInt("position")  
  2.   
  3. val viewModel = ViewModelProviders.of(activity)  
  4.         .get(position.toString(), PageViewModel::class.java) // position をキーとして指定  
各ページで別の ViewModel インスタンスを使い、ViewPager のページ移動で Fragment が作り直されても ViewModel は以前のインスタンスが取れるようになりました。

D/PageFragment: 0 : net.yanzm.focussample.PageViewModel@a704306
D/PageFragment: 1 : net.yanzm.focussample.PageViewModel@2dc8dc7
D/PageFragment: 2 : net.yanzm.focussample.PageViewModel@a052ff4
D/PageFragment: 3 : net.yanzm.focussample.PageViewModel@389f31d
D/PageFragment: 0 : net.yanzm.focussample.PageViewModel@a704306 ← 同じ a704306 が返ってきている。


ViewPager を持っているのが Fragment の場合は、parentFragment を指定すれば ViewModel を ViewPager を持っている Fragment にひも付けることができます。
  1. val position = arguments!!.getInt("position")  
  2.   
  3. val viewModel = ViewModelProviders.of(parentFragment!!)  
  4.         .get(position.toString(), PageViewModel::class.java) // position をキーとして指定  




0 件のコメント:

コメントを投稿