Fragment manager state handler

Important

This article describes functionality and guidance that is in public preview and may be substantially modified before it's generally available. Microsoft makes no warranties, express or implied, with respect to the information provided here.

The FragmentManagerStateHandler class helps restore fragments when a screen mode transition occurs.

When we change the mode of an app from dual screen to single screen mode, all the fragments are recreated even if they are not visible.

Using this library, only the needed fragments are recreated.

The fragments are restored from the activity bundle. This approach has two bundles that are swapped at runtime depending on the span mode. There is one bundle for the single screen and another one for the span mode. This is possible only by adding an activity lifecycle callback and swapping these two bundles inside the onActivityPreCreated callback.

The component automatically detects the screen mode. It restores just the fragments we need, depending on the screen mode.

Important

The component only works with activities from AndroidX.

How to use

class SampleApp : Application() {
    override fun onCreate() {
        super.onCreate()
        FragmentManagerStateHandler.init(this)
    }
}
class SampleActivity : AppCompatActivity() {
    companion object {
        private const val FRAGMENT_DUAL_START = "FragmentDualStart"
        private const val FRAGMENT_DUAL_END = "FragmentDualEnd"
        private const val FRAGMENT_SINGLE_SCREEN = "FragmentSingleScreen"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_sample)
        registerWindowInfoFlow()
    }

    private fun registerWindowInfoFlow() {
        val windowInfoRepository = windowInfoRepository()
        lifecycleScope.launch(Dispatchers.Main) {
            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                windowInfoRepository.windowLayoutInfo.collectIndexed { index, info ->
                    if (index == 0) {
                        onScreenInfoChanged(info)
                    }
                }
            }
        }
    }

    /**
     * Called whenever the screen info was changed.
     * @param windowLayoutInfo object used to retrieve screen information
     */
    private fun onScreenInfoChanged(windowLayoutInfo: WindowLayoutInfo) {
        when {
            windowLayoutInfo.isInDualMode() -> setupDualScreenFragments()
            else -> setupSingleScreenFragments()
        }
    }

    /**
     * Adds fragments for the single screen mode
     */
    private fun setupSingleScreenFragments() {
        if (supportFragmentManager.findFragmentByTag(FRAGMENT_SINGLE_SCREEN) == null) {
            supportFragmentManager.inTransaction {
                replace(R.id.first_container_id, SingleScreenFragment(), FRAGMENT_SINGLE_SCREEN)
            }
        }
    }

    /**
     * Adds fragments for the dual screen mode
     */
    private fun setupDualScreenFragments() {
        if (supportFragmentManager.findFragmentByTag(FRAGMENT_DUAL_START) == null &&
            supportFragmentManager.findFragmentByTag(FRAGMENT_DUAL_END) == null
        ) {
            supportFragmentManager.inTransaction {
                replace(R.id.first_container_id, DualStartFragment(), FRAGMENT_DUAL_START)
            }

            supportFragmentManager.inTransaction {
                replace(R.id.second_container_id, DualEndFragment(), FRAGMENT_DUAL_END)
            }
        }
    }
}

inline fun FragmentManager.inTransaction(func: FragmentTransaction.() -> Unit) {
    val fragmentTransaction = beginTransaction()
    fragmentTransaction.func()
    fragmentTransaction.commit()
}

Next steps

Learn about other Dual-screen layout libraries.