fragment created twice on orientation change - android

I know this question has been asked, but I haven't succeeded with the answers.
I have a fragment with a recycler view in it. I have a button which can show and hide this fragment. This all works fine until the orietation of the screen is changed. Then the fragment is recreated, and the one on top is shown and hidden, but there is one behind which stays there.
I understand I need to use
if (savedInstanceState == null)
somewhere, but cannot manage to succeed where. Thanks very much,
Here is my code.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_maps);
recyclerViewFragment = new RecyclerViewFragment();
FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
trans.add(R.id.recycle_view_container, recyclerViewFragment, RECYCLER_FRAGMENT);
trans.commit();
trans.show(recyclerViewFragment);
Button showHideButton = (Button)findViewById(R.id.button_show_hide);
showHideButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
showHideFragment(recyclerViewFragment);
}
});
showHideButton.playSoundEffect(SoundEffectConstants.CLICK);
}
public void showHideFragment(final Fragment fragment){
FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
trans.setCustomAnimations(android.R.anim.slide_in_left , android.R.anim.slide_out_right);
if (fragment.isHidden()) {
trans.show(fragment);
Log.d("hidden","Show");
} else {
trans.hide(fragment);
Log.d("Shown","Hide");
}
trans.commit();
}
Thanks very much guys!!!!!!!

I suggest make some changes to your code
I suppose that RECYCLER_FRAGMENT is a constant that contains a tag used to mark your fragment
RecyclerViewFragment recyclerViewFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_maps);
if(savedInstanceState == null) {
recyclerViewFragment = new RecyclerViewFragment();
FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
trans.add(R.id.recycle_view_container, recyclerViewFragment, RECYCLER_FRAGMENT);
trans.commit();
}else{
recyclerViewFragment = getSupportFragmentManager().findFragmentByTag(RECYCLER_FRAGMENT);
if(savedInstanceState.getString("vi").equals("hid")){
getSupportFragmentManager().beginTransaction().hide(recyclerViewFragment).commit();
}
}
Button showHideButton = (Button)findViewById(R.id.button_show_hide);
showHideButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
FragmentManager manager = getSupportFragmentManager();
if (fragment.isVisible()) {
manager.beginTransaction().hide(recyclerViewFragment).commit();
} else {
manager.beginTransaction().show(recyclerViewFragment).commit();
}
}
});
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if(recyclerViewFragment.isVisible() == true){
outState.putString("vi","vis");
}else{
outState.putString("vi", "hid");
}
}

Instead of this:
trans.add(R.id.recycle_view_container, recyclerViewFragment, RECYCLER_FRAGMENT);
try this:
trans.replace(R.id.recycle_view_container, recyclerViewFragment, RECYCLER_FRAGMENT);
The name is misleading. Replace actually works as add also, if the first time. What is happening is that when your activity rotate, onCreate() is called again so you are adding the same fragment on top of the existing one

Related

getBackStackEntryCount return 0 when Activity is recreated

