Elements quit responding to visibility changes after switching fragments - android

I am creating an app that holds a recyclerview in a fragment and the main activity uses a bottom navigation bar to switch fragments. In my main fragment, when a recyclerview item is long clicked, an appbar appears with some buttons and checkboxes. When I press the back button, I would like to hide their visibility to return the fragment to its default state.
My issue is that after first starting the app, the items appear from the long click and then hide when the back button is pressed as it should. If I switch to another fragment, and then switch back, the back button still responds but the elements no longer respond to visibility changes.
This is my onBackPressed() function in my MainActivity.kt:
#SuppressLint("RestrictedApi")
override fun onBackPressed() {
btnDelete.visibility = View.GONE
btnDelete.isVisible = false
radioButton.visibility = View.GONE
radioButton.isVisible = false
constrain.visibility = View.GONE
constrain.isVisible = false
radioButton.isChecked = false
radioButton.isSelected = false
fabButton.visibility = View.VISIBLE
fabButton.isVisible = true
(recyclerView_main.adapter as NoteAdapter).hideItems()
}
This is my method of switching fragments in my MainActivity.kt:
if(savedInstanceState == null){
this.supportFragmentManager.beginTransaction()
.replace(R.id.frag_container, selectedFragment).commit()
}
navView.setOnNavigationItemSelectedListener{
for (i in 0 until supportFragmentManager.backStackEntryCount){
this.supportFragmentManager.popBackStack()
}
when(it.itemId){
R.id.nav_home ->{
this.supportFragmentManager.beginTransaction().remove(selectedFragment).commit()
selectedFragment = HomeFragment()
}
R.id.nav_fav ->{
this.supportFragmentManager.beginTransaction().remove(selectedFragment).commit()
selectedFragment = FavoriteFragment()
}
R.id.nav_trash ->{
this.supportFragmentManager.beginTransaction().remove(selectedFragment).commit()
selectedFragment = TrashFragment()
}
}
this.supportFragmentManager.beginTransaction()
.replace(R.id.frag_container, selectedFragment).commit()
true
}

Related

MutableStateFlow is triggered after returning to the app

In my MainActivity I have BottomNavigation. My activity is connected with MainViewModel. When app starts I fetch data from firebase. Until the data is downloaded, app displays ProgressBar and BottomNavigation is hide (view.visibility = GONE). When data has been downloaded I hide ProgressBar and show BottomNavigation with the app's content. It works great.
In another part of the app user can open gallery and choose photo. The problem is when activity with photo to choose has been closed, MutableStateFlow is triggered and bottomNavigation displays again but it should be hide in that specific part(fragment) of the app.
Why my MutableStateFlow is triggered although I don't send to it anything when user come back from gallery activity?
MainActivity (onStart):
private val mainSharedViewModel by viewModel<MainSharedViewModel>()
override fun onStart() {
super.onStart()
lifecycle.addObserver(mainSharedViewModel)
val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottomNavigationView)
val navHostFragment: FragmentContainerView = findViewById(R.id.bottomNavHostFragment)
bottomNavController = navHostFragment.findNavController()
bottomNavigationView.apply {
visibility = View.GONE
setupWithNavController(navHostFragment.findNavController())
}
//the fragment from wchich I open GalleryActivity is hide (else ->)
navHostFragment.findNavController().addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.mainFragment,
R.id.profileFragment,
R.id.homeFragment -> bottomNavigationView.visibility = View.VISIBLE
else -> bottomNavigationView.visibility = View.GONE
}
}
mainSharedViewModel.viewModelScope.launch {
mainSharedViewModel.state.userDataLoadingState.collect {
if (it == UserDataLoading.LOADED) {
bottomNavigationView.visibility = View.VISIBLE
} else {
bottomNavigationView.visibility = View.GONE
}
}
}
}
ViewModel:
class MainSharedViewState {
val userDataLoadingState = MutableStateFlow(UserDataLoading.LOADING) }
enum class UserDataLoading {
LOADING, UNKNOWN_ERROR, NO_CONNECTION_ERROR, LOADED }
When you come back from the gallery, the stateflow value is still set as Loaded, as the Viewmodel has not been cleared (and the activity was set to Stopped, not destroyed. It is still in the back stack.) This is why the bottomNavigationView is visible when you come back.
Although your architecture/solution is not how I would have done it, in your circumstances I guess you could change the value of the MutableStateFlow when the activity's onStop is called. Either that or use a MutableSharedFlow instead with a replayCount of 0 so that there is no value collected (although then, the bottomNavigationView will still be set as Visible if it is visible by default in XML.)
SOLVED:
I've created
val userDataLoadingState = MutableSharedFlow<UserDataLoading>(replay = 0)
and when my ViewModel is created I set
state.userDataLoadingState.emit(UserDataLoading.LOADING)
and I collect data in Activity
lifecycleScope.launch {
mainSharedViewModel.state.userDataLoadingState.collect {
if (it == UserDataLoading.LOADED) {
bottomNavigationView.visibility = View.VISIBLE
} else {
bottomNavigationView.visibility = View.GONE
}
}
}
Now it works great. I don't know why it didn't work before.

