Shared Element Transition is broken between three or more activities - android

Problem
I want to achieve shared element transition between Activity A and B, and between Activity B and C.
I did everything based on the android transition documentation.
There is no problem between:
A => B => back to A
A => B and B => C => back to B
But if I do:
A => B and B => C => back to B => back to A
The last step will not have any shared element transition (actually not only shared element transition, even there is only fading it could be lost).
I have been looking for solutions everywhere, but it seems everybody only needs A => B (and B => A) shared element transition but doesn't care any more transitions from B => C and back to A.
Example
See an example I created based on android's animation samples, where Activity A = MainActivity, B = DetailActivity, C = DetailDetailActivity. Clicking the button on Activity B will navigate to Activity C.

Because while executing C => B, it will re-construct a new EnterTransitionCoordinator for B, replacing the old one contains the correct mPendingExitNames which it is important to indicate views need to be transitioned between A and B.
public boolean startExitBackTransition(final Activity activity) {
ArrayList<String> pendingExitNames = getPendingExitNames(); // here
if (pendingExitNames == null || mCalledExitCoordinator != null) {
return false;
} else {
// ...
}
}
The solution seems to be a little bit tricky. You can refer to ThreeActivityTransitionDemo and pay attention to the SharedElementUtils.

Add this to Activity B
#Override
protected void onStop() {
if(Build.VERSION.SDK_INT == Build.VERSION_CODES.Q && !isFinishing()){
new Instrumentation().callActivityOnSaveInstanceState(this, new Bundle());
}
super.onStop();
}

Related

How to avoid duplicate activities in back stack android?

I have an activity A, B, C, activity A opens activity B (A -> B) and activity B opens activity C now the back stack will be (A -> B -> C) Now Activity C opens activity B (A -> B -> C -> B) and at some condition the activity C will be destroyed in that case (A -> B -> B) is the back stack. when i click back in Activity B then the app again goes to activity B only which i don't want to do. i need to go back directly to activity A. I tried using single top but the single top works only while the activity getting created not something happens in the back stack. Any way can we achieve this ?
You can try this:
override onBackPressed method on activity B
#Override
public void onBackPressed() {
Intent i = new Intent(B.class, A.class);
i.startActivity(i);
}
add this code to each of your activities.
#Override
public void onBackPressed() {
super.onBackPressed();
finish();
}
this code will finish your activity from stack when you move back to the last activity.

onActivityResult only called once

I have the following Activity sequence in a folder picker part of an app:
Activity A starts B. B then starts instances of itself that stack as the user goes deeper into the folder tree. Whenever an instance of B is completed (folder chosen), it and all the preceding Bs should finish and A should be shown again, with the propagated result. If the user backtracks, the preceding Bs should not finish, since the user should be able to go back and forth between folder subtrees.
I'm trying to accomplish this by using startActivityForResult, both from A to B and then for each new instance of B. When the user finishes an instance B, I use setResult and want to check it in each preceding instance's onActivityResult method. If it's RESULT_OK, I set the result again and finish that instance, until all of them are finished in a cascading sequence and A is shown again with the result.
The sequence works as long as I go A => B(1) => B(2) => B(3) and finish, without backtracking in the B part.
However, if I go A => B(1) => B(2) => back to B(1) => B(3) and finish, onActivityResult is only called in B(1) when going back from B(2) the first time, not when I later go back to it when finishing from B(3). B(1) is simply resumed when going back from B(3) in this sequence. This breaks the cascading finish sequence.
Why is onActivityResult only called once for instance B(1)? Shouldn't it be called each time a child activity sets a result and finishes?
Here's some code:
=== Activity A:
Starting the first B instance:
Intent intent = new Intent(ActivityA.this, ActivityB.class);
startActivityForResult(intent, RequestCodes.ActivityB);
Receiving the final B result:
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if(resultCode == RESULT_OK){
...
}
}
=== Activity B:
Starting new child B from itself:
Intent intent = new Intent(ActivityB.this, ActivityB.class);
startActivityForResult(intent, RequestCodes.ActivityB);
Setting the result when choosing a folder:
Intent result = new Intent();
result.putExtra(...);
setResult(Activity.RESULT_OK, result);
finish();
Receiving the child B result in each parent B instance (which is only called the first time in B(1) for some reason):
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if(resultCode == RESULT_OK){
setResult(Activity.RESULT_OK, data);
finish();
}
}
Thanks in advance.
This is a horrible architecture! You shouldn't create multiple instances of B when traversing a tree. Eventually you will run out of memory! You should use a single instance of your Activity and when the user moves up or down in the tree you should just change the data that you display in the Activity. You should have an internal data model that describes the tree and you only need to remember what node in the tree you are currently displaying. If you also want to support the user going back (by using the BACK button), you can create your own stack (not a stack of activities, but just a stack of tree node references) that you can use to determine which node of the tree to go back to from the current node. This is a simple data management problem and your architecture has forced this to become a View management problem.

