In my main layout I have a central container which contains a fragment.
I started with FragmentTransaction.replace(...) to replace the fragment with other fragments but this recreate each fragment on each replacement, which makes the UI laggy (some fragments have complexe layout etc).
Instead of replace(), some suggest using .show() and .hide() to "cache" rendered fragments, and which leads to the code below:
private HashMap<String, Fragment> mCachedFragments = new HashMap<String, Fragment>();
private Fragment mCurrentFragment = null;
#Override
public void onNavigationDrawerItemSelected(int position) {
if (position == mCurrentSectionID)
return;
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left);
if (mCurrentFragment != null) {
Log.d("TRANSACTION", "putting fragment "+mCurrentFragment.getClass().getName());
mCachedFragments.put(mCurrentFragment.getClass().getName(), mCurrentFragment);
transaction.hide(mCurrentFragment);
}
Log.d("Nav", "selected: " + position);
Fragment dest = null;
switch (position) {
case BitsListFragment.FRAGMENT_ID:
dest = mCachedFragments.get(BitsListFragment.class.getName());
if (dest == null) {
dest = BitsListFragment.newInstance();
transaction
.add(R.id.container, dest);
} else {
Log.d("TRANSACTION", "bitlist retrieved");
transaction.show(dest);
}
onSectionAttached(BitsListFragment.FRAGMENT_ID);
break;
case AchievementsFragment.FRAGMENT_ID:
dest = mCachedFragments.get(AchievementsFragment.class.getName());
if (dest == null) {
dest = AchievementsFragment.newInstance();
transaction
.add(R.id.container, dest);
} else {
Log.d("TRANSACTION", "achievements retrieved");
transaction.show(dest);
}
onSectionAttached(AchievementsFragment.FRAGMENT_ID);
break;
case StatsFragment.FRAGMENT_ID:
dest = mCachedFragments.get(StatsFragment.class.getName());
if (dest == null) {
dest = StatsFragment.newInstance();
transaction
.add(R.id.container, dest);
} else {
Log.d("TRANSACTION", "stats retrieved");
transaction.show(dest);
}
onSectionAttached(StatsFragment.FRAGMENT_ID);
break;
}
mCurrentFragment = dest;
transaction.commit();
}
The code here works perfectly as long as the Activity does not gets recreated. The fragments gets retrieved and shown, and currentFragment is hidden when transition takes place.
As you can see, I'm basically keeping a reference to the CurrentFragment and a hashmap of all the cached fragments. The problem is that when I rotation the screen, the MainActivity gets recreated and these references are reset to null or emptied. Which ends up with BitsListFragment getting created multiple times and shown on screen overlapped.
Is there a better way to cache fragments/replace fragments without recreating them?
Thanks!
Actually found another way to do this: instead of keeping an reference on my own, we can add the fragment to the backstack and then retrieve it using corresponding tag. This let's fragmentManager manages the caching by itself. And the second time you access a fragment, it doesn't gets recreated.
The only hack that you should watch out is to override onBackPressed() so that you don't return to last fragment if you don't need to.
#Override
public void onNavigationDrawerItemSelected(int position) {
if (position == mCurrentSectionID)
return;
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left);
Fragment dest;
switch (position) {
case BitsListFragment.FRAGMENT_ID:
dest = fragmentManager.findFragmentByTag(BitsListFragment.class.getName());
if (dest == null) {
Log.d("TRANSACTION", "instanciating new fragment");
dest = BitsListFragment.newInstance();
}
transaction.replace(R.id.container, dest, BitsListFragment.class.getName());
transaction.addToBackStack(BitsListFragment.class.getName());
break;
case AchievementsFragment.FRAGMENT_ID:
dest = fragmentManager.findFragmentByTag(AchievementsFragment.class.getName());
if (dest == null) {
Log.d("TRANSACTION", "instanciating new fragment");
dest = AchievementsFragment.newInstance();
}
transaction.replace(R.id.container, dest, AchievementsFragment.class.getName());
transaction.addToBackStack(AchievementsFragment.class.getName());
break;
case StatsFragment.FRAGMENT_ID:
dest = fragmentManager.findFragmentByTag(StatsFragment.class.getName());
if (dest == null) {
Log.d("TRANSACTION", "instanciating new fragment");
dest = StatsFragment.newInstance();
}
transaction.replace(R.id.container, dest, StatsFragment.class.getName());
transaction.addToBackStack(StatsFragment.class.getName());
break;
}
transaction.commit();
onSectionAttached(position);
}
Related
I am using BottomNavigationBar and switch fragments with this. However, animations start to work only after the second showing of the same fragment. If you do not understand what, watch this video: https://drive.google.com/file/d/1AEAO_-wG2k17sMvpbrHu3UjlA4iAi6DQ/view?usp=sharing
onCreate():
fm = getSupportFragmentManager().beginTransaction();
fm.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
fm.add(R.id.gen_fr, settings).hide(settings);
fm.add(R.id.gen_fr, events).hide(events);
fm.add(R.id.gen_fr, refs).hide(refs);
fm.add(R.id.gen_fr, earn);
fm.commit();
When switching fragments:
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
fm = getSupportFragmentManager().beginTransaction();
fm.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
switch (item.getItemId()) {
case R.id.main_room:
if (active != earn) {
fm.hide(active).show(earn);
active = earn;
}
break;
case R.id.refs_room:
if (active != refs) {
fm.hide(active).show(refs);
active = refs;
}
break;
case R.id.events_room:
if (active != events) {
fm.hide(active).show(events);
active = events;
}
break;
case R.id.settings_room:
if (active != settings) {
fm.hide(active).show(settings);
active = settings;
}
break;
}
fm.commit();
return true;
I understand there were a lot of such issues. But a strange situation is happening with my option. Well, it’s not very clear. You can tell what is wrong.
I hide activeFragment if it is not null. Then I add a Fragment if it is not. And if there is, then I show. From the first time, everything works fine when I create it using the add() method or replace(). But if I go back and try to show() the already hidden Fragment, then it's just a white screen, there is no Fragment. What could be the problem?
private void loadFragment(Fragment fragment, String tag) {
if (fragment != null) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction transaction = fm.beginTransaction();
if (activeFragment != null) {
transaction.hide(activeFragment);
}
if (fm.findFragmentByTag(tag) == null) {
transaction.add(R.id.fragment_container, fragment, tag);
} else {
transaction.show(fragment);
}
activeFragment = fragment;
transaction.commit();
}
}
My MainActivity has a Drawer and instantiates a new Fragment, depending on the clicked MenuItem (based on this tutorial).
I monitored the memory, which is slightly increasing on every Fragment change and I worry, that fragmentClass.newInstance() is not the right way.
// MainActivity
public boolean onNavigationItemSelected(MenuItem item) {
int id = item.getItemId();
Fragment fragment = null;
Class fragmentClass = null;
if (id == R.id.nav_camera) {
fragmentClass = CameraFragment.class;
} else if (id == R.id.nav_gallery) {
fragmentClass = GalleryFragment.class;
} else if (id == R.id.nav_slideshow) {
fragmentClass = SlideshowFragment.class;
} else if (id == R.id.nav_manage) {
fragmentClass = ManageFragment.class;
} else if (id == R.id.nav_share) {
fragmentClass = ShareFragment.class;
} else if (id == R.id.nav_send) {
fragmentClass = SendFragment.class;
}
try {
fragment = (Fragment) fragmentClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.layout_main, fragment).commit();
}
I would expect something like:
// Pseudocode
if (fragmentManager.instanceExists(fragmentClass)) {
// load instantiated fragment
} else {
// newInstance()
}
How could I avoid memory leaks or is this even related?
Thank you!
Practically, there is no problems in that code. Things you can optimize are:
Reworking Fragment#instantiate() method (check docs)
Caching instances in something like Map in order not to create the new one every time you navigate through the drawer
Also, you can check if fragment exists in FragmentManager using FragmentManager#findFragmentById() or FragmentManager#findFragmentByTag()
I make the application "Phone book".
The application has a menu, which is sorting by name or category. I do it like this in MainActivity:
Bundle bundle = new Bundle();
Fragment lvFragSortBy = new ListViewFragment();
switch (item.getItemId()) {
case R.id.menuSortOrderName:
item.setChecked(true);
bundle.putString("sortBy", "name");
lvFragSortBy.setArguments(bundle);
getFragmentManager()
.beginTransaction()
.replace(R.id.fragContainer, lvFragSortBy)
.commit();
return true;
case R.id.menuSortOrderCategory:
item.setChecked(true);
bundle.putString("sortBy", "category");
lvFragSortBy.setArguments(bundle);
getFragmentManager()
.beginTransaction()
.replace(R.id.fragContainer, lvFragSortBy)
.commit();
return true;
In ListViewFragment:
Bundle sortBundle = getArguments();
if (sortBundle != null) {
orderBy = sortBundle.getString("sortBy");
}
And then:
protected ArrayList<Contact> doInBackground(Void... params) {
try {
db = new DbWorker(ctx);
contacts = (ArrayList<Contact>) db.selectAllContacts(orderBy);
} catch (Exception e) {
Log.e(TAG, "Exception in AsyncTask.doInBackground");
}
return contacts;
}
This code works fine but I would like to know can it be done better?
Maybe there's another way.
Thanks to everybody, but I found the answer here: https://codereview.stackexchange.com/questions/147878/the-sorting-in-the-app-with-fragments/147881#147881
For proper operation of the fragments, you must use newInstance() pattern.
i have an activity with a navigation drawer built using the library https://github.com/mikepenz/MaterialDrawer
I want to load 4 different pages from selecting 4 different items from the navigation drawer.
I decided to use fragments for each item in the navigation drawer, so when any item is pressed, a new fragment for that option is created and shown to the user.
What i want is that, regardless of which fragment the user is currently in, or which fragment he came from, whenever he presses the back button, he is returned to the original activity.
This is the code i have so far, but apparently the Fragment Backstack is not being updated.
result = new DrawerBuilder().withSelectedItem(-1)
.withActivity(this)
.withToolbar(toolbar)
.withAccountHeader(headerResult)
.addDrawerItems(
item1,item2,item3,item4
)
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
#Override
public boolean onItemClick(View view, int position, IDrawerItem drawerItem) {
result.setSelection(position,false);
clearStack();
switch (position) {
case 1:
fragment = new admin_fragment_data_entry();
break;
case 2:
fragment = new admin_fragment_data_edit();
break;
case 3:
break;
case 4:
break;
}
initiateFragment();
return false;
}
}
)
.build();
#Override
public void onBackPressed() {
int count = getFragmentManager().getBackStackEntryCount();
//handle the back press :D close the drawer first and if the drawer is closed close the activity
if (result != null && result.isDrawerOpen()) {
result.closeDrawer();
}
else if (count == 0) {
super.onBackPressed();
//additional code
}
else {
getFragmentManager().popBackStack();
}
}
private void initiateFragment(){
if (fragment != null) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.addToBackStack(null);
ft.replace(R.id.container_body, fragment);
ft.commit();
}
}
private void clearStack(){
int count = getFragmentManager().getBackStackEntryCount();
while(count > 0){
getFragmentManager().popBackStack();
count--;
}
}
Any help would be highly appreciated.
Also, should i have used fragments here? Or i should have just started 4 different activities for 4 different options from navigation drawer?
Are you using the correct fragment manager to get your backstack count?
I notice in other parts of your class you're using getSupportFragmentManager instead.