My activity enters twice in onCreateView() and it resets my saved variables - android

I was having some problems with an activity from my app.
I was saving some data with onSavedInstanceState() and restoring in onCreateView() method (this activity has two fragments and it has onCreate an onCreateView). The data restored were corrected but I could not put them correctly in a spinner from my activity.
I have put some breakpoints before, within and after at this loop:
ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_spinner_item, list);
spinner.setAdapter(adapter);
**if(savedInstanceState != null) {
Log.i("SPINNER POS", ""+posSaved);
spinner.setSelection(posSaved);
}**
and I have could observer that the app access twice to onCreateView method ... the first time the posSaved variable is correct, but the second time this variable is 0 and due to this reason the spinner never shows the position saved in posSaved variable, always it shows the 0 position (like by default) and then is like I do not use onSavedInstanceState method ...
... why app enter twice in onCreateView() method? How can I solve this situation?
PD. This activity also enters twice in onCreate() method.
My Activity CODE
public class Menu_Seeker_Filter extends Fragment {
... (variables declarated) ...
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if ((getFragmentManager().findFragmentById(R.id.results_fragment) != null)) { //Fragments
mTwoPane = true;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.activity_seeker_filtro, container, false);
if (savedInstanceState != null) {
// Restore value of members from saved state
posSaved = savedInstanceState.getInt("POSITION");
}
...
ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_spinner_item, list);
spinner.setAdapter(adapter);
spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
((TextView) parent.getChildAt(0)).setTextSize(18);
((TextView) parent.getChildAt(0)).setTypeface(font2);
text = (parent.getItemAtPosition(pos).toString());
posSaved = pos;
}
public void onNothingSelected(AdapterView<?> parent) {
//Do nothing.
}
});
if(savedInstanceState != null) {
Log.i("POSITION", ""+posSaved);
spinner.setSelection(posSaved);
}
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
// Save the user's current game state
savedInstanceState.putInt("POSITION", spinner.getSelectedItemPosition());
// Always call the superclass so it can save the view hierarchy state
super.onSaveInstanceState(savedInstanceState);
}
}
and this activity-fragment is called in this class:
public class Menu_Seeker extends FragmentActivity {
...(variables declarated)...
final Menu_Seeker_Filter fragment = new Menu_Seeker_Filter();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_seeker_filter);
if ((getSupportFragmentManager().findFragmentById(R.id.results_fragment) != null)) { //Fragments
mTwoPane = true;
getSupportFragmentManager().beginTransaction().replace(R.id.seeker_fragment, fragment).commit();
}else { //No Fragments
getSupportFragmentManager().beginTransaction().replace(R.id.framelayout_filter, fragment).commit();
}
...
}
Thanks.

Related

ListFragment sometimes skips code in onLoadFinished() method of Loader

