Designer here. I have a question about reverse navigation in Android OS.
My question is, in the scenario here
link to the image
The app's home page has a direct access to a certain content (level 3) within a module (level 2).
The user tap on that content from the homepage. In this case, how should the UP navigation behaves? (The two red lines)
Should it bring the user UP to the level 2 Module B page? (Which is one level up based on the app hierarchy)
OR
Should it bring the user back to homepage? (Which is the last place they were at)
========
From my understanding (and years of using Android phones), the UP navigation (from Top app bar) is always navigate the user one level up within the app hierarchy. the BACK navigation (system back button/gesture) is navigate chronologically back to where the user were. (In general)
(Ref: https://m2.material.io/design/navigation/understanding-navigation.html#reverse-navigation)
But based on this page: https://developer.android.com/guide/navigation/navigation-principles#up_and_back_are_identical_within_your_apps_task
"Within your app's task, the Up and Back buttons behave identically." which means that the UP button should direct the user back to the homepage, not level 2. (? I'm very confused... )
Is this a relatively new change for Android dev?
Which Red line is the correct behavior? Thank you :)
I checked two guidelines from Google,and they seem contradict with each other
https://m2.material.io/design/navigation/understanding-navigation.html#reverse-navigation
and
https://developer.android.com/guide/navigation/navigation-principles#up_and_back_are_identical_within_your_apps_task
Related
The app I am developing attempts to adopt, as much as possible, the most native experience for Android & iOS users.
To do so, the app runs a CupertinoApp and a MaterialApp.
On iOS, I use the CupertinoScaffold showing a CupertinoTabBar with BottomNavigationBarItems. It performs very well as expected:
pages are created only once, the first time I click on a tab;
pages are restored when returning to a previously displayed tab: in one of my tab, there is a list and its scroll position is always maintained, persisted.
on Android, the story is different: I am using the Drawer widget.
When I click on an entry, I call Navigator#push to show the associated page. However, this will keep adding new instances of each entry's page in the stack.
I don't seem to be able to return to an existing page. At least, not how I can do it with iOS.
Looking at the Navigator I see functions that seem to achieve what I am looking for:
popUntil => will show my existing screen, but at the expanses of all the other ones that will therefore make a sacrifice for the sake of just one page. So much ado for nothing...
pushReplacement => will show a new instance of the target page and destroy the current one. Not desirable.
What am I missing ? How can I achieve that the CupertinoTabBar seems to be able to do?
Well, I solved my issue by taking another direction.
Simply put I don't think there is a way, with the Navigator to achieve what I want.
The solution I find out was: Keep state of widgets with Bottom Navigation Bar in Flutter
This guide suggests to use a IndexedStack that displays only one child at the time while keeping its state.
So this is how I managed to make it work:
Create your MaterialApp
Set its home to be a custom stateful widget called MaterialHomePage
This MaterialHomePage builds a Scaffold:
with an AppBar and its title (the title of the active page);
with a drawer and its arguments (more on that later)
and the body is that so-called wonderful IndexedStack:
set its children to be the list of pages / widgets, the same pages available in the drawer
set index to be the property _selectedPageIndex of your MaterialHomePage
and with a function that will be triggered when the user chooses a page to navigate to, in which case the usual call to setState occurs inside of which you may update the _selectedPageIndex property as well as _selectedPageIndexproperty`.
The Drawer:
takes two parameters:
the current active page index (defaults to 0 at app launch)
a Function with the target page title & index
must list as many entries (usually ListTile) as pages you have declared in the IndexedStack displayed in MaterialHomePage.
upon clic on the entry, it calls the aforementioned function and passes the index of the clicked entry as well as the associated page title.
At the end, what happens:
The user opens the drawer.
The user chooses a new destination (let's say: entry #3).
MaterialHomePage gets notified (via callback) of the user choice which triggers a setStage that will both:
update the current page thanks to IndexedStack with index = 3
and update its AppBar's title
and voilĂ there you go: each page gets retained, no loss of state and a smooth navigation.
NB: if the user uses the Android's back button feature, at this point it will close the app since the Navigator stack is empty. Maybe there is a way to listen for such an event if you desire to default to the first entry / page (displayed at launch) if the user was in a another page.
NB2: I also wonder if there is a way to animate page transitions.
PS: Let me know if the answers is a fine solution in which case I'll validate it. Or if you find a proper way to achieve it.
In my Codename One app I have 2 Forms A & B. A Form includes a MapContainer updated every 10 s and a floating button. If the user clicks on the floating button, they can take a picture and then a Dialog is shown and if they choose "OK" the B Form is shown :
new B_Form(theme).show(); // (where theme is the Resources used in A Form).
This B Form includes buttons to take some action (ie record audio, play the recorded audio, go to Form A). The buttons work in the simulator (although recording is not supported so an error is shown but it is expected), that is the user can click on it.
However on an actual device (Android KitKat) B Form is shown but no action can be taken although the buttons are all enabled. The buttons even don't show their "pressed style" when being pressed, and remain in "unselected state".
The only button that works is the setBackCommand from the Toolbar (ie the left arrow on the upper left corner of the screen).
So it looks like the B Form was not taken into account. Moreover if I swipe my finger on the B Form then A Form is shown and the map is moved. If I removed the MapContainer from the A Form then B Form works as expected on the device.
EDIT
Surprisingly enough if I call a Form C from the side menu bar via the hamburger menu, the buttons on that Form C work seamlessly!
So I get the feeling that the MapContainer is causing me trouble, what can I do to make B Form work as expected ?
Any help appreciated,
Regards,
The MapContainer is a peer component, we enabled the new peers on Android which hide some of the complexities of the peer system but might trigger other issues.
This might be a misbehavior of the peer component system, although it's hard for me to understand how to reproduce this. The side menu would work because the side menu is a completely different form without the map container in it.
So here is the workaround I found.
Before showing Form B, I removed the MapContainer and revalidated the Form A :
googleMap.remove(); // googleMap is my MapContainer defined somewhere else
this.revalidate();
Although it works I still don't understand why the problem described in the question happens.
I am building a Xamarin Android app and need to make a design choice. I am still rather new in Android development, and don't know any other developers in my area personally that I can ask for help on this matter.
The app has a "BottomBar" with several buttons / tabs. If a button is clicked, a new screen should appear.
All 4 screens contain a lot of data, mostly lists (or RecycleViews?) of images with text and when clicking on a listItem or similar, an associated detail screen should appear.
At this point I wrote code that loads new activities when I click on one of the bottombar buttons. E.G., btnA loads activityA, btnB loads activityB and so on. The BottomBar is regenerated in each new activity and also keeps track of its instance State, so if I am in activityA and click on btnB, ActivityB loads and in activityB btnB is highlighted as the active tab, without me having to write a single line of code. Basically, this is working great out of the box.
I am wondering however if I should add a big fragment container in a host activity, and load different fragments in that container on BottomBar button clicks instead. Maybe this approach has a performance benefit or some other benefits that I am not aware of as yet. As for now, we don't have any intentions to create a tablet version, we only want to create a mobile version of the app. I understand that I can reuse fragments with a bigger screen estate (tablets), but this should not be necessary.
Again, everything is working ok as it is, but I am only at the beginning of building the app, and would like to make good design choices early.
The BottomBar component that I am using (Xamarin c#):
https://github.com/pocheshire/BottomNavigationBar
The above version is a port of this (Java):
https://github.com/roughike/BottomBar
Any advice is appreciated...
Sorry for long waiting.
If you want to follow the path that Google recommends in their guides that you need swap the fragments in the container by pressing the tab.
Link to Google guides
I have an app that is using a DrawerLayout and each item in the drawer will load a Fragment. The structure looks like:
News -> News Story
Photos -> Gallery -> Photo
Events -> Event
Directory -> User Type -> User Profile
Each top level is an item in the Drawer, tapping one will take you to that fragment and you can go deeper into that section by adding additional fragments on the stack, e.g. navigating from Photos to a Gallery to a photo in that gallery.
It gets confusing when I need to manage the navigation of each of these sections as individual stacks. On iOS I would use a UINavigationController for each section so that I could manage either separately. But on Android I am not sure how to do this. It is almost like I need to have multiple Fragment Manager instances. Without this, I feel I will run into issues when:
A user is deep into the News stack then switches to Photos, they navigate deep into that stack, but want to return to where they were in the News section. How can they return to that stack? And, navigate back up the News stack?
This seems like a major design issue with Android, unless I am missing something. Any ideas on how to solve this?
EDIT:
Basically I want what they showed at Google I/O:
https://www.youtube.com/watch?v=XpqyiBR0lJ4
I just built an app with this design. I just kept adding fragments to the stack when the user was in 'News' or any sub category of 'News'. If the user navigated to 'Photos' I would just pop all of the fragments from stack with:
getFragmentManager().popBackStackImmediate(null,FragmentManager.POP_BACK_STACK_INCLUSIVE);
and start adding to the stack again. I'm not sure if this is the 'right way' but it worked fine for me. I would just log the last fragment they were viewing in 'News' and restore it when they return to 'News' if that's the behavior you are looking for.
I managed multiple categories/sub-cats just fine using one Activity.
I started to develop a single page web app with angularjs and now I'm defining the navigation. So, I end up using 2 levels of navigation:
1st level: Main navigation using ng-view.
2nd level: SubView navigation with the top and bottom bars using ng-include.
This is our iphone scenario:
The iphone scenario seems ok for me because we control all navigation with our buttons.
But, now lets think in android scenario where the user can use the history back button(physical button) to navigate back. How can we support it if we use ng-include for the subnavigation?
Thanks in advance
You could add a parameter to your URL to make it work with Android history.
#/main?page=1
#/main?page=2
Then use that to control the state of your app, and then android back button will work.
You can set url parameters with $location.search:
$location.search('page', 4);
$location.search docs: http://docs.angularjs.org/api/ng.$location#search
And one more thing: You'll want to add reloadOnSearch: false option to your $routeProvider.when() declaration for your view. By default, the whole view reloads when you change a query parameter with $location.search(). Setting that to false will make it not reload, which is what you want in this case:
$routeProvider.when('/main', { reloadOnSearch: false });
reloadOnSearch docs: http://docs.angularjs.org/api/ng.$routeProvider#when