I have this problem with overlapping fragments. I read other resolutions, but they seem to be over complicated as for problem like this. It happens when app is not used for sometime and then it's resumed or recreated and it seems that it remembers last fragment and creates the new one as if app have been destroyed last time.
Is that something related to savedInstanceState?
Code:
// used in onCreate()
private void setUpFragments(){
Fragment fragment;
switch (fragmentName){
case CALLS:
fragment = new CallLogsFragment();
break;
case CONTACTS:
fragment = new ContactListFragment();
break;
case SETUP:
fragment = new SetupFragment();
break;
case REGISTER:
fragment = new RegisterFragment();
break;
default:
fragment = new SetupFragment();
}
getFragmentManager().beginTransaction()
.add(R.id.fragment_container, fragment)
.commit();
}
#Override
public boolean onNavigationItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.nav_calls:
setFragment(new CallLogsFragment());
break;
case R.id.nav_contacts:
setFragment(new ContactListFragment());
break;
case R.id.nav_add_contacts:
setFragment(new AddContactsFragment());
break;
case R.id.nav_setup:
setFragment(new SetupFragment());
break;
default:
setFragment(new ContactListFragment());
}
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
return true;
}
public void setFragment(Fragment fragment){
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, fragment);
transaction.addToBackStack(null);
transaction.commit();
}
After default you do not have
break;
default:
fragment = new SetupFragment();
than it should be in both cases
default:
fragment = new SetupFragment();
break;
You can check my code:
in the begging of the program (in onCreate)
getSupportFragmentManager()
.beginTransaction()
.addToBackStack(null)
.add(FRAME_LAYOUT,
createFragment(defaultCurrentItem),
createItemDescription(defaultCurrentItem))
.commit();
The function to begin transaction
private Fragment createFragment(int position){
switch (position) {
case BOT_NAV_POSITION_SETTINGS:
return new SettingsFragment_();
case BOT_NAV_POSITION_STATISTICS:
return new StatisticsFragment_();
case BOT_NAV_POSITION_MAIN:
return new MainFragment_();
case BOT_NAV_POSITION_FORTH:
return new ForthFragment_();
case BOT_NAV_POSITION_REMINDERS:
return new ReminderFragment_();
}
throw new IllegalArgumentException();
}
And later when you need to change fragment
getSupportFragmentManager()
.beginTransaction()
.replace(FRAME_LAYOUT,
createFragment(position),
createItemDescription(position))
.addToBackStack(null)
.commit();
You are right, setup the fragments(i.e call setupFragments) only if activity is newly created(i.e savedInstanceState == null).
On activity recreation, activity's fragment manager will restore its old state. If you didn't check for savedInstanceState, yours newly committed fragment and fragment manager's restored fragment will be overlapped.
Related
i'm using a navigation drawer to switch between fragments , each time i move to a new one ,it's completely recreated and the previous one is completely destroyed, so i want to conserve the state of fragments while swapping and back again
Runnable mPendingRunnable = new Runnable() {
#Override
public void run() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out);
Fragment fragment=getHomeFragment();
fragmentTransaction.replace(R.id.frame,fragment,CURRENT_TAG);
fragmentTransaction.commitAllowingStateLoss();
}
};
if (mPendingRunnable != null) {
mHandler.post(mPendingRunnable);
}
and the getHomeFragment() :
public Fragment getHomeFragment() {
switch (navItemIndex) {
case 0:
HomeFragment homeFragment = new HomeFragment();
return homeFragment;
case 1:
BoiteRFragment boiteRFragment = new BoiteRFragment();
return boiteRFragment;
case 2:
InfoPersFragment infoPersFragment = new InfoPersFragment();
return infoPersFragment;
case 3:
NotificationsFragment notificationsFragment = new NotificationsFragment();
return notificationsFragment;
case 4:
PrametresFragment prametresFragment = new PrametresFragment();
return prametresFragment;
default:
return new HomeFragment();
}
}
I have a navigation view with some items in it, and when one item is pressed it will go to that fragment. For example, if you press the "Home" item, on the drawer, it will bring up the home fragment.
navigationView = (NavigationView)findViewById(R.id.navigation_view);
navigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.home_id:
HomeFragment homeFragment = (HomeFragment) fragmentManager.findFragmentByTag("Home");
if (homeFragment == null)
homeFragment = new HomeFragment();
fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.Main, homeFragment, "Home");
fragmentTransaction.commit();
getSupportActionBar().setTitle("Home");
item.setChecked(true);
drawerLayout.closeDrawers();
break;
case R.id.chat_id:
ChatFragment chatFragment = (ChatFragment) fragmentManager.findFragmentByTag("Chat");
if (chatFragment == null)
chatFragment = new ChatFragment();
fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.Main, chatFragment, "Chat");
fragmentTransaction.commit();
getSupportActionBar().setTitle("Chat");
item.setChecked(true);
drawerLayout.closeDrawers();
break;
}
}
}
So what I think is happening is that when I am using the replace() method, it is destroying the chat fragment. What if I have data on the chat fragment, and I want to make sure the data does not go away when I change fragments.
In the ChatFragment class I have put this override method:
#Override
public void onSaveInstanceState(Bundle outState)
{
Log.d("CHATFRAG", "onSaveInstanceState: been called ");
super.onSaveInstanceState(outState);
outState.putString("textView",textView.getText().toString());
//Save the fragment's state here
}
Now when I switch between fragments in my app, this method does not get called, which means the fragment is being destroyed.
Is there an alternative way where I can swap the current fragment that is on screen, with the desired fragment(without destroying both)?
I will be having more fragments (up to ten), is there an optimised solution that will not require duplicate code?
you are replacing fragment with other, so first one will get distroyed. If you dont want it to destroy you need to add it instead of replace
fragmentTransaction.add(R.id.Main, homeFragment, "Home");
You can create your fragments in advance, for instance in your activity's onCreate() method:
private HomeFragment homeFragment;
private ChatFragment chatFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
homeFragment = new HomeFragment();
chatFragment = new ChatFragment();
}
Then in your navigation code:
#Override
public boolean onNavigationItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.home_id:
if (homeFragment == null)
homeFragment = new HomeFragment();
fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.Main, homeFragment, "Home");
fragmentTransaction.commit();
getSupportActionBar().setTitle("Home");
item.setChecked(true);
drawerLayout.closeDrawers();
break;
case R.id.chat_id:
if (chatFragment == null)
chatFragment = new ChatFragment();
fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.Main, chatFragment, "Chat");
fragmentTransaction.commit();
getSupportActionBar().setTitle("Chat");
item.setChecked(true);
drawerLayout.closeDrawers();
break;
}
}
Although I think Ravi Rupareliya's answer is the best choice (using .add), I'll share another way I think might work.
You can add two FrameLayout on top of each other, .add a Fragment into both, and then alternate their visibility.
There is problem with my fragment, they are showed together, one on the second fragment. How to disapear, and only show one of them?
Definiton:
button1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
fr = new avc);
FragmentTransaction ft = ((TestingActivity)context).getFragmentManager().beginTransaction();
ft.replace(R.id.test, fr);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.addToBackStack(null);
ft.commit();
}
});
And definiton of container below:
<FrameLayout
android:id="#+id/test"
android:layout_width="match_parent"
android:layout_height="match_parent" />
You can use some thing like this. Make a function for showing a fragment and call each time this function with different parameter.
eg. If You want to show "HomeFragment" then call displayView(0) and if you want to show "FindPeopleFragment" then call displayView(1)
private void displayView(int position) {
// update the main content by replacing fragments
Fragment fragment = null;
switch (position) {
case 0:
fragment = new HomeFragment();
break;
case 1:
fragment = new FindPeopleFragment();
break;
case 2:
fragment = new PhotosFragment();
break;
case 3:
fragment = new CommunityFragment();
break;
case 4:
fragment = new PagesFragment();
break;
case 5:
fragment = new WhatsHotFragment();
break;
default:
break;
}
if (fragment != null) {
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.frame_container, fragment).commit();
// update selected item and title, then close the drawer
mDrawerList.setItemChecked(position, true);
mDrawerList.setSelection(position);
setTitle(navMenuTitles[position]);
mDrawerLayout.closeDrawer(mDrawerList);
} else {
// error in creating fragment
Log.e("MainActivity", "Error in creating fragment");
}
}
So I have my navigation drawer with 5 different options. They all open a new fragment that I have created. The first one is Home, and I am trying to find a way to bring it back to the first screen that shows up under the navigation drawer. It has the id of "container", in the main_activity.xml. I do not want to use and intent to call the entire class again to load up. Also I do not want to be able to use the back button from another intent. I am confused on how to make this happen.
#Override
public void onNavigationDrawerItemSelected(int position) {
FragmentHowItWorks fragmentHow;
FragmentSettings fragmentSettings;
FragmentTransaction transaction = getFragmentManager().beginTransaction();
switch(position){
case 0:
// should I call the layout?
// this is the "Home" option
break;
case 1:
fragmentHow = new FragmentHowItWorks();
transaction.replace(R.id.container, fragmentHow);
transaction.addToBackStack(null);
transaction.commit();
break;
case 2:
fragmentSettings = new FragmentSettings();
transaction.replace(R.id.container, fragmentSettings);
transaction.addToBackStack(null);
transaction.commit();
break
case 3:
fragment = new FragmentHowItWorks();
transaction.replace(R.id.container, fragment);
transaction.addToBackStack(null);
transaction.commit();
break;
case 4:
fragment = new FragmentHowItWorks();
transaction.replace(R.id.container, fragment);
transaction.addToBackStack(null);
transaction.commit();
break;
}
}
Use methods add,hide and show,
Step1 Prepare all your fragments
Fragment fragment1 = new FragmentOne();
Fragment fragment2 = new FragmentTwo();
Fragment fragment3 = new FragmentThree();
Fragment mCurrentFragment = null;
Step2 Show/hide your fragments
#Override
public void onNavigationDrawerItemSelected(int position) {
Fragment fragment;
switch (position) {
case 1:
fragment = fragment1;
break;
case 2:
fragment = fragment2;
break;
case 3:
fragment = fragment3;
break;
}
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if(mCurrentFragment == null) {
ft.add(R.id.container, fragment).commit();
mCurrentFragment = fragment;
} else if(fragment.isAdded()) {
ft.hide(mCurrentFragment).show(fragment).commit();
} else {
ft.hide(mCurrentFragment).add(R.id.container, fragment).commit();
}
mCurrentFragment = fragment;
}
You can do this ,
You can get the name of current fragment which is in the container . This will return name including the package + fragment name
String name = getFragmentManager().findFragmentById(container id ).getClass().getName();
When you click on the home index of drawer, check weather the current name id equal to the home.
If it is home, you don't need to perform any action.
I know that this was asked and answered long time ago but, I would like to show my approach on this problem, maybe it will help anyone else:
I added a Fragment Name over each Fragment that I use like:
public class MainFragment extends BaseFragment {
public static final String FRAGMENT_TAG = "main";
// ... all your fragment
}
And on the Drawer Layout:
public void methodThatSetsTheUi() {
mDrawerView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
getFragmentManager().findFragmentByTag("main");
String tag = getFragmentTag(position);
replaceFragment(getOrCreateFragment(tag), tag);
mDrawerLayout.closeDrawer(mDrawerView);
}
});
}
#NonNull
private String getFragmentTag(int position) {
String tag;
switch (position) {
case MAIN_FRAGMENT_DRAWER_POSITION:
tag = MainFragment.FRAGMENT_TAG;
break;
case FAVORITE_FRAGMENT_DRAWER_POSITION:
tag = FavoriteFragment.FRAGMENT_TAG;
break;
default:
throw new AssertionError("That selection is wrong");
}
return tag;
}
private BaseFragment getOrCreateFragment(String fragmentName) {
BaseFragment fragment = (BaseFragment) getFragmentManager().findFragmentByTag(fragmentName);
if(fragment == null) {
fragment = FragmentFactory.createFragment(fragmentName);
}
return fragment;
}
and well, the FragmentFactory is just a simple Factory:
public final class FragmentFactory {
public static BaseFragment createFragment(String fragmentName) {
switch(fragmentName) {
case MainFragment.FRAGMENT_TAG:
return MainFragment.newInstance();
case FavoriteFragment.FRAGMENT_TAG:
return FavoriteFragment.newInstance();
// ... all fragments here.
default:
return null;
}
}
Hope to help someone.
I basically have a layout divided into two halves. On my left i have buttons that trigger the various fragments that are displayed in the layout on my right.
When i click on each button the respective fragment is loaded in the fragment display area. Some of the fragments for example Fragment A and Fragment D display complex data by querying a database or getting data from the internet etc. As long as the app is running this data will not change once it is loaded. My question is can i just revert the fragment to the previous state and display it. To be more clear -> i click Fragment A button, in the Fragment A class all the calculations are done and displayed, then i click Fragment B and fragment B is displayed and then C. Now when i click back on Fragment A button i just want it to load back the data, not redo calculation/connections/db queries.
Code being used :
public void onClick(View v) {
switch (position) {
case 0:
newContent = new FragmentA();
break;
case 1:
newContent = new FragmentB();
break;
case 2:
newContent = new FragmentC();
break;
case 3:
newContent = new FragmentD();
break;
case 4:
newContent = new FragmentE();
break;
default: break;
}
if (newContent != null){
mContent = newContent;
switchFragment(newContent);
}
}
public void switchFragment(Fragment fragment) {
mContent = fragment;
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.replacelayout, fragment)
.commit();
}
Fragment Code example
public class FragmentA extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragmenta, container, false);
}
#Override
public void onActivityCreated (Bundle savedInstanceState){
super.onActivityCreated(savedInstanceState);
RUN ASYNTASKS TO CONNECT/QUERYDB AND DIPLAY THE DATA
}
}
Don't know how to go about it - using backStack ?? onResume() ?? because i am not sure what function is invoked when the .replace is invoked.
Thanks in Advance
Instead of using fragment.replace you can use fragment.show and fragment.hide fragmentByTagName. This will keep they're states in memory. It might look something like this.
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
//find the fragment by View or Tag
MyFrag myFrag = (MyFrag)fragmentManager.findFragmentByTag(TAG);
fragmentTransaction.hide(myOldFrag);
fragmentTransaction.show(myNewFrag);
fragmentTransaction.commit();
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Fragment newContent = null;
getSupportFragmentManager()
.beginTransaction()
.hide(mContent)
.commit();
switch (position) {
case 0:
getSupportFragmentManager()
.beginTransaction()
.show(home)
.commit();
mContent = home;
getSlidingMenu().showContent();
break;
case 1:
if(ifdownexsists == true){
getSupportFragmentManager()
.beginTransaction()
.show(download)
.commit();
mContent = download;
getSlidingMenu().showContent();}
else
{ download = new DownloadFile();
getSupportFragmentManager()
.beginTransaction()
.add(R.id.replacelayout, download)
.commit();
mContent = download;
ifdownexsists = true;
getSlidingMenu().showContent();
}
break;
case 2:
if(ifexsists == true){
getSupportFragmentManager()
.beginTransaction()
.show(oldCheckValue)
.commit();
mContent = oldCheckValue;
getSlidingMenu().showContent();}
else
{ oldCheckValue = new CheckValues();
getSupportFragmentManager()
.beginTransaction()
.add(R.id.replacelayout, oldCheckValue)
.commit();
mContent = oldCheckValue;
ifexsists = true;
getSlidingMenu().showContent();
}
break;
Works like a charm ... even keeps the progress of the progressbars in the background and shows them correctly when the fragment becomes visible again