I've handled fragments before but that was with a view pager and tablayout and I was able to keep the fragments state by building an array and returning a frag from that array.
but now using fragment manager, I have access to add and replace, but both create new instances of my fragments without saving what has occured (i.e. typing on editText).
How can I keep or reuse the fragments I've created when navigating from the menu (keep in mind some of these fragments will have a child backstack, the back button should only take you out of deep fragments not my main ones)
current code
private void initNavigation(){
drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
/* Initialize fragments for navigation drawer */
fragments = new Fragment[]{
new ProfileFragment(),
new StatsFragment()};
/* Set selection of navigation item */
nav_view = (NavigationView) findViewById(R.id.nav_view);
nav_view.setNavigationItemSelectedListener(
new NavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
item.setChecked(true);
FragmentManager fragmentManager = getSupportFragmentManager();
switch (item.getItemId()) {
case R.id.nav_routine:
if(fragmentManager.findFragmentByTag("TAG0") != null){
Log.i("fm", "reloading profile frag");
fragmentManager.beginTransaction().replace(R.id.content_main,
fragmentManager.findFragmentByTag("TAG0"), "TAG0");
} else{
fragmentManager.beginTransaction().replace(R.id.content_main, fragments[0], "TAG0").commit();
}
break;
default:
if(fragmentManager.findFragmentByTag("TAG1") != null){
Log.i("fm", "reloading stats frag");
fragmentManager.beginTransaction().replace(R.id.content_main,
fragmentManager.findFragmentByTag("TAG1"), "TAG1");
} else{
fragmentManager.beginTransaction().replace(R.id.content_main, fragments[1], "TAG1").commit();
}
break;
}
drawerLayout.closeDrawers();
return true;
}
}
);
}
Edit solved:
I found out that in order for a view to be saved it must have an ID (makes sense). Also I wasn't using .commit() with some of my .replace in my prior code
nav_view.setNavigationItemSelectedListener(
new NavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
item.setChecked(true);
FragmentManager fragmentManager = getSupportFragmentManager();
switch (item.getItemId()) {
case R.id.nav_routine:
fragmentManager.beginTransaction().replace(R.id.content_main, fragments[0], "TAG0").commit();
break;
default:
fragmentManager.beginTransaction().replace(R.id.content_main, fragments[1], "TAG1").commit();
break;
}
drawerLayout.closeDrawers();
return true;
}
}
);
Im not clear with your question .But this is how keeping backState of fragments.
Fragment mfragment=new Fragmentname();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.fragment_container, mfragment);
String backStateName = mfragment.getClass().getName();
if (position != 0) {
ft.addToBackStack(backStateName);
}
ft.commit();
As you could save the data of a Fragment when leaving from it I think you already know about SavedInstanceState. Anyway, while you're transacting one fragment to another from your Activity using fragment transaction, you need to add addToBackStack(null) before your commit. So the transaction should look like
fragmentManager.beginTransaction().replace(R.id.content_main, fragments[0], "TAG0").addToBackStack(null).commit();
And override the onBackPressed function of your Activity like this.
#Override
public void onBackPressed() {
if (getSupportFragmentManager().getBackStackEntryCount() > 0)
getSupportFragmentManager().popBackStack();
else
super.onBackPressed();
}
Related
I have 3 fragments, Home, Menu and orders they can be loaded by bottomnavigtioview items and the title shown by it as well
once navigate from Home to Orders then if you want to go back to Home From Orders the title still "Orders"
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
Fragment fragment;
switch (item.getItemId()) {
case R.id.navigation_home:
toolbar.setTitle(getResources().getString(R.string.title_home));
fragment = new HomeFragment();
loadFragment(fragment);
return true;
case R.id.navigation_menu:
toolbar.setTitle(getResources().getString(R.string.fragment_title_menu));
fragment = new ProductFragment();
loadFragment(fragment);
return true;
case R.id.navigation_orders:
toolbar.setTitle(getResources().getString(R.string.title_orders));
fragment = new OrdersFragment();
loadFragment(fragment);
return true;
}
return false;
}
};
private void loadFragment(Fragment fragment) {
// load fragment
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.frame_container, fragment);
transaction.addToBackStack(null);
transaction.commit();
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = this;
navigation = findViewById(R.id.navigation);
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
// attaching bottom sheet behaviour - hide / show on scroll
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) navigation.getLayoutParams();
layoutParams.setBehavior(new BottomNavigationBehavior());
toolbar.setTitle(getResources().getString(R.string.title_home));
Fragment f = HomeFragment.newInstance();
getSupportFragmentManager().beginTransaction()
.replace(R.id.frame_container, f)
.commit();
}`
I have Also Tried this but its not working
`
fragmentmanger.addOnBackStackChangedListener -> public void onBackStackChanged() {
}`
Instead of calling toolbar.setTitle in your onNavigationItemSelected method, move it to onCreateView of each fragment. When you navigate back to a Fragment, onCreateView is executed again.
It worked for me when calling it in onResume like this in each fragment:
if(getActivity()!=null) {
((MainActivity)getActivity()).setActionBarTitle
(context.getResources().getString(R.string.YOUR_TITLE));
this is the function setActionBarTitle in MainActivity
public void setActionBarTitle(String title) {
toolbar.setTitle(title);
}
I have a bottom navigation bar that contains 4 fragments and when a tab is selected a new instance of that fragment is loaded.
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 = HomeFragment.newInstance();
break;
case R.id.navigation_cards:
fragment = CardsFragment.newInstance();
break;
case R.id.navigation_deals:
fragment = DealsFragment.newInstance();
break;
case R.id.navigation_settings:
fragment = SettingsFragment.newInstance();
break;
}
if (fragment != null) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.content, fragment);
fragmentTransaction.commit();
}
return true;
}
};
Now in my HomeFragment there is a RecyclerView that On Item Select it opens a new Fragment:
myViewHolder.cardView.setOnClickListener(view -> {
System.out.println("clicked");
Fragment fragment = new TargetDetailsFragment();
FragmentTransaction ft = ((AppCompatActivity) context).getSupportFragmentManager()
.beginTransaction();
ft.replace(R.id.content, fragment).addToBackStack(null);
ft.commit();
});
I want to add a back button to the TargetDetails Fragments that takes you back to the home page when selected and I attempted doing that by implementing OnBackStackChangedListener in the Main activity
#Override
public void onBackStackChanged() {
shouldDisplayHomeUp();
}
public void shouldDisplayHomeUp(){
//Enable Up button only if there are entries in the back stack
boolean canback = getSupportFragmentManager().getBackStackEntryCount()>0;
getSupportActionBar().setDisplayHomeAsUpEnabled(canback);
}
#Override
public boolean onSupportNavigateUp() {
//This method is called when the up button is pressed. Just the pop back stack.
getSupportFragmentManager().popBackStack();
return true;
}
}
but the problem is when I click on it its reloads the HomeFragment Again but I simply want it to go back to the saved instance of that Fragment
Here is my code that I have used for the ViewPager, the idea is to see if the current page number is 0 than to proceed super.onBackPressed(); otherwise go to the previous fragment:
#Override
public void onBackPressed() {
if(vpPager.getCurrentItem()!=0) {
vpPager.setCurrentItem(vpPager.getCurrentItem()-1, true);
} else {
super.onBackPressed();
}
}
By adding below code in your Activity.
The fragment back stack can be managed.
#Override
public void onBackPressed() {
int count = getFragmentManager().getBackStackEntryCount();
if (count == 0) {
super.onBackPressed();
//additional code
} else {
getFragmentManager().popBackStack();
}
}
Hello #Bolu Okunaiya i think you should try this it will help you to manage backstack to desired fragment without loading same fragment again.
For Stop loading previous fragment you should use "add" instead of "replace" with your FragmentTransaction
Inside your MainActivity
#Override
public void onBackStackChanged() {
//shouldDisplayHomeUp();
Fragment currentFragment = getActivity().getFragmentManager()
.findFragmentById(R.id.fragment_container);
if (currentFragment instanceof TargetDetails) {
Log.v(TAG, "your current fragment is TargetDetails");
popBackStackImmediate(); //<<<< immediate parent fragment will open,which is your HomeFragement
}
}
To use popBackStackImmediate() you need to *replace FragmentA with FragmentB and use addToBackstack() before commit().
I have created a tabbed activity inside a navigation drawer activity. I have linked the lists inside the navigation drawer with individual fragments. I also used the command ft.addToBackStack(null) before ft.commit(). It was perfectly fine and I was able to use it to close the fragments inside the navigation drawer. now I have created webviews inside the fragments of the tabbed activity (home page). I added the onBackPressed to go back inside the webview and now the addToBackStack method stopped working.
For the navigation drawer fragments,
private void dispaySelectedScreen(int id) {
Fragment fragment = null;
switch (id) {
case R.id.fragment1:
fragment = new Fragment1();
break;
case R.id.fragment2:
fragment = new Fragment2();
break;
case R.id.fragment3:
fragment = new Fragment3();
break;
case R.id.fragment4:
fragment = new fragment4();
break;
}
if (fragment != null) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_main, fragment);
ft.addToBackStack(null);
ft.commit();
}
onBackPressed Method
#Override
public void onBackPressed() {
if (hWebView.canGoBack()) {
hWebView.goBack();
} else if (!getFragmentManager().popBackStackImmediate())
if (sWebView.canGoBack()) {
sWebView.goBack();
} else if (!getFragmentManager().popBackStackImmediate())
if (dWebView.canGoBack()) {
dWebView.goBack();
} else if (!getFragmentManager().popBackStackImmediate());
}
Please help me out!!!
try this code: override onBackPressed() in your tabbed activity (home page).
#Override
public void onBackPressed() {
FragmentManager fm = getSupportFragmentManager();
if (hWebView.canGoBack()) {
hWebView.goBack();
} else if (!getFragmentManager().popBackStackImmediate())
if (fm.getBackStackEntryCount() > 0) {
fm.popBackStack();
} else {
super.onBackPressed();
}
}
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.
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