BottomNavigationView not selecting item on fragment backstack

I have a small app with on activity and two fragments inside. The fragments are loaded with the BottomNavitationView.
MonitoringFragment gets loaded on the OnCreate in the activity.
I want to add this one to the backstack so when I'm inside the second fragment (ConnectionFragment) and i press back I get to the first fragment. This works fine. However the BotttonNavigationView doesn't get updated (doesn't set the first item as selected when returning from second fragment. picture 3). I assume it doesn't handle this behavior by itself and I have to implement it myself but every attempt I made was unsuccessfull.
Activity code:
Fragment activeFragment = null;
BottomNavigationView bottomNavigationView = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
bottomNavigationView = findViewById(R.id.bottom_navigation);
bottomNavigationView.setOnNavigationItemSelectedListener(item -> {
switch (item.getItemId()) {
case R.id.monitoring:
setCurrentFragment(new MonitoringFragment(), false);
break;
case R.id.connection:
setCurrentFragment(new ConnectionFragment(), true);
break;
}
return true;
});
setCurrentFragment(new MonitoringFragment(), true);
}
private void setCurrentFragment(Fragment fragment, boolean addToBackStack) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.fragment_container, fragment);
if (addToBackStack) {
fragmentTransaction.addToBackStack(null);
}
fragmentTransaction.commit();
activeFragment = fragment;
}
Thanks!
Yes, BottomNavigationView is not setup with fragmentManager. You might set prop select tab by yourself, when a fragment gets resumed.
Or you can use navigation component with BottomNavigationView, those working ok together.
With the second approach when pressing back button will not return you from 2nd fragment to 1st one.
Faced a similar problem, here is how I solved it
I added OnDestinationChangedListener to my navcontroller, and created several arraylists with destination label of fragments in my progect.
When current destination is in one of these fragments, bottom menu button is checked
I hope the code example is clearer than my explanation)
val destListNews = arrayListOf(
"news_fragment",
"NewsViewingFragment"
)
val destListAds = arrayListOf(
"ads_view_fragment",
"ads_fragment",
"AdsAddFragment"
)
val destListPass = arrayListOf(
"pass_fragment",
"pass_creation_fragment",
"PassViewFragment"
)
val destListVotes = arrayListOf(
"VotesListFragment",
"VotesAddFragment",
"ChooseVoteTypeFragment"
)
navController.addOnDestinationChangedListener { controller, destination, arguments ->
when (destination.label) {
in destListNews -> {
bottom_nav.menu.getItem(0).isChecked = true
}
in destListAds -> {
bottom_nav.menu.getItem(1).isChecked = true
}
in destListPass -> {
bottom_nav.menu.getItem(2).isChecked = true
}
in destListVotes -> {
bottom_nav.menu.getItem(3).isChecked = true
}
else -> {
bottom_nav.menu.getItem(4).isChecked = true
}
}
}

How to prevent crashes during quick tapping on bottomNavigationView android?

In my app I have main activity with container and bottomNavigationView with 5 items. At container I can show fragments with information. I can change fragment by clicking on buttons which are placed on bottomnavigation view. I noticed the problem:
when I click on items very quickly I receive or app crashes because my view is looking for view-elements or another problem is that my parent activity toolbar show wrong title/subtitle. For example I can click on item 1 and then quickly tap on item 2 and if I continue tapping toooo quickly I can see that toolbar show content which demand to wrong fragment. I thought that maybe checking fragment visibility will solve this problem but it didn't help me. I tried also to check if fragment is visible from onCreateView at fragment and it didn't help me too. Here is the code of bottomNavigationView listener:
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
val transaction = supportFragmentManager.beginTransaction()
val bundle = Bundle()
when (item.itemId) {
R.id.item_id_1-> {
supportActionBar!!.show()
[....]
transaction.replace(R.id.contentContainerT, jobList).addToBackStack(null).commit()
textSetter("Item_0", resources.getString(R.string.all_jobs) + " " + sp!!.getString("all_jobs", ""))
return#OnNavigationItemSelectedListener true
}
R.id.item_id_2-> {
bottom_navigation_t.menu.findItem(R.id.received_mess).title.chunked(1)
disableShowHideAnimation(supportActionBar!!)
supportActionBar!!.show()
[.....]
textSetter(resources.getString(R.string.title_activity_message_center), resources.getString(R.string.received))
transaction.replace(R.id.contentContainerT, messageList).addToBackStack(null).commit()
return#OnNavigationItemSelectedListener true
}
R.id.item_id_3-> {
disableShowHideAnimation(supportActionBar!!)
supportActionBar!!.hide()
transaction.replace(R.id.contentContainerT, PersonalPage()).addToBackStack(null).commit()
return#OnNavigationItemSelectedListener true
}
R.id.item_id_4-> {
disableShowHideAnimation(supportActionBar!!)
supportActionBar!!.show()
[....]
transaction.replace(R.id.contentContainerT, notepadScr).addToBackStack(null).commit()
return#OnNavigationItemSelectedListener true
}
R.id.item_id_5-> {
disableShowHideAnimation(supportActionBar!!)
supportActionBar!!.hide()
textSetter(resources.getString(R.string.more_bottom_nav), "")
transaction.replace(R.id.contentContainerT, MoreScreenK()).addToBackStack(null).commit()
return#OnNavigationItemSelectedListener true
}
}
false
}
maybe problem at the setter function, I don't know.