I have a ListFragment which fetches data from the net using a Loader. I use a new instance of this ListFragment in every page of my ViewPager. It works perfectly, but when I use TabLayout or moves pages quickly, the Fragment keeps loading and does not display the data in the ListView.
When I checked using log messages, I found that the ListFragment skips some lines of code in the onLoadFinished() method. It does not make the ProgressBar invisible. It does add items to Adapter, but it is not being displayed in the ListView. This problem also happens in the first page of the ViewPager.
Is there any specific rule to be followed when using ListFragments in a ViewPager?
Here is the ListFragment class. If you look at the onLoadFinished() method, you can see the lines causing problem:
public class ListViewFragment extends ListFragment
implements LoaderManager.LoaderCallbacks<List<GameNews>> {
public static ListViewFragment newInstance(String url) {
Log.d("ListViewFragment", "newInstance created");
ListViewFragment f = new ListViewFragment();
// Supply url input as an argument.
Bundle args = new Bundle();
args.putString("url", url);
f.setArguments(args);
return f;
}
List<GameNews> TotalNews;
ListView gameListView;
LinearLayout emptyView;
Button retryButton;
ListAdapter adapter ;
private View progressBar;
final private int game_loader = 0;
ArrayList<String> urls = new ArrayList<>();
String mUrlString;
int index;
//LIFE CYCLE OF FRAGMENT
//------------------------------------------------------------------
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mUrlString = getArguments().getString("url");
urls.add(mUrlString);
TotalNews = new ArrayList<GameNews>();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_list_view, container, false);
ArrayList<GameNews> gameList = new ArrayList<>();
adapter = new ListAdapter(getActivity(), gameList);
return rootView;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
emptyView = (LinearLayout)
view.findViewById(R.id.no_internet_view);
progressBar = view.findViewById(R.id.progress_bar);
retryButton = (Button) view.findViewById(R.id.retry_button);
gameListView = getListView();
emptyView.setVisibility(View.INVISIBLE);
}
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setListAdapter(adapter);
//If connected to net start the loader
if (isConnected()) {
getActivity().getSupportLoaderManager().restartLoader(game_loader,
null,
ListViewFragment.this);
}
}
//OVERRIDED METHODS OF LOADERMANAGER
//---------------------------------------------------------------------
#Override
public android.support.v4.content.Loader<List<GameNews>> onCreateLoader(int i, Bundle bundle) {
AdManager manager = new AdManager(getActivity());
return new FragmentLoader(getActivity(), urls, 1000);
}
//HERE IS THE PROBLEM PLEASE FOCUS INSIDE THIS METHOD
//-------------------------------------------------------
#Override
public void onLoadFinished(Loader<List<GameNews>> loader, List<GameNews> games) {
progressBar.setVisibility(View.INVISIBLE); //This line of code is not executed
adapter.clear();
TotalNews.addAll(games);
adapter.addAll(games);//And the listView is not populated
}
//-------------------------------------------------------
#Override
public void onLoaderReset(Loader<List<GameNews>> loader) {
adapter.clear();
}
//REUSABLE METHODS
//------------------------------------------------------------------
//Method checks if there is internet
public boolean isConnected() {
ConnectivityManager manager = (ConnectivityManager)
getActivity().getSystemService(CONNECTIVITY_SERVICE);
NetworkInfo info = manager.getActiveNetworkInfo();
if (info != null && info.isConnected()) {
return true;
}
else {
return false;
}
}
}
Your Fragment class is using the Activity's LoaderManager:
getActivity().getSupportLoaderManager().restartLoader(...);
And each instance is using the same ID in its restartLoader() call:
final private int game_loader = 0;
This means that each Fragment instance was using and restarting the same Loader over and over again, leading to the weird behavior you observed.
The solution is quite simple: use Fragment's local LoaderManager, instead of the Activity's.
getLoaderManager().restartLoader(...);
With this, you don't need to worry about changing the ID in each instance, since Loaders are unique to their Fragment, and the Loader will be properly handled over the Fragment's lifetime, which would likely not have been the case when using the Activity's LoaderManager.

Saving and restoring state using fragments

I'm trying to understand the process of saving and restoring state using fragments. I've created sliding navigation menu using it.
In one of the fragments there is this code:
public class FifthFragment extends Fragment {
CheckBox cb;
View view;
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fifth_layout, container, false);
cb = (CheckBox) view.findViewById(R.id.checkBox);
return view;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
// Restore save state
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// save state
}
}
For example I want to save the state of the CheckBox before user exits the fragment and restore it when the fragment is created again. How to achieve this?
EDIT:
According to raxellson's answer I've changed my fragment to this:
public class FifthFragment extends Fragment {
private static final String CHECK_BOX_STATE = "string";
CheckBox cb;
View view;
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fifth_layout, container, false);
cb = (CheckBox) view.findViewById(R.id.checkBox);
if (savedInstanceState == null) {
Log.i("statenull", "null");
}
if (savedInstanceState != null) {
// Restore last state for checked position.
boolean checked = savedInstanceState.getBoolean(CHECK_BOX_STATE, false);
cb.setChecked(checked);
}
return view;
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(CHECK_BOX_STATE, cb.isChecked());
}
}
I got logged I/statenull: null so savedInstanceState was not saved. What am I doing wrong?
You want to save the value of your current checked state in onSaveInstanceState.
Something like this:
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(CHECK_BOX_STATE, cb.getChecked());
}
and then when your view is created you want to get the value if it's present. And set your CheckBox state with it.
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fifth_layout, container, false);
cb = (CheckBox) view.findViewById(R.id.checkBox);
if (savedInstanceState != null) {
// Restore last state for checked position.
boolean checked = savedInstanceState.getBoolean(CHECK_BOX_STATE, false);
cb.setChecked(checked);
}
return view;
}
EDIT:
When you add the fragment, make sure to add it with a tag or id so that you can retrieve the same instance.
You could do a helper method to retrieve fragment and set the fragment.
private void setFragment(String tag, Fragment newFragment) {
FragmentManager fm = getSupportFragmentManager();
Fragment savedFragment = fm.getFragmentByTag(tag);
fm.replace(R.id.container, savedFragment != null ? savedFragment : newFragment, tag);
fm.commit();
}
so you your switch you can call the helper method instead.
switch (position) {
case 0:
setFragment("A", new FragmentA());
break;
....
}
Note: This is just an example not best practice since you are creating new fragments every time in your switch case now anyways. But it might point you in the right direction.
After see all the example. Here is the solution for save fragment state:
Two steps for this:
1.
String saveValue;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
saveValue = "";
} else {
saveValue = savedInstanceState.getString("saveInstance");
}
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
//save the values of fragment if destroy on second to back
if (!saveValue.isEmpty())
savedInstanceState.putString("saveInstance", saveValue);
}
In onSaveInstanceState you can save your values. And after destroy fragment you can receive your values through onCreate.

