ARTICLE AD BOX
I have an audio player app, it's contain a PlaylistsFragment that contains 4 cards which navigate to other fragments (FavouritesFragment, RecentlyPlayedFragment, MostPlayedFragment, RecentlyAddedFragment). When I navigate to any of these fragments and press the back button, the PlaylistsFragment appears as a blank / empty screen. However, if I switch to another tab and come back, the fragment displays correctly



This my Current Implementation :
@AndroidEntryPoint class PlaylistsFragment : Fragment(R.layout.fragment_playlists), MenuProvider, PlaylistsAdapter.PlaylistAdapterListener { companion object { private const val TAG = "PlaylistsFragment" const val NEW_PLAYLIST_ID_MARKER = -100L } private var _binding: FragmentPlaylistsBinding? = null private val binding get() = _binding!! private val playlistsViewModel by viewModels<PlaylistsViewModel>() private lateinit var playlistsAdapter: PlaylistsAdapter private val selectedPositions: MutableSet<Int> = mutableSetOf() private var actionMode: ActionMode? = null private var originalPlaylists = arrayListOf<Playlist>() private var mMainActivityUiController: MainActivityUiController? = null private var menu: Menu? = null private var sortType: PlaylistSortType = PlaylistSortType.NAME_ASC private lateinit var requestPermissionLauncher: ActivityResultLauncher<String> private var storagePermissionGranted = false private var menuHost: MenuHost? = null private val recentlyPlayedViewModel by viewModels<RecentlyPlayedViewModel>() private val mostPlayedViewModel by viewModels<MostPlayedViewModel>() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setupPermissionLauncher() } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = FragmentPlaylistsBinding.inflate(inflater, container, false) playlistsAdapter = PlaylistsAdapter(this) menuHost = requireActivity() menuHost?.addMenuProvider(this, viewLifecycleOwner, Lifecycle.State.RESUMED) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) Log.d(TAG, "onViewCreated") binding.playlistsFragment.visibility = View.VISIBLE setupRecyclerView() observePlaylistList() checkPermissionsAndLoadDataIfNeeded() setupMainActivityUiController() observeRecentlyPlayedCount() observeMostPlayedCount() observeFavouriteClipsCount() binding.addNewPlaylistButton.setOnClickListener { CreatePlaylistDialog.show(requireContext()) { playlistName -> Log.d( TAG, "Playlist name entered: $playlistName. Navigating to AddClipsToPlaylistFragment for new playlist." ) val fragment = AddClipsToPlaylistFragment.newInstance(NEW_PLAYLIST_ID_MARKER, playlistName) parentFragmentManager.beginTransaction() .replace(R.id.main, fragment) .addToBackStack(null) .commit() } } binding.cardRecentlyPlayed.setOnClickListener { val recentlyPlayedFragment = RecentlyPlayedFragment() val fragmentManager = requireActivity().supportFragmentManager val transaction = fragmentManager.beginTransaction() transaction.apply { addToBackStack(null) setReorderingAllowed(true) hide(this@PlaylistsFragment) add(R.id.main, recentlyPlayedFragment) commit() } binding.playlistsFragment.visibility = GONE mMainActivityUiController?.hideTabLayout() } binding.cardMostPlayed.setOnClickListener { val mostPlayedFragment = MostPlayedFragment() val fragmentManager = requireActivity().supportFragmentManager val transaction = fragmentManager.beginTransaction() transaction.apply { addToBackStack(null) setReorderingAllowed(true) hide(this@PlaylistsFragment) add(R.id.main, mostPlayedFragment) commit() } binding.playlistsFragment.visibility = GONE mMainActivityUiController?.hideTabLayout() } binding.cardFavorites.setOnClickListener { val favouritesClipsFragment = FavouritesClipsFragment() val fragmentManager = requireActivity().supportFragmentManager val transaction = fragmentManager.beginTransaction() transaction.apply { addToBackStack(null) setReorderingAllowed(true) hide(this@PlaylistsFragment) add(R.id.main, favouritesClipsFragment) commit() } binding.playlistsFragment.visibility = GONE mMainActivityUiController?.hideTabLayout() } } override fun onResume() { super.onResume() Log.d(TAG, "onResume: Fragment resumed.") (activity as? MainActivity)?.getToolbar()?.visibility = View.VISIBLE mMainActivityUiController?.showTabLayout() binding.playlistsFragment.visibility = View.VISIBLE } override fun onDestroyView() { super.onDestroyView() Log.d(TAG, "onDestroyView: Cleaning up.") menuHost?.removeMenuProvider(this) binding.recyclerPlaylists.adapter = null actionMode?.finish() actionMode = null _binding = null } private fun setupMainActivityUiController() { if (activity is MainActivityUiController) { mMainActivityUiController = activity as MainActivityUiController } } private fun observePlaylistList() { viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { Log.d(TAG, "Observer: STARTED collecting playlistList StateFlow.") playlistsViewModel.playlists.collect { resource -> if (!isAdded || _binding == null) { Log.w(TAG, "Observer: Fragment view null or not added. Skipping UI update.") return@collect } Log.d( TAG, "Observer: Collected playlistList resource: ${resource::class.java.simpleName}" ) binding.progressBar.visibility = if (resource is Resource.Loading) View.VISIBLE else GONE val showList = resource is Resource.Success && resource.data.isNotEmpty() val showEmpty = (resource is Resource.Success && resource.data.isEmpty()) || resource is Resource.Empty binding.recyclerPlaylists.visibility = if (showList) View.VISIBLE else View.INVISIBLE binding.emptyView.visibility = if (showEmpty) View.VISIBLE else GONE when (resource) { is Resource.Success -> { val playlists = resource.data originalPlaylists.clear() originalPlaylists.addAll(playlists) playlistsAdapter.submitList(ArrayList(playlists)) { val itemCount = playlistsAdapter.itemCount binding.recyclerPlaylists.visibility = if (itemCount > 0) View.VISIBLE else View.INVISIBLE binding.emptyView.visibility = if (itemCount == 0) View.VISIBLE else GONE binding.emptyView.text = getString(R.string.no_playlists_found) } Log.d( TAG, "Successfully submitted ${playlists.size} playlists to adapter." ) } is Resource.Error -> { binding.emptyView.text = resource.message Log.e(TAG, "Error loading playlists: ${resource.message}") } is Resource.Empty -> { playlistsAdapter.submitList(emptyList()) binding.emptyView.text = getString(R.string.no_playlists_found) } else -> {} } } playlistsViewModel.counts.collect { resource -> if (resource is Resource.Success) { binding.countRecentlyAdded.text = "${resource.data.recentlyAddedCount} الأغاني" binding.countFavorites.text = "${resource.data.favoriteCount} الأغاني" binding.countMostPlayed.text = "${resource.data.mostPlayedCount} الأغاني" } else if (resource is Resource.Empty) { binding.countRecentlyAdded.text = "0 الأغاني" binding.countFavorites.text = "0 الأغاني" binding.countMostPlayed.text = "0 الأغاني" binding.countRecentlyPlayed.text = "0 الأغاني" } } } } } private fun customizeMenuItem(menu: Menu, itemId: Int, title: String) { val menuItem = menu.findItem(itemId) val spannableString = SpannableString(" $title") val drawable = ContextCompat.getDrawable(requireContext(), R.drawable.baseline_arrow_left_24) drawable?.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight) val imageSpan = android.text.style.ImageSpan(drawable!!, android.text.style.ImageSpan.ALIGN_BOTTOM) spannableString.setSpan( imageSpan, 0, 1, android.text.SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE ) menuItem.title = spannableString } override fun onPlaylistClicked(playlist: Playlist) { Log.d(TAG, "Playlist clicked: ${playlist.name}") = val playlistDetailsFragment = PlaylistDetailsFragment().apply { arguments = Bundle().apply { putParcelable("playlist", playlist) } } val fragmentManager = requireActivity().supportFragmentManager val transaction = fragmentManager.beginTransaction() transaction.apply { addToBackStack(null) setReorderingAllowed(true) hide(this@PlaylistsFragment) add(R.id.main, playlistDetailsFragment, "playlistFragment") commit() } binding.playlistsFragment.visibility = GONE mMainActivityUiController?.hideTabLayout() } override fun onPlaylistMoreOptionsClicked(playlist: Playlist, anchorView: View) { showBottomSheet(playlist, anchorView) } override fun onItemSelectionChanged(position: Int, isSelected: Boolean) { Log.d(TAG, "Playlist selection changed: Pos $position, isSelected=$isSelected") if (isSelected) selectedPositions.add(position) else selectedPositions.remove(position) updateActionModeTitle() } override fun onStartSelectionMode(position: Int) { TODO("Not yet implemented") } @SuppressLint("MissingInflatedId") private fun showBottomSheet(playlist: Playlist, anchorView: View) { val bottomSheetDialog = android.app.AlertDialog.Builder(requireContext()) .setTitle(playlist.name) .setItems(arrayOf("Play", "Play Next", "Add to Queue", "Delete")) { _, which -> when (which) { 0 -> Toast.makeText( requireContext(), "Play ${playlist.name}", Toast.LENGTH_SHORT ).show() 1 -> Toast.makeText( requireContext(), "Play Next ${playlist.name}", Toast.LENGTH_SHORT ).show() 2 -> Toast.makeText( requireContext(), "Add to Queue ${playlist.name}", Toast.LENGTH_SHORT ).show() 3 -> showDeletePlaylistConfirmationDialog(playlist) } } .create() bottomSheetDialog.show() } private fun showDeletePlaylistConfirmationDialog(playlist: Playlist) { MaterialAlertDialogBuilder(requireContext()) .setTitle(getString(R.string.delete_playlist_title)) .setMessage(getString(R.string.delete_playlist_confirmation_message, playlist.name)) .setPositiveButton(R.string.delete) { _, _ -> playlistsViewModel.deletePlaylist(playlist.id) Toast.makeText(requireContext(), "Deleted ${playlist.name}", Toast.LENGTH_SHORT) .show() } .setNegativeButton(R.string.cancel, null) .show() } class CreatePlaylistDialog { companion object { @JvmStatic fun show(context: Context, onPlaylistCreated: (String) -> Unit) { val dialog = Dialog(context) dialog.requestWindowFeature(Window.FEATURE_NO_TITLE) dialog.setContentView(R.layout.dialog_create_playlist) // Set dialog properties dialog.window?.setBackgroundDrawable(Color.TRANSPARENT.toDrawable()) dialog.window?.setLayout( (context.resources.displayMetrics.widthPixels * 0.9).toInt(), ViewGroup.LayoutParams.WRAP_CONTENT ) val editTextPlaylistName = dialog.findViewById<EditText>(R.id.editTextPlaylistName) val btnCreatePlaylist = dialog.findViewById<Button>(R.id.btnCreatePlaylist) val btnCancel = dialog.findViewById<Button>(R.id.btnCancel) // Set focus and show keyboard editTextPlaylistName.requestFocus() val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0) btnCreatePlaylist.setOnClickListener { val playlistName = editTextPlaylistName.text.toString().trim() if (playlistName.isNotEmpty()) { onPlaylistCreated(playlistName) dialog.dismiss() } else { editTextPlaylistName.error = "يرجى إدخال اسم قائمة التشغيل" } } btnCancel.setOnClickListener { dialog.dismiss() } dialog.setOnDismissListener { imm.hideSoftInputFromWindow(editTextPlaylistName.windowToken, 0) } dialog.show() } } } }What I've Tried So far:
Using replace() instead of hide()/add(): This caused fragments to overlap with each other, showing content from multiple fragments simultaneously
Adding onHiddenChanged() callback:
override fun onHiddenChanged(hidden: Boolean) { super.onHiddenChanged(hidden) if (!hidden) { _binding?.let { it.playlistsFragment.visibility = View.VISIBLE it.root.bringToFront() } (activity as? MainActivity)?.getToolbar()?.visibility = View.VISIBLE mMainActivityUiController?.showTabLayout() } } Manually removing old fragments before navigation: private fun navigateToFragment(fragment: Fragment) { val fragmentManager = requireActivity().supportFragmentManager fragmentManager.fragments.forEach { existingFragment -> if (existingFragment != this && existingFragment !is PlaylistsFragment && existingFragment.isAdded) { fragmentManager.beginTransaction() .remove(existingFragment) .commitNow() } } fragmentManager.beginTransaction().apply { setReorderingAllowed(true) hide(this@PlaylistsFragment) add(R.id.main, fragment) addToBackStack(null) commit() } mMainActivityUiController?.hideTabLayout() } Using bringToFront() in onResume and onHiddenChanged: The view appears to be present in the hierarchy but not visible on screenThe Layout of Playlists Fragment
<?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/playlistsFragment" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#1A1A1A" tools:context=".ui.playlists_ui.PlaylistsFragment"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:padding="16dp"> <!-- ProgressBar --> <ProgressBar android:id="@+id/progressBar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:visibility="gone" /> <!-- Empty View for no data or error state --> <TextView android:id="@+id/emptyView" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:text="@string/no_playlists_found" android:textColor="#FFFFFF" android:textSize="16sp" android:visibility="gone" /> <!-- Playlists count --> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="24dp" android:gravity="center_vertical" android:orientation="horizontal" android:layoutDirection="rtl"> <TextView android:id="@+id/playlistsCountText" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="6" android:textColor="#FFFFFF" android:textSize="18sp" android:textStyle="bold" android:textAlignment="textStart" tools:text="6 Playlists" /> <ImageView android:id="@+id/addNewPlaylistButton" android:layout_width="@dimen/_24sdp" android:layout_height="@dimen/_24sdp" android:layout_weight="1" android:background="?selectableItemBackground" android:clickable="true" android:focusable="true" android:src="@drawable/ic_add_48" /> </LinearLayout> <!-- Playlist cards grid --> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"> <!-- Row 1 --> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="16dp" android:orientation="horizontal" android:layoutDirection="ltr"> <!-- Recently Added --> <androidx.cardview.widget.CardView android:id="@+id/card_recently_added" android:layout_width="0dp" android:layout_height="120dp" android:layout_marginStart="8dp" android:layout_marginEnd="16dp" android:layout_weight="1" android:backgroundTint="#2E7D8F" app:cardCornerRadius="12dp" app:cardElevation="0dp"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:padding="16dp" android:layoutDirection="rtl"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="@string/recently_added" android:textColor="#FFFFFF" android:textSize="16sp" android:textStyle="bold" /> <TextView android:id="@+id/countRecentlyAdded" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentStart="true" android:layout_alignParentBottom="true" android:textColor="#B3FFFFFF" android:textSize="12sp" tools:text="94 Songs" /> </RelativeLayout> </androidx.cardview.widget.CardView> <!-- Favorites --> <androidx.cardview.widget.CardView android:id="@+id/card_favorites" android:layout_width="0dp" android:layout_height="120dp" android:layout_marginEnd="8dp" android:layout_weight="1" android:backgroundTint="#B83D8E" app:cardCornerRadius="12dp" app:cardElevation="0dp"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:padding="16dp" android:layoutDirection="rtl"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="@string/my_favorites" android:textColor="#FFFFFF" android:textSize="16sp" android:textStyle="bold" /> <TextView android:id="@+id/countFavorites" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentStart="true" android:layout_alignParentBottom="true" android:textColor="#B3FFFFFF" android:textSize="12sp" tools:text="0 Songs" /> </RelativeLayout> </androidx.cardview.widget.CardView> </LinearLayout> <!-- Row 2 --> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="32dp" android:orientation="horizontal"> <!-- Most Played --> <androidx.cardview.widget.CardView android:id="@+id/card_most_played" android:layout_width="0dp" android:layout_height="120dp" android:layout_marginStart="8dp" android:layout_marginEnd="16dp" android:layout_weight="1" android:backgroundTint="#B8632A" app:cardCornerRadius="12dp" app:cardElevation="0dp"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:padding="16dp" android:layoutDirection="rtl"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="@string/most_played" android:textColor="#FFFFFF" android:textSize="16sp" android:textStyle="bold" /> <TextView android:id="@+id/countMostPlayed" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentStart="true" android:layout_alignParentBottom="true" android:textColor="#B3FFFFFF" android:textSize="12sp" tools:text="0 Songs" /> </RelativeLayout> </androidx.cardview.widget.CardView> <!-- Recently Played --> <androidx.cardview.widget.CardView android:id="@+id/card_recently_played" android:layout_width="0dp" android:layout_height="120dp" android:layout_marginEnd="8dp" android:layout_weight="1" android:backgroundTint="#4A5D8A" app:cardCornerRadius="12dp" app:cardElevation="0dp"> <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" android:padding="16dp" android:layoutDirection="rtl"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:text="@string/recently_played" android:textColor="#FFFFFF" android:textSize="16sp" android:textStyle="bold" /> <TextView android:id="@+id/countRecentlyPlayed" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentStart="true" android:layout_alignParentBottom="true" android:textColor="#B3FFFFFF" android:textSize="12sp" tools:text="0 Songs" /> </RelativeLayout> </androidx.cardview.widget.CardView> </LinearLayout> </LinearLayout> <!-- My playlists title --> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="16dp" android:text="@string/my_playlists" android:textColor="#FFFFFF" android:textSize="18sp" android:textStyle="bold" /> <!-- RecyclerView --> <androidx.recyclerview.widget.RecyclerView android:id="@+id/recycler_playlists" android:layout_width="match_parent" android:layout_height="match_parent" android:nestedScrollingEnabled="false" tools:listitem="@layout/item_playlist" /> </LinearLayout> </ScrollView>So, what is the correct approach to ensure the fragment is visible when navigating back from the backstack without causing overlap with the child navigation's content when navigating to them?
