-
Notifications
You must be signed in to change notification settings - Fork 318
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix navigation view memory leak - some live data objects from navigat… #2051
Conversation
…ion view model were never released as part of the view on destroy.
Codecov Report
@@ Coverage Diff @@
## master #2051 +/- ##
============================================
+ Coverage 41.5% 41.54% +0.04%
- Complexity 1348 1351 +3
============================================
Files 286 286
Lines 9127 9156 +29
Branches 684 684
============================================
+ Hits 3788 3804 +16
- Misses 5020 5033 +13
Partials 319 319 |
|
||
private NavigationPresenter navigationPresenter; | ||
private NavigationViewModel navigationViewModel; |
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.
could be declared final
NavigationViewSubscriber(NavigationPresenter navigationPresenter) { | ||
NavigationViewSubscriber(final LifecycleOwner owner, | ||
final NavigationViewModel navigationViewModel, | ||
final NavigationPresenter navigationPresenter) { | ||
this.navigationPresenter = navigationPresenter; |
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.
could be declared final
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.
Done
|
||
private NavigationPresenter navigationPresenter; | ||
private NavigationViewModel navigationViewModel; | ||
private LifecycleOwner lifecycleOwner; |
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.
could be declared final
@@ -65,7 +66,7 @@ | |||
* | |||
* @since 0.7.0 | |||
*/ | |||
public class NavigationView extends CoordinatorLayout implements LifecycleObserver, OnMapReadyCallback, | |||
public class NavigationView extends CoordinatorLayout implements LifecycleOwner, OnMapReadyCallback, |
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.
since every Activity and Fragment implements LifecycleOwner
i'm not sure if this is a legit fix, considering that we already expose on*
callbacks would it be reasonable to expect users to wire them up to activity or fragment lifecycle callbacks?
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 exactly why I did it. When we implement LifecycleOwner
in NavigationView
- we remove the obligation from user to pass LifecycleOwner into this view by smth like setLifecycleOwner()
. In our documentation we describes how to operate with NavigationView
, and there only need to call on*
callbacks. All other stuff regarding lifecycle in NavigationView
we made internally and we shouldn't ask users to do any other movements. So implementing NavigationView
as LifecycleOwner
(for it "child" views, like IntructionView
, SummaryBottomSheet
and so on) allow us to minimize additional user interactions with our NavigationView
for it correct work.
I mean NavigationView
is smth like "parent" or container for other views (IntructionView
, SummaryBottomSheet
, etc.), so in this nested views we just subscribe for observing lifecycle of its "parent" (NavigationView
).
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.
so afaik view has much simpler lifecycle, looking at the change are we essentially forcing activity/fragment lifecycle onto view? what's the benefit of doing it? should we just simply migrate over to Fragment?
cc: @Guardiola31337
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.
in my perspective this view has grown beyond the scope of what any view should contain. leading to a fragment would be a logical choice. but then again there are people that use views for everything and completely avoid fragments and activities so this seems like a personal choice more than anything
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.
This is looking good so far @Lebedinsky Great work!
I left some comments.
@@ -206,6 +214,12 @@ public void onStop() { | |||
} | |||
} | |||
|
|||
@NonNull |
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.
Could we add Javadoc for public API?
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.
No need here, it is just overrided method for LifecycleOwner
@@ -176,18 +181,21 @@ public void onFeedbackDismissed() { | |||
* @param navigationViewModel to which this View is subscribing | |||
* @since 0.6.2 | |||
*/ | |||
public void subscribe(NavigationViewModel navigationViewModel) { | |||
public void subscribe(LifecycleOwner owner, NavigationViewModel navigationViewModel) { |
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.
Let's make sure we update the release notes and the android docs site with these changes (and add the SEMVER
label here 😅), as we are breaking subscribe
API here 👍
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.
Done
@@ -69,8 +76,13 @@ protected void onFinishInflate() { | |||
bind(); | |||
} | |||
|
|||
public void subscribe(NavigationViewModel navigationViewModel) { | |||
navigationViewModel.summaryModel.observe((LifecycleOwner) getContext(), new Observer<SummaryModel>() { | |||
public void subscribe(LifecycleOwner owner, NavigationViewModel navigationViewModel) { |
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.
Same as before re: SemVer
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.
Done
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've left some nit-pickings @Lebedinsky
@@ -90,6 +91,8 @@ | |||
private boolean isMapInitialized; | |||
private boolean isSubscribed; | |||
|
|||
private LifecycleRegistry lifecycleRegistry; |
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.
NIT No need for new line
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.
Done
@@ -590,7 +604,7 @@ private void updateInstructionMutedState(boolean isMuted) { | |||
int paddingBuffer = (int) resources.getDimension(R.dimen.route_overview_buffer_padding); | |||
int instructionHeight = (int) (resources.getDimension(R.dimen.instruction_layout_height) + paddingBuffer); | |||
int summaryHeight = (int) resources.getDimension(R.dimen.summary_bottomsheet_height); | |||
return new int[] {leftRightPadding, instructionHeight, leftRightPadding, summaryHeight}; | |||
return new int[]{leftRightPadding, instructionHeight, leftRightPadding, summaryHeight}; |
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.
NIT 👀 how to set up Mapbox checkstyle here https://github.com/mapbox/mapbox-gl-native/wiki/Setting-up-Mapbox-checkstyle
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.
Done
|
||
NavigationViewSubscriber(NavigationPresenter navigationPresenter) { | ||
NavigationViewSubscriber(final LifecycleOwner owner, | ||
final NavigationViewModel navigationViewModel, |
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.
NIT no need for new lines, also could we rearrange the properties to match constructor for better readability?
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'm agree with you, Done
@@ -103,6 +106,8 @@ | |||
private SoundButton soundButton; | |||
private FeedbackButton feedbackButton; | |||
|
|||
private LifecycleOwner lifecycleOwner; |
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.
NIT no need for new line
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.
Done
public void subscribe(LifecycleOwner owner, NavigationViewModel navigationViewModel) { | ||
lifecycleOwner = owner; | ||
lifecycleOwner.getLifecycle().addObserver(this); | ||
|
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.
NIT no need for new line
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.
Done
cc @william-reed for visibility Please feel free to share your feedback and if this approach aligns with what you were thinking. Thanks a lot! |
One thing that we missed here was:
Please add the entry to the |
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.
A couple of comments around the tests.
private NavigationViewSubscriber theNavigationViewSubscriber; | ||
|
||
@Before | ||
public void setup() { |
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.
|
||
theNavigationViewSubscriber.unsubscribe(); | ||
|
||
verify(navigationViewModel.retrieveRoute()).removeObservers(eq(lifecycleOwner)); |
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.
Generally is preferable having only 1 assertion per test 👀 https://speakerdeck.com/guardiola31337/elegant-unit-testing-mobilization-2016?slide=57
The problem of having multiple assertions are:
- It's annoying to have to look at the stacktrace to know what's failing (which assertion failed?)
- You don't have information about the remaining assertions (because when an assertion fails it breaks the execution of the test)
That's why is preferable to have only 1 assertion per test and one test per scenario. Basically, if one of them fails you'll know at a glance what in the code might have caused that failure and in which part (class, method, etc.).
Fix code style for PR #2051. Add changes to Changelog.
|
||
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) | ||
void unsubscribe() { | ||
navigationViewModel.retrieveRoute().removeObservers(lifecycleOwner); |
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 dont think this is necessary considering the lifecycle is tied to this view anyway
Thanks for doing this, this was exactly how I had envisioned it with a custom |
Description
Fixes NavigationView memory leak.
Fixes #2012
bug
,feature
,new API(s)
,SEMVER
, etc.)Goal
Some
LiveData
objects fromNavigationViewModel
were never released as part of the viewonDestroy()
causing a memory leak. The goal here is to avoid that leak.Implementation
This PR prevents that leak unsubscribing
NavigationViewModel
LiveData
objects previously added by removing the observers of theLifecycleOwner
adding unsubscribe methods toNavigationViewSubscriber
,InstructionView
andSummaryBottomSheet
that are called automatically when calledonDestroy()
method for lifecycle owner of this views. HereLifecycle
owner isNavigationView
by adding to itLifecycleRegistry
.Testing
Please describe the manual tests that you ran to verify your changes
SNAPSHOT
upstream dependencies if needed) through testapp/demo app and run all activities to avoid regressionsChecklist
CHANGELOG
including this PR