bluelinelabs / Conductor

A small, yet full-featured framework that allows building View-based Android applications
Apache License 2.0
3.9k stars 343 forks source link

Help with child controller custom back handling #536

Open ursusursus opened 5 years ago

ursusursus commented 5 years ago

Hi, I have a drawer layout, (whole screen is a controller), where drawer has its own backstack, and content (the view under drawer when it is opened) has also its own backstack; so two child routers

When drawer is opened, I want to handle the drawer router back clicks; if not, then the content's

Mind you the first controller placed in the drawer router has handleBack overriden with override fun handleBack() = false. Which in my mind reads I dont wanna handle back at all, just pass it through

What I want is: When I press back when the drawer is opened, that means I should pop that drawer router; and when there is only one controller left on drawer router, just close the drawer

So the the back implementation in the root controller is like this:

override fun handleBack(): Boolean {
    if (drawerLayout.isDrawerOpen()) {
        if (!drawerRouter.handleBack()) {
            drawerLayout.closeDrawer()
        }
        return true
    } else {
        if (contentRouter.handleBack()) {            
            return true
        }
    }
    return false
}

However this has a bug, where if there is only 1 controller in the drawer router, because of this Conductor code

class Controller ....
    @UiThread
    public boolean handleBack() {
        ThreadUtils.ensureMainThread();

        if (!backstack.isEmpty()) {
            //noinspection ConstantConditions
            if (backstack.peek().controller.handleBack()) {
                return true;
            } else if (popCurrentController()) { <-------- THIS
                return true;
            }
        }

        return false;
    }

tldr;

When there is only one, the expected would be to have its handleBack called; which in my case always returns false, and therefore it should pass through to the root controller, which would return hardcoded true in this case, thuss stopping the back press handling; with nothing actually happening (besides the drawer closing)

However the THIS line shows it gets popped, thuss calling its onDestroy, which is not what I want. I would not expect it to get popped if I return false from its handleBack. Another interesting fact is that the controller's view stays in layout, and then when another controller is placed onto drawer router, I can see 2 overlapping controllers (bug)

PaulWoitaschek commented 5 years ago

For me the default handleBack almost always does the wrong thing. Especially it pops controllers from pager adapters so I end up with an activity without controllers.

I have a BaseController where I override handleBack and return false. Now my controllers can overwrite that and return true if they want to handle back themselfes. In that case if they return true, nothing is closed because of the following behavior:

Now I handle onBackPressed in my activity and check for the backstack size.

ursusursus commented 5 years ago

@PaulWoitaschek so this is what I tried, it works; thanks

However this controller is retainViewMode = Controller.RetainViewMode.RETAIN_DETACH, and after I move away to another controller, then back, then push something onto leftDrawerRouter (so the backstack size is 2), however I can see the 1st controller view, wth?

If I remove the retain mode, it works as should

override fun handleBack(): Boolean {
        if (drawerLayout.isDrawerOpen(leftDrawerControllerContainer)) {
            if (leftDrawerRouter.backstackSize > 1) {
                leftDrawerRouter.handleBack()
            } else {
                drawerLayout.closeDrawer(leftDrawerControllerContainer)
            }
            return true

        } else if (drawerLayout.isDrawerOpen(rightDrawerControllerContainer)) {
            if (rightDrawerRouter.backstackSize > 1) {
                rightDrawerRouter.handleBack()
            } else {
                drawerLayout.closeDrawer(rightDrawerControllerContainer)
            }
            return true

        } else {
            if (chatRouter.handleBack()) {
                return true
            }
        }
        return false
    }

Any ideas?