Recently I created a social app. I didn't use fragment and the project is almost finished. I have several Activities like UserProfile, Followers, Followings activity.
Normally it's just working fine. But if user click UserA UserProfile activity -> and then click A's Followers -> select UserB Userprofile activity -> click B's followers activity -> select UserC Userprofile activity....
In this loop, the app would get pretty slower because it opened too many activities at same time and the back stack hold all of them.
I just wonder if there's any optimization I could do for this situation? Because UserProfile activity layout would always same except the user information content. Is that possible to use Fragment for each activity, even though different activities would show up in sequence one by one?
Thanks!
You should architect this in a different way. You should only ever have one UserProfileActivity in the stack. If you already have the UserProfileActivity for User A in the stack, and you want to show the UserProfileActivity for User B, just call startActivity() for UserProfileActivity with Intent.FLAG_ACTIVITY_REORDER_TO_FRONT and pass some extras to indicate that the Activity should show User B. Use the same concept for all of your activities.
To make sure that the BACK button navigation works correctly, you will need to override onBackPressed() and figure out what Activity needs to be shown and with what data. Then call startActivity() and also set Intent.FLAG_ACTIVITY_REORDER_TO_FRONT and provide extras so the Activity will show the correct data.
To assist in keeping track of where you are in the navigation, you might want to create a stack of items that are stored in a static variable somewhere. Each item would indicate what Activity is being shown and with what data. Every time you launch a new Activity, you push a new item on to this stack, and every time the user presses the BACK key, you pop the top item off the stack and then look at the one underneath it to determine what Activity to start and what data to send in the extras.
With this scheme, the user can click around all day long and you will never have more than one instance of each Activity, but the user will still be able to navigate all the way back.
Related
How can I click a notification, bring my app to the foreground if needed, and keep the stack as it was (even when the app went to the background)?
I have a NotificationClickActivity that is launched when the user clicks a notification.
When the notification is clicked, I have two possible scenarios:
User is logged out from the app
User is logged in.
In the first scenario, NotificationClickActivity starts the login process and receives the result. If OK, I launch MainActivity. The task is cleared so MainActivity is the only activity that I have in the task. I have no problems in this scenario.
In the second scenario NotificationClickActivity does this:
finish()
startActivity(MainActivity.createIntent(this).setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP).putExtra("notification", notification))
With the above code, if the app is already running (MainActivity is the root and top activity) and if on the background or in the foreground, I have no problems. MainActivity onNewIntent is called and I can do whatever I what with the notification. In this case, I dispatch the notification to a repository.
The problem I'm facing is when I have activities on top of MainActivity. Imagine the following stack:
MainActivity -> ActivityOne -> ActivityTwo -> ActivityN
With the code that I currently have if I click the notification the stack becomes:
MainActivity -> ActivityOne -> ActivityTwo -> ActivityN -> MainActivity
I want the stack to stay MainActivity -> ActivityOne -> ActivityTwo -> ActivityN but still pass the notification to MainActivity so it can be dispatched to the repository.
Any idea how can I achieve this?
I've been playing with the intent flags for some time but without success.
Of course, I what this without any visible animations or whatever so that the user does not realize what is going on.
Think you're looking for FLAG_ACTIVITY_REORDER_TO_FRONT
.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP || FLAG_ACTIVITY_REORDER_TO_FRONT)
This will bring the activity to the top of the stack if its already running.
consider a task consisting of four activities: A, B, C, D. If D calls startActivity() with an Intent that resolves to the component of activity B, then B will be brought to the front of the history stack, with this resulting order: A, C, D, B.
In the above example, if you want to go from A,B,C,D to A,B,C,D,B instead, then you need to make sure your activity supports multiple instances.
Suggest FLAG_ACTIVITY_SINGLE_TOP to be specified in android manifest for the activity instead. Then in your onNewIntent you should check if the user is logged in. If the user is not, you should launch the login process with startActivityForResult and handle the login result with onActivityResult.
This assumes your login can be initiated with an intent, which calls setResult with enough information for the caller to determine if the login was successful.
As for animations, I think you will have to use fragments and disable animations, or disable animations for the activity, and do custom transitions only when you want it. This may even not work with activities, since the OS/OEM defined transitions may just happen with activities regardless anyway, so you'll very likely have to make sure rest of the app is in the same activity and use fragments for navigation.
This is not possible with this approach. If the current task contains MainActivity and also other activities on top of that, there is no way to send an Intent to MainActivity (without removing the activities on top of MainActivty) using startActivity or by launching an Activity.
If you need to get information to MainActivity then you need to use another mechanism to do this:
Send a broadcast Intent and have MainActivity listen for the broadcast
Use an event bus implementation
Use a "global variable" to pass this information between the 2 activities
Situation:
Starting from my host activityA/fragment A, I click on a button to start host activityB/fragment B. Fragment B is a fragment which enables filtering options for Fragment A. After selecting the options, the user can press the navigate up button to return to fragment A.
Problem:
I would like the state of the options selected to be retained when navigating to Fragment B more than once. Since this is a filtering option, it would be preferable to not save this in storage beyond the duration of the application (It's just filtering. Not necessary to keep the information stored for a long time. Just in between navigation).
Things I've tried:
OnSavedInstanceState - realized that onNavigateUpTo() / finish() don't trigger onSavedInstanceState
android:launchmode="singleTop" - Because the activity is finished and destroyed from the backstack, no instance of the activity is available to receive the new intent.
setRetainInstance - Activity is destroyed so attached fragment also destroyed.
A possible solution I've found is to use setResult and return the values to Fragment A. Then put these values as EXTRAs into a new intent when starting Fragment B again. This solution seems clunky. Is there a cleaner solution that just allows Fragment B to "remember" its state instead of passing values back and forth?
As you have stated the fragment is destroyed when user goes back so there is no way to get the "state" back. Also it does not make sense to store state as logically the fragment/activity is not needed from platform point of view.
So the way to 'remember' the state of fragment is to store the filter data in some other variable(s) and give it back to it when it is re-launched. What you are trying to do it perfectly fine i.e. return the filter data as result and send it back to fragment at re-launch.
To make it simpler you may write a class containing all the filter options and make it Parcelable. You may choose to make it a global so that you may not need to send it across activities, I would not prefer to do that though.
One UX issue is when user presses up/back it is generally expected that user has cancelled the operation. I as a user expect when I press back it cancels the operation and when I press "apply" it applies the filters. You may need to rethink about the user experience about applying filter on back press.
I have three top level activities in my application. Activity A, B & C.
Each one of these activities hosts a navigation drawer. I am trying to figure out the best way to manage the activity stack between these three activities.
For example, When I start the application, Activity A is launched.
Activity A has a navigation drawer like Activities B & C. When I click on Activity B in the drawer, Activity B is launched and clicking on Activity C in the drawer launches Activity C etc...
I don't want to finish these Activies when the drawer launches a new Activity because they load data from a backend service, and when I click the back button I want it to send the application to the background.
Essentially, I am looking for a way to launch the activity if it does not exist, and if it does, just resume it. How can I accomplish this?
I think decoupling retrieving data from the activity is the best option.
The following paragraph is from Tasks and Back Stack:
Because the activities in the back stack are never rearranged, if your application allows users to start a particular activity from more than one activity, a new instance of that activity is created and pushed onto the stack (rather than bringing any previous instance of the activity to the top). As such, one activity in your application might be instantiated multiple times (even from different tasks), as shown in figure 3. As such, if the user navigates backward using the Back button, each instance of the activity is revealed in the order they were opened (each with their own UI state).
So in your case, grabbing the data in the background when the app starts using async tasks and storing them in the database might work out better.
One way to do it would be:
On create of the home activity, quickly grab the home activity's data via async task while showing a progress bar. When done, store it, and display it. Then, launch async tasks for the data for other activities. There are some conditions that could be tricky. For example, you have to make sure you show a progress bar if the user quickly switches to Activity B or C before your data is ready.
Perhaps using a singleton might suite your needs if you do not want to use the DB. Depending on the size of your data, parceling your data and passing it through a bundle might also prove to be a good technique.
I have a text message app which has two main activities:
TextChatActivity - a conversation. Can have multiple instances for multiple conversations.
ConvListActivity - a list of all the activities.
In addition, a TextChatActivity of a certain conversation can be opened via a status bar notification.
What I want to do is to bring a specific TextChatActivity instance to the front.
Let's say I have 2 open conversations (TextChatActivity) A and B, and B is on front.
Now I got a notification that leads to conversation A. I want it to bring conversation A TextChatActivity to front.
How can I do that without opening a new instance of TextChatActivity for conversation A?
Thanks!
Actually, you can't. If you have multiple instances of an activity in the stack, there is no way to address a unique instance of an activity so that you could bring it to the front.
Your architecture is not good. Because of the way Android works, you would be better off if you had a single instance of this activity, and allow the user to switch between conversations, not by creating a new instance of an activity, but just by switching out the underlying data for the existing activity. In this way, you only ever have one instance of the activity and you can simply change the data that you are displaying.
In your "notification" example, the Intent that you start from the notification should have an "extra" that indicates which conversation the user wants to show. You should ensure that there is only one instance of your TextChatActivity by declaring it with launchMode="singleTop" or by setting FLAG_ACTIVITY_SINGLE_TOP when you start it. When onNewIntent() is called in your activity, check the "extras" and adjust the content to show the desired conversation.
If you want to create the "illusion" of an activity per conversation, then you can override `onBackPressed() and manage your own "stack" of conversations in the activity, so that when a user presses the BACK key, you can go back in the "stack" of conversations and show him the previous one, just by modifying the data that is displayed.
Just start an activity and set the flag FLAG_ACTIVITY_CLEAR_TOP
I'm making an app which has a flow roughly as below:
User starts on the main screen with an empty list, hits menu, and goes to "add item." (Activity A)
User is given a new activity which allows them to specify search criteria, then hits "go" to do a search. (Activity B)
User gets a list of results, and can click on one of them to view more details. (Activity C)
User sees details of item, and can use a menu item to save it to their list in Activity A. (Activity D)
Right now, I am having each Activity call each other Activity for results, and then it is passing the result all the way back up the stack as it returns to Activity A.
Is there a way to jump this, since all I want is for a result in Activity D to get to Activity A directly?
Note that a user should still be able to navigate backwards (using the back button) through each activity, but if they explicitly save the item in Activity D, I want it to jump straight to Activity A.
I recommend just invoking the activities (not using the *ForResult) calls, then having activity D invoke Activity A with an INTENT_ADD_ITEM with data, then have Activity A add the item.
Hope this helps...
Just so that people can benefit from what I learned later...
The key to solving this problem is using flags with Intent, in this case using FLAG_ACTIVITY_CLEAR_TOP. Other flags are useful as well in controlling the flow of your UI.
It is a bad idea to try to solve this problem by chaining startActivityForResult() through activities. It means that it's difficult to change the flow of your application.