nhaarman / acorn

Mastering Android navigation :chipmunk:
https://nhaarman.github.io/acorn
Apache License 2.0
181 stars 7 forks source link

Don't remove view on push #141

Open manueldidonna opened 5 years ago

manueldidonna commented 5 years ago

Is it possible to push a scene on top of another without removing other views from the parent?

I use a custom horizontal draggable view and while sliding users must see the view below

nhaarman commented 5 years ago

You can have a look at the experimental ConcurrentPairNavigator, which allows a second Scene to be pushed on top of the initial Scene, and have both Scenes active at the same time.

It is considered experimental because it requires quite a bit of boilerplate: you must implement your layouts twice to account for transitions and configuration changes, and you need to define a SceneTransition for the transitions - which I'm not really happy about yet.

The page linked above shows a detailed example of how you could realise this.


This is assuming the draggable view is something that is added as a result of a navigation action. If you want to have multiple Scenes on a single screen, you can 'just' create the two Scenes separately (without any view providing) and create a third Scene that is used to provide the view and delegates the lifecycle methods to the first two Scenes. See #10 for a discussion on this.

manueldidonna commented 5 years ago

What I need is a similar behaviour to fragment navigation with replace/add instead of push (with Stack navigator) or something like Conductor where the backstack could be manipulated with push & pop functions but you can decide to don't remove other controllers' view


The draggable view is the default layout of every scene (except the root one)

nhaarman commented 5 years ago

I'm still not really sure I fully grasp your use case, but here are some thoughts.

Acorn's perspective on app navigation is to have navigational state completely decoupled from the UI. The UI observes changes in the navigation state and reacts appropriately by displaying the right View elements. This means that all information necessary must be available in that navigational state; if multiple Scenes need to provide the data for that draggable view, then they must all do that individually. At least that is how the ext artifacts are designed.

Something different that could be possible, but which isn't directly 'supported' by the library through default implementations, is to have two separate Navigators running in parallel: one for the main navigation, one for the 'draggable view'. The Activity could then subscribe to both Navigators and deal with the layout. However this is completely uncharted territory, so you might run into some problems to get things to work there.

nhaarman commented 5 years ago

You could also completely disregard the 'draggable view' from your main navigation flow and let the default Acorn implementations do the main navigation for you. Then inflate a custom layout in your Activity that contains the draggable view and pass a container layout to Acorn:

<FrameLayout>
   <FrameLayout id="@+id/contentLayout" />
   <MyDraggableView />
</FrameLayout>
class MyActivity: AcornAppCompatActivity() {

  override fun onCreate(bundle: Bundle?) {
     setContentView(R.layout.activity_main)
     super.onCreate(bundle)
   }

   override fun provideRootView() : ViewGroup {
      return findViewById<ViewGroup>(R.id.contentLayout)
    }
}

Then do some extra work to make your draggable view work as you want it to. You could add an extra listener to your navigator to be able to hide and show it based on your requirements. This is just some spitballing here though.

manueldidonna commented 5 years ago

I took a look to samples & android-ext impls. UI reacts to a navigation event displaying the right contents with a transition and there are some navigator implementations. Right?

I'm interested in the StackNavigator class. It has a 'push' function that accepts a scene. If I understood the UI linked to the given scene replaces the current scene's UI and the current scene is stopped. I don't want to remove the current UI, I want both in the same container. Is it possible?

nhaarman commented 5 years ago

I don't want to remove the current UI, I want both in the same container

Sure, implement a custom SceneTransition that leaves the shared view intact.

NickvanDyke commented 5 years ago

Would this be the proper way to implement dialogs as Scenes too? In the ViewController, create an AlertDialog, passing it the view, and use a SceneTransition that doesn't remove the view/scene that's underneath? Initially I was going to use ConcurrentPairNavigator, but that seems wrong for this since we couldn't make the dialog part of the layout file that needs to contain the entirety of both Scenes.

nhaarman commented 5 years ago

@NickvanDyke What kind of dialog do you need?

NickvanDyke commented 5 years ago

@nhaarman The view would be wrapped in an AlertDialog. So far, it seems the proper way to do this would be to create and show an AlertDialog in the ViewController's init, calling AlertDialog.setView(view) with the view that the ViewController receives in its constructor. And then use a SceneTransition that doesn't remove the view underneath, and doesn't add the new ViewController's view to the parent and doesn't do any animating, since showing the dialog will take care of that. And in detach of the ViewController's Scene, call a method on the ViewController that dismisses the dialog. Does that sound right?

nhaarman commented 5 years ago

Interesting approach! I'd have to look into this a bit more, for example to properly deal with configuration changes and such.

NickvanDyke commented 5 years ago

I'll give it a shot and report back. I used a similar approach with Conductor and it worked well

manueldidonna commented 4 years ago

Hello guys! I finally get the time to build a fully fledged note taking app with acorn (that I love to use) but I still need to wrap a view in a bottom sheet which is a slideable ViewGroup. I don't know which is the best way to handle the view inflation on configuration changes.

Here is my use case:

What will happen on configuration change?

Here is what I thought to do

Tell me if there's a better way to handle this sort of things. I hope to share a gist with a working code in the next weeks. I really appreciate all the work that Niek Haarman has done!

NickvanDyke commented 4 years ago

@manueldidonna What I've done in this case is make Scene B nested within Scene A - i.e. A retains a reference to B, and forwards its lifecycle callbacks to B, including attach/detach. It also saves and restores B's state when its own is. Then, the layout that is inflated for A also has B's views in it. A's viewcontroller also instantiates B's view controller, passing to it the part of its layout that belongs to B. It also makes B's view controller available to Scene A, so that it can call attach/detach on Scene B.

This way, you can still separate logic into Scene B, but don't need to fuss with properly re-inflating views since Scene A contains the views for both itself and Scene B.

manueldidonna commented 4 years ago

@NickvanDyke thanks. I will update this issue as soon as I have some working code