-1

I am trying to fix a bug where my app crashes if a user performs an orientation change during the onboarding process (I used a 3 fragment viewpager2 layout within JetPack NavHost). I tried adding my viewmodel to it so that it would be lifecycle aware during onboarding, but I received this identical error message both before and after adding the viewModel:

java.lang.IllegalStateException: FragmentManager is already executing transactions

So I think the FragmentManager object within my ViewPager2 is causing a crash. The OnBoarding works fine if I don't switch my orientation, and then works as intended post OnBoarding.

Is it best practice to lock the Onboarding to a vertical layout? I did some research via Play apps and found a lot of them went about OnBoarding in this way.

Code Reference for my First Screen of OnBoarding:

class FirstScreen : Fragment() {
   private var _binding: FragmentFirstScreenBinding? = null
   private val binding get() = _binding!!

   private val sharedViewModel: PrimaryViewModel by activityViewModels()

   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
      super.onViewCreated(view, savedInstanceState)

      binding.apply {
         viewModel = sharedViewModel
         firstScreen = this@FirstScreen
      }
   }

   override fun onCreateView(
      inflater: LayoutInflater, container: ViewGroup?,
      savedInstanceState: Bundle?
   ): View? {
      // Inflate the layout for this fragment
      _binding = FragmentFirstScreenBinding.inflate(inflater, container, false)

      val viewPager = activity?.findViewById<ViewPager2>(R.id.onboardingViewPager)

      binding.firstScreenNext.setOnClickListener {
         viewPager?.currentItem = 1
      }

      return binding.root
   }}

Adapter construction from ViewPageFragment:

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    // Inflate the layout for this fragment
    _binding = FragmentViewPagerBinding.inflate(inflater, container, false)

    val fragmentList = arrayListOf<Fragment>(
        FirstScreen(),
        SecondScreen(),
        ThirdScreen()
    )

    val adapter = ViewPagerAdapter(
        fragmentList,
        requireActivity().supportFragmentManager,
        lifecycle
    )

    binding.onboardingViewPager.adapter = adapter

    return binding.root

ViewPagerAdapter:

class ViewPagerAdapter(
   list: ArrayList<Fragment>,
   fm: FragmentManager,
   lifeCycle: Lifecycle) : FragmentStateAdapter(fm, lifeCycle) {

   private val fragmentList = list

   override fun getItemCount(): Int {
      return fragmentList.size
   }

   override fun createFragment(position: Int): Fragment {
      return fragmentList[position]
   }}

Logcat stack trace:

2022-07-11 19:40:27.179 21638-21638/com.campfire.geostereo E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.campfire.geostereo, PID: 21638
java.lang.IllegalStateException: FragmentManager is already executing transactions
    at androidx.fragment.app.FragmentManager.ensureExecReady(FragmentManager.java:1686)
    at androidx.fragment.app.FragmentManager.execSingleAction(FragmentManager.java:1716)
    at androidx.fragment.app.BackStackRecord.commitNow(BackStackRecord.java:317)
    at androidx.viewpager2.adapter.FragmentStateAdapter$FragmentMaxLifecycleEnforcer.updateFragmentMaxLifecycle(FragmentStateAdapter.java:726)
    at androidx.viewpager2.adapter.FragmentStateAdapter$FragmentMaxLifecycleEnforcer$3.onStateChanged(FragmentStateAdapter.java:657)
    at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:360)
    at androidx.lifecycle.LifecycleRegistry.forwardPass(LifecycleRegistry.java:271)
    at androidx.lifecycle.LifecycleRegistry.sync(LifecycleRegistry.java:313)
    at androidx.lifecycle.LifecycleRegistry.moveToState(LifecycleRegistry.java:151)
    at androidx.lifecycle.LifecycleRegistry.handleLifecycleEvent(LifecycleRegistry.java:134)
    at androidx.fragment.app.Fragment.performStart(Fragment.java:3167)
    at androidx.fragment.app.FragmentStateManager.start(FragmentStateManager.java:588)
    at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:279)
    at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:113)
    at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1424)
    at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2968)
    at androidx.fragment.app.FragmentManager.dispatchStart(FragmentManager.java:2893)
    at androidx.fragment.app.Fragment.performStart(Fragment.java:3171)
    at androidx.fragment.app.FragmentStateManager.start(FragmentStateManager.java:588)
    at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:279)
    at androidx.fragment.app.FragmentStore.moveToExpectedState(FragmentStore.java:113)
    at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1424)
    at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:2968)
    at androidx.fragment.app.FragmentManager.dispatchStart(FragmentManager.java:2893)
    at androidx.fragment.app.FragmentController.dispatchStart(FragmentController.java:274)
    at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:359)
    at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:246)
    at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1467)
    at android.app.Activity.performStart(Activity.java:8082)
    at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3732)
    at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221)
    at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201)
    at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173)
    at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2253)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loopOnce(Looper.java:201)
    at android.os.Looper.loop(Looper.java:288)
    at android.app.ActivityThread.main(ActivityThread.java:7870)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
CoolHands
  • 25
  • 7
  • Please include the entire stack trace. – ianhanniballake Jul 12 '22 at 01:49
  • As long as you code format your stack trace, it is fine to add it directly to your question. – ianhanniballake Jul 12 '22 at 02:39
  • Your code indicates that you're using the wrong FragmentManager when constructing your adapter. Please include your adapter code and where you create your adapter. – ianhanniballake Jul 12 '22 at 03:09
  • @ianhanniballake adapter code included! were you able to determine that was the source based off the at androidx.viewpager2.adapter line in stack trace? – CoolHands Jul 12 '22 at 05:26
  • 1
    You didn't include where you are creating your adapter - that's the part you've gotten wrong (generally, you should never, ever be using the FragmentManager+Lifecycle constructor - use the constructor that take a Fragment or Activity, depending on what you have). Your stack trace is enough to tell that you are using the wrong FragmentManager, since the correct FragmentManager (i.e., the fragment's childFragmentManager) is never executing operations while the parent is handling a Lifecycle event. – ianhanniballake Jul 12 '22 at 21:10
  • @ianhanniballake the childFragmentManager was the fix, didn't realize fragments within fragments need a childfm. – CoolHands Jul 13 '22 at 07:52

1 Answers1

0

I solved my issue thanks to @ianhanniballake, I was using the incorrect fragment manager in my constructor. Since I am dealing with a ViewPager2 that handles fragments within my ViewPagerFragment, I needed to use a childFragmentManager since there is nested fragments. For more background, this question on childFM vs supportFM goes into more detail.

CoolHands
  • 25
  • 7