0

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.

Apri
  • 1,241
  • 1
  • 8
  • 33

2 Answers2

0

Put this line in AndroidManifest.xml in the entry of your Activity in which you are loading your Fragments:

android:configChanges="layoutDirection|keyboardHidden|orientation|screenSize"

By this, Data will not be reset when your Fragment is recreated by any condition.

Example:

<activity
        android:name=".activity.MainActivity"
        android:configChanges="layoutDirection|keyboardHidden|orientation|screenSize"
        android:label="@string/app_name"></activity>
Rajat Mehra
  • 1,462
  • 14
  • 19
  • This prevents the activity from being recreated. But I want the activity to recreate, because for some Ui elements I have a different layout in landscape mode. – Apri Jul 16 '19 at 10:32