-
Notifications
You must be signed in to change notification settings - Fork 101
Bugfix: Presenter not destroyed when Fragment is popped off the backstack #78
Bugfix: Presenter not destroyed when Fragment is popped off the backstack #78
Conversation
…e TiFragmentDelegate class.
…test class for all methods that don’t require the PutInMapAnswer.
…ations() and isFinishing() in tests
I didn’t made it final so somebody could work around this restriction
Added a buch of new tests for TiActivity covering all destroy cases Removed “Don’t keep Activitites check”. It can be detected by checking isFinishing and isChangingConfigurations. isFinishing is false when the activity moves to background even when “don’t keep activities” is enabled. Removed support for NonConfigurationInstance. The savior works much better.
I was able to simplify the Also:
I'm now happy with the result and look forward for the merge. Next step: write release notes 😁 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looked through PR, not very familiar to leave more suggestions yet :)
The PR fixed an issue in our app.
*/ | ||
void setFragmentRetainInstance(final boolean retain); | ||
boolean isInBackstack(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
isFragmentInBackstack() may fit better
import java.util.List; | ||
import java.util.Map; | ||
|
||
public class ActivityScopedPresenters { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ActivityScopedPresenters not necessary but it makes code more readable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't yet looked into the updated code.
But I got always the following output, when I destroy an activity:
Stacktrace
04-25 09:23:54.032 23793-23793/net.grandcentrix.thirtyinch.sample V/ThirtyInch: SampleFragment:TiFragment@cd04102: binding the cached view to Presenter MainThreadProxy@d1f9626-SampleFragment@cd04102{presenter=SamplePresenter@2067a13}
04-25 09:23:54.033 23793-23793/net.grandcentrix.thirtyinch.sample V/ThirtyInch: SamplePresenter:TiPresenter@2067a13: onAttachView(TiView)
04-25 09:23:54.033 23793-23793/net.grandcentrix.thirtyinch.sample V/ThirtyInch: SamplePresenter:TiPresenter@2067a13: deprecated onWakeUp()
04-25 09:23:54.033 23793-23793/net.grandcentrix.thirtyinch.sample V/ThirtyInch: HelloWorldActivity:TiActivity@f765445: binding the cached view to Presenter DistinctUntilChangedProxy@edd875f-MainThreadProxy@520e03-HelloWorldActivity:TiActivity@f765445{presenter = HelloWorldPresenter@acefc1}
04-25 09:23:54.033 23793-23793/net.grandcentrix.thirtyinch.sample V/ThirtyInch: HelloWorldPresenter:TiPresenter@acefc1: onAttachView(TiView)
04-25 09:23:54.034 23793-23793/net.grandcentrix.thirtyinch.sample V/ThirtyInch: HelloWorldPresenter:TiPresenter@acefc1: deprecated onWakeUp()
04-25 09:23:54.409 23793-23793/net.grandcentrix.thirtyinch.sample V/ThirtyInch: SamplePresenter:TiPresenter@e9e1afa: deprecated onSleep()
04-25 09:23:54.409 23793-23793/net.grandcentrix.thirtyinch.sample V/ThirtyInch: SamplePresenter:TiPresenter@e9e1afa: onDetachView()
04-25 09:23:54.409 23793-23793/net.grandcentrix.thirtyinch.sample V/ThirtyInch: HelloWorldPresenter:TiPresenter@7132889: deprecated onSleep()
04-25 09:23:54.409 23793-23793/net.grandcentrix.thirtyinch.sample V/ThirtyInch: HelloWorldPresenter:TiPresenter@7132889: onDetachView()
04-25 09:23:54.409 23793-23793/net.grandcentrix.thirtyinch.sample V/ThirtyInch: ActivityInstanceObserver: destroying HelloWorldActivity:TiActivity@7d2b253{presenter = HelloWorldPresenter@7132889}
04-25 09:23:54.409 23793-23793/net.grandcentrix.thirtyinch.sample V/ThirtyInch: ActivityInstanceObserver: isFinishing = true
04-25 09:23:54.409 23793-23793/net.grandcentrix.thirtyinch.sample V/ThirtyInch: ActivityInstanceObserver: isChangingConfigurations = false
04-25 09:23:54.409 23793-23793/net.grandcentrix.thirtyinch.sample D/ThirtyInch: PresenterSavior: Activity is finishing, free remaining presenters HelloWorldActivity:TiActivity@7d2b253{presenter = HelloWorldPresenter@7132889}
04-25 09:23:54.410 23793-23793/net.grandcentrix.thirtyinch.sample V/ThirtyInch: SamplePresenter:TiPresenter@e9e1afa: onDestroy()
04-25 09:23:54.410 23793-23793/net.grandcentrix.thirtyinch.sample D/ThirtyInch: ActivityScopedPresenters@c78308e: remove SamplePresenter:245242618:1798809768000 SamplePresenter:TiPresenter@e9e1afa{view = null}
04-25 09:23:54.410 23793-23793/net.grandcentrix.thirtyinch.sample V/ThirtyInch: HelloWorldPresenter:TiPresenter@7132889: onDestroy()
04-25 09:23:54.410 23793-23793/net.grandcentrix.thirtyinch.sample D/ThirtyInch: ActivityScopedPresenters@c78308e: remove HelloWorldPresenter:118696073:1798798494000 HelloWorldPresenter:TiPresenter@7132889{view = null}
04-25 09:23:54.410 23793-23793/net.grandcentrix.thirtyinch.sample V/ThirtyInch: SamplePresenter:TiPresenter@e9e1afa: not calling onDetachView(), not woken up
04-25 09:23:54.411 23793-23793/net.grandcentrix.thirtyinch.sample V/ThirtyInch: SampleFragment:TiFragment@e45c925: Hosting Activity is finishing, destroying presenter SamplePresenter:TiPresenter@e9e1afa{view = null}
04-25 09:23:54.411 23793-23793/net.grandcentrix.thirtyinch.sample W/ThirtyInch: SamplePresenter:TiPresenter@e9e1afa: not calling onDestroy(), destroy was already called
04-25 09:23:54.412 23793-23793/net.grandcentrix.thirtyinch.sample V/ThirtyInch: HelloWorldActivity:TiActivity@7d2b253: Activity is finishing, destroying presenter HelloWorldPresenter:TiPresenter@7132889{view = null}
04-25 09:23:54.412 23793-23793/net.grandcentrix.thirtyinch.sample W/ThirtyInch: HelloWorldPresenter:TiPresenter@7132889: not calling onDestroy(), destroy was already called
HelloWorldPresenter:TiPresenter@7132889: not calling onDestroy(), destroy was already called
makes me a little bit stomach pain...
It seems that we calling destroy() multiple times 🤔
That needs to be fixed...
How to test:
Add to the sample:
startActivity(new Intent(HelloWorldActivity.this, HelloWorldActivity.class));
In recreate click listener and remove recreate()
.
Then start the sample. Click on recreate button and press back.
Update:
This behaviour is "ok".
We have discussed that internally.
See also #78 (comment)
private boolean isDontKeepActivities() { | ||
// default behaviour | ||
int dontKeepActivities = 0; | ||
try { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use your util here now. Otherwise AndroidDeveloperOptions
is not used and can removed
0); | ||
} | ||
|
||
return alwaysFinishActivitiesInt == 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we use that in our sample:
Change that to alwaysFinishActivitiesInt != DEFAULT_VALUE
.
if (Build.VERSION.SDK_INT >= 17) { | ||
alwaysFinishActivitiesInt = Settings.System | ||
.getInt(context.getContentResolver(), Settings.Global.ALWAYS_FINISH_ACTIVITIES, | ||
0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we use that.
Move this zero and the zero below to an constant like DEFAULT_VALUE = -1
* {@link FragmentManager}. When {@link FragmentTransaction#remove(Fragment)} or {@link | ||
* FragmentTransaction#replace(int, Fragment)} results in removing this {@link TiFragment} from the | ||
* {@link FragmentManager} the {@link TiPresenter} gets destroyed. When the same {@link TiFragment} | ||
* instance will be added again a new {@link TiPresenter} will be created by calling {@link |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is not true.
Replace the following in our sample app:
- recreate()
+ final Fragment fragment = getSupportFragmentManager()
+ .findFragmentById(R.id.fragment_container);
+ getSupportFragmentManager().beginTransaction().remove(fragment).commitNow();
+ Observable.just(null).delay(3, TimeUnit.SECONDS).subscribe(
+ o -> getSupportFragmentManager().beginTransaction()
+ .add(R.id.fragment_container, fragment).addToBackStack(null)
+ .commit());
I got the following output:
Logcat
04-25 09:50:41.876 5895-5895/net.grandcentrix.thirtyinch.sample V/ThirtyInch: SamplePresenter:TiPresenter@70beced: deprecated onSleep()
04-25 09:50:41.876 5895-5895/net.grandcentrix.thirtyinch.sample V/ThirtyInch: SamplePresenter:TiPresenter@70beced: onDetachView()
04-25 09:50:41.876 5895-5895/net.grandcentrix.thirtyinch.sample V/ThirtyInch: SamplePresenter:TiPresenter@70beced: not calling onDetachView(), not woken up
04-25 09:50:44.881 5895-5895/net.grandcentrix.thirtyinch.sample V/ThirtyInch: CallOnMainThreadInterceptor: wrapping View SampleFragment@cd04102{presenter=SamplePresenter@70beced} in MainThreadProxy@60c384c-SampleFragment@cd04102{presenter=SamplePresenter@70beced}
04-25 09:50:44.881 5895-5895/net.grandcentrix.thirtyinch.sample V/ThirtyInch: DistinctUntilChangedInterceptor: wrapping View MainThreadProxy@60c384c-SampleFragment@cd04102{presenter=SamplePresenter@70beced} in MainThreadProxy@60c384c-SampleFragment@cd04102{presenter=SamplePresenter@70beced}
04-25 09:50:44.881 5895-5895/net.grandcentrix.thirtyinch.sample V/ThirtyInch: CallOnMainThreadInterceptor: wrapping View MainThreadProxy@60c384c-SampleFragment@cd04102{presenter=SamplePresenter@70beced} in MainThreadProxy@3235095-MainThreadProxy@60c384c-SampleFragment@cd04102{presenter=SamplePresenter@70beced}
04-25 09:50:44.881 5895-5895/net.grandcentrix.thirtyinch.sample V/ThirtyInch: DistinctUntilChangedInterceptor: wrapping View MainThreadProxy@3235095-MainThreadProxy@60c384c-SampleFragment@cd04102{presenter=SamplePresenter@70beced} in MainThreadProxy@3235095-MainThreadProxy@60c384c-SampleFragment@cd04102{presenter=SamplePresenter@70beced}
04-25 09:50:44.881 5895-5895/net.grandcentrix.thirtyinch.sample V/ThirtyInch: SampleFragment:TiFragment@cd04102: binding NEW view to Presenter MainThreadProxy@3235095-MainThreadProxy@60c384c-SampleFragment@cd04102{presenter=SamplePresenter@70beced}
04-25 09:50:44.881 5895-5895/net.grandcentrix.thirtyinch.sample V/ThirtyInch: SamplePresenter:TiPresenter@70beced: onAttachView(TiView)
04-25 09:50:44.881 5895-5895/net.grandcentrix.thirtyinch.sample V/ThirtyInch: SamplePresenter:TiPresenter@70beced: deprecated onWakeUp()
You see the same (TiPresenter@70beced
) presenter instance will be used
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added this case to the fragment lifecycle test Activity and can't reproduce it. The presenter is always destroyed and a new one is generated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was discussed and had the right behaviour.
The second (or following) added fragment got the same presenter.
Why? Because we added it to the backstack. And the default behaviour of TiFragment
s-Presenter is to keep as long as you are managed by the fragment manager. And if you are in the backstack, you are managed by them ...
* {@link TiPresenter} will be destroyed accordingly. | ||
* </p> | ||
* <p> | ||
* Using {@code setRetainInstance(true)} is now allowed as it causes many troubles. You should favor |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is
nownot allowed
@Override | ||
public void onActivityDestroyed(final Activity activity) { | ||
|
||
// TODO check if "Don't keep Activities" case is handled correctly |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is that todo done?
/** | ||
* Simple wrapper around a {@link HashMap} to save {@link TiPresenter} by id. For every {@link | ||
* android.app.Activity} containing a {@link TiPresenter} a corresponding {@link | ||
* ActivityScopedPresenters} will be created |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add the info, that this will store also the Presenters for the Fragments
which are added to that Activity
.
* android.app.Activity} containing a {@link TiPresenter} a corresponding {@link | ||
* ActivityScopedPresenters} will be created | ||
*/ | ||
public class ActivityScopedPresenters { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think about implementing Map
directly or extending from HashMap
. But not needed now. So keep it like it is ;)
/** | ||
* Access to the {@link PresenterSavior} singleton to save presenters across orientation changes | ||
* | ||
* @return singleton for |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove that. I guess that is clear :D
public void free(final String presenterId) { | ||
mPresenters.remove(presenterId); | ||
public void onActivityFinished(final Activity activity, final String activityId) { | ||
final ActivityScopedPresenters scope = mScopes.remove(activityId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move that below the statement mScope.isEmpty()
. We need it only after this statement...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the scope is only required in on branch, true. But removing the scope is the important part here. required for both branches.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then maybe moving up the second statement? 🤔
multiple calls to |
This happens every time because of multiple triggers (Savior, Fragment and Activity)
I've checked the ViewPager behaviour like we discussed. |
…r_not_destroyed Add sample/test for viewpager
Hello everyone,
currently I'm working on a fix for #33.
We thought it would be better if we add all relevant Unit-Tests first so it would be easier to develop the bugfix. Additionally I've created a new Activity for the "Sample" application which can be be used to interactively test the Fragment lifecycle under different circumstances (e.g. back stack or configuration change).
I would like to open this PR as early as possible to make it open for discussion.
Please feel free to comment and contribute!