Free fragment view bindings when their fragments are destroyed.

This commit is contained in:
Isira Seneviratne 2023-04-08 07:07:48 +05:30
parent f3df9f7eb9
commit cea8d0062c
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 import kotlin.math.abs
class AudioPlayerFragment : Fragment(), AudioPlayerOptions { 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 lateinit var audioHelper: AudioHelper
private val mainActivity get() = context as MainActivity private val mainActivity get() = context as MainActivity
private val viewModel: PlayerViewModel by activityViewModels() private val viewModel: PlayerViewModel by activityViewModels()
@ -84,7 +86,7 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentAudioPlayerBinding.inflate(layoutInflater) _binding = FragmentAudioPlayerBinding.inflate(inflater)
return binding.root return binding.root
} }
@ -183,8 +185,6 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
mainActivity.supportFragmentManager.commit { mainActivity.supportFragmentManager.commit {
remove(this@AudioPlayerFragment) remove(this@AudioPlayerFragment)
} }
onDestroy()
} }
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
@ -277,6 +277,7 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
* Update the position, duration and text views belonging to the seek bar * Update the position, duration and text views belonging to the seek bar
*/ */
private fun updateSeekBar() { private fun updateSeekBar() {
val binding = _binding ?: return
val duration = playerService?.getDuration()?.takeIf { it > 0 } ?: let { val duration = playerService?.getDuration()?.takeIf { it > 0 } ?: let {
// if there's no duration available, clear everything // if there's no duration available, clear everything
binding.timeBar.value = 0f binding.timeBar.value = 0f
@ -313,6 +314,11 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
initializeSeekBar() initializeSeekBar()
} }
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onDestroy() { override fun onDestroy() {
// unregister all listeners and the connected [playerService] // unregister all listeners and the connected [playerService]
playerService?.onIsPlayingChanged = null playerService?.onIsPlayingChanged = null

View File

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

View File

@ -13,7 +13,9 @@ import com.github.libretube.ui.adapters.CommentsAdapter
import com.github.libretube.ui.models.CommentsViewModel import com.github.libretube.ui.models.CommentsViewModel
class CommentsMainFragment : Fragment() { class CommentsMainFragment : Fragment() {
private lateinit var binding: FragmentCommentsBinding private var _binding: FragmentCommentsBinding? = null
private val binding get() = _binding!!
private lateinit var commentsAdapter: CommentsAdapter private lateinit var commentsAdapter: CommentsAdapter
private val viewModel: CommentsViewModel by activityViewModels() private val viewModel: CommentsViewModel by activityViewModels()
@ -23,7 +25,7 @@ class CommentsMainFragment : Fragment() {
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentCommentsBinding.inflate(inflater, container, false) _binding = FragmentCommentsBinding.inflate(inflater, container, false)
return binding.root return binding.root
} }
@ -33,9 +35,8 @@ class CommentsMainFragment : Fragment() {
binding.commentsRV.layoutManager = LinearLayoutManager(requireContext()) binding.commentsRV.layoutManager = LinearLayoutManager(requireContext())
binding.commentsRV.setItemViewCacheSize(20) binding.commentsRV.setItemViewCacheSize(20)
binding.commentsRV.viewTreeObserver binding.commentsRV.viewTreeObserver.addOnScrollChangedListener {
.addOnScrollChangedListener { if (_binding?.commentsRV?.canScrollVertically(1) == false) {
if (!binding.commentsRV.canScrollVertically(1)) {
viewModel.fetchNextComments() viewModel.fetchNextComments()
} }
} }
@ -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 import kotlinx.coroutines.withContext
class CommentsRepliesFragment : Fragment() { 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 repliesPage: CommentsPage
private lateinit var repliesAdapter: CommentsAdapter private lateinit var repliesAdapter: CommentsAdapter
private val viewModel: CommentsViewModel by activityViewModels() private val viewModel: CommentsViewModel by activityViewModels()
@ -37,7 +39,7 @@ class CommentsRepliesFragment : Fragment() {
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentCommentsBinding.inflate(inflater, container, false) _binding = FragmentCommentsBinding.inflate(inflater, container, false)
return binding.root return binding.root
} }
@ -63,9 +65,8 @@ class CommentsRepliesFragment : Fragment() {
binding.commentsRV.layoutManager = LinearLayoutManager(context) binding.commentsRV.layoutManager = LinearLayoutManager(context)
binding.commentsRV.adapter = repliesAdapter binding.commentsRV.adapter = repliesAdapter
binding.commentsRV.viewTreeObserver binding.commentsRV.viewTreeObserver.addOnScrollChangedListener {
.addOnScrollChangedListener { if (_binding?.commentsRV?.canScrollVertically(1) == false &&
if (!binding.commentsRV.canScrollVertically(1) &&
::repliesPage.isInitialized && ::repliesPage.isInitialized &&
repliesPage.nextpage != null repliesPage.nextpage != null
) { ) {
@ -78,6 +79,11 @@ class CommentsRepliesFragment : Fragment() {
loadInitialReplies(videoId, comment.repliesPage ?: "", repliesAdapter) loadInitialReplies(videoId, comment.repliesPage ?: "", repliesAdapter)
} }
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private fun loadInitialReplies( private fun loadInitialReplies(
videoId: String, videoId: String,
nextPage: String, nextPage: String,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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