I have multiple fragment, one bottom navigation view on bottom of Main Activity. The problem appears when I want to set selected item on Bottom Navigation View when fragment pop back from stack, setSelectedItemId always trigger OnBackStackChangedListener, thus make loop event. Here are the code
fragmentManager.addOnBackStackChangedListener {
var f : Fragment = fragmentManager.findFragmentById(R.id.frame)
if(f is HomeFragment){
bottomNavigation.selectedItemID = R.id.navigation_home
}
}
I've check from the documentation at developer.android.com and various post on StackOverflow or even forum and I don't find any proper solution for my case.
Any solution? thanks
Related
My project
Single activity pattern with fragments in kotlin.
Navigation component + bottom navigation view together.
There are four tabs(fragments) in bottom navigation view.
My issue is changing each tab in bottom navigation, then each fragment is re-created which due to the app is laggy.
So my target is making only one instance of each fragment there.
What I tried is:
adding app:launchSingleTop="true" for the tab fragment in grap.xml. DOESN'T WORK.
This idea is if the tab fragment can be pop backed then use it directly or create new. But this only works sometimes. Some times the tab fragment does not re-created but some times are!
I think the reason is pop back stack clear it for some time? Not sure.
binding.bottomNavigationView.setOnItemSelectedListener { item: MenuItem ->
if (!navController.popBackStack(item.itemId, false)) {
NavigationUI.onNavDestinationSelected(item , navController)
}
true
}
I used navController.navigate(item.itemId, null, NavOptions.Builder().setPopUpTo(item.itemId, false).build()) to replace NavigationUI.onNavDestinationSelected(item , navController), still doesn't work.
Any idea? thanks!
just add this piece of code to avoid recreation
binding.bottomNavigationView.setOnItemReselectedListener { }
I am making an Android app in Kotlin (following MVVM as much as I can if it matters) and my app structure is as follows:
1 Activity (MainActivity) which contains a FrameLayout and a BottomNavigationView.
The FrameLayout is filled dynamically with Fragments per BottomNavigationView clicks. One Fragment then opens an another fragment on click. That latter fragment is structured as follows:
1 NavigationView (navigationQuestions) and 1 FrameLayout (frameQuestion)
The FrameLayout should change Fragments based on NavigationView clicks.
Those containing fragments contain a textview and a listview.
I have implemented all of the above without many issues.
The problems arise when I need to communicate backwards between the last child fragment and its parent fragment because I need to, based on the item in the listview that's been clicked, change the color of the navigationview text that opened that fragment and change the color of that listview entry. I have tried calling the parentFragment but I can't access it's variables, tried with bundles, but they always seem to be null etc.
Also, I can't seem to maintain the state the last fragment is in when I change to an another one with the navigationview.
I am changing fragments like this:
navigationQuestions.setNavigationItemSelectedListener {
val transaction = this.activity?.supportFragmentManager!!.beginTransaction()
val index : Int = it.title.toString().toInt()-1
transaction.replace(R.id.frameQuestion, fragmentQuestions[index])
transaction.commit()
return#setNavigationItemSelectedListener true
}
fragmentQuestions is a MutableList that I create on the start of the class and fill when I fill the navigationView. The reason I did this is because each time I pressed the navigationView, a new instance of that Fragment was created, which isn't really what I want, so this solves it.
I have tried saving the state of the fragment with various override combinations including onPause(), onInstanceSaved, onViewDestroyed() etc, but my Bundle always remains null.
So, the question is, is there an efficient way to, on listView click, color the navigationView entry that belongs to the parent fragment and keep the current fragment saved so that when I switch to an another navigationview fragment and back, it remains the way it was?
I am using onCreateView in all my Fragment classes and this is the listview onitemclicklistener:
answersList.setOnItemClickListener { parent, view, position, id ->
if(position == question.correct-1) {
(view as TextView).setTextColor(resources.getColor(R.color.greenanswer, null))
parent.isEnabled = false
}
else {
(view as TextView).setTextColor(resources.getColor(R.color.redanswer, null))
parent.isEnabled = false
}
}
I have tried accessing the navigationview with something like
(parent.parent.parent.parent as ViewGroup).get(0)
But I quickly realized that I can't access it that way :(
The look of the navigationview and the framelayout is:
image
Any help & tips? I can provide more detailed code of any part necessary, didn't want to overwhelm the question with code which isn't needed as there is a lot of code.
Thanks in advance :)
you can pass data to the parent fragment by calling
setFragmentResult(
REQUEST_KEY_FRAGMENT,
bundleOf(EXTRA_DATA to "but your data here")
)
and on the parent activity u should listen to the result as following :
setFragmentResultListener(REQUEST_KEY_FRAGMENT) { key, bundle ->
bundle.getString(EXTRA_DATA)?.let {
}
}
you should have this dependency in your project:
implementation "androidx.fragment:fragment-ktx:1.3.0-beta01"
The code is in the Android Studio template.
Android Studio - Create new project - Navigation Drawer Activity
There are an Activity and three Fragments (HomeFragment, GalleryFragment, SlideshowFragment) in the template project. I use an AppBarConfiguration object to manage three Fragment. Now I create a function in each Fragment.
My title may be a bit vague. My problem is how to execute the method in the current fragment by clicking the button in Activity.
Now my solution is as follows.
In each fragment. Get the activity object and find the button. Then set the click method. Here is my Kotlin code.
activity?.findViewById<FloatingActionButton>(R.id.fab)?.setOnClickListener { view ->
myFunction()
}
It works fine. But I think this is not elegant enough. And there are some problems with this. If I only set the click event of one fragment, when I switch to another fragment, the click event just now will be executed after I click the button.
I guess the reason for the above problem is that the click event of the activity is bound by the fragment.
My other solution is as follows.
Create an ActivityViewModel and set a LiveData value. Change value after clicking the button. Get ActivityViewModel in fragment and observe the value in each fragment. Then execute the method when the value is changed
My third solution is as follows.
Get each fragment object and determine whether it is visible. Then perform the function of the visible fragment. Here is my Kotlin code.
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)
var fragment1 = navHostFragment!!.childFragmentManager.fragments[0] as HomeFragment
var fragment2 = navHostFragment!!.childFragmentManager.fragments[1] as GalleryFragment
var fragment3 = navHostFragment!!.childFragmentManager.fragments[2] as SlideshowFragment
if(fragment1.isVisiable){
fragment1.myFunction()
}else if(fragment2.isVisiable){
fragment2.myFunction()
}else if(fragment3.isVisiable){
fragment3.myFunction()
}
But I still think the methods above are not elegant enough.
Can I use the interface to achieve the above functions? And how to achieve it?
I am not a native English speaker. Sorry for my bad English.
Thanks.
Good day. So I've been working around with NavComponent of Jetpack for Android
I've thought that management of BackStack of fragments had to be implemented there already, well in fact it is there but I have faced an issue.
Here is my structure:
I have and entry Activity
I have a NavHost in the activity
I have Bottom Navigation bar in the Activity
For each Bottom Item I am using separate Fragments to navigate through.
Here is the code for the navigation.
bottomNavigationView.setOnNavigationItemSelectedListener {
when (it.itemId) {
R.id.navigation_home -> {
navController.apply {
navigate(R.id.navigation_home)
}
true
}
R.id.navigation_dashboard -> {
navController.apply {
navigate(R.id.dashboardFragment)
}
true
}
R.id.navigation_notifications -> {
true
}
else -> {
false
}
}
}
Never mind the last item.
So the issue is next.
If I try to switch between home and dashboard multiple times, when I press back then the stack surely will start popping all the items included there. So if I move like 6 times it will take me 12 attempts to actually exit the app.
Currently I couldn't find any source where for example the navigate() method will accept some sort of argument to cash my fragments instead of recreating it each time and adding to the BackStack.
So what kind of approach would you suggest?
If I to manage the BackStack manually on each back button pressed, what's the purpose of NavController at all? Just for creating and FORWARD navigation?
I think I'm missing some source in Android's official docs.
Thank you beforehand.
P.S.
using navController.popBackStack() before calling navigate() surely isn't the correct choice.
According to the documentation here :
NavigationUI can also handle bottom navigation. When a user selects a menu item, the NavController calls onNavDestinationSelected() and automatically updates the selected item in the bottom navigation bar.
to do so you have to give your bottom navigation items an ids as same as the corresponding destination in your navigation graph , and then tie you bottom view to the controller like this :
NavHostFragment navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment);
NavController navController = navHostFragment.getNavController();
BottomNavigationView bottomNav = findViewById(R.id.bottom_nav);
NavigationUI.setupWithNavController(bottomNav, navController);
Note : from my personal experience , when the startDestination in the graph , that start by default is not currently in back stack (In my case it was the landing page which i pop it out when going to home fragment) then the app act with weird behavior like this . so make sure the start destination is existed in your back stack on should work fine .
In my application I created a Navigation Drawer which has 5 items, once an item is clicked in the Navigation Drawer, I can correctly check the item and show the new fragment using the fragment manager.
But currently I have three problems:
My application has floating action buttons, that of course perform actions and open new fragments, so I need to handle that "Fragment changing" in the navigation drawer too, setting as checked the current item which corresponds to the fragment showed.
I SORT OF solved this problem, but using a single method that
performs the fragment transaction in the MainActivity and meanwhile
checks the new item. So everytime a Fragment want to call another
Fragment, it uses this function passing the nav_drawer_item_id as a
parameter. I think that this solutions sucks, so if you have a better idea, it would be really welcome!
My biggest problem is that when the user press the Android Back Button, the fragment pops back to previous one (or sometimes I call popback() programmatically because I am done with that specific fragment )
I really don't know how to solve this problem because I can't check the correct item in the navigation drawer once a fragment is popped back, I need to know which fragment it is.
I need to show the new fragment once the navigation drawer is closed in order to respect "Android Rules" ( And because it's nice ).
I sort of solved this problem too, by setting a "nextFragment" inside the navigation drawer onNavigationItemSelectedListener and by showing the new fragment inside the drawer onDrawerClosed. Is it the only solution?
I'm not posting any code because it really doesn't deserve to be seen, so here is some extra info:
I have a single activity which contains the Navigation Drawer and currently handles the fragment transactions.
I have 6 fragments, 5 called from the Navigation Drawer and 1 called only from others UI components, but this one doesn't need to be checked on the drawer of course.
I tought about a solution and it was like "Setting the checked item FROM the fragment that is showed", but I didn't find any way to get the Navigation Drawer from the Fragment in order to set the proper item checked.
Implement FragmentManager.OnBackStackChangedListener in your activity to get notified whenever something is popped off the back stack and sync the navigation drawer from the callback. That should get rid of the problem caused when the back button is pressed.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
.....
getSupportFragmentManager().addOnBackStackChangedListener(this);
}
#Override
public void onBackStackChanged() {
// Get the currently active fragment
Fragment fragment =
getSupportFragmentManager().findFragmentById(R.id.fragments_frame);
// Check which fragment is active, check the
// correct selection and set the title accordingly
if (fragment instanceof FragmentAtPosition0) {
setCheckedAndTitle(0);
} else if (fragment instanceof FragmentAtPosition1) {
setCheckedAndTitle(1);
}
}
private void setCheckedAndTitle(int position) {
MenuItem item = navView.getMenu().getItem(position);
item.setChecked(true);
setTitle(item.getTitle());
}
As for the other two cases that you mentioned, I think the solutions you have mentioned are the preferred ways.