Listview fragment is getting recreated on pressing backbutton

I'm using loader in my ListView fragment, and it's getting recreated on pressing "back" button. Can you tell me how to handle this senario?
Here is my ListView fragment code. Here I have a boolean variable that I'm setting as true on clicking on list item. but once the back button is pressed onCreateView will get called so the backbutton will be false.
public class GTFragment extends SherlockFragment implements LoaderCallbacks<Cursor>{
ListView mTListview = null;
GoogleTasksAdapter mGTasksAdapter = null;
private SQLiteCursorLoader mTLoader=null;
private LoaderManager mTLoaderManager;
private String mSelectedListID = null;
private boolean mIsBackbuttonisPressed = false;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.task_home_activity, container, false);
if(!mIsBackbuttonisPressed)
getLoaderManager().initLoader(0, null, this);
mTListview = (ListView) view.findViewById(R.id.id_task_list_home_activity);
mGTasksAdapter = new GoogleTasksAdapter(getActivity());
mTListview.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> listview,
View clickedview, int position, long arg3) {
// TODO Auto-generated method stub
GoogleTaskItem item = new GoogleTaskItem();
Cursor coursor = ((GoogleTasksAdapter)listview.getAdapter()).getCursor();
if(coursor.moveToPosition(position))
{
mIsBackbuttonisPressed = true;
GoogleTaskController.get_googletask_controllerObj()
.LaunchTaskPreviewActivity();
}
}
});
mTListview.setAdapter(mGTasksAdapter);
mIsBackbuttonisPressed = false;
return view;
}
My fragment activity class code
public class TLActivity extends SherlockFragmentActivity {
LeftSliderTaskListOptions mTaskOptionsFragment = null;
GoogleTasksFragment mTFragment = null;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);
setContentView(R.layout.layout_gt_list);
// FragmentTransaction tfragment = this.getSupportFragmentManager().beginTransaction();
mTFragment = new GTasksFragment();
t.replace(R.id.id_tfragment, mTFragment);
t.commit();
}
instead of
t.replace(R.id.id_tfragment, mTFragment);
use
t.add(R.id.id_tfragment, mTFragment);
It worked for me
I don't think that the accepted answer is right because Fragment.onSaveInstanceState will not be called until the activity hosting it needs to save its state: The docs states:
There are many situations where a fragment may be mostly torn down
(such as when placed on the back stack with no UI showing), but its
state will not be saved until its owning activity actually needs to
save its state.
In other words: if you're using a Activity with multiple fragments for each screen (which is very common), the fragment state will not be saved when you move the next screen.
You also can't use Fragment.setRetainInstance because he's meant only to fragments that aren't on the back stack.
Most of the time, you don't have to think about this but sometimes it's important. Like when you have scrolled a list and want to "remember" the scroll location.
I took a long time to realize that the fragments put on the back stack are kind of saved and you can reuse the view that you already created instead of creating one every time the fragment calls onCreateView. My setup is something like this:
public abstract class BaseFragment extends Fragment {
private boolean mSaveView = false;
private SoftReference<View> mViewReference;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (mSaveView) {
if (mViewReference != null) {
final View savedView = mViewReference.get();
if (savedView != null) {
if (savedView.getParent() != null) {
((ViewGroup) savedView.getParent()).removeView(savedView);
return savedView;
}
}
}
}
final View view = inflater.inflate(getFragmentResource(), container, false);
mViewReference = new SoftReference<View>(view);
return view;
}
protected void setSaveView(boolean value) {
mSaveView = value;
}
}
public class MyFragment extends BaseFragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setSaveView(true);
final View view = super.onCreateView(inflater, container, savedInstanceState);
ListView placesList = (ListView) view.findViewById(R.id.places_list);
if (placesList.getAdapter() == null) { // this check is important so you don't restart your adapter
placesList.setAdapter(createAdapter());
}
}
}
You have multiple options to rectify this issue.
Override onSaveInstanceState like this:
#Override
public void onSaveInstanceState (Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("mIsBackbuttonisPressed", mIsBackbuttonisPressed);
}
and then in your onCreateView you can get your variable back by:
if (savedInstanceState != null)
mIsBackbuttonisPressed = savedInstanceState.getBoolean("mIsBackbuttonisPressed", false);
You can set this.setRetainInstance(true); in your onCreate method of your fragment.
If you could post your Activity code with creates your fragment I can also tell you other options. (P.S I cannot write it as a comment so posting it in the answer.)