Fragment transactions and activity transitions

I have this scenario in my app:
One activity(A) with multiple fragments calling another activity(B) at some point.
Flow for that goes like this: F1 => F2 => F3 => B or F1 => F2 => B where F(n) represents fragment.After I finish activity B it returns me to F3 or F2 but my goal is to show user F1 so I tried sending event via event bus and replacing any other fragment with F1,note that I'm adding every fragment to backstack.So I succeed with it but if I call fragment F2 or F3 application crashes also sometimes I get "IllegalStateException: Can not perform this action after onSaveInstanceState".
So after trying a lot of approaches I simply did this :
public void onClick(View v){
//started activity B
//replaced current fragment with F1
}
The end result of this was seeing F1 before activity B ,and everything else worked fine without crashing.So to solve that glitch I replaced fragment 100 ms after activity B is started.
public void onClick(View v){
//started activity B
new Handler().(new Runable(){
#Override
public void run()
{
//replaced current fragment with F1
}
},100);
}
But I feel this is ugly way to solve this problem and I want to ask you if there is better solution?
EDIT:
I was inspired by spcial answer so I did similar thing with states.
In activity A I have two variables.
boolean wasAnotherActivityCalled=false;
String showFragment=null;
In my fragment I have this :
public void onClick(View v){
//started activity B
getActivity().wasAnotherActivityCalled=true;
getActivity().showFragment=FragmentOne.class.getSimpleName();
}
in activity A I have this :
#Override
protected void onResumeFragments() {
super.onResumeFragments();
if(wasAnotherActivityCalled)
{
if(showFragment.equals(F1.class.getSimpleName()))
{ //do your logic here}
wasAnotherActivityCalled=false;
showFragment=null;
}
}
I have something similiar in my App. What I did was to use a simple "state-machine" where I have an int attribute which represents the current state (0 = Fragment1, 1 = Fragment2...etc) and an ArrayList with all fragments which I need. If I have to switch the fragment I also increment the state and just load the fragment from the ArrayList.
In my onPause() method I save the state in sharedPreferences() and in the onResume() method I load the state from sharedPreferences() and do a initFragment(state) where I just replace the fragmentLayout with the fragment from fragmentArray[state] :-)
With this behaviour I could handle backstack on my own, can go back and furth and save the state of the current needed fragment everytime the user changes the activity and comes back. Furthermore I don't commit the fragments into backstack because it is already handled through myself.
I hope I could help you with this.
Dont add your fragment into backstack while commiting it

How to implement the following?