I know this has to be simple and I'm probably not seeing the solution.
Here is the brief description of what I have:
SignInActivity(AppCompatActivity) - handle the Firebase authentication, on success calls the method:
private void onAuthSuccess(FirebaseUser user) {
// Go to MainActivity
startActivity(new Intent(SignInActivity.this, MainActivity.class));
finish();
}
MainActivity(AppCompatActivity) - handle the menus for the application, this menu in particular are fragments with buttons. When a button is clicked I change the fragment that contains other buttons. Something like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(R.anim.slide_in_up, R.anim.slide_out_left)
.replace(R.id.fragmentContent, MainMenuFragment.newInstance())
.commitNow();
}
getSupportFragmentManager().addOnBackStackChangedListener(new
FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
int stackHeight = getSupportFragmentManager().getBackStackEntryCount();
getSupportActionBar().setHomeButtonEnabled(stackHeight > 0);
getSupportActionBar().setDisplayHomeAsUpEnabled(stackHeight > 0);
}
});
}
public void replaceFragments(Class fragmentClass, boolean isBack) {
Fragment fragment = null;
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
if (isBack) {
ft.setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right);
} else {
ft.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left, R.anim.slide_in_left, R.anim.slide_out_right);
}
try {
fragment = (Fragment) fragmentClass.newInstance();
} catch (Exception e) {
fragment = null;
e.printStackTrace();
}
if (fragment != null) {
ft.replace(R.id.fragmentContent, fragment);
ft.addToBackStack(fragmentClass.getName());
ft.commit();
}
}
MainMenuFragment(Fragment) - First set of options, several buttons on top of each other. Depending on the button clicked will call MainActivity.replaceFragment passing the next Fragment to go.
public class MainMenuFragment extends Fragment {
private static final String TAG = "MainMenuFragment";
public static MainMenuFragment newInstance() {
return new MainMenuFragment();
}
public MainMenuFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_main_menu, container, false);
final Button btnAssets = view.findViewById(R.id.btnAssets);
final Button btnAudit = view.findViewById(R.id.btnAudit);
btnAssets.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.i(TAG, "BtnAssets_onClick");
((MainActivity)getActivity()).replaceFragments(AssetMenuFragment.class);
}
});
btnAudit.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.i(TAG, "BtnAudit_onClick");
((MainActivity)getActivity()).replaceFragments(AuditMenuFragment.class);
}
});
return view;
}
}
The AssetMenuFragment and the AuditMenuFragment are pretty much the same as the MainMenu, only the text for the buttons and some layout details changes.
When I'm using the app I first signIn, which leads me to the MainActivity, which loads the MainMenuFragment on onCreate. There I'm presented with two buttons, one to go to the AssetMenuFragment and the other to go to the AuditMenuFragment, they replace the fragment with their according layouts.
If I click the Asset button, once the fragment is replaced, because of:
getSupportActionBar().setHomeButtonEnabled(stackHeight > 0);
getSupportActionBar().setDisplayHomeAsUpEnabled(stackHeight > 0);
I'm presented with the back arrow to go back to MainMenuFragment. Everything works as expected.
Now the problem! If I'm in this AssetMenuFragment, with my beautiful back arrow showing on the ActionBar and decided to click the "Square" button on the device, which is probably run the onPause and onStop, and them click on the app again, which will run the onCreate and onStart again, my back arrow disappears, because now int stackHeight = getSupportFragmentManager().getBackStackEntryCount(); is zero.
How can I save my stack and restore it later so I can press back on the AssetMenuFragment and go back to MainMenuFragment.
It is a lot to read, but I'll appreciate the help, thanks!
In the end I knew it had to be something simple.
Both checks are correct.
getSupportActionBar().setHomeButtonEnabled(stackHeight > 0);
getSupportActionBar().setDisplayHomeAsUpEnabled(stackHeight > 0);
The problem was that I didn't check for them on onCreate, only on the getSupportFragmentManager().addOnBackStackChangedListener event.
Here is the MainActivity now:
private ActionBar actionBar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (savedInstanceState == null) {
getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left)
.replace(R.id.fragmentContent, MainMenuFragment.newInstance())
.commitNow();
}
actionBar = getSupportActionBar();
updateActionBarBackButton();
getSupportFragmentManager().addOnBackStackChangedListener(new
FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
updateActionBarBackButton();
}
});
}
private void updateActionBarBackButton() {
int stackHeight = getSupportFragmentManager().getBackStackEntryCount();
getSupportActionBar().setHomeButtonEnabled(stackHeight > 0);
getSupportActionBar().setDisplayHomeAsUpEnabled(stackHeight > 0);
}

android how to manage fragments in navigation bar without data lose

