Managing fragments with bottom navigation bar - android

I face problem with changing fragment in my container. I have three navigation: Home, Special offers, Profile. In Home navigation it could be fragment1_1 or fragment2_2 depending on situation. My problem is getting showed fragment from my container. I try to get using findFragmentById, but when I in Profile navigation and try to go to Home my code do not hide() fragment from Pofile. I tried to see the logs and I see that it hides Home and shows Home. My code for navigation:
botNav.setOnNavigationItemSelectedListener {
when(it.itemId){
R.id.act_home -> {
if (!it.isChecked){
val homeFragment = supportFragmentManager.findFragmentByTag("Home")
activeFragment = supportFragmentManager.findFragmentById(R.id.fragment_container)
activeFragment?.let { hideFragment(it) }
showFragment(homeFragment!!)
}
}
R.id.act_profile_info -> {
if (!it.isChecked) {
activeFragment = supportFragmentManager.findFragmentById(R.id.fragment_container)
activeFragment?.let { hideFragment(it) }
showFragment(profileFragment)
}
}
R.id.act_special_offer -> {
if (!it.isChecked) {
activeFragment = supportFragmentManager.findFragmentById(R.id.fragment_container)
activeFragment?.let { hideFragment(it) }
showFragment(specialFragment)
}
}
}
return#setOnNavigationItemSelectedListener true
}
So now I want to understand why it is acting so and how can I get shown fragment from FrameLayout container.
For adding fragments for my navigation I used addFragment() function.

You can try this. Working fine for me tested.
Call this bellow method whenever want to add and show previous existing fragment
/**
* Method for add and replace and set fragment if exist in stack
*/
fun setAndReplaceFragment(fragmentWantToAdd: Fragment, tag: String) {
val manager = supportFragmentManager
val fragmentFind = manager.findFragmentByTag(tag)
if (fragmentFind != null) {
val ft = manager.beginTransaction()
ft.replace(R.id.mFrmContainer, fragmentFind, tag)
ft.addToBackStack(tag)
ft.commit()
} else {
val ft = manager.beginTransaction()
ft.replace(R.id.mFrmContainer, fragmentWantToAdd, tag)
ft.addToBackStack(tag)
ft.commit()
}
}

you can try this one :
//Fragment1 is your new fragment to be shown.
Fragment fragment=new Fragment1();
if (fragment != null) {
FragmentTransaction ft=getSupportFragmentManager().beginTransaction();
//frams is your backup fragment upon on your navigation/new black
fragment(thi will beshown if the Fragment1 is not working)
ft.replace(R.id.frams, fragment);
ft.commit();

Related

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
}
}
}

Fragments Resume: Show and hide transactions don't work

