Merge pull request #3502 from Isira-Seneviratne/Fragment_bindings

Free fragment view bindings when their fragments are destroyed.
This commit is contained in:
Isira Seneviratne 2023-04-09 09:11:41 +05:30 committed by GitHub
commit 32b130b28d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 143 additions and 70 deletions

View File

@ -41,7 +41,9 @@ import com.github.libretube.util.PlayingQueue
import kotlin.math.abs
class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
private lateinit var binding: FragmentAudioPlayerBinding
private var _binding: FragmentAudioPlayerBinding? = null
private val binding get() = _binding!!
private lateinit var audioHelper: AudioHelper
private val mainActivity get() = context as MainActivity
private val viewModel: PlayerViewModel by activityViewModels()
@ -84,7 +86,7 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentAudioPlayerBinding.inflate(layoutInflater)
_binding = FragmentAudioPlayerBinding.inflate(inflater)
return binding.root
}
@ -183,8 +185,6 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
mainActivity.supportFragmentManager.commit {
remove(this@AudioPlayerFragment)
}
onDestroy()
}
@SuppressLint("ClickableViewAccessibility")
@ -277,6 +277,7 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
* Update the position, duration and text views belonging to the seek bar
*/
private fun updateSeekBar() {
val binding = _binding ?: return
val duration = playerService?.getDuration()?.takeIf { it > 0 } ?: let {
// if there's no duration available, clear everything
binding.timeBar.value = 0f
@ -313,6 +314,11 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
initializeSeekBar()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onDestroy() {
// unregister all listeners and the connected [playerService]
playerService?.onIsPlayingChanged = null

View File

@ -34,7 +34,8 @@ import kotlinx.coroutines.withContext
import retrofit2.HttpException
class ChannelFragment : Fragment() {
private lateinit var binding: FragmentChannelBinding
private var _binding: FragmentChannelBinding? = null
private val binding get() = _binding!!
private var channelId: String? = null
private var channelName: String? = null
@ -67,7 +68,7 @@ class ChannelFragment : Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentChannelBinding.inflate(layoutInflater, container, false)
_binding = FragmentChannelBinding.inflate(inflater, container, false)
return binding.root
}
@ -87,16 +88,20 @@ class ChannelFragment : Fragment() {
refreshChannel()
}
binding.channelScrollView.viewTreeObserver
.addOnScrollChangedListener {
if (!binding.channelScrollView.canScrollVertically(1)) {
try {
onScrollEnd.invoke()
} catch (e: Exception) {
Log.e("tabs failed", e.toString())
}
binding.channelScrollView.viewTreeObserver.addOnScrollChangedListener {
if (_binding?.channelScrollView?.canScrollVertically(1) == false) {
try {
onScrollEnd()
} catch (e: Exception) {
Log.e("tabs failed", e.toString())
}
}
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private fun fetchChannel() {

View File

@ -13,7 +13,9 @@ import com.github.libretube.ui.adapters.CommentsAdapter
import com.github.libretube.ui.models.CommentsViewModel
class CommentsMainFragment : Fragment() {
private lateinit var binding: FragmentCommentsBinding
private var _binding: FragmentCommentsBinding? = null
private val binding get() = _binding!!
private lateinit var commentsAdapter: CommentsAdapter
private val viewModel: CommentsViewModel by activityViewModels()
@ -23,7 +25,7 @@ class CommentsMainFragment : Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentCommentsBinding.inflate(inflater, container, false)
_binding = FragmentCommentsBinding.inflate(inflater, container, false)
return binding.root
}
@ -33,12 +35,11 @@ class CommentsMainFragment : Fragment() {
binding.commentsRV.layoutManager = LinearLayoutManager(requireContext())
binding.commentsRV.setItemViewCacheSize(20)
binding.commentsRV.viewTreeObserver
.addOnScrollChangedListener {
if (!binding.commentsRV.canScrollVertically(1)) {
viewModel.fetchNextComments()
}
binding.commentsRV.viewTreeObserver.addOnScrollChangedListener {
if (_binding?.commentsRV?.canScrollVertically(1) == false) {
viewModel.fetchNextComments()
}
}
commentsAdapter = CommentsAdapter(
this,
@ -74,4 +75,9 @@ class CommentsMainFragment : Fragment() {
)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@ -25,7 +25,9 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class CommentsRepliesFragment : Fragment() {
private lateinit var binding: FragmentCommentsBinding
private var _binding: FragmentCommentsBinding? = null
private val binding get() = _binding!!
private lateinit var repliesPage: CommentsPage
private lateinit var repliesAdapter: CommentsAdapter
private val viewModel: CommentsViewModel by activityViewModels()
@ -37,7 +39,7 @@ class CommentsRepliesFragment : Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentCommentsBinding.inflate(inflater, container, false)
_binding = FragmentCommentsBinding.inflate(inflater, container, false)
return binding.root
}
@ -63,21 +65,25 @@ class CommentsRepliesFragment : Fragment() {
binding.commentsRV.layoutManager = LinearLayoutManager(context)
binding.commentsRV.adapter = repliesAdapter
binding.commentsRV.viewTreeObserver
.addOnScrollChangedListener {
if (!binding.commentsRV.canScrollVertically(1) &&
::repliesPage.isInitialized &&
repliesPage.nextpage != null
) {
fetchReplies(videoId, repliesPage.nextpage!!) {
repliesAdapter.updateItems(repliesPage.comments)
}
binding.commentsRV.viewTreeObserver.addOnScrollChangedListener {
if (_binding?.commentsRV?.canScrollVertically(1) == false &&
::repliesPage.isInitialized &&
repliesPage.nextpage != null
) {
fetchReplies(videoId, repliesPage.nextpage!!) {
repliesAdapter.updateItems(repliesPage.comments)
}
}
}
loadInitialReplies(videoId, comment.repliesPage ?: "", repliesAdapter)
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private fun loadInitialReplies(
videoId: String,
nextPage: String,

View File

@ -32,7 +32,9 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
class DownloadsFragment : Fragment() {
private lateinit var binding: FragmentDownloadsBinding
private var _binding: FragmentDownloadsBinding? = null
private val binding get() = _binding!!
private var binder: DownloadService.LocalBinder? = null
private val downloads = mutableListOf<DownloadWithItems>()
private val downloadReceiver = DownloadReceiver()
@ -63,7 +65,7 @@ class DownloadsFragment : Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentDownloadsBinding.inflate(layoutInflater)
_binding = FragmentDownloadsBinding.inflate(inflater)
return binding.root
}
@ -185,4 +187,9 @@ class DownloadsFragment : Fragment() {
context?.unbindService(serviceConnection)
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

View File

@ -28,7 +28,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class HomeFragment : Fragment() {
private lateinit var binding: FragmentHomeBinding
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
private val subscriptionsViewModel: SubscriptionsViewModel by activityViewModels()
override fun onCreateView(
@ -36,7 +38,7 @@ class HomeFragment : Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentHomeBinding.inflate(layoutInflater, container, false)
_binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root
}
@ -67,6 +69,11 @@ class HomeFragment : Fragment() {
fetchHomeFeed()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private fun fetchHomeFeed() {
lifecycleScope.launchWhenCreated {
loadTrending()
@ -85,6 +92,7 @@ class HomeFragment : Fragment() {
RetrofitInstance.api.getTrending(region).take(10)
}
}.getOrNull()?.takeIf { it.isNotEmpty() } ?: return
val binding = _binding ?: return
makeVisible(binding.trendingRV, binding.trendingTV)
binding.trendingRV.layoutManager = GridLayoutManager(context, 2)
@ -106,6 +114,7 @@ class HomeFragment : Fragment() {
}
}.getOrNull()?.takeIf { it.isNotEmpty() } ?: return
}.take(20)
val binding = _binding ?: return
makeVisible(binding.featuredRV, binding.featuredTV)
binding.featuredRV.layoutManager = LinearLayoutManager(
@ -165,6 +174,7 @@ class HomeFragment : Fragment() {
views.forEach {
it.visibility = View.VISIBLE
}
val binding = _binding ?: return
binding.progress.visibility = View.GONE
binding.scroll.visibility = View.VISIBLE
binding.refresh.isRefreshing = false

View File

@ -33,7 +33,9 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class LibraryFragment : Fragment() {
private lateinit var binding: FragmentLibraryBinding
private var _binding: FragmentLibraryBinding? = null
private val binding get() = _binding!!
private val playerViewModel: PlayerViewModel by activityViewModels()
override fun onCreateView(
@ -41,7 +43,7 @@ class LibraryFragment : Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentLibraryBinding.inflate(layoutInflater, container, false)
_binding = FragmentLibraryBinding.inflate(inflater, container, false)
return binding.root
}
@ -92,6 +94,11 @@ class LibraryFragment : Fragment() {
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private fun initBookmarks() {
lifecycleScope.launch {
val bookmarks = withContext(Dispatchers.IO) {

View File

@ -39,7 +39,8 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
class PlaylistFragment : Fragment() {
private lateinit var binding: FragmentPlaylistBinding
private var _binding: FragmentPlaylistBinding? = null
private val binding get() = _binding!!
// general playlist information
private var playlistId: String? = null
@ -69,7 +70,7 @@ class PlaylistFragment : Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentPlaylistBinding.inflate(layoutInflater, container, false)
_binding = FragmentPlaylistBinding.inflate(inflater, container, false)
return binding.root
}
@ -95,6 +96,11 @@ class PlaylistFragment : Fragment() {
fetchPlaylist()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private fun updateBookmarkRes() {
binding.bookmark.setIconResource(
if (isBookmarked) R.drawable.ic_bookmark else R.drawable.ic_bookmark_outlined
@ -211,21 +217,20 @@ class PlaylistFragment : Fragment() {
})
binding.playlistRecView.adapter = playlistAdapter
binding.playlistScrollview.viewTreeObserver
.addOnScrollChangedListener {
if (!binding.playlistScrollview.canScrollVertically(1)) {
if (isLoading) return@addOnScrollChangedListener
// append more playlists to the recycler view
if (playlistType != PlaylistType.PUBLIC) {
isLoading = true
playlistAdapter?.showMoreItems()
isLoading = false
} else {
fetchNextPage()
}
binding.playlistScrollview.viewTreeObserver.addOnScrollChangedListener {
if (_binding?.playlistScrollview?.canScrollVertically(1) == false &&
!isLoading
) {
// append more playlists to the recycler view
if (playlistType != PlaylistType.PUBLIC) {
isLoading = true
playlistAdapter?.showMoreItems()
isLoading = false
} else {
fetchNextPage()
}
}
}
// listener for swiping to the left or right
if (playlistType != PlaylistType.PUBLIC) {

View File

@ -39,7 +39,7 @@ class SearchFragment : Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentSearchBinding.inflate(layoutInflater)
_binding = FragmentSearchBinding.inflate(inflater)
return binding.root
}

View File

@ -34,7 +34,9 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
class SubscriptionsFragment : Fragment() {
private lateinit var binding: FragmentSubscriptionsBinding
private var _binding: FragmentSubscriptionsBinding? = null
private val binding get() = _binding!!
private val viewModel: SubscriptionsViewModel by activityViewModels()
private var channelGroups: List<SubscriptionGroup> = listOf()
private var selectedFilterGroup: Int = 0
@ -56,7 +58,7 @@ class SubscriptionsFragment : Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentSubscriptionsBinding.inflate(layoutInflater, container, false)
_binding = FragmentSubscriptionsBinding.inflate(inflater, container, false)
return binding.root
}
@ -147,22 +149,27 @@ class SubscriptionsFragment : Fragment() {
}
}
binding.scrollviewSub.viewTreeObserver
.addOnScrollChangedListener {
if (!binding.scrollviewSub.canScrollVertically(1)) {
// scroll view is at bottom
if (viewModel.videoFeed.value == null) return@addOnScrollChangedListener
binding.subRefresh.isRefreshing = true
subscriptionsAdapter?.updateItems()
binding.subRefresh.isRefreshing = false
}
binding.scrollviewSub.viewTreeObserver.addOnScrollChangedListener {
val binding = _binding
if (binding?.scrollviewSub?.canScrollVertically(1) == false &&
viewModel.videoFeed.value != null // scroll view is at bottom
) {
binding.subRefresh.isRefreshing = true
subscriptionsAdapter?.updateItems()
binding.subRefresh.isRefreshing = false
}
}
lifecycleScope.launch {
initChannelGroups()
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
@SuppressLint("InflateParams")
private suspend fun initChannelGroups() {
channelGroups = DatabaseHolder.Database.subscriptionGroupsDao().getAll()

View File

@ -23,14 +23,15 @@ import kotlinx.coroutines.withContext
import retrofit2.HttpException
class TrendsFragment : Fragment() {
private lateinit var binding: FragmentTrendsBinding
private var _binding: FragmentTrendsBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentTrendsBinding.inflate(layoutInflater, container, false)
_binding = FragmentTrendsBinding.inflate(inflater, container, false)
return binding.root
}
@ -44,6 +45,11 @@ class TrendsFragment : Fragment() {
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private fun fetchTrending() {
lifecycleScope.launchWhenCreated {
val response = try {

View File

@ -30,7 +30,8 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
class WatchHistoryFragment : Fragment() {
private lateinit var binding: FragmentWatchHistoryBinding
private var _binding: FragmentWatchHistoryBinding? = null
private val binding get() = _binding!!
private val playerViewModel: PlayerViewModel by activityViewModels()
private var isLoading = false
@ -40,7 +41,7 @@ class WatchHistoryFragment : Fragment() {
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentWatchHistoryBinding.inflate(layoutInflater, container, false)
_binding = FragmentWatchHistoryBinding.inflate(inflater, container, false)
return binding.root
}
@ -149,7 +150,9 @@ class WatchHistoryFragment : Fragment() {
// add a listener for scroll end, delay needed to prevent loading new ones the first time
Handler(Looper.getMainLooper()).postDelayed(200) {
binding.historyScrollView.viewTreeObserver.addOnScrollChangedListener {
if (!binding.historyScrollView.canScrollVertically(1) && !isLoading) {
if (_binding?.historyScrollView?.canScrollVertically(1) == false &&
!isLoading
) {
isLoading = true
watchHistoryAdapter.showMoreItems()
isLoading = false
@ -157,4 +160,9 @@ class WatchHistoryFragment : Fragment() {
}
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}