I'm new to Android, and looks like there are still some things missing in my understanding of the activity lifecycle. I have a tabbed application created with a FragmentActivity. A custom ViewPager is managing the presentation of tabs and in one of those tabs I want a simple drill-down with the ability to use the hardware "Back" button to go back up a level to the previous view.
I implemented this drill-down by just substituting an appropriate Fragment into the tab by checking the value of a flag (which determines where in the drill down we are):
public class SectionsPagerAdapter extends FragmentStatePagerAdapter {
#Override
public Fragment getItem(int position) {
Fragment fragment;
switch (position) {
case 0 :
if (stateFlag == 0) fragment = (Fragment) new InitialFragment();
else fragment = (Fragment) new FinalResultsFragment();
break;
case 1:
fragment = (Fragment) new SecondTabFragment();
break;
default:
fragment = (Fragment) new ThirdTabFragment();
break;
}
return fragment;
}
To go back to the "previous" Fragment I do this:
#Override
public void onBackPressed() {
int p = mViewPager.getCurrentItem();
switch (p) {
case 0:
if (stateFlag == 0)) {
super.onBackPressed();
} else {
stateFlag = 0;
mViewPager.getAdapter().notifyDataSetChanged();
mViewPager.setCurrentItem(0);
}
break;
default:
super.onBackPressed();
break;
}
}
This works fine, until I try changing the orientation. The Fragments come back from that change successfully, but when I try pressing the Back button while in the FinalResultsFragment state, I get
IllegalStateException: Fragment already active
E/AndroidRuntime(1094): at android.support.v4.view.PagerAdapter.notifyDataSetChanged(PagerAdapter.java:276)
Why is the change in orientation breaking this particular part of the code?
Edit:
So if I open the app, first tab active, I press a button that loads a different fragment in that tab, then press the Back button - it's all good and the first fragment is loaded, as expected.
But if I open the app, first tab active, I press a button that loads a different fragment in that tab, the rotate the screen, the fragment is re-loaded just fine, then press the Back button - crash!
Edit:
here is the complete error log: http://pastebin.com/ESDTstsm
Related
I am using MeowBottomNavigation and from one fragment, I need to redirect to another fragment. but when I am doing that, the navbar displays the old menu selection.
Please help me.
This is on Home Fragment
enter image description here
Now, its displaying data from 2nd fragment but nav shows HOME only. Ti should be 2nd nav.
enter image description here
CODE
getSupportFragmentManager().beginTransaction().replace(R.id.framelayout, fragment).commit();
bottomNavigation = findViewById(R.id.bottomNav);
bottomNavigation.add(new MeowBottomNavigation.Model(1, R.drawable.ic_nav_home));
bottomNavigation.add(new MeowBottomNavigation.Model(2, R.drawable.ic_baseline_category_24));
bottomNavigation.setOnShowListener(new MeowBottomNavigation.ShowListener(){
#Override public void onShowItem(MeowBottomNavigation.Model item) {
Fragment fragment = null;
switch (item.getId()){
case 1: fragment = new HomeFragment(); break;
case 2: fragment = new CategoryFragment(); break;
}
fragmentLoad(fragment);
}
});
bottomNavigation.show(1, true);
}
public void fragmentLoad(Fragment fragment) {}
When I open a fragment from BottomNavigationBar, it opens perfectly. When I press back button, previous fragment opens but state of BottomNavigationBar does not change.
As in my screenshots, when i backpressed from Account fragment, Home fragment opens but state of BottomNavigationBar has not been changed.
Screenshot 1 - https://drive.google.com/file/d/12cDvhwO1jpG2A1PUsfHQGProqx2cT6Bp/view
Screenshot 2 - https://drive.google.com/file/d/1Zws5sMJeXxts6k6IEBGUGyJYfZP58Czs/view
btnNavBar.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.action_item1:
loadFragment(fragmentManager, new HomeFragment(), "Home");
break;
case R.id.action_item2:
loadFragment(fragmentManager, new SearchFragment(), "Search");
break;
case R.id.action_item3:
loadFragment(fragmentManager, new AccountFragment(), "Account");
break;
}
return true;
}
});
public static void loadFragment(FragmentManager fragmentManager, Fragment fragment, String tag) {
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.frameLayoutContainer, fragment);
fragmentTransaction.commit();
}
I suggest you to not add the fragments to the back stack because it's not the expected behaviour for the Android users. The bottom bar is here to show 3 to 5 different destinations to the user (for example, a news feed, a user profile, etc.). When you select a tab, you should just change the current fragment (and not create a fragments stack). The back button should only close the app, or re-open the previous screen if there is one (definitely not re-open the previous tab :) ). I suggest you to look at this Material Design guidelines for BottomNavigationBar.
The background
I have single activity in my App which loads 2 fragments based on some menu item selection
public class ActivityMain extends AppCompatActivity{
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState == null) {
loadFragment(1); // DEFAULT FRAGMENT, AT THE BEGINNING
}
}
..........................
..........................
// This method is called above, ALSO onItemClick in the Navigation Drawer (code not included for brevity)
public void loadFragment(int position) {
Fragment fragment = null;
switch (position) {
case 1:
fragment = new Fragment1();
break;
case 2:
fragment = new Fragment2();
break;
default:
break;
}
if (fragment != null) {
getFragmentManager().beginTransaction().replace(R.id.fragment_container, fragment, "frag_" + position).addToBackStack(null).commit();
}
}
}
"Fragment1" is a simple fragment with a fixed text in a TextView. "Fragment2" uses SlidingTabLayout to load a Fragment "FragmentViewPager" in a viewpager using FragmentStatePagerAdapter.
The issue I am facing:
Even if I remove "Fragment2" from the Activity's Frame Layout (using getFragmentManager().beginTransaction().remove), Fragment "FragmentViewPager" does not get destroyed, rather it resumes every time Activity resumes.
Question
Why FragmentViewPager is not destroyed with "Fragment2"?
If you are using FragmentStatePagerAdapter then this will not destroy fragment and when you swipe and come back to that fragment it will show old data without refresh. Because of not destroyed by viewpager.
My MainActivity contains a Navigation Drawer with two fragments. The first fragment is loaded automatically when the app starts. I want the app to switch from fragment two to fragment one when the back button is pressed or if the first fragment is added then exit the app. I am adding the fragmentTransaction of the first fragment to a stack and then calling popBackStack in my onBackPressed method. However the behaviour is pretty weird.
When I'm on the first fragment the application should exit (ie, execute super.onBackPressed) however when on the first fragment and pressing back the first fragment is removed from the fragmentholder leading to a blank screen and then on pressing the second time the app closes.
When I'm on the second fragment nothing happens when the back button is pressed the first time and on pressing the back button a second time the app closes. Here's the relevant code from the MainActivity.java
#Override
public void onBackPressed() {
if (musicService.isPng()) moveTaskToBack(true);
if (getFragmentManager().getBackStackEntryCount() > 0)
getFragmentManager().popBackStack("returnFragment", 0);
else super.onBackPressed();
}
private void loadSelection(int i) {
navList.setItemChecked(i, true);
switch (i) {
case 0:
FirstFragment firstFragment = new FirstFragment();
fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragmentholder, firstFragment)
.addToBackStack("returnFragment")
.commit();
break;
case 1:
SecondFragment secondFragment = new SecondFragment();
fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.fragmentholder, secondFragment)
.commit();
break;
case 2:
musicService.removeNotification();
musicService.stopSelf();
MainActivity.this.finish();
}
}
loadSelection(0) is called in the onCreate of the MainActivity
Unlike given in the code, I have tried various modes of implementing the popBackStack() method but all of them lead to the same result. Just to add I don't want to implement a workaround since there are only 2 fragments since I am already working on adding new fragments.
try this,
call method .addToBackStack("returnFragment") while loading second fragment and remove it from first transaction.
I have an Android activity that holds and manages six fragments, is fragment is a step in a flow, some of the fragments are replaced and some of them are added.
The Activity just uses a Framelayout as the container for the fragments as follows:
<FrameLayout
android:id="#+id/content"
android:layout_below="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Then the flow of the fragments is like this:
//Activity starts, add first Fragment
fragmentManager.beginTransaction().replace(R.id.content, FirstFragment.newInstance(listOfItems)).commit();
then
//User pressed button, activity got callback from first fragment
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(R.id.content, fragment2);
transaction.addToBackStack("frag2");
transaction.commit();
then
//Another callback from Frag2, perform the add of frag 3
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.add(R.id.content, fragment3);
transaction.addToBackStack("frag3");
transaction.commit();
And so on....
I also manage the back stack from the Activity like this:
//Controlling the back stack when the user selects the soft back button in the toolbar
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
if (fragmentManager.getBackStackEntryCount() == 0) {
super.onBackPressed();
overridePendingTransition(R.anim.no_change, R.anim.slide_down);
} else {
if(!BaseFragment.handleBackPressed(getSupportFragmentManager())){
super.onBackPressed();
Fragment fragment = fragmentManager.getFragments()
.get(fragmentManager.getBackStackEntryCount());
fragment.onResume(); //Make sure the fragment that is currently at the top of the stack calls its onResume method
}
}
return true;
}
return super.onOptionsItemSelected(item);
}
//Controlling the back stack when the user selects the "hardware" back button
#Override
public void onBackPressed() {
if (fragmentManager.getBackStackEntryCount() == 0) {
super.onBackPressed();
overridePendingTransition(R.anim.no_change, R.anim.slide_down);
} else {
if(!BaseFragment.handleBackPressed(getSupportFragmentManager())){
super.onBackPressed();
Fragment fragment = fragmentManager.getFragments()
.get(fragmentManager.getBackStackEntryCount());
fragment.onResume(); //Make sure the fragment that is currently at the top of the stack calls its onResume method
}
}
}
My problem is that I open the app and go to this Activity which loads the fragments and then go through the flow to a certain stage ( I haven't narrowed it down yet) then I press the home button and blank my screen. Now after a certain amount of time when I open the app again it opens on the fragment I left but everything seems to be messed up, when I press back it seems to pop the wrong fragment and the UI becomes mixed up with the different fragments.
My guess is that when I open the app again the Activity onResume or the Fragment onResume or some lifecycle event is being called that I am not handling correctly?
So I was wondering is there best practices, guidelines or patterns that should be adhered to when using a Fragment pattern like I am doing so?
Since you have so many fragments in one activity, and they use the same container, that means all fragments are in the same place, and only one fragment will show at a time.
So why don't you use ViewPager and let FragmentPagerAdapter manager these fragments? In this way, you do not need to manager fragment lifecycle by yourself, you just need to override FragmentPagerAdapter methods:
to create fragment instance by getItem,
to update fragment by getItemPosition and Adapter.notifyDataSetChanged(),
to show selected fragment by mViewPager.setCurrentItem(i)
Code snippets, detail refer to https://github.com/li2/Update_Replace_Fragment_In_ViewPager/
private FragmentPagerAdapter mViewPagerAdapter = new FragmentPagerAdapter(getSupportFragmentManager()) {
#Override
public int getCount() {
return PAGE_COUNT;
}
// Return the Fragment associated with a specified position.
#Override
public Fragment getItem(int position) {
Log.d(TAG, "getItem(" + position + ")");
if (position == 0) {
return Page0Fragment.newInstance(mDate);
} else if (position == 1) {
return Page1Fragment.newInstance(mContent);
}
return null;
}
#Override
// To update fragment in ViewPager, we should override getItemPosition() method,
// in this method, we call the fragment's public updating method.
public int getItemPosition(Object object) {
Log.d(TAG, "getItemPosition(" + object.getClass().getSimpleName() + ")");
if (object instanceof Page0Fragment) {
((Page0Fragment) object).updateDate(mDate);
} else if (object instanceof Page1Fragment) {
((Page1Fragment) object).updateContent(mContent);
}
return super.getItemPosition(object);
};
};