Zhuinden / simple-stack

[ACTIVE] Simple Stack, a backstack library / navigation framework for simpler navigation and state management (for fragments, views, or whatevers).
Apache License 2.0
1.36k stars 76 forks source link

Simple example to handle back button INSIDE fragment #258

Closed omkar-tenkale closed 2 years ago

omkar-tenkale commented 2 years ago

i know its possible with ScopedServices.HandlesBack but donno how

omkar-tenkale commented 2 years ago

Used this approach

class BackHandler():ScopedServices.HandlesBack{
    var callback : Callable<Boolean>? = null
    override fun onBackEvent(): Boolean {
        callback?.let { return it.call() } ?: return false
    }
}
@Parcelize
class ABCKey() : DefaultFragmentKey(), DefaultServiceProvider.HasServices {
    override fun instantiateFragment(): Fragment = ABCFragment()

    override fun getScopeTag(): String = javaClass.name

    override fun bindServices(serviceBinder: ServiceBinder) {
        with(serviceBinder) {
            add(BackHandler(),"BackHandler")
        }
    }
}

Inside fragment

    private val backHandler by lazy {  lookup<BackHandler>("BackHandler") }
backHandler.callback = object : Callable<Boolean>{
            override fun call(): Boolean {
                if(shouldHandle()){
                    //handleBackPress
                    return true
                }
                return false
            }
        }

Not sure if this can be improved 🤔 @Zhuinden

Zhuinden commented 2 years ago

This is my current code for this

    private val backPressedCallback = object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
            val topKey = backstack.top<FragmentKey>()

            val fragment = supportFragmentManager.findFragmentByTag(topKey.fragmentTag)

            if (fragment is BackHandler) {
                val handled = fragment.onBackPressed()

                if (handled) {
                    return
                }
            }

            val handled = Navigator.onBackPressed(this@MainActivity)

            if (handled) {
                return
            }

            // default fallback
            this.remove() // this is the only safe way to invoke onBackPressed while cancelling the execution of this callback
            onBackPressed() // this is the only safe way to invoke onBackPressed while cancelling the execution of this callback
            this@MainActivity.onBackPressedDispatcher.addCallback(this) // this is the only safe way to invoke onBackPressed while cancelling the execution of this callback
        }
    }

// ...

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)

        onBackPressedDispatcher.addCallback(backPressedCallback)
        ...
    }

    @Suppress("RedundantModalityModifier")
    override final fun onBackPressed() {
        super.onBackPressed() // onBackPressed() was killed by Google (thanks Google)
    }

And

interface BackHandler {
    fun onBackPressed(): Boolean
}

Then in my Fragment I can do this:

    override fun onBackPressed(): Boolean {
        if(...) {
             ...
             return true
        }

        return false
    }

But when using this hacky-looking approach using OnBackPressedDispatcher, it's actually possible to do this in the fragment (i relied on this in 1 fragment, which is why I'm using back like this in this project)

// Fragment onViewCreated
        requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, backPressedCallback)

        backPressedCallback.isEnabled = isOverlayShowing
    }
omkar-tenkale commented 2 years ago

onBackPressed() was killed by Google (thanks Google) when?

(readme section not very clear)

Zhuinden commented 2 years ago

@omkar-tenkale if you want to use OnBackPressedDispatcher (which is internally used by BackHandler in Compose), then you cannot use onBackPressed because the order is wrong. You would execute your logic in onBackPressed before super.onBackPressed can try to delegate the back press to the Fragment's onBackPressedDispatcher.

It is a major design oversight from Google, actually. It took me a while to be convinced that yes, removing the callback and re-adding the callback is the correct way to do it. In fact, it is the only correct way to do it. 🤨

But it also comes with that [targetSdkVersion 34 and Android Tiramisu deprecates onBackPressed() entirely](https://developer.android.com/reference/android/app/Activity#onBackPressed()), so I guess they also noticed that AndroidX broke the expected behaviors. I have an issue tracking this https://github.com/Zhuinden/simple-stack/issues/259 but can't do much about it until Tiramisu is out.

omkar-tenkale commented 2 years ago

oh, got it now, thx!