I have an activity that contains a fragment in it. The fragment has a progress bar and a text view, along with some other stuff. The progress bar and the text view both are visible sometimes, invisible at other times depending on some application logic.
What I'm trying to do is save the current state of both these views when the screen is rotated. Here is the relevant code in my fragment -
#Override
public void onSaveInstanceState(Bundle outState) {
// pbTranscribe is my progress bar
if (pbTranscribe != null) {
Log.d(TAG, "saving pb visibility");
boolean visibility = (pbTranscribe.getVisibility() == ProgressBar.VISIBLE);
outState.putBoolean("pbVisible", visibility);
}
}
#Override
public void onViewStateRestored(Bundle savedInstanceState) {
super.onViewStateRestored(savedInstanceState);
if (savedInstanceState != null) {
Log.d(TAG, "bundle available !!");
boolean pbVisible = savedInstanceState.getBoolean("pbVisible", false);
Log.d(TAG, "is visible? " + pbVisible);
if (pbVisible) {
pbTranscribe.setVisibility(ProgressBar.VISIBLE);
}
}
}
The above code doesn't seem to be working for some reason. If I rotate my screen when the progress bar is visible, logcat prints all the above messages ("saving pb visibility", "bundle available !!", "is visible? true"). I know for a fact that my application logic doesn't set the visibility to invisible during this time.
Even though the value obtained from the bundle is true, the progress bar doesnt become visible, i.e. pbTranscribe.setVisibility(ProgressBar.VISIBLE); is apparently not doing its job.
Where am I going wrong ? How do I successfully maintain the progress bar state ?
I have also tried to restore the state in onCreateView() and onActivityCreated(), same results. Also, I have tried saving the text view state in a similar fashion, but that also gives the same results. Saving the text view state with android:freezesText="true" also did not do the trick.
EDIT: This is how I add the fragment to the activity, in the activity's onCreate() method -
#Override
protected void onCreate(Bundle savedInstanceState) {
...
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragments_frame, new WatsonFragment())
.commit();
...
}
When screen rotated, Activity will call onCreate and create another WatsonFragment, modify you code to this
if(savedInstanceState==null){
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragments_frame, new WatsonFragment())
.commit();
}
Related
EDIT:
After some more tinkering around, I found out the reason for my previous problem (see below. TL;DR: I'm trying to pass a Bundle from an activity to its fragment by replacing the fragment) is that when replacing fragments like this:
AddEditActivityFragment fragment = new AddEditActivityFragment();
Bundle arguments = getIntent().getExtras();
fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction()
.replace(R.id.add_edit_fragment, fragment)
.commit();
the replaced fragment does not get destroyed (as it should be according to a bunch of sources), which I verified by overriding and adding logging to onPause, onStop, onDestroyView, onDestroy, etc. None of them are called.
Calling popBackStackImmediate also does nothing.
What can I do?
Previous question title:
"Setting FloatingActionButton icon dynamically with setImageDrawable doesn't have any effect."
I have a FAB inside a fragment (called AddEditFragment), that serves to save user input to database.
To differentiate between editing rows from the DB and creating new ones, I had its icon set either a "send" icon or a "save" icon.
By default, the button is set to "send":
<android.support.design.widget.FloatingActionButton
android:id="#+id/add_edit_fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="#dimen/fab_margin"
app:layout_anchor="#id/add_edit_toolbar"
app:layout_anchorGravity="bottom|right|end"
app:srcCompat="#drawable/ic_send_white_24dp"/>
I used to set the icon to "save" by implementing an interface in my fragment, which serves to pass data from the containing activity to the fragment, using this method:
#Override
public void receiveData(Serializable data) {
Log.d(TAG, "receiveData: called");
mFoodItem = (FoodItem) data;
if (mFoodItem != null) {
mEditMode = true;
fab.setImageDrawable(ContextCompat.getDrawable(getContext(), R.drawable.ic_save_white_24dp));
utilDisplayFoodItem();
}
}
The call the setImageDrawable worked fine and changed the icon from "send" to "save" properly.
Here is when I run into trouble.
I am trying to remove my AddEditFragment class' dependency on my AddEditActivity class, by removing said interface implementation and passing the data required by the fragment via Bundle:
#Override
protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate: starts");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_edit);
AddEditActivityFragment fragment = new AddEditActivityFragment();
Bundle arguments = getIntent().getExtras();
boolean editMode = arguments != null;
fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction()
.replace(R.id.add_edit_fragment, fragment)
.commit();
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close_white_24dp);
getSupportActionBar().setTitle((editMode) ? "Edit Item:" : "Create Item:");
getSupportActionBar().setElevation(0);
Log.d(TAG, "onCreate: ends");
}
In this context, I removed receiveData method from the fragment, and placed this line:
fab.setImageDrawable(ContextCompat.getDrawable(getContext(), R.drawable.ic_save_white_24dp));
in various likely places in my fragment class (the likeliest being inside its onCreateView method).
It doesn't seem to have any effect. My cases are:
Just adding said setImageDrawable call - sets icon to "save" in both add/edit modes, but then I have no "send" icon.
Setting either case dynamically (setImageDrawable call is inside if block) - icon is set to "send" in both add/edit modes.
Removing default "send" icon from XML, then setting either case dynamically - icon is set to "send" in both add/edit modes.
Removing default icon from XML, then setting only to "save" dynamically (no if block) - sets icon to "save" in both add/edit modes, but then I have no "send" icon.
It seems the setImageDrawable call, which worked perfectly in my receiveData interface method, doesn't have any effect (at least when an icon is already set, or when inside if block).
I'm at a loss and would appreciate any help!
In reply to #ColdFire:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d(TAG, "onCreateView: starts");
View view = inflater.inflate(R.layout.fragment_add_edit, container, false);
ButterKnife.bind(this, view);
Bundle arguments = getArguments();
if (arguments != null) {
mFoodItem = (FoodItem) arguments.getSerializable(FoodItem.class.getSimpleName());
if (mFoodItem != null) {
mEditMode = true;
// Tried calling setImageDrawable here
}
}
// Tried calling setImageDrawable here
if (!mEditMode) {
// If adding an item, initialize it for right now's date and time
Calendar now = Calendar.getInstance();
mFoodItem.setDate(now.get(Calendar.YEAR), now.get(Calendar.MONTH), now.get(Calendar.DAY_OF_MONTH));
mFoodItem.setTime(now.getTimeInMillis() / Constants.MILLISECONDS);
}
utilDisplayFoodItem();
utilSetOnClickListeners();
setHasOptionsMenu(true);
// Tried calling setImageDrawable here
Log.d(TAG, "onCreateView: ends");
return view;
}
I should mention that everything else that depends on the Bundle data works correctly.
You might want to clear back stack by using the following method
private void clearBackStack() {
FragmentManager manager = getSupportFragmentManager();
if(manager.getBackStackEntryCount() > 0) {
FragmentManager.BackStackEntry first = manager.getBackStackEntryAt(0);
manager.popBackStackImmediate(first.getId(), FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}
You might want to check this post, Thanks
This will only destroy the current fragment to be replaced. If you want to destroy all you might want to read this stack question
I have an app that has a main activity that the user can select an item from. That selection brings up a fragment (TracksActivityFragment) that itself is another list. When an item of that list is selected, a fragment is added that is a DialogFragment. So far so good, but when I rotate the device, the AFragment's onCreate() gets called and then the DailogFragment's onCreate() gets called, then it dies with the IllegalStateException saying that it dies on AFragment's Activity line 20 (setContentView).
Here is a part of that Activity with the line in question:
public class TracksActivity extends AppCompatActivity
{
private String mArtistName;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tracks); //DIES HERE
Here is the onCreate of the fragment
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if(savedInstanceState != null)
{
//We've got data saved. Reconstitute it
mTrackRowItemList = savedInstanceState.getParcelableArrayList(KEY_ITEMS_LIST);
}
}
The DialogFragment gets created in the TracksFragment like this:
PlayerFragment fragment = PlayerFragment.newInstance(mTrackRowItemList, i, mArtistBitmapFilename);
// The device is smaller, so show the fragment fullscreen
FragmentTransaction transaction = fragMan.beginTransaction();
// For a little polish, specify a transition animation
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
// To make it fullscreen, use the 'content' root view as the container
// for the fragment, which is always the root view for the activity
transaction.add(android.R.id.content, fragment).addToBackStack(null).commit();
Here is the DialogFragment's onCreate
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
if (savedInstanceState != null)
{
//We've got data saved. Reconstitute it
if (mPlayer != null)
{
mPlayer.seekTo(savedInstanceState.getInt(KEY_SONG_POSITION));
}
}
}
Not sure why it goes back to the TracksFragment since it had the DialogFragment active on rotation, but since that is the case, it would seem like I would need to recreate the entireDialogPlayer object, But it seems to keep this around as the call to its onCreate happens.
Anyone know what it is that needs to be done here?
OK, this was asked before but I discounted the solution...I should not have.
For some reason, Android wants the Tracks layout XML to use a FrameLayout instead of a fragment.
So, just replace fragment with FrameLayout in the layout xml file and all is well.
I have a ViewPager (instantiated with FragmentStatePagerAdapter) with some Fragment attached to it.
In a specific usecase I need to reset instanceBean and UI for most of the fragments in the pager.
After some googling I have tried some solutions like this but the side effects were not easy manageable. Other solution like this doesn't match my needs.
So I decided to go straight with the manual reset of the UI and instanceBean obj like in the code below:
The code
Single fragment reset
public void initFragment() {
notaBean = new NoteFragmentTO();
fromSpinnerListener = false;
}
public void resetFragment() {
initFragment();
NoteFragment.retainInstanceState = false;
}
This is done with the following code from the parent Activity:
Fragment reset from parent
private void resetAfterSaving() {
mIndicator.setCurrentItem(POSITION_F*****);
f*****Info.resetFragment();
mIndicator.setCurrentItem(POSITION_NOTE);
noteInfo.resetFragment();
mIndicator.setCurrentItem(POSITION_M*****);
m*****Info.resetFragment();
mIndicator.setCurrentItem(POSITION_V*****);
v*****.resetFragment();
}
AfterViews method:
#AfterViews
public void afterView() {
if (mSavedInstanceState != null) {
restoreState(mSavedInstanceState);
}
NoteFragment.retainInstanceState = true;
// Inits the adapters
noteAdapter = new NoteArrayAdapter(this, noteDefaultList);
sp_viol_nota_default.setAdapter(noteAdapter);
//sp_viol_nota_default.seton
et_viol_nota.setOnFocusChangeListener(new OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
String readText = et_viol_nota.getText().toString().trim();
notaBean.setNota(readText == "" ? null : readText);
}
}
});
}
OnSavedInstanceState
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelableArrayList(KEY_NOTE_D_LIST, (ArrayList<VlzAnagraficaNoteagente>) noteDefaultList);
outState.putInt(KEY_NOTE_D_POSITION, !NoteFragment.retainInstanceState ? 0 : notePosition);
notaBean.setNota(!NoteFragment.retainInstanceState ? "" : et_viol_nota.getText().toString().trim());
outState.putParcelable(NoteFragmentTO.INTENT_KEY, notaBean);
}
Why do I set every page before resetting them?
Because like explained here:
When pages are not visible to the user, their entire fragment may be destroyed, only keeping the saved state of that fragment.
and because until I don't select the relative fragment the #AfterViews method (that is everything processed right after OnCreateView of the fragment) is not executed.
This throws NullPointerException for a thousand of reason (Usually in the #AfterViews method You launch RestoreState method, initializes adapter, do UI stuff).
Setting the relative page before the reset let #AfterViews method be processed.
Before checking what would happened when rotating the device, all the fragment I need are correcly reset.
When rotating the device, the error comes out:
The views (mainly EditText) go back to their previous state BEFORE my reset.
What happens?
When switching between the page, at a certain point the page will be destroyed and OnSavedInstanceState is called everytime for each page.
I have already handled the OnSavedInstanceState (like above) that when the boolean is false saves the state like if it had just been created.
I found that until within AfterView method the EditText has its text set to blank (like I want) but going on with the debug the EditText goes back to its previous state, so at the end it will show the last text it had.
Question
How can I keep the manually set (in OnSavedInstanceState) EditText text after destroying/recreating a fragment?
In my application i have a FragmentPager. Now each Fragment has a next button with which i navigate to the next fragment. Via the next button i know that the user is navigation away from the view. But how do i know if the user has clicked on the tabs. Will the
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(KEY_CONTENT, mContent);
}
function be called when a user presses a different tab ? Can i save the state and restore it on the
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if ((savedInstanceState != null)
&& savedInstanceState.containsKey(KEY_CONTENT)) {
mContent = savedInstanceState.getString(KEY_CONTENT);
}
}
will this work ? What other way can i know that the user has clicked on the different tab ?
Kind Regards
First of all, are you using ActionBarSherlock for you actionbar/tabs? I recommend that you do since you'll get a cross-version way of working with the actionbar.
In any case, you should add a listener to each tab before adding it to the actionbar. With the listener implemented you know when a tab has been selected, reselected and unselected
I'm not sure about when the onSaveInstanceState is called (try it using the debugger!), but with the listener implemented you'll get a fool-proof way of knowing what goes on with your tabs.
I implemented my layout based on this tutorial: http://android-developers.blogspot.hu/2011/02/android-30-fragments-api.html
The differences are:
I have different fragments to show, based on the choice in the left
list
The "details fragments" (those that come to the right) have different options menus
My problem is that if I have already selected something from the left and then rotate the phone to portrait, the last optionsmenu is still there and is visible.
I think the problem comes from the last active "details" fragment is recreated after the orientation change. to test it I created these two methods:
#Override
public void onStart() {
super.onStart();
setHasOptionsMenu(true);
}
#Override
public void onStop() {
super.onStop();
setHasOptionsMenu(false);
}
And I'm showing the right fragment like this:
case R.id.prefs_medicines:
if (mDualPane) {
// Check what fragment is shown, replace if needed.
View prefsFrame = getActivity().findViewById(R.id.preferences);
if (prefsFrame != null) {
// Make new fragment to show this selection.
MedicineListF prefF = new MedicineListF();
// Execute a transaction, replacing any existing
// fragment with this one inside the frame.
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.preferences, prefF);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
}
} else {
// Otherwise we need to launch a new activity to display
// the dialog fragment with selected text.
Intent intent = new Intent();
intent.setClass(getActivity(), MedicinePrefsActivity.class);
startActivity(intent);
}
break;
in one of my "details" fragment. when I debugged it, the onstart was called after the rotation.
The problem in pictures:
1: in landscape it's OK
Landscape mode http://img834.imageshack.us/img834/8918/error1d.png
2: in portrait: optionsmenu not needed
Portrait mode http://img860.imageshack.us/img860/8636/error2r.png
How can I get rid of the optionsmenu in portrait mode?
I had the same problem, and resolved it by setting setHasOptionsMenu(true) in the fragment only when savedInstanceState is null. If onCreate gets a bundle then the fragment is being restored in an orientation change to portrait, so don't display the menu.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState == null) {
setHasOptionsMenu(true);
}
}