Using Android's Navigation Component, I have 3 fragments hosted by a single Activity. The launch fragment is a splash screen fragment (A), if the user is not logged in, I launch the login fragment (B), if they are logged in, I launch a list fragment (C).
So launch routes are either A->B->C or A->C.
When you land on B or C, pressing back should kill the app. The NavigationController though is instead backs up to A (I think, A's onActivityCreated is certainly called at which point it crashes which is probably unrelated).
Pop behaviors in the graph editor for A -> B seem to allow me to pop to different fragments but there doesn't seem to be an option to just kill the app.
Do I really need to override onBackPressed for this behavior and just kill the activity? Because this is easier without the NavigationController, usually I would just finish an activity as I start a new one.
Open text tab in Graph Editor to view xml code, find your two actions A -> B and A -> C and put tag: app:clearTask="true", it's should kill you app when user press back button.
Example:
<fragment
android:id="#+id/launcher_fragment"
android:name="com.example.LauncherFragment"
android:label="launcher_fragment">
<action
android:id="#+id/action_launcher_to_login"
app:destination="#id/login_fragment"
app:clearTask="true"/>
<action
android:id="#+id/action_launcher_to_list"
app:destination="#id/list_fragment"
app:clearTask="true" />
</fragment>
Related
I'm trying to implement circular logic with the Navigation component but I'm concerned that I'm not doing it right and lifecycle methods are unnecessarily being called.
I have 1 activity that has 3 fragments. Navigation between the fragments looks like this:
A -> B -> C -> back to A etc..
A and B are regular Fragments while C is a DialogFragment. C has two buttons - Cancel and Done. If Cancel is pressed, a navigation action is called (using findNavController().navigate(<action>)) and the app will show A. If Done is pressed, C is dismissed and the app will show B and the user can then return to A by pressing back. This all works as I expect, however...
My concern is that each route back to A results in different lifecycle methods being called in A. If navigation returns to A after the user accepts C and presses back on B, onCreateView(), onViewCreated(), and onResume() is called by A. BUT, if navigation returns to A after C is cancelled, A calls many more lifecycle methods (onAttach(). onCreate(), onCreateView(), onViewCreated, onResume(), onDestroy(), onDetach()). Why is there a difference? Why is onCreate() being called again in A? Shouldn't it just use the existing instance of A instead of creating a new one?
I can't figure out why it's doing this or if it's even something I should be concerned about. I'm confident that the stack is appropriately managed as the user navigates between fragments because the navigation action between C and A uses the popUpTo and popUpToInclusive attributes (as recommended here in the docs). I've also tried setting the launchSingleTop attribute in the action between C and A but I get the same behaviour (extra lifecycle methods being called in A).
Here is the xml for the C fragment:
<dialog
android:id="#+id/C"
android:name="C"
... >
<action
android:id="#+id/C_to_A"
app:destination="#id/A"
app:popUpTo="#id/A"
app:popUpToInclusive="true" />
</dialog>
I call action C_to_A from C when the user presses the cancel button.
Any help clearing up my confusion would be very welcome.
An action has two steps:
popping any destinations set via popUpTo / popUpToInclusive
Navigating (i.e., creating a new instance of) a destination set via app:destination
Actions can be any combination of just the pop, just the navigate, or both - the UI in the Navigation Editor when you right click a destination and select New Action will give you each of these options.
Therefore if you want an action that only pops back to a destination you know is on the back stack, then you can remove your app:destination attribute and only pop:
<action
android:id="#+id/C_to_A"
app:popUpTo="#id/A"
app:popUpToInclusive="false" />
Note that by using app:popUpToInclusive="false", you ensure that A will be on the top of the stack after the action is executed.
I'm currently using Android Navigation Architecture in my project. It has a feature that can launch any fragment with a shortcut.
Suppose my navigation graph looks like this: A->B->C->D.
When I'm at fragment A , I directly navigate to fragment D with NavController.navigate(R.id.fragment_d). But when I'm press back button, it returns to fragment A. Is there any way to let the destination navigate back to its parent in navigation graph? (I mean, in this case, D back to C, B then back to A).
Thanks in advance.
Generally, you should always avoid creating a synthetic back stack when within your own app (and only do this when launching your app from a deep link outside your app, i.e., via an app shortcut).
However, you can approach this in one of two ways:
If you're okay with resetting the state on A, you can use NavDeepLinkBuilder to construct a set of Intents suitable for restarting your task on the chosen destination:
navController.createDeepLink()
.setDestination(R.id.destination_d)
.createTaskStackBuilder()
.startActivites()
Just call navigate() multiple times.
navController.navigate(R.id.destination_b)
navController.navigate(R.id.destination_c)
navController.navigate(R.id.destination_d)
I think you should be able to use navControler.navigateUp() on the destination if you have correctly introduced the parent fragment for your fragments. Also, consider the difference between the device back button and up back-arrow to go up to the parent
I have one Activity with 3 fragments which form a workflow to collect user input.
Normally, Fragment A is the first fragment -> Launches B -> Launches C. B is supposed to launch A if the back button is pressed, and similarly C's Back button is supposed to launch B.
However, in some cases, A is supposed to launch C directly, and then C's back should launch A again.
I prefer that C should not know who launched it. I.e. I want C's "backstack" to operate without C knowing who launched it.
I tried using the usual addToBackstack approach, but I'm facing a problem when the Activity gets killed after the user lets the app go into the background while C was open.
I would like the user to return to "C" instead of starting all over from A. To achieve this I'm using the saved Instance state, and detecting which fragment was previously active, and launching it from the activity.
The problem starts when the user wants to go back from C, after the Activity was recreated after being killed. C doesn't know who launched it: A or B. How do I make the Back button work as expected for this case?
Found some answers in this excellent video by Android Developers + Adam Powell:
Fragments: Google I/O 2016
In summary, Fragments and the Fragment BackStack are considered part of the navigational state of the app, so, as long as I don't mess with the backstack when the activity is launched, the OS will also restore the FragmentBackStack, thus the BackStack will know who launched C (A or B) even if the activity gets re-created. Thus, popBackStack will move from C to A or B as required.
The main/launcher activity in my app is a login page (Activity A). Once the user is authenticated, they are taken to the main area of the application, e.g. Activity B. So now the current activity stack of this task is A > B.
I then press the home button on the phone and am taken to the Android home screen. I re-launch my app via short cut key in HTC Desire Z(See Image after Space there are two Short Cuts 1 and 2), and I am taken to Activity A, instead of Activity B. Either the activity stack is now A > B > A, or there are now two separate tasks with activity stacks A > B, and A respectively. What I want is to be taken back to Activity B when I relaunch the app..
I followed this link
The above solution worked for 2.3.3 but in ICS 4.0.3 it has an issue that i am not taken to Activity B.
How do i resolve this,In ICS I am not able to see the what Intent flag system is using to launch activity when short Cut is Pressed,is this a System BUG?
Please Help
NITZ
The pattern that I tend to use for the login like this. I'll use A to mean Login and B to mean Main application.
I make B the launcher Activity, and in its onCreate() I check if a login is needed and if so, then I immediately launch Activity A. Once A is done, i finish() it, so that I'm back to B.
This way my activity stack stack never contains the Login activity, except when it's being used. ie, after a Login is done, then only B is on the stack.
In my situation, there is one case in which I need to make sure the activity only runs one at a time.
I found if I set the LauchMode of the activity, I can reach the single instance aim, but it won't update the view of the activity.
This activity is launched by startActivityForResult, and we send the URI with the intent to the activity.
Let's discuss with this certain case:
gallery - lauch this activity with imageA.
camera - lauch this activity with imageB.
My request is not to destroy the old activity, but the activity that just received the new intent infomation should refresh the view.
I found a new method, onNewIntent.
This method can refresh the intent before resume. I will try it.
You can have an Activity with a manifest attribute of singleInstance.
As soon as the activity is relaunched , onResume gets called. You can update the view with the new image and invalidate the older View.
<activity ..
android:launchMode= "singleInstance" />
In your manifest, use android:launchMode="singleTask" instead of android:launchMode="singleInstance". Using singleInstance only returns to the existing instance if it is at the top of the stack. singleTask, on the other hand, return to the existing instance of the activity, as it is always at the root of the task.
Then, when your instance is launched, override onNewIntent to update your UI according to the new intent.
Refer to the Andorid documentation for details.
Be careful when using android:launchMode="singleInstance" in multiple activities application.
Here is my test on Pixel 4XL (Android 10)
Example, we have 3 activities (without declare taskAffinity)
A (entry activity)
B (start from A)
C (singleInstance, start from B)
If we start A->B->C. Now A,B in a task and C in a different task.
From C, press back -> will see B, press back -> will see A, press back -> app close. Everything working efine.
However, if we start A->B->C then press Home -> app go to to background.
Now there is 2 situations
User open app again from Recent app.
User will see C screen. Press back -> app will close (don't go to B)
User open app again by click on app icon
User will see B screen. Press back -> will see A, press back -> app will close (don't go to C)
The behaviour after open application is different between both case. But in both case, the application is always available in Recent app and you never able to remove application from Recent app (after remove it will appear again). Only able to remove it by Force stop in Setting.
If you run adb shell dumpsys activity activities, you will see that activities still exist in Stack, but you can not go back to it.
Another case
If we start A->B->C->A. Now A,B,A in a task and C in a different task (A is current visible to user).
From A, press back -> will see B (not C), press back-> will see A, press back -> will see C, press back -> app will close. (at least here we can go back to C)
Now if, we start A->B->C->A then press Home. In both case, user open app again by Recent or click on app icon, user will see A screen. Press back -> will see B, press back -> will see A, press back -> app will close (never see C screen)
Another note
The above example, although application run on 2 tasks but in Recent apps, it only show 1 task.
If you set the taskAffinity, in the Recent apps, you will see 2 tasks. In that case, you can choose the task which contain activity that you want to display but when you press back, it just go back to the activity in same task or close app (not able to go back to activity in another task).
Also, if you click on app icon to open app again, we have the same behavior like above demo.
I think singleTask and singleTop is less complex than singleInstance, so if it is enough for solve your problem, consider to use them instead of singleInstance