I have a fragment class that contains a ViewPager with two sites. These sites contain a few widgets like CheckBoxes and I want them to stay checked when the orientation changes.
MainFragment:
class StatsFragment: Fragment() {
private val fragmentStatsCat = StatsCategoryFragment()
private val fragmentStatsMon = StatsMonthFragment()
private lateinit var pager: ViewPager
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view: View = inflater.inflate(R.layout.fragment_stats, container, false)
pager = view.findViewById(R.id.stats_container)
val pagerAdapter = StatsScreenSlidePagerAdapter(childFragmentManager)
pager.adapter = pagerAdapter
return view
}
private inner class StatsScreenSlidePagerAdapter(fm: FragmentManager): FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
override fun getItem(position: Int): Fragment {
return if (position == 0) {
fragmentStatsCat
} else {
fragmentStatsMon
}
}
override fun getCount(): Int = 2
override fun saveState(): Parcelable? {
return null
}
}
}
One of the ViewPagerFragments:
class StatsMonthFragment: Fragment() {
companion object {
private const val CHECKBOX_KEY = "isChecked"
private const val CATEGORY_KEY = "category"
}
private lateinit var btnPrevCat: ImageButton
private lateinit var btnNextCat: ImageButton
private lateinit var cbAllCats: CheckBox
private lateinit var categories: List<Category>
private var i: Int = 0
private var isCbChecked: Boolean? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if(savedInstanceState != null) {
i = savedInstanceState.getInt(CATEGORY_KEY)
isCbChecked = savedInstanceState.getBoolean(CHECKBOX_KEY)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_stats_months, container, false)
btnNextCat = view.findViewById(R.id.btn_next_cat)
btnPrevCat = view.findViewById(R.id.btn_prev_cat)
cbAllCats = view.findViewById(R.id.cb_all_cats)
categories = DatabaseInitializer.getInstance().getAllCategories(AppDatabase.getInstance(context).categoryDao())
if(isCbChecked == null || isCbChecked!!) {
val initFragment = StatsSelectedFilterFragment() //This is just a fragment with a TextView; I use fragments for that for the CustomAnimations
initFragment.setText(categories[i].cat_name)
childFragmentManager.beginTransaction().add(R.id.container_stats_categories, initFragment).commit()
} else {
val fragment = StatsSelectedFilterFragment()
fragment.setText(getString(R.string.all_categories))
childFragmentManager.beginTransaction()
.replace(R.id.container_stats_categories, fragment)
.commit()
}
btnNextCat.setOnClickListener {
val fragment = StatsSelectedFilterFragment()
fragment.setText(categories[i].cat_name)
childFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left)
.replace(R.id.container_stats_categories, fragment)
.commit()
}
btnPrevCat.setOnClickListener {
val fragment = StatsSelectedFilterFragment()
fragment.setText(categories[i].cat_name)
childFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right)
.replace(R.id.container_stats_categories, fragment)
.commit()
}
cbAllCats.setOnCheckedChangeListener{_,checked->
if(checked) {
val fragment = StatsSelectedFilterFragment()
fragment.setText(getString(R.string.all_categories))
childFragmentManager.beginTransaction()
.replace(R.id.container_stats_categories, fragment)
.commit()
} else {
val fragment = StatsSelectedFilterFragment()
fragment.setText(categories[i].cat_name)
childFragmentManager.beginTransaction()
.replace(R.id.container_stats_categories, fragment)
.commit()
}
}
return view
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
val isAllCatsChecked = cbAllCats.isChecked
outState.putBoolean(CHECKBOX_KEY, isAllCatsChecked)
outState.putInt(CATEGORY_KEY, i)
}
}
Now on every screen rotation, the fragment gets recreated twice, first with savedInstanceState != null
and then with savedInstanceState == null
, which means that I don't have access to the old settings in the Fragments. As I know, it is because the fragment gets recreated, and then the MainFragment containing the viewpager gets also recreated, which means that the fragments within the viewPager get created a second time. I have tried something like this in the MainFragment:
private lateinit var fragmentStatsCat: StatsCategoryFragment
private lateinit var fragmentStatsMon: StatsMonthFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if(savedInstanceState == null) {
fragmentStatsMon = StatsMonthFragment()
fragmentStatsCat = StatsCategoryFragment()
} else {
//Finding old fragments
}
}
Maybe this would solve the problem (I am not sure), but I don't know how I can find the fragments, as they are inside a ViewPager.
EDIT I have now found a solution to find the fragments when the activity is recreated:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if(savedInstanceState == null)
{
fragmentStatsMon = StatsMonthFragment()
fragmentStatsCat = StatsCategoryFragment()
} else {
fragmentStatsMon = childFragmentManager.getFragment(savedInstanceState, FRAG_MONTH_TAG) as StatsMonthFragment
fragmentStatsCat = childFragmentManager.getFragment(savedInstanceState, FRAG_CAT_TAG) as StatsCategoryFragment
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
childFragmentManager.putFragment(outState, FRAG_CAT_TAG, fragmentStatsCat)
childFragmentManager.putFragment(outState, FRAG_MONTH_TAG, fragmentStatsMon)
}
This puts a reference in the Bundle
with which I can get the fragment afterwards. But now, I have another problem. When the activity is recreated, it throws the following exception:
java.lang.IllegalStateException: Fragment already added: StatsCategoryFragment{f06ec65 (a1991f39-3af4-4a74-9aeb-79274beae04a) id=0x7f09014a}
I don't know how to solve that because I don't really manually add the fragments. Also, I don't really get it because the viewpager and the adapter also get recreated, don't they? So why are the fragments still attached to the adapter?
I also don't know in which line of code this happens, I could neither find it out in the logcat nor by debugging.