Stop fragment refresh in bottom nav using navhost

后端 未结 8 1663
夕颜
夕颜 2020-12-31 12:14

This problem has been asked a few times now, but we are in 2020 now, did anyone find a good usable solution to this yet?

I want to be able to navigate using the bott

相关标签:
8条回答
  • 2020-12-31 12:34

    Try this:

    public class MainActivity extends AppCompatActivity {
    
    
        final Fragment fragment1 = new HomeFragment();
        final Fragment fragment2 = new DashboardFragment();
        final Fragment fragment3 = new NotificationsFragment();
        final FragmentManager fm = getSupportFragmentManager();
        Fragment active = fragment1;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
            setSupportActionBar(toolbar);
    
    
            BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
            navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
    
            fm.beginTransaction().add(R.id.main_container, fragment3, "3").hide(fragment3).commit();
            fm.beginTransaction().add(R.id.main_container, fragment2, "2").hide(fragment2).commit();
            fm.beginTransaction().add(R.id.main_container,fragment1, "1").commit();
    
        }
    
    
        private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
                = new BottomNavigationView.OnNavigationItemSelectedListener() {
    
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                switch (item.getItemId()) {
                    case R.id.navigation_home:
                        fm.beginTransaction().hide(active).show(fragment1).commit();
                        active = fragment1;
                        return true;
    
                    case R.id.navigation_dashboard:
                        fm.beginTransaction().hide(active).show(fragment2).commit();
                        active = fragment2;
                        return true;
    
                    case R.id.navigation_notifications:
                        fm.beginTransaction().hide(active).show(fragment3).commit();
                        active = fragment3;
                        return true;
                }
                return false;
            }
        };
    
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            getMenuInflater().inflate(R.menu.main_menu, menu);
            return super.onCreateOptionsMenu(menu);
        }
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            int id = item.getItemId();
    
            if (id == R.id.action_settings) {
                startActivity(new Intent(MainActivity.this, SettingsActivity.class));
                return true;
            }
    
            return super.onOptionsItemSelected(item);
        }
    
    
    }
    
    0 讨论(0)
  • 2020-12-31 12:36

    Use this snippet:

    private fun attachFragment(fragmentTag: String) {
        val fragmentTransaction = supportFragmentManager.beginTransaction()
        supportFragmentManager.findFragmentByTag(fragmentTag)?.let {
            if (supportFragmentManager.backStackEntryCount == 0) return
            val currentFragmentTag = supportFragmentManager.getBackStackEntryAt(supportFragmentManager.backStackEntryCount - 1).name
            (supportFragmentManager.findFragmentByTag(currentFragmentTag) as? FragmentBase)?.let { curFrag ->
                fragmentTransaction.hide(curFrag)
            }
            fragmentTransaction.show(it)
        } ?: run {
            when (fragmentTag) {
                FragmentHome.TAG -> FragmentBase.newInstance<FragmentHome>()
                FragmentAccount.TAG -> FragmentBase.newInstance<FragmentAccount>()
                else -> null
            }?.let {
                fragmentTransaction.add(R.id.container, it, fragmentTag)
                fragmentTransaction.addToBackStack(fragmentTag)
            }
        }
        fragmentTransaction.commit()
    }
    

    You can use this pass the tag of specific fragment that you want to show now, using method attachFragment(FragmentHome.TAG)

    0 讨论(0)
  • 2020-12-31 12:40

    If you are using Jetpack, the easiest way to solve this is using ViewModel

    You have to save all valuable data and not make unnecessary database loads or network calls everytime you go to a fragment from another.

    UI controllers such as activities and fragments are primarily intended to display UI data, react to user actions, or handle operating system communication, such as permission requests.

    Here is when we use ViewModels

    ViewModel objects are automatically retained during configuration changes so that data they hold is immediately available to the next activity or fragment instance.

    So if the fragment is recreated, all your data will be there instantly instead of make another call to database or network. Its important to know that if the activity or fragment that holds the ViewModel is reacreated, you will receive the same ViewModel instance created before.

    But in this case you have to specify the ViewModel to have activity scope instead of fragment scope, independently if you are using a shared ViewModel for all the fragments, or a different ViewModel for every fragment.

    Here is a little example using LiveData too:

    //Using KTX
    val model by activityViewModels<MyViewModel>()
    model.getData().observe(viewLifecycleOwner, Observer<DataModel>{ data ->
            // update UI
        })
    
    //Not using KTX
    val model by lazy {ViewModelProvider(activity as ViewModelStoreOwner)[MyViewModel::class.java]}
    model.getData().observe(viewLifecycleOwner, Observer<DataModel>{ data ->
            // update UI
        })
    

    And that's it! Google is actively working on multiple back stack support for bottom tab Navigation and claim that it'll arrive on Navigation 2.4.0 as said here and on this issue tracker if you want and/or your problem is more related to multiple back stack, you can check out those links

    Remember fragments still be recreated, usually you don't change component behavior, instead, you adapt your data to them!

    I leave you some useful links:

    ViewModel Overview Android Developers

    How to communicate between fragments and activities with ViewModels - on Medium

    Restoring UI State using ViewModels - on Medium

    0 讨论(0)
  • 2020-12-31 12:40

    If you use NavigationUI.setupWithNavController(), the NavOptions are defined for you with NavigationUI.onNavDestinationSelected(). These options include launchSingleTop and, if the menu item is not secondary, a popUpTo the root of the graph.

    The problem is, that launchSingleTop still replaces the top fragment with a new one. To resolve this issue, you'd have to create your own setupWithNavController() and onNavDestinationSelected() functions. In onNavDestinationSelected() you'd just adapt the NavOptions to your needs.

    0 讨论(0)
  • 2020-12-31 12:41

    Kotlin 2020 Google's Recommended Solution

    Many of these solutions call the Fragment constructor in the Main Activity. However, following Google's recommended pattern, this is not needed.

    Setup Navigation Graph Tabs

    Firstly create a navigation graph xml for each of your tabs under the res/navigation directory.

    Filename: tab0.xml

    <?xml version="1.0" encoding="utf-8"?>
    <navigation 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/tab0"
        app:startDestination="@id/fragmentA"
        tools:ignore="UnusedNavigation">
    
        <fragment
            android:id="@+id/fragmentA"
            android:label="@string/fragment_A_title"
            android:name="com.app.subdomain.fragA"
        >
        </fragment>
    </navigation>
    

    Repeat the above template for your other tabs. Important all fragments and the navigation graph has an id (e.g. @+id/tab0, @+id/fragmentA).

    Setup Bottom Navigation View

    Ensure the navigation ids are the same as the ones specified on the bottom menu xml.

    <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android">
    
        <item android:title="@string/fragment_A_title"
            android:id="@+id/tab0"
            android:icon="@drawable/ic_baseline_book_24"/>
    
        <item android:title="@string/fragment_B_title"
            android:id="@+id/tab1"
            android:icon="@drawable/ic_baseline_add_alert_24"/>
    
        <item android:title="@string/fragment_C_title"
            android:id="@+id/tab2"
            android:icon="@drawable/ic_baseline_book_24"/>
    
        <item android:title="@string/fragment_D_title"
            android:id="@+id/tab3"
            android:icon="@drawable/ic_baseline_more_horiz_24"/>
    
    </menu>
    

    Setup Activity Main XML

    Ensure FragmentContainerView is being used and not <fragment and do not set the app:navGraph attribute. This will set later in code

    
    <androidx.fragment.app.FragmentContainerView
          android:id="@+id/fragmentContainerView"
          android:name="androidx.navigation.fragment.NavHostFragment"
          android:layout_width="0dp"
          android:layout_height="0dp"
          app:defaultNavHost="true"
          app:layout_constraintBottom_toTopOf="@+id/bottomNavigationView"
          app:layout_constraintEnd_toEndOf="parent"
          app:layout_constraintStart_toStartOf="parent"
          app:layout_constraintTop_toBottomOf="@id/main_toolbar"
    />
    

    Main Activity XML

    Copy over the following Code into your main activity Kotlin file and call setupBottomNavigationBar within OnCreateView. Ensure you navGraphIds use R.navigation.whatever and not R.id.whatever

    private lateinit var currentNavController: LiveData<NavController>
    
    private fun setupBottomNavigationBar() {
      val bottomNavigationView = findViewById<BottomNavigationView>(R.id.bottomNavigationView)
      val navGraphIds = listOf(R.navigation.tab0, R.navigation.tab1, R.navigation.tab2, R.navigation.tab3)
      val controller = bottomNavigationView.setupWithNavController(
          navGraphIds = navGraphIds,
          fragmentManager = supportFragmentManager,
          containerId = R.id.fragmentContainerView,
          intent = intent
      )
      controller.observe(this, { navController ->
          val toolbar = findViewById<Toolbar>(R.id.main_toolbar)
          val appBarConfiguration = AppBarConfiguration(navGraphIds.toSet())
          NavigationUI.setupWithNavController(toolbar, navController, appBarConfiguration)
          setSupportActionBar(toolbar)
      })
      currentNavController = controller
    }
    
    override fun onSupportNavigateUp(): Boolean {
      return currentNavController?.value?.navigateUp() ?: false
    }
    

    Copy NavigationExtensions.kt File

    Copy the following file to your codebase

    Source

    • Google's Solution
    0 讨论(0)
  • 2020-12-31 12:46

    Try something like this

    navView.setOnNavigationItemSelectedListener(onNavigationItemSelectedListener)
    

    And

    private val onNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
        when (item.itemId) {
            R.id.home -> {
                fragmentManager.beginTransaction().hide(active).show(homeFragment).commit()
                active = homeFragment
                return@OnNavigationItemSelectedListener true
            }
            R.id.news -> {
                fragmentManager.beginTransaction().hide(active).show(newsFragment).commit()
                active = newsFragment
                return@OnNavigationItemSelectedListener true
            }
            R.id.markets -> {
                fragmentManager.beginTransaction().hide(active).show(marketsFragment).commit()
                active = marketsFragment
                return@OnNavigationItemSelectedListener true
            }
            R.id.explore -> {
                fragmentManager.beginTransaction().hide(active).show(exploreFragment).commit()
                active = exploreFragment
                return@OnNavigationItemSelectedListener true
            }
        }
        false
    }
    
    0 讨论(0)
提交回复
热议问题