Given:
6 activities. (A, B, C, D, E, F)
Each activity consists of several edittexts or a camera implementation and a next button to goto next activity (The activity which is going to be opened depends on the value entered by the user).
If edittexts are displayable or not comes from the server.
Incase there are no edittexts on the layout then only next button is shown.
Flow of the application: A->B->C(->D)->E->F
The Activity D opens only when a certain condition is met in activity C.
Todo:
Incase, The activity contains no edittext and only next button i should be able to skip this activity.
If the flow of my application is like: A->B->C->E->F Then when i press hard back the flow should be F->E->C->B->A
If the flow of my application is like: A->B->C->D->E->F Then when i press hard back the flow should be F->E->C->B->A
If the flow of my application is like: A->C->D->E->F Here we skipped B because there are no views in Activity B, When i press hard back the flow should be F->E->C->A
what i did:
To skip the activity when no fields are available inside onCreate(), I am checking if any field is displayable if no then i add the activity name in a stack if it is not present in it and then open the next activity.
// Block for skipping this screen
if (skipScreen) {
Intent i = new Intent(B.this, C.class);
startActivity(i);
finish();
} else {
if (!Constants.st.contains(B.class)) {
Constants.st.push(B.class);
}
}
And when i press back from an activity i pop() the name of that activity from the stack and peek() at the top of the stack and jump to that activity.
public void onBackPressed() {
super.onBackPressed();
Constants.st.pop();
Intent i = null;
if (Constants.st.isEmpty()) {
i = new Intent(B.this, A.class);
} else {
Class<Activity> jumpTo = Constants.st.peek();
i = new Intent(B.this, jumpTo);
}
startActivity(i);
finish();
}
I think the statePattern will be very helpfull. Here is a short tutorial.

Clearing the Android back stack

I'm trying to do something similar to this question.
I've got a main app A, and sub-apps B and C. B is a searchable dictionary, and C is a definition, so if you search for many words, you can end up with the stack looking like A > B > C > B > B > C > B > C > C > C... etc
Whenever I go back, I'd like to go back to the original B, so I want the back stack to basically stay at A > B all the time.
I've currently got it set up using an Intent so when the back button is pressed from C, it goes to a new instance of B, but that's obviously not what I want.
Ideas?
Shouldn't hitting BACK from C take you to the original B? So the stack should look like:
A>
B>
C<
B>
C>
C<
C<
B<
A
with > going to the next activity, and < being the back button?
You could override onBackPressed for all the activities, so for C it loads a new B, B it loads a new A, and A you would do moveTaskToBack(true);, but that's less of a solution and more of a hack to make it work.
Try to use onResume so that B comes up like it would on default. Set any pertinent variables to what they are in a new activity. If you only want this when you're coming from C, have a class boolean that is set to true when C is loaded, and check it in onResume or onRestart. If it's true, set everything to default/blank, and set the boolean to false. If not, load it how it was (this is if they hit home and come back to the app, basically). I'm not at my work desk, but I think you'll want:
#Override
public void onResume() {
super.onResume();
if(fromC == true){
//Set all variables to load default dictionary
fromC = false;
}
}
}
This should make the original B act like a new B.
Or instead, you could do it the easy way, and when you call the intent to load a new B like this:
startActivity(getIntent());
finish();
That'll make there only be one B. Making it load as a blank when you go back is a little bit different and requires class variables, or some other tricky trick that I am thinking of. This is something like what you'll want to do: .zip of small sample
If you can get your code reworked to something like that almost (where you can use the appropriate variables to set up things to work right if that makes any sense).
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (splash.sdk < 5 && keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
onBackPressed();
}
return super.onKeyDown(keyCode, event);
}
//This will make the back button exit the app to the home screen.
#Override
public void onBackPressed() {
moveTaskToBack(true);
return;
}
You can set an Intent flag to do this. Say you are in Activity C and you want to call Activity B: you add a flag to the Intent called FLAG_ACTIVITY_CLEAR_TOP. So:
class C extends Activity {
//...
private void gotob() {
Intent go = new Intent(this, B.class);
go.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(go);
}
//...
}
What this does is either to go to the top instance of B in the back stack (and clear everything else off the top), or create a new instance if none exists. I haven't worked out if there is a way to do this with launch flags though—there are some similar modes but I don't think any are identical.

Categories

Resources