Skip to content
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

Use view binding in fragments. #4814

Merged

Conversation

Isira-Seneviratne
Copy link
Member

@Isira-Seneviratne Isira-Seneviratne commented Nov 6, 2020

What is it?

  • Bugfix (user facing)
  • Feature (user facing)
  • Codebase improvement (dev facing)
  • Meta improvement to the project (dev facing)

Description of the changes in your PR

  • Use view binding in the fragments, as Kotlin synthetics have been deprecated in favor of view binding.

APK testing

debug.zip

Due diligence

@Isira-Seneviratne Isira-Seneviratne force-pushed the Use_view_binding_in_fragments branch 2 times, most recently from f46a5f6 to 8650962 Compare November 19, 2020 02:08
@Isira-Seneviratne Isira-Seneviratne force-pushed the Use_view_binding_in_fragments branch 5 times, most recently from 21b90fc to 6c21e33 Compare November 23, 2020 01:17
@FunkyMuse
Copy link

@Isira-Seneviratne
You need to release the binding inside the fragments, otherwise you're gonna have memory leaks, i wrote an article

@Isira-Seneviratne
Copy link
Member Author

@Isira-Seneviratne
You need to release the binding inside the fragments, otherwise you're gonna have memory leaks, i wrote an article

I already did that, did I miss a fragment?

@Stypox
Copy link
Member

Stypox commented Dec 12, 2020

The same I said in #4762 (comment) applies here ;-)

@Isira-Seneviratne Isira-Seneviratne force-pushed the Use_view_binding_in_fragments branch 5 times, most recently from b31ca51 to 08bb597 Compare December 19, 2020 07:54
@TacoTheDank
Copy link
Member

@Stypox Bumping this for examination and merging, as it's been updated.

Copy link
Member

@Stypox Stypox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good, thank you! In the review I'm just asking clarifications for things I am not sure about.
I tested by navigating in many different fragments and everything looked good; I also took a heap dump with LeakCanary and the only leaks that were pointed out were unrelated to view binding. @TobiGr almost ready to be merged