I have a store app
In this application, the fragments each take their own data from the server and are managed by navigation bar.
I want to receive data any fragment one only once , and will only maintain its last state some time later.
I used the replace , hide functions, but I did not get good results
Can anyone suggest a solution?
nav_partition.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View arg0) {
FragmentManager fm = getSupportFragmentManager();
Fragment lastFragment = fm.findFragmentById(R.id.fragment_layout);
if (lastFragment instanceof FragmentPartition)
return;
FragmentTransaction fragmentTransaction = fm.beginTransaction();
fragmentTransaction.hide(lastFragment);
if (fragmentPartition == null)
fragmentPartition = new FragmentPartition();
G.log_toast("is added : " + fragmentPartition);
fragmentTransaction.replace(R.id.fragment_layout, fragmentPartition);
fragmentTransaction.commit();
}
});
You can use savedinstancestate bundle for saving your data. and it can be reused on resuming. Try bellow code.
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putBoolean("bln", true);
savedInstanceState.putInt("it", 1);
savedInstanceState.putString("str", "Hello");
}
#Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
boolean bln = savedInstanceState.getBoolean("bln");
int in = savedInstanceState.getInt("it");
String str = savedInstanceState.getString("str");
}

onBackPressed with fragments and saving fragment state

I want to save fragment state so I use fragment .add() and fragment .show() methods.
the part I use to add fragments in my mainActivity is
android.support.v4.app.FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.main_content, fragment, CURRENT_TAG);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
On back pressed in the fragment and getting back to it the get fragment by tag returns null
if (getSupportFragmentManager().findFragmentByTag(CURRENT_TAG) != null) {
drawerLayout.closeDrawers();
Runnable mPendingRunnable = new Runnable() {
#Override
public void run() {
android.support.v4.app.FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out);
hideTransactions(CURRENT_TAG);
fragmentTransaction.show(getSupportFragmentManager().findFragmentByTag(CURRENT_TAG)).commit();
}
};
although onBackPressed the state of the fragment is right(TAG_PREV returns not null) in the on back pressed method
#Override
public void onBackPressed() {
if (drawerLayout.isDrawerOpen(GravityCompat.START)) {
drawerLayout.closeDrawers();
return;
}
if (CURRENT_TAG == TAG_HOME) {
return;
}
navItemIndex = 0;
TAG_PREV = CURRENT_TAG;
CURRENT_TAG = TAG_HOME;
loadHomeFragment();
super.onBackPressed();
}
addToBackStack saves the transaction, in this case: fragmentTransaction.add(R.id.main_content, fragment, CURRENT_TAG); and when you press back(onBackPressed) you are reversing that transaction. So that will imply you are removing the previously added fragment.
It is not possible to maintain entire fragment in back stack, There is no provision given by android teams.
You have to use onSaveInstanceState for saving current state/data when you are leaving fragment.
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
Log.i(TAG, " onSaveInstanceState.");
savedInstanceState.putString("greeting", "Hello");
}
After that when again coming on that fragment you have to use.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String greeting = (savedInstanceState != null) ? savedInstanceState.getString("greeting") : "null";
Log.i(TAG, " onCreate: " + greeting);
}
Please let me if it dosen't work for you.

Android SlidingMenu lag at first use