BottomNavigationView lags on fragment transaction

The problem
I'm using the BottomNavigationView from the Android Design Support Library on one of my Activities, alongside with Fragments for each navigation item.
Each time I select an item on the bar, I do a fragment transaction, like the snippet below (some parts of the code was removed for brevity):
private var fragmentToSet: Fragment? = null
private val onNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
fragmentToSet = when (item.itemId) {
// Choose fragment based on selection
// ...
}
// ...
supportFragmentManager.beginTransaction()
.replace(R.id.container, fragmentToSet)
.commit()
}
The problem is... The bottom bar animation gets super laggy, and only finishes after the fragment is fully loaded and displayed on the screen.
This issue is not exactly new since it can also happen while using the Navigation Menu, but at least it's possible to solve it by using the DrawerLayout.DrawerListener and do the actual Fragment transaction only after the drawer is closed.
What I've tried so far
I tried to "cache" the fragments, holding their reference to avoid recreating the objects every time (e.g. MyFragment.newInstance()), but that didn't work.
I also tried to use handlers, which kinda solved the problem, but it might lead me to an exception in some cases. Something like the snippet below:
handler.postDelayed({changeFragment(fragmentToSet!!)}, 200)
Is there a way to solve this issue without using handlers (or other async calls), on a similar fashion to this solution while using the Navigation Menu?
I handled this situation by hiding and showing fragments using fragment manager. I wrote a sample code to deal with it as below.
class MainActivity : BaseActivity() {
private val homeFragment = HomeFragment.newInstance()
private val categoryFragment = CategoryFragment.newInstance()
private val searchFragment = SearchFragment.newInstance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
navigation.menu.findItem(R.id.navigation_home).isChecked = true
supportFragmentManager.beginTransaction()
.add(R.id.containerFrameLayout, homeFragment)
.add(R.id.containerFrameLayout, categoryFragment)
.add(R.id.containerFrameLayout, searchFragment)
.commit()
setTabStateFragment(TabState.HOME).commit()
}
override fun onBackPressed() {
if (supportFragmentManager.backStackEntryCount > 0 || !homeFragment.isHidden) {
super.onBackPressed()
} else {
setTabStateFragment(TabState.HOME).commit()
navigation.menu.findItem(R.id.navigation_home).isChecked = true
}
}
private fun setTabStateFragment(state: TabState): FragmentTransaction {
supportFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
val transaction = supportFragmentManager.beginTransaction()
transaction.setCustomAnimations(R.anim.fragment_enter, R.anim.fragment_exit)
when (state) {
TabState.HOME -> {
transaction.show(homeFragment)
transaction.hide(categoryFragment)
transaction.hide(searchFragment)
}
TabState.CATEGORY -> {
transaction.hide(homeFragment)
transaction.show(categoryFragment)
transaction.hide(searchFragment)
}
TabState.SEARCH -> {
transaction.hide(homeFragment)
transaction.hide(categoryFragment)
transaction.show(searchFragment)
}
}
return transaction
}
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_home -> {
setTabStateFragment(TabState.HOME).commit()
return#OnNavigationItemSelectedListener true
}
R.id.navigation_category -> {
setTabStateFragment(TabState.CATEGORY).commit()
return#OnNavigationItemSelectedListener true
}
R.id.navigation_search -> {
setTabStateFragment(TabState.SEARCH).commit()
return#OnNavigationItemSelectedListener true
}
}
false
}
internal enum class TabState {
HOME,
CATEGORY,
SEARCH,
}
}

Two Fragments overlapping each other

I am using a BottomNavigationView for a Tab-Interface in my main activity. in onCreate(), the switchTab method is called with the initial fragment. Tapping the the bottom navigation calls switchTab() for the respective tabs and should hide the current and display the new one. If the fragment was not added to the SupportFragmentManager, it gets added, otherwise it will be shown. Here's my code snippet:
private fun switchTab(fragment: Fragment, tag: String): Boolean {
val currentFragment = supportFragmentManager.fragments.find { it.tag == tag }
val ta = supportFragmentManager.beginTransaction()
if (currentFragment != null) {
ta.hide(currentFragment)
}
if (supportFragmentManager.fragments.contains(fragment)) {
ta.show(fragment)
} else {
ta.add(R.id.contentContainer, fragment, tag)
}
ta.commit()
return true
}
Now the problem is that sometimes two fragments are visible and overlay each other, making the userinterface unusable. How can this happen?

Categories

Resources