viewPager.setAdapter(null);
if (binding != null) {
binding.pager.setAdapter(null);
binding = null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you setting binding = null here and not in onDestroyView like all other fragments?

(unrelated to this PR, just a note for the future) pagerAdapter should be set to null here, as LeakCanary reports it as leaking.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because the pager adapter is being set to null in that method, and the onDestroyView method is called before onDestroy: https://developer.android.com/guide/fragments/lifecycle.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That logic should be in onDestroyView, since the view gets re-created all the time.

onDestroy happens when the fragment is killed, what does that mean?
onDestroyView is called everytime whilst onDestroy isn't.

You should move it to onDestroyView

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay.

Comment on lines +52 to +53
private var _feedBinding: FragmentFeedBinding? = null
private val feedBinding get() = _feedBinding!!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't you just make them lateinit to prevent having to use asserting getters? Though I have little experience in Kotlin, so I may be wrong ;-)

Copy link
Member Author

@Isira-Seneviratne Isira-Seneviratne Dec 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lateinit variables can't have null assigned to them, and the variable needs to be set to null to allow the binding to be garbage collected: https://developer.android.com/topic/libraries/view-binding#fragments.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I see

} else if (progressState.progressMessage > 0) {
loading_progress_text?.setText(progressState.progressMessage)
_feedBinding?.loadingProgressText?.setText(progressState.progressMessage)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you not using feedBinding.loadingProgressText. here and below?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was getting a NullPointerException when using the non-null feedBinding property.

Comment on lines +73 to +74
private var _binding: FragmentSubscriptionBinding? = null
private val binding get() = _binding!!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here and in other places

@Stypox
Copy link
Member

Stypox commented Dec 21, 2020

@TobiGr didn't we also want to merge this before releasing 0.20.7?

This was referenced Dec 21, 2020
@TobiGr
Copy link
Member

TobiGr commented Dec 21, 2020

Yes, I forgot to add this PR to the "to do" section.

@Stypox
Copy link
Member

Stypox commented Dec 22, 2020

I get this binding leak with your latest apk, I don't know if it is a real leak. The heap dump was captured while I was in the VideoDetailFragment, if that counts anyhow:

┬───
│ GC Root: System class
│
├─ org.schabi.newpipe.player.helper.PlayerHolder class
│    Leaking: NO (VideoDetailFragment↓ is not leaking and a class is never
│    leaking)
│    ↓ static PlayerHolder.listener
├─ org.schabi.newpipe.fragments.detail.VideoDetailFragment instance
│    Leaking: NO (MainFragment↓ is not leaking and Fragment#mFragmentManager is
│    not null)
│    playerService instance of org.schabi.newpipe.player.MainPlayer
│    activity instance of org.schabi.newpipe.MainActivity with mDestroyed =
│    false
│    ↓ VideoDetailFragment.mFragmentManager
├─ androidx.fragment.app.FragmentManagerImpl instance
│    Leaking: NO (MainFragment↓ is not leaking)
│    ↓ FragmentManagerImpl.mBackStack
├─ java.util.ArrayList instance
│    Leaking: NO (MainFragment↓ is not leaking)
│    ↓ ArrayList.elementData
├─ java.lang.Object[] array
│    Leaking: NO (MainFragment↓ is not leaking)
│    ↓ Object[].[0]
├─ androidx.fragment.app.BackStackRecord instance
│    Leaking: NO (MainFragment↓ is not leaking)
│    ↓ BackStackRecord.mOps
├─ java.util.ArrayList instance
│    Leaking: NO (MainFragment↓ is not leaking)
│    ↓ ArrayList.elementData
├─ java.lang.Object[] array
│    Leaking: NO (MainFragment↓ is not leaking)
│    ↓ Object[].[0]
├─ androidx.fragment.app.FragmentTransaction$Op instance
│    Leaking: NO (MainFragment↓ is not leaking)
│    ↓ FragmentTransaction$Op.mFragment
├─ org.schabi.newpipe.fragments.MainFragment instance
│    Leaking: NO (Fragment#mFragmentManager is not null)
│    activity instance of org.schabi.newpipe.MainActivity with mDestroyed =
│    false
│    ↓ MainFragment.binding
│                   ~~~~~~~
├─ org.schabi.newpipe.databinding.FragmentMainBinding instance
│    Leaking: UNKNOWN
│    Retaining 30904 bytes in 600 objects
│    ↓ FragmentMainBinding.rootView
│                          ~~~~~~~~
╰→ android.widget.RelativeLayout instance
​     Leaking: YES (ObjectWatcher was watching this because org.schabi.newpipe.
​     fragments.MainFragment received Fragment#onDestroyView() callback
​     (references to its views should be cleared to prevent leaks))
​     Retaining 1537 bytes in 36 objects
​     key = 3ca5677d-b29b-4eb9-9230-dd17bda8f58e
​     watchDurationMillis = 36738
​     retainedDurationMillis = 26725
​     View not part of a window view hierarchy
​     View.mAttachInfo is null (view detached)
​     View.mWindowAttachCount = 1
​     mContext instance of org.schabi.newpipe.MainActivity with mDestroyed =
​     false

METADATA

Build.VERSION.SDK_INT: 24
Build.MANUFACTURER: HUAWEI
LeakCanary version: 2.5
App process name: org.schabi.newpipe.debug.Useviewbindinginfragments
Stats: LruCache[maxSize=3000,hits=5153,misses=68531,hitRate=6%]
RandomAccess[bytes=3893630,reads=68531,travel=72315166573,range=25866324,size=46
366704]
Analysis duration: 11173 ms

@Isira-Seneviratne
Copy link
Member Author

I get this binding leak with your latest apk, I don't know if it is a real leak. The heap dump was captured while I was in the VideoDetailFragment, if that counts anyhow:

┬───
│ GC Root: System class
│
├─ org.schabi.newpipe.player.helper.PlayerHolder class
│    Leaking: NO (VideoDetailFragment↓ is not leaking and a class is never
│    leaking)
│    ↓ static PlayerHolder.listener
├─ org.schabi.newpipe.fragments.detail.VideoDetailFragment instance
│    Leaking: NO (MainFragment↓ is not leaking and Fragment#mFragmentManager is
│    not null)
│    playerService instance of org.schabi.newpipe.player.MainPlayer
│    activity instance of org.schabi.newpipe.MainActivity with mDestroyed =
│    false
│    ↓ VideoDetailFragment.mFragmentManager
├─ androidx.fragment.app.FragmentManagerImpl instance
│    Leaking: NO (MainFragment↓ is not leaking)
│    ↓ FragmentManagerImpl.mBackStack
├─ java.util.ArrayList instance
│    Leaking: NO (MainFragment↓ is not leaking)
│    ↓ ArrayList.elementData
├─ java.lang.Object[] array
│    Leaking: NO (MainFragment↓ is not leaking)
│    ↓ Object[].[0]
├─ androidx.fragment.app.BackStackRecord instance
│    Leaking: NO (MainFragment↓ is not leaking)
│    ↓ BackStackRecord.mOps
├─ java.util.ArrayList instance
│    Leaking: NO (MainFragment↓ is not leaking)
│    ↓ ArrayList.elementData
├─ java.lang.Object[] array
│    Leaking: NO (MainFragment↓ is not leaking)
│    ↓ Object[].[0]
├─ androidx.fragment.app.FragmentTransaction$Op instance
│    Leaking: NO (MainFragment↓ is not leaking)
│    ↓ FragmentTransaction$Op.mFragment
├─ org.schabi.newpipe.fragments.MainFragment instance
│    Leaking: NO (Fragment#mFragmentManager is not null)
│    activity instance of org.schabi.newpipe.MainActivity with mDestroyed =
│    false
│    ↓ MainFragment.binding
│                   ~~~~~~~
├─ org.schabi.newpipe.databinding.FragmentMainBinding instance
│    Leaking: UNKNOWN
│    Retaining 30904 bytes in 600 objects
│    ↓ FragmentMainBinding.rootView
│                          ~~~~~~~~
╰→ android.widget.RelativeLayout instance
​     Leaking: YES (ObjectWatcher was watching this because org.schabi.newpipe.
​     fragments.MainFragment received Fragment#onDestroyView() callback
​     (references to its views should be cleared to prevent leaks))
​     Retaining 1537 bytes in 36 objects
​     key = 3ca5677d-b29b-4eb9-9230-dd17bda8f58e
​     watchDurationMillis = 36738
​     retainedDurationMillis = 26725
​     View not part of a window view hierarchy
​     View.mAttachInfo is null (view detached)
​     View.mWindowAttachCount = 1
​     mContext instance of org.schabi.newpipe.MainActivity with mDestroyed =
​     false

METADATA

Build.VERSION.SDK_INT: 24
Build.MANUFACTURER: HUAWEI
LeakCanary version: 2.5
App process name: org.schabi.newpipe.debug.Useviewbindinginfragments
Stats: LruCache[maxSize=3000,hits=5153,misses=68531,hitRate=6%]
RandomAccess[bytes=3893630,reads=68531,travel=72315166573,range=25866324,size=46
366704]
Analysis duration: 11173 ms

Maybe it's because I moved the null set operation to onDestroyView?

@Stypox
Copy link
Member

Stypox commented Dec 23, 2020

@Isira-Seneviratne the apk I used is from the PR description, so from 4 days ago by looking at the edits, therefore it is before I did the review and you pushed the change so that's not the cause. Could you provide an updated apk?

@FunkyMuse
Copy link

I get this binding leak with your latest apk, I don't know if it is a real leak. The heap dump was captured while I was in the VideoDetailFragment, if that counts anyhow:

┬───
│ GC Root: System class
│
├─ org.schabi.newpipe.player.helper.PlayerHolder class
│    Leaking: NO (VideoDetailFragment↓ is not leaking and a class is never
│    leaking)
│    ↓ static PlayerHolder.listener
├─ org.schabi.newpipe.fragments.detail.VideoDetailFragment instance
│    Leaking: NO (MainFragment↓ is not leaking and Fragment#mFragmentManager is
│    not null)
│    playerService instance of org.schabi.newpipe.player.MainPlayer
│    activity instance of org.schabi.newpipe.MainActivity with mDestroyed =
│    false
│    ↓ VideoDetailFragment.mFragmentManager
├─ androidx.fragment.app.FragmentManagerImpl instance
│    Leaking: NO (MainFragment↓ is not leaking)
│    ↓ FragmentManagerImpl.mBackStack
├─ java.util.ArrayList instance
│    Leaking: NO (MainFragment↓ is not leaking)
│    ↓ ArrayList.elementData
├─ java.lang.Object[] array
│    Leaking: NO (MainFragment↓ is not leaking)
│    ↓ Object[].[0]
├─ androidx.fragment.app.BackStackRecord instance
│    Leaking: NO (MainFragment↓ is not leaking)
│    ↓ BackStackRecord.mOps
├─ java.util.ArrayList instance
│    Leaking: NO (MainFragment↓ is not leaking)
│    ↓ ArrayList.elementData
├─ java.lang.Object[] array
│    Leaking: NO (MainFragment↓ is not leaking)
│    ↓ Object[].[0]
├─ androidx.fragment.app.FragmentTransaction$Op instance
│    Leaking: NO (MainFragment↓ is not leaking)
│    ↓ FragmentTransaction$Op.mFragment
├─ org.schabi.newpipe.fragments.MainFragment instance
│    Leaking: NO (Fragment#mFragmentManager is not null)
│    activity instance of org.schabi.newpipe.MainActivity with mDestroyed =
│    false
│    ↓ MainFragment.binding
│                   ~~~~~~~
├─ org.schabi.newpipe.databinding.FragmentMainBinding instance
│    Leaking: UNKNOWN
│    Retaining 30904 bytes in 600 objects
│    ↓ FragmentMainBinding.rootView
│                          ~~~~~~~~
╰→ android.widget.RelativeLayout instance
​     Leaking: YES (ObjectWatcher was watching this because org.schabi.newpipe.
​     fragments.MainFragment received Fragment#onDestroyView() callback
​     (references to its views should be cleared to prevent leaks))
​     Retaining 1537 bytes in 36 objects
​     key = 3ca5677d-b29b-4eb9-9230-dd17bda8f58e
​     watchDurationMillis = 36738
​     retainedDurationMillis = 26725
​     View not part of a window view hierarchy
​     View.mAttachInfo is null (view detached)
​     View.mWindowAttachCount = 1
​     mContext instance of org.schabi.newpipe.MainActivity with mDestroyed =
​     false

METADATA

Build.VERSION.SDK_INT: 24
Build.MANUFACTURER: HUAWEI
LeakCanary version: 2.5
App process name: org.schabi.newpipe.debug.Useviewbindinginfragments
Stats: LruCache[maxSize=3000,hits=5153,misses=68531,hitRate=6%]
RandomAccess[bytes=3893630,reads=68531,travel=72315166573,range=25866324,size=46
366704]
Analysis duration: 11173 ms

Maybe it's because I moved the null set operation to onDestroyView?

Sometimes LeakCanary has false positives, you can see that it cleary suggests that you should release all your resources in onDestryoView and if you do so then it's confused, but if you aren't doing it, then something's wrong.

@Isira-Seneviratne
Copy link
Member Author

@Isira-Seneviratne the apk I used is from the PR description, so from 4 days ago by looking at the edits, therefore it is before I did the review and you pushed the change so that's not the cause. Could you provide an updated apk?

Sure.

Copy link
Member

@Stypox Stypox left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested again with the apk from the CI and I couldn't reproduce the leak anymore. I also tested more but everything worked well. Thank you :-D

@Stypox Stypox merged commit 94b086d into TeamNewPipe:dev Jan 14, 2021
@Isira-Seneviratne Isira-Seneviratne deleted the Use_view_binding_in_fragments branch January 15, 2021 00:36
This was referenced Jan 18, 2021
tossj pushed a commit to tossj/NewPipe-legacy that referenced this pull request Apr 20, 2021
…e_view_binding_in_fragments

Use view binding in fragments.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants