Problem:
Each time the BottomNavigationView is clicked the fragment is re-created.
Currently in each fragment I have Recycler View that runs only once when creating the Fragment.
In this case, the RecyclerView data is loaded every time. And that's not what I'd like.
How could I work so that the Fragment remains active after it is clicked the first time. And when you go back to the Fragment already created, just display it without recreating it every time?
My attempts:
BottomNavigationView bottomNavigationView =(BottomNavigationView) findViewById(R.id.bottomNavView_Bar);
BottomNavigationViewHelper.disableShiftMode(bottomNavigationView);
final Menu menu = bottomNavigationView.getMenu();
fragmentManager = getSupportFragmentManager();
final FragmentTransaction transaction = fragmentManager.beginTransaction();
/* Fragmento Start */
transaction.replace(R.id.content_home, new MainFragment()).commit();
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Intent in;
Fragment fragment = null;
Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.content_home);
switch (item.getItemId()){
case R.id.ic_home:
if (!(currentFragment instanceof MainFragment)) {
fragment = new MainFragment();
toolbar.setTitle("Main");
item = menu.getItem(0);
item.setChecked(true);
}
break;
case R.id.ic_categorias:
if (!(currentFragment instanceof CategoriasFragment)) {
fragment = new CategoriasFragment();
toolbar.setTitle("Categorias");
item = menu.getItem(1);
item.setChecked(true);
}
break;
case R.id.ic_calendar:
if (!(currentFragment instanceof FeedUserFragment)) {
fragment = new FeedUserFragment();
item = menu.getItem(2);
toolbar.setTitle("Feed");
item.setChecked(true);
}
break;
case R.id.ic_explore:
if (!(currentFragment instanceof ExploreFragment)) {
fragment = new ExploreFragment();
item = menu.getItem(3);
toolbar.setTitle("Explorar");
item.setChecked(true);
}
break;
case R.id.ic_person:
if (!(currentFragment instanceof Opcoes)) {
fragment = new Opcoes();
toolbar.setTitle("Opções");
item = menu.getItem(4);
item.setChecked(true);
}
break;
}
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.content_home, fragment);
transaction.commit();
return false;
}
});
You are replacing your fragment every time ie after your switch block
complete.
Solution: Replace only when condition meets and use addToBackStack which helps to maintain stacks. You can read more about from this site
Try this,
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Intent in;
Fragment fragment = null;
Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.content_home);
switch (item.getItemId()){
case R.id.ic_home:
if (!(currentFragment instanceof MainFragment)) {
fragment = new MainFragment();
toolbar.setTitle("Main");
item = menu.getItem(0);
item.setChecked(true);
loadFragment(fragment,"mainMenu");
}
break;
case R.id.ic_categorias:
if (!(currentFragment instanceof CategoriasFragment)) {
fragment = new CategoriasFragment();
toolbar.setTitle("Categorias");
item = menu.getItem(1);
item.setChecked(true);
loadFragment(fragment,"categories");
}
break;
case R.id.ic_calendar:
if (!(currentFragment instanceof FeedUserFragment)) {
fragment = new FeedUserFragment();
item = menu.getItem(2);
toolbar.setTitle("Feed");
item.setChecked(true);
loadFragment(fragment,"feedUsers");
}
break;
case R.id.ic_explore:
if (!(currentFragment instanceof ExploreFragment)) {
fragment = new ExploreFragment();
item = menu.getItem(3);
toolbar.setTitle("Explorar");
item.setChecked(true);
loadFragment(fragment,"explore");
}
break;
case R.id.ic_person:
if (!(currentFragment instanceof Opcoes)) {
fragment = new Opcoes();
toolbar.setTitle("Opções");
item = menu.getItem(4);
item.setChecked(true);
loadFragment(fragment,"opcoes");
}
break;
}
return false;
}
});
/**
* Create new method which replace your old fragment
*/
public void loadFragment(Fragment fragment, String tag){
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.content_home, fragment);
transaction.addToBackStack(tag);
transaction.commit();
}
I would try transaction.add() and search for fragments in back stack with this method findFragmentByTag. https://developer.android.com/reference/android/support/v4/app/FragmentManager.html#findFragmentByTag(java.lang.String).
Related
I have the following bottom navbar code to switch between 3 fragments:
public class MainActivity extends AppCompatActivity {
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Fragment fragment = null;
switch (item.getItemId()) {
case R.id.navigation_home:
fragment = new HomeFragment();
break;
case R.id.navigation_dashboard:
fragment = new DashboardFragment();
break;
case R.id.navigation_notifications:
fragment = new NotificationsFragment();
break;
}
return loadFragment(fragment);
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
loadFragment(new HomeFragment());
BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
}
private boolean loadFragment(Fragment fragment) {
//switching fragment
if (fragment != null) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragment_container, fragment)
.commit();
return true;
}
return false;
}
}
In the fragments there are RecyclerViews with lists. Every time I switch between the tabs (between fragments), it looks like the fragment is reloaded, and the lists jump to the top. I want to prevent that reloading so that the user stays on the same place in the list he viewed before switching fragments
The problem is that you are creating a new instance every time. You can cache the instance like:
private Fragment mHomeFragment = new HomeFragment();
private Fragment mDashboardFragment = new DashboardFragment();
private Fragment mNotificationsFragment = new NotificationsFragment();
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Fragment fragment = null;
switch (item.getItemId()) {
case R.id.navigation_home:
fragment = mHomeFragment;
break;
case R.id.navigation_dashboard:
fragment = mDashboardFragment;
break;
case R.id.navigation_notifications:
fragment = mNotificationsFragment;
break;
}
return loadFragment(fragment);
}
As we could see, you are always replace your fragment when clicks on bottom navigation, replace means previous fragment removes and state cleans. The solution is do not create your fragment each time and use attach/detach method for showing actual fragment. Here is already described about these methods.
I want to call web service before my Fragment1 initialize. But In MainActivity first my Fragment1 Replacing then my webservice call occurs. Now how to handle that ? I am using Retrofit for web service calling.
I can not call Webservice in Fragment 1. Because i dont want to repeat during fragment initialization.
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main);
bottomNavigationView = (BottomNavigationView findViewById(R.id.navigation);
//Here My webservice call which response value sent to Fragment 1
bottomNavigationView.setOnNavigationItemSelectedListener
(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
getSupportActionBar().setHomeButtonEnabled(false);
}
android.app.Fragment selectedFragment = null;
switch (item.getItemId()) {
case R.id.action_item1:
selectedFragment = Fragment1.newInstance();
toolbar.setTitle("1");
break;
case R.id.action_item2:
selectedFragment = Fragment2.newInstance();
toolbar.setTitle("2");
break;
default:
selectedFragment = Fragment1.newInstance();
toolbar.setTitle("1");
break;
}
android.app.FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.frame_layout, selectedFragment);
transaction.commit();
return true;
}
});
android.app.FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.frame_layout, Fragment1.newInstance());
transaction.commit();
}
}
In my StartActivity the BottomNavigationBar Listener has the following setup:
private GuideFragment guideFragment = new GuideFragment();
private MapFragment mapFragment = new MapFragment();
private MoreFragment moreFragment = new MoreFragment();
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Fragment selectedFragment = null;
switch (item.getItemId()) {
case R.id.navigation_guide:
selectedFragment = guideFragment;
break;
case R.id.navigation_map:
selectedFragment = mapFragment;
break;
case R.id.navigation_more:
selectedFragment = moreFragment;
break;
}
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.content, selectedFragment);
transaction.commit();
return true;
}
};
As I mentioned above I want to prevent that the selected fragments always reloads the sources/view.
I tried out some stuff like - in the fragments:
if (rootView == null)
inflater.inflate...
But the fragments still recreate the view and load (in my case) webresources new.
I read something that a PageView could help, especially
offScreenPageLimit
should do the magic.
My main question is where should I implement a PageViewer - Is it possible in my StartActivity?
Or can I solve the problem in an other way?
I did it boys!
There is no ViewPager necessary.
Here is my solution (all coded in StartActivity not in Fragments):
private final GuideFragment guideFragment = new GuideFragment();
private final MapFragment mapFragment = new MapFragment();
private final MoreFragment moreFragment = new MoreFragment();
private final android.app.FragmentManager fm = getFragmentManager();
Fragment active = guideFragment;
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.navigation_guide:
if(active != guideFragment) {
fm.beginTransaction().show(guideFragment).commit();
}
else {
fm.beginTransaction().hide(active).show(guideFragment).commit();
}
active = guideFragment;
break;
case R.id.navigation_map:
fm.beginTransaction().hide(active).show(mapFragment).commit();
active = mapFragment;
break;
case R.id.navigation_more:
fm.beginTransaction().hide(active).show(moreFragment).commit();
active = moreFragment;
break;
}
return true;
}
};
and in onCreate list the transaction commits.
fm.beginTransaction().add(R.id.content,moreFragment).commit();
fm.beginTransaction().add(R.id.content, mapFragment).commit();
fm.beginTransaction().add(R.id.content, guideFragment).commit();
It is very important to commit the first tabs fragment last(fragm3,fragm2,fragm1) if you have 3 tabs.
Highly speed performance on the smartphone now by not loading every fragment new/refresh.
How can I limit a tap on a BottomNavigatioView item to just once, so that my mapfragment doesn't have to reload itself when someone spamms the item?
My current Solution is this one:
fragmentManager = getSupportFragmentManager();
bottomNavigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Fragment fragment = null;
Fragment currentFragment = fragmentManager.findFragmentById(R.id.main_container);
int id = item.getItemId();
switch (id){
case R.id.World:
if (!(currentFragment instanceof MapsFragment)) {
fragment = new MapsFragment();
}
break;
case R.id.Friends:
fragment = new FriendsFragment();
break;
case R.id.Chats:
fragment = new You_Fragment();
break;
}
final FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.main_container, fragment).commit();
return true;
}
});
but it doesn't work since the fragment throws a Nullpointer.
What can i change to achieve the above?
Try this! Worked for me:
fragmentManager = getSupportFragmentManager();
bottomNavigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Fragment fragment = null;
Fragment currentFragment = getSupportFragmentManager().findFragmentById(R.id.main_container);
int id = item.getItemId();
switch (id){
case R.id.World:
if (currentFragment instanceof MapsFragment) {
return false;
} else {
fragment = new MapsFragment();
}
break;
case R.id.Friends:
fragment = new FriendsFragment();
break;
case R.id.Chats:
fragment = new You_Fragment();
break;
}
final FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.main_container, fragment).commit();
return true;
}
});
My App starts with a AddressFragment. From the NavigationDrawer I start (amongst others) a new AddressFragment with:
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, new AddressFragment())
.addToBackStack(null)
.commit();
But I would rather just go back the first instance. How could I do that?
Or more general, how can I find out, whether an instance of a fragment already exists and then start that, if yes, otherwise create one?
When creating the fragment set a tag for it, then later you can find it through the fragment manager and replace/create accordingly.
FragmentManager fManager = getFragmentManager();
FragmentTransaction fTransaction = fManager.beginTransaction();
Fragment fragment = fManager.findFragmentByTag("uniqueTag");
// If fragment doesn't exist yet, create one
if (fragment == null) {
fTransaction.add(R.id.fragment_list, new ListFrag(), "uniqueTag");
}
else { // re-use the old fragment
fTransaction.replace(R.id.fragment_list, fragment, "uniqueTag");
}
Step one:
Optimize current code to allow a Fragment to have his own "TAG"
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, new AddressFragment())
.addToBackStack(**AddressFragment.class.getName()**)
.commit();
Step two:
Somewhere in your application flow you will need to determine if a fragment exists:
public static boolean isFragmentInBackstack(final FragmentManager fragmentManager, final String fragmentTagName) {
for (int entry = 0; entry < fragmentManager.getBackStackEntryCount(); entry++) {
if (fragmentTagName.equals(fragmentManager.getBackStackEntryAt(entry).getName())) {
return true;
}
}
return false;
}
Step three:
perform fragment action
if (exists) {
// Fragment exists, go back to that fragment
//// you can also use POP_BACK_STACK_INCLUSIVE flag, depending on flow
mFragmentManager.popBackStackImmediate(AddressFragment.class.getName(), 0);
} else {
// Fragment doesn't exist
// STEP 1 + additional backstack management
}
This is tested answer, Hope this will help you
First Make these field globally in MainActivity
private static final int TIME_INTERVAL = 2000;
private long mBackPressed;
private FragmentManager fm;
private FragmentTransaction ft;
private DrawerLayout drawer;
Now in onNavigationItemSelected() implement like that
#Override
public boolean onNavigationItemSelected(MenuItem item) {
Fragment fragment = null;
Class fragmentClass = null;
switch (item.getItemId()) {
case R.id.nav_home:
fragmentClass = Fragment.class;//this is MainAvctivty extends AppCompatActivity
break;
case R.id.nav_f1:
fragmentClass = FragmentOne.class;
break;
case R.id.nav_f2:
fragmentClass = FragmentTwo.class;
break;
case R.id.nav_f3:
fragmentClass = FragmentThree.class;
break;
case R.id.nav_f4:
fragmentClass = FragmentFour.class;
break;
case R.id.nav_f5:
fragmentClass = FragmentFive.class;
break;
default:
fragmentClass = Fragment.class;
}
try {
fragment = (Fragment) fragmentClass.newInstance();
} catch (Exception e) {e.printStackTrace();}
//Insert the fragment by replacing any existing fragment
fm = getSupportFragmentManager();
ft = fm.beginTransaction();
ft.replace(R.id.frame_container, fragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.addToBackStack(null);
ft.commit();
item.setChecked(true);
setTitle(item.getTitle());
drawer.closeDrawers();
return true;
}
Now handle onbackpressed like below
#Override
public void onBackPressed() {
if (drawer != null) {
fm = getSupportFragmentManager();
ft = fm.beginTransaction();
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else if (fm.getBackStackEntryCount() > 0) {
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_CLOSE);
ft.commit();
} else if (mBackPressed + TIME_INTERVAL > System.currentTimeMillis()) {
super.onBackPressed();
} else {
Toast.makeText(this,"Press again to exit the app",Toast.LENGTH_SHORT).show();
}
mBackPressed = System.currentTimeMillis();
}
}
First time you create the fragment with a tag.
When you want to replace it try to get the fragment with tag and if it return null you create a new one