I want to resume android fragments that are in the backstack. When switching between different tabs on bottomnavigation,i don't want the views of fragments to be recreated.
I read this, this,this and some other questions on stackoverflow related to this. Common suggestions are to use show and hide method of fragment transactions but it is not working. Here is my kotlin code:
bottomnavigation.setOnNavigationItemSelectedListener {
item ->
when(item.itemId){
R.id.first_fragment_item -> {
var fragment:Fragment = FirstFragment.newInstance()
replaceFragment(fragment)
return#setOnNavigationItemSelectedListener true
}
R.id.second_fragment_item -> {
var fragment:Fragment = SecondFragment.newInstance()
replaceFragment(fragment)
return#setOnNavigationItemSelectedListener true
}
}
return#setOnNavigationItemSelectedListener false
}
}
fun replaceFragment(fragment:Fragment) {
var fragmentName:String = fragment::class.simpleName!!
if(supportFragmentManager.findFragmentByTag(fragmentName)==null) {
val fragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.fragment_container, fragment, fragmentName)
fragmentTransaction.addToBackStack(fragmentName)
fragmentTransaction.commit()
}
else{
if(fragmentName == "FirstFragment") {
supportFragmentManager.beginTransaction().
hide(supportFragmentManager.findFragmentByTag("SecondFragment"))
.show(supportFragmentManager.findFragmentByTag("FirstFragment"))
.commit()
}
else{
supportFragmentManager.beginTransaction()
.hide(supportFragmentManager.findFragmentByTag("FirstFragment"))
.show(supportFragmentManager.findFragmentByTag("SecondFragment"))
.commit()
}
}
}
The last fragment of backstack is always shown. When i want to hide it and show
the other fragment, that fragment screen becomes white with nothing on it.
My bottomnavigation has four items but for testing purpose i am only using two. Two of fragments are FirstFragment and SecondFragment. I am using v4.app.fragments.
With the help of Kishan Viramgama comment's, i solved my problem. I used "add" method instead of "replace". This is my code for resuming fragments: `
fun replaceFragment(fragment: Fragment) {
var nameOfFragment: String? = fragment::class.simpleName
var fragmentTransaction = supportFragmentManager.beginTransaction()
var fm: FragmentManager = supportFragmentManager
if (supportFragmentManager.findFragmentByTag(nameOfFragment) == null) {
fragmentTransaction.add(R.id.root_fragments, fragment, nameOfFragment) //if fragment is not added to the backstack,add it/ don't use replace because it creates new fragment emptying fragment container
fragmentTransaction.addToBackStack(nameOfFragment)
fragmentTransaction.commit()
} else {
var fragmentToShow:Fragment = supportFragmentManager.findFragmentByTag(nameOfFragment) //if fragment is already in backstack,show it and hide all other fragments.
for (i in 0 until fm.backStackEntryCount) {
var fragmentToHide: Fragment = fm.findFragmentByTag(fm.getBackStackEntryAt(i).name)
if (fragmentToHide::class.simpleName != fragment::class.simpleName)
fragmentTransaction.hide(fragmentToHide)
}
fragmentTransaction.show(fragmentToShow)
fragmentTransaction.commit()
}
}
`

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?

Any code improvement in adding/replacing fragment

I have started to study Kotlin and do not know all the functionality of the language.
The function is used to show the fragment in the FrameLayout. The logic is so that the first time it should always add() the fragment and next times it will replace(). Also in some cases I need to use addToBackStack() and also in the same situations to disable the left-side menu.
fun showFragment(fragment : Fragment,
isReplace: Boolean = true,
backStackTag: String? = null,
isEnabled: Boolean = true)
{
/* Defining fragment transaction */
val fragmentTransaction = supportFragmentManager
.beginTransaction()
/* Select if to replace or add a fragment */
if(isReplace)
fragmentTransaction.replace(R.id.frameLayoutContent, fragment, backStackTag)
else
fragmentTransaction.add(R.id.frameLayoutContent, fragment)
/* Select if to add to back stack */
if(backStackTag != null)
fragmentTransaction.addToBackStack(fragment.javaClass.name)
fragmentTransaction.commit()
enableDrawer(isEnabled)
}
Question: Are there some possible improvements of the function code related to the specifications of Kotlin language to make code cleaner as for now the function looks as a mass.
I have posted a blog about the below answer.
I will write an Extension function to the FragmentManager which accepts a Lambda with Receiver as an argument.
inline fun FragmentManager.inTransaction(func: FragmentTransaction.() -> Unit) {
val fragmentTransaction = beginTransaction()
fragmentTransaction.func()
fragmentTransaction.commit()
}
To add a fragment, we can call like this from the Activity now:
supportFragmentManager.inTransaction {
add(R.id.frameLayoutContent, fragment)
}
The advantage of this is we don't have to call beginTransaction() and commit() every time we add or replace a Fragment now. How many hours have you wasted debugging only to find out that you have missed calling commit() in Java?
Next, I will write Extension functions to AppCompatActivity:
fun AppCompatActivity.addFragment(fragment: Fragment, frameId: Int, backStackTag: String? = null) {
supportFragmentManager.inTransaction {
add(frameId, fragment)
backStackTag?.let { addToBackStack(fragment.javaClass.name) }
}
}
fun AppCompatActivity.replaceFragment(fragment: Fragment, frameId: Int, backStackTag: String? = null) {
supportFragmentManager.inTransaction {
replace(frameId, fragment)
backStackTag?.let { addToBackStack(fragment.javaClass.name) }
}
}
So that now we can add/ replace Fragment from any Activity in a single line, without any additional qualifiers:
addFragment(yourFragment, R.id.frameLayoutContent, "tag")
replaceFragment(yourFragment, R.id.frameLayoutContent, "tag")
I like to use with and let when I have some code related to using a lot some variable and null checks
So I'd do something like this:
fun showFragment(fragment : Fragment,
isReplace: Boolean = true,
backStackTag: String? = null,
isEnabled: Boolean = true)
{
/* Defining fragment transaction */
with(supportFragmentManager.beginTransaction()) {
/* Select if to replace or add a fragment */
if(isReplace)
replace(R.id.frameLayoutContent, fragment, backStackTag)
else
add(R.id.frameLayoutContent, fragment)
/* Select if to add to back stack */
backStackTag?.let { addToBackStack(it) }
commit()
}
enableDrawer(isEnabled)
}
Everything is fine. Probably here
if(backStackTag != null)
fragmentTransaction.addToBackStack(fragment.javaClass.name)
you want to add fragment using backStackTag like this
if(backStackTag != null)
fragmentTransaction.addToBackStack(backStackTag)
You can create an extension function like this:
Import android.support.v4.app.FragmentManager and android.support.v4.app.FragmentTransaction if you wish to make your app compatible with devices running system versions as low as Android 1.6 using supportFragmentManager(which uses the support library) instead of fragmentManager.
Define the following function on the top level, i.e. directly under the package:
inline fun FragmentManager.inTransaction(func: FragmentTransaction.() Unit) {
val fragmentTransaction = beginTransaction()
fragmentTransaction.func()
fragmentTransaction.commit()
}
Call the function from any activity:
supportFragmentManager.inTransaction {
add(R.id.frameLayoutContent, fragment)
}
You can use below code for replace:
fun Fragment.moveAnotherFragment(fragment: Fragment,addToBackStack: Boolean = true, func : Bundle?. () -> Unit = {}) {
fragment.enterTransition =
activity?.supportFragmentManager?.beginTransaction()?.apply {
replace(
R.id.frame, fragment.apply {
enterTransition = Slide(Gravity.END)
exitTransition = Slide(Gravity.START)
arguments.apply(func)
}
)
if (addToBackStack) addToBackStack(null)
commit()
}
}

Remove all fragments from container

Is there a way to remove all fragments which are already added the specific view with its view id?
For example I want to remove all fragments which is added into R.id.fragmentcontainer view.
Its very simple just loop through all the fragments and remove it
for (Fragment fragment : getSupportFragmentManager().getFragments()) {
getSupportFragmentManager().beginTransaction().remove(fragment).commit();
}
But in case of Navigation Drawer be sure to check it, if you try to remove it you will get error.
for (Fragment fragment : getSupportFragmentManager().getFragments()) {
if (fragment instanceof NavigationDrawerFragment) {
continue;
}
else {
getSupportFragmentManager().beginTransaction().remove(fragment).commit();
}
}
Last but very important be sure to check for null before doing any fragment transactions
for (Fragment fragment : getSupportFragmentManager().getFragments()) {
if (fragment instanceof NavigationDrawerFragment) {
continue;
}
else if (fragment != null) {
getSupportFragmentManager().beginTransaction().remove(fragment).commit();
}
}
In case someone is looking for a code in Kotlin:
supportFragmentManager.apply {
for (fragment in fragments) {
beginTransaction().remove(fragment).commit()
}
popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE)
}
It is indeed very simple.
private static void removeAllFragments(FragmentManager fragmentManager) {
while (fragmentManager.getBackStackEntryCount() > 0) {
fragmentManager.popBackStackImmediate();
}
}
More optimized version
There is no need in multiple call of commit so lets call it one time at the end
supportFragmentManager.fragments.let {
if (it.isNotEmpty()) {
supportFragmentManager.beginTransaction().apply {
for (fragment in it) {
remove(fragment)
}
commit()
}
}
}
Use this code
activity?.let {
it.supportFragmentManager.fragments.forEach { fragment ->
it.supportFragmentManager.beginTransaction().remove(fragment).commit()
}
}
Hope it helps.
Thank you.
Save all your fragments in an ArrayList.
Initializing:
List<Fragment> activeCenterFragments = new ArrayList<Fragment>();
Adding fragment to list:
private void addCenterFragments(Fragment fragment) {
fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.empty_center_layout, fragment);
activeCenterFragments.add(fragment);
fragmentTransaction.commit();
}
When you want to remove all them, do the following:
private void removeActiveCenterFragments() {
if (activeCenterFragments.size() > 0) {
fragmentTransaction = fragmentManager.beginTransaction();
for (Fragment activeFragment : activeCenterFragments) {
fragmentTransaction.remove(activeFragment);
}
activeCenterFragments.clear();
fragmentTransaction.commit();
}
}
I have used this method in production for years, and it works like a charm. Let me know if you have any questions.
Try this, hope it helps :D
try {
if(manager.getFragments()!=null){
if(manager.getBackStackEntryCount()>0) {
for (int i = 0; i < manager.getBackStackEntryCount(); i++)
manager.popBackStack();
manager.beginTransaction().remove(getSupportFragmentManager()
.findFragmentById(R.id.main_content))
.commit();
}
}
}catch (Exception e){
e.printStackTrace();
}
As an addition to useful answers I have to say that if you have added few fragments to specific view container, for example:
getSupportFragmentManager()
.beginTransaction()
.add(containerViewId, fragmentA)
.add(containerViewId, fragmentB)
.commit();
and if you need just replace them by one, you can simply call replace without removing fragmentA and fragmentB
getSupportFragmentManager()
.beginTransaction()
.replace(containerViewId, fragment)
.commit();
According to the docs replace removes all fragments before adding new:
Replace an existing fragment that was added to a container. This is
essentially the same as calling remove(Fragment) for all currently
added fragments that were added with the same containerViewId and then
add(int, Fragment, String) with the same arguments given here.
This Kotlin extension does the job:
fun FragmentManager.removeAll(containerId: Int) {
beginTransaction().apply {
fragments.filter {
it.id == containerId
}.forEach {
remove(it)
}
}.commit()
}
Here is much simplier way to clear all fragments from Fragment Container View
view.findViewById(R.id.fragmentcontainer).removeAllViews()
if you are using viewbinding then
binding.fragmentcontainer.removeAllViews()

Categories

Resources