android fragment vs spinner onitemselected listener

i have a fragment with spinner in which i add data from internet
in onCreateView() i init my spinner and add listener for it
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_main, null);
cityChoose = (Spinner) view.findViewById(R.id.cityChoose);
cityChoose.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
HashMap temp = new HashMap();
temp.put("finset", cities.get(position).getValue());
HashMap map = new HashMap();
map.put("city", temp);
new DownloadData().execute(new Object[] { PARAM_DEALS_LIST,
map, getStoreID(), "1", "15" });
}
}
Then in on onCreate i first add data to spinner
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Tools.isNetworkAvailable(getActivity().getApplicationContext())) {
new DownloadData().execute(new Object[] { PARAM_BOTH, "",
getStoreID(), "1", "15" });
} else {
Toast.makeText(getActivity(),
getResources().getString(R.string.toastNoInternet),
Toast.LENGTH_SHORT).show();
}
}
Then if user go from this fragment to another fragment and click back button from it i need to show in spinner my data that i was add early i do this in such way
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (spinAdapter != null) {
cityChoose.setAdapter(spinAdapter);
}
}
And my problem is this:
If i first go to the fragment, spinner will download data and filled with it by adapter, then if i go to another fragment and then push back button the spinner will get data from adapter, all works good, but if i in fragment with spinner click on one of its items and then go to another fragment and went back it will init its onItemSelected() method and download data one more time, but the adapter already has this data.
How i can avoid initing onItemSelected() in spinner when i back to this fragment from another ?

Keep list fragment selected item position on orientation change

I have a list fragment on the left of another fragment and is essentially the standard click an item and update the right fragment pattern. When they click an item in the list fragment they are choosing the news article category and I need to keep whatever one is selected when they rotate the device. How do I do that? My current code doesn't work.
My code is as follows:
public class SideMenuFragment extends ListFragment {
ArrayList<SideItem> sideItems;
SideAdapter sideAdapter;
public SideMenuFragment() {
this.setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.list, null);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
sideItems = new ArrayList<SideItem>();
...add bunch of items
sideAdapter = new SideAdapter(getActivity(), sideItems);
getListView().setVerticalScrollBarEnabled(false);
setListAdapter(sideAdapter);
if (savedInstanceState != null) {
sideAdapter.setSelectedItem(savedInstanceState.getInt("sidePosition"));
sideAdapter.notifyDataSetChanged();
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("sidePosition", sideAdapter.getSelectedItem());
}
#Override
public void onListItemClick(ListView lv, View v, int position, long id) {
if (sideAdapter.getSelectedItem() != position) {
sideAdapter.setSelectedItem(position);
sideAdapter.notifyDataSetChanged();
}
switch (position) {
...switch the fragment depending on position.
}
}
// the meat of switching the above fragment
private void switchFragment(Fragment fragment, String title) {
if (getActivity() == null)
return;
if (getActivity() instanceof HomeActivity) {
HomeActivity a = (HomeActivity) getActivity();
a.switchContent(fragment, title);
}
}
}
First, add your Fragment in xml if the Activity layout.
In Activity onCreate
getFragmentManager().findFragmentById(R.id.youtfragmentid).setRetainInstance(true)
This means that the fragment will not be recreated on activity recreate.
Don't change your ListView in onActivityCreated - because it will be rebuilt every time orientation changes. If you set a new adapter - the states of children will be reseted.
Add checking for null or a boolean flag that the view already was created.
Next time onActivityCreated gets called, your list adapter should not change
if (sideAdapter == null) {
sideAdapter = new SideAdapter(getActivity(), sideItems);
getListView().setVerticalScrollBarEnabled(false);
setListAdapter(sideAdapter);
}
Also, don't create new view in onCreateView instead use previously created one.
private View v;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (v == null) {
v = inflater.inflate(R.layout.list, null);
} else {
// detatch from container and return the same view
((ViewGroup) getListView().getParent()).removeAllViews();
}
return v;
}

Categories

Resources