I am experiencing some problems with the slidingmenu library from jfeinstein10: https://github.com/jfeinstein10/SlidingMenu
The problem I observe is that at first use, when the menu opens, it lags showing some black space. Then when I close it and reuse it again, everything is smooth, nothing to declare.
I basically have an Activity extending the SlidingFragmentActivity, and 2 fragments. One for the left menu and another one for the main content.
Here is how I implemented it, I'm investigating this but I really don't know what I've missed:
public class CavendishSlidingFragmentActivity extends SlidingFragmentActivity {
private final String TAG = CavendishSlidingFragmentActivity.class.getName();
protected FragmentManager fragmentManager;
protected Fragment mContent;
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
fragmentManager = getSupportFragmentManager();
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
getSlidingMenu().setBehindOffset((int) (metrics.widthPixels * 0.2));
}
public void onClickMenu(View v)
{
if (getSlidingMenu().isMenuShowing())
getSlidingMenu().showContent();
else
getSlidingMenu().showMenu();
}
public void onClickMenuItem(View v)
{
Long position = (Long) v.getTag();
switch (position.intValue())
{
case Utils.CAT_RESTAURANT:
showFragment(new RestaurantFragment());
break;
case Utils.CAT_SLEEP:
showFragment(new SleepFragment());
break;
case Utils.CAT_DISCOVER:
showFragment(new DiscoverFragment());
break;
case Utils.CAT_TRANSPORT:
showFragment(new TransportFragment());
break;
}
}
private void showFragment(Fragment fragment)
{
if (mContent != fragment)
{
mContent = fragment;
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.main_fragment, fragment);
fragmentTransaction.commit();
}
getSlidingMenu().showContent();
}
}
MainActivity.java
public class MainActivity extends CavendishSlidingFragmentActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setBehindContentView(R.layout.fragment_menu);
setContentView(R.layout.fragment_main);
// set the Above View Fragment
if (savedInstanceState != null)
mContent = fragmentManager.getFragment(savedInstanceState, "mContent");
if (mContent == null)
{
mContent = new RestaurantFragment();
}
fragmentManager
.beginTransaction()
.replace(R.id.main_fragment, mContent)
.commit();
}
#Override
public void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
fragmentManager.putFragment(outState, "mContent", mContent);
}
}
I can provide further code but I think the issue might be here, since the menu is drawing itself at first use, causing the lag. I believe I have to force the menu to be drawn before showing it.
Looks like there is something wrong with the library when rendering the menu while it's being opened. My design didn't fit the library I guess, well I just passed to the default Android navigation drawer. Bit different in behavior but the design remains unchanged, works fine for me.

How to implement saveFragmentInstanceState?

Could you anyone advice me how to implement saveFragmentInstanceState() or some other methods to retrieving fragment instance when back button is pressed. I use own stack for fragment, viz code bellow:
public class stackA extends ActivityInTab {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i("StackA", "onCreate");
if(savedInstanceState == null){
navigateTo(new fragmentA());
}
}
}
Next there is implementation of ActivityInTab class. I think for this class must be implemented methods for saving and retrieving fragment state but still I can't find the way how to do this.
abstract class ActivityInTab extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_in_tab);
Log.i("ActivityInTab", "onCreate");
}
protected void navigateTo(Fragment newFragment) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
ft.replace(R.id.content, newFragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.addToBackStack(null);
ft.commit();
}
#Override
public void onBackPressed() {
Log.i("ActivityInTab", "onBackPressed");
FragmentManager manager = getSupportFragmentManager();
if (manager.getBackStackEntryCount() > 1) {
super.onBackPressed();
} else {
// Otherwise, ask user if he wants to leave :)
//showExitDialog();
super.onBackPressed();
}
}
}
And finally, this is imlementation of Fragments:
public class fragmentA extends Fragment {
private LinearLayout ll;
private FragmentActivity fa;
private String textViewText;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.i("Fragment A", "onCreateView");
fa = super.getActivity();
ll = (LinearLayout) inflater.inflate(R.layout.fragmenta, container,
false);
Button next = (Button) ll.findViewById(R.id.button1);
Button randomBtn = (Button) ll.findViewById(R.id.random_button);
final TextView randomText = (TextView) ll
.findViewById(R.id.random_textview);
next.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
((ActivityInTab) getActivity()).navigateTo(new
fragmentB());
}
});
randomBtn.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// TODO Auto-generated method stub
randomText.setText(String.valueOf(Math.random()));
textViewText = randomText.getText().toString();
}
});
if (savedInstanceState != null) {
randomText.setText(savedInstanceState.getString("TextView"));
}
return ll;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// setRetainInstance(true);
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString("TextView", textViewText);
}
}
I'm able to save instance state only for orientation changes using onSaveInstanceState(Bundle outState), but no for back button.
I would be very grateful for any advice, thanks.
In the onBackPressed Method you have access to the FragmentManager which can retrieve any fragment for you using the FragmentManager methods "findFragmentById" or "findFragmentByTag".
You can get direct access to any fragment whos state you want to save using either of those two methods depending on how you added the fragments.

Categories

Resources