Easiest way to save data in savedInstanceState when managing an adapter(viewpager) - android

I'm working with an adapter(viewpager) and when the app changes orientation it loses its information. Which is the easiest way to conserve this information? I was going to use savedInstanceState inside the adapter but it doesn't recognise it.

Finally, I have done it following the advice of Jofre as he pointed in Activity restart on rotation Android
and looking in http://developer.android.com/intl/es/guide/topics/resources/runtime-changes.html
YOU MUST NOT MODIFY MANIFEST. DON'T ADD android:configChanges
SOLUTION:
//ALL THE VARIABLES YOU NEED TO SAVE IN YOUR ADAPTER PUT THEM AS PUBLIC, IE:
//public int a = 2;
//public String b = "a";
Public class Yourclass extends Activity{
Classofyouradapter adapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.yourlayout);
final Save_object data = (Save_object) getLastNonConfigurationInstance();
adapter = new Classofyouradapter(your variables);
if (data != null) {
fillit(data);
}
myPager = (ViewPager) findViewById(R.id.yourpanelpager);
myPager.setAdapter(adapter);
}
#Override
public Object onRetainNonConfigurationInstance() {
final Save_object data = new Save_object();
return data;
}
public class Save_object{
int a = adapter.a;
String b = adapter.b;
//Rest of variables
}
public void fillit(Save_object data){
adapter.a= data.a;
adapter.b= data.b;
//Rest of variables
}
}

Related

Custom Objects not being passed to other Activity via intent PutExtra

So I am making an android app that implements some custom classes. I want to create an object of class Menu_Organizer to other activities, but after I inizialize the object and send it to the next Activity the object is NULL. Here are the classes:
Menu Items class
import java.io.Serializable;
public class Menu_Items implements Serializable {
private String Name = "";
private String Description = "";
private float Price = 0.0f;
private String Category = "";
/* Getters and Setters*/
}
Menu Organizer class:
public class Menu_Organizer implements Serializable {
ArrayList<String> Categories;
ArrayList<Menu_Items> Food;
// EDITED
public Menu_Organizer() {
Categories = new ArrayList<String>();
Food = new ArrayList<Menu_Items>();
}
/* Other class methods */
}
First Activity (main)
public class MainActivity extends AppCompatActivity {
private Button btn;
public Menu_Organizer menu;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
onclick();
}
public void onclick() {
btn = (Button) findViewById(R.id. btn);
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Menu_Organizer menu = new Menu_Organizer();
menu.New_Menu_Item("Carne", "Pollo", "Pollo Asado Rico!", 4.55f);
Intent activity2= new Intent(MainActivity.this,temp.class);
activity2.putExtra("Menu", menu);
startActivity(activity2);
}
});
}
}
Second Activity
public class temp extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_Second);
Bundle bundle = getIntent().getExtras();
Menu_Organizer menu = (Menu_Organizer) bundle.getSerializable("Menu");
String str= menu.Food.get(0).getName();
}
}
Alright, i think that the issue is, that when you pass your class object in a key value pair, you do it in an Intent object, but when you resolve your intent, you do that via a bundle object. So, in you temp Activity, you should resolve the intent like:
Intent intent = this.getIntent();
Menu_Organizer menu = (Menu_Organizer) intent.getSerializableExtra("Menu");
Try this, this should work. If you want to do it via a bundle, then create a bundle object first, then put whatever you want in that bundle. Add the bundle to your intent, and then resolve the bundle in your next Activity.
Just a tip, Class names generally do not contain _ in them, use CamelCase naming convention for all classes.
I would recommend using EventBus library for this kind of thing. It is quite easy to use and gives you exactly this: sending and receiving custom object classes from one place to another (Fragments, Activities, Services, whatever you wish can send and receive objects).
I personally don't like intents cause they have too many limitations.

Parcelable from activity to fragment

I am new to parcelable and I am trying to pass data from an Activity (MainActivity) to a fragment (MainFragment) but I`m struggle to get this right.
I made a class (InfoBean) with all the (parcelable) data. When I send the data from the MainActivity, the data from bean.newTheme (2131296447) is there but as soon as I try to retrieve in the Fragment, the value is 0!
Could someone pls have a look, what I`m doing wrong? Thank you for your help.
Send data (MainActivity):
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
InfoBean bean = new InfoBean();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SecureSharedPreferences theme = SecureSharedPreferences.getInstance(this, "MyPrefsFile");
int newTheme = theme.getInt("themeCustom", 0);
bean.newTheme = newTheme;
Bundle bundle = new Bundle();
bundle.putInt("theme", bean.newTheme); // debug shows value 2131296447
MainFragment mf = new MainFragment();
mf.setArguments(bundle);
//
}
}
Retrieve data (MainFragment):
public class MainFragment extends Fragment {
InfoBean bean = new InfoBean();
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//
Bundle bundle = this.getArguments(); // Debugging shows 0!
if (bundle != null) {
bean.newTheme = bundle.getInt("theme");
}
if (bean.newTheme == 2131296447) { // White Theme
mCardView1.setBackgroundColor(Color.parseColor("#E8EBED"));
} else { // Dark Theme
mCardView1.setBackgroundColor(Color.parseColor("#282929"));
relLay.setBackgroundColor(Color.parseColor("#1B1C1C"));
}
return rootView;
}
}
InfoBean.class:
public class InfoBean implements Parcelable {
public int newTheme;
public int THEME_DARK = R.style.DarkTheme;
public int THEME_LIGHT = R.style.LightTheme;
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.newTheme);
dest.writeInt(this.THEME_DARK);
dest.writeInt(this.THEME_LIGHT);
}
public InfoBean() {
}
protected InfoBean(Parcel in) {
this.newTheme = in.readInt();
this.THEME_DARK = in.readInt();
this.THEME_LIGHT = in.readInt();
}
public static final Parcelable.Creator<InfoBean> CREATOR = new Parcelable.Creator<InfoBean>() {
#Override
public InfoBean createFromParcel(Parcel source) {
return new InfoBean(source);
}
#Override
public InfoBean[] newArray(int size) {
return new InfoBean[size];
}
};
}
If you have embedded fragment in your XML you can't use the setArguments() like that in your program. It is better to use dynamic fragment creation.
There is a brief example in android developer website which can guide you: http://developer.android.com/reference/android/app/Fragment.html there is also another implementation when you have embedded fragments and how to process arguments with that.
There is also another resource here which may help you:
Set arguments of fragment from activity
In your activity, you are using .putInt("theme" .....) but in the fragment you call .getParcelable("theme"). You're getting 0 because you're attempting to get two different data types.
Update
Since you have your fragment embedded in xml, you can't pass values to fragment class. To that you need to make it excute through java code and remove that xml. Make fragment Transaction then it will work
Update
You should try retrieving values in onCreate method of fragment.
#overide
protect void onCreate(bundle onSavedInstance){
if (savedInstance != null) {
bean.newTheme = bundle.getInt("theme");
}
}
Try this
if (bundle != null) {
bean.newTheme = bundle.getInt("theme");
}
instead of
if (bundle != null) {
bean.newTheme = bundle.getParcelable("theme");
}

How to save custom ListFragment state with orientation change

I am being more thorough with hope the question will actually be easier to understand.
Activity purpose: allow users to select images from gallery; display thumbnail of image in ListFragment along with title user gave the image; when user is finished save each image's uri and title, and the name user gave this collection of images.
Problem: When device is rotated the FragmentList is losing all the images and titles the user already chose, ie, all the rows of the list are missing.
Attempted problem solving:
Implemented the RetainedFragment to save the List collection on device rotation. Previously I had not done this and figured "Ah, the adapter is fed a blank List collection on creation. I'll save the state of the List and then when Activity's onCreate is called I can feed the retained List to the Adapter constructor and it'll work." But it didn't.
Then I thought, "Of course it is not working, you haven't notified the adapter of the change!" So I put the adapter.notifyDataSetChanged() in the onCreate. This didn't work.
Then I moved the adapter.notifyDataSetChanged() to onStart thinking I might need to notify the adapter later in the activity's lifecycle. Didn't work.
Note: I have another activity in this same app that use this same custom ListViewFragment and the state of the ListFragment is being preserved with device orientation changes. That activity has two principle differences: the fragment is hard coded into the .xml (I don't think that would make a difference, other than perhaps maybe Android's native saving of .xml fragments is different than programmatically added ones); and that activity uses a Loader and LoaderManager and gets its data from a Provider that I built (which gathers data from my SQLite database). Looking at the differences between these two activities is what caused me to think "you're not handling the data feeding the adapter appropriately somehow" and inspired me to use the RetainedFragment to save the List collection when the device is rotated.
...which is prompting me to think about figuring out how to, as Android says on their Loader page about LoaderManager:
"An abstract class associated with an Activity or Fragment for managing one or more Loader instances. This helps an application manage longer-running operations in conjunction with the Activity or Fragment lifecycle; the most common use of this is with a CursorLoader, however applications are free to write their own loaders for loading other types of data."
It is the "loading other types of data" part that has me thinking "Could I use a LoaderManager to load the List data? Two reasons I shy from this: 1) what I have already, at least conceptually, ought to work; 2) what I'm doing currently isn't really a "longer-running operation" at all, I don't think.
Research:
StackOverflow Fool proof way to handle Fragment on orientation change
save state of a fragment.
I think the RetainedFragment I am using saves what needs to be saved.(?)
Once for all, how to correctly save instance state of Fragments in back stack?
Save backstack fragments.
Not shown in my code pasted below, but my activity dynamically creates three other fragments and I use the following if savedInstanceState !=null and those fragments' states are saved without doing any work in onSaveInstanceState() (this is partly why it feels like my problem isn't with doing something in onSaveInstanceState because Android handles the saving my other fragments state so shouldn't it do it, too, with the ListFragment? Seems like it should).
if(savedInstanceState.containsKey(AddActivity_Frag1.F1_TAG)){
frag1 = (AddActivity_Frag1)getFragmentManager().getFragment(savedInstanceState, AddActivity_Frag1.F1_TAG);
}
Understanding Fragment's setRetainInstance(boolean)
Many of the StackOverflow questions surrounding my query seem to be mostly about how to save the scroll position of the ListFragment with orientation change but I don't need to do that (though I did read them looking for tips that might help).
Android Fragments
Android Loaders
Android Caching Bitmaps (RetainFragment stuff)
Activity - with many, hopefully unrelated things, removed:
public class AddActivity extends Activity{
// data collection
List<ImageBean> beanList;
// adapter
AddCollectionAdapter adapter;
// ListViewFragment tag
private static final String LVF_TAG = "list fragment tag";
// fragment handles
ListViewFragment listFrag;
// Handles images; LruCache for bitmapes
ImageHandler imageHandler;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add2);
// Create ImageHandler that holds LruCache
imageHandler = new ImageHandler(this, getFragmentManager());
// Obtain retained List<ImageBean> or create new List<ImageBean>.
RetainedFragment retainFragment = RetainedFragment.findOrCreateRetainFragment(getFragmentManager());
beanList = retainFragment.list;
if(beanList == null){
beanList = new ArrayList<ImageBean>();
retainFragment.list = beanList;
}
// create fragments
if(savedInstanceState == null){
listFrag = new ListViewFragment();
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.add(R.id.add_fragFrame, listFrag, LVF_TAG);
ft.commit();
}else{
listFrag = (ListViewFragment)getFragmentManager().findFragmentByTag(LVF_TAG);
}
// create adapter
adapter = new AddCollectionAdapter(this, beanList);
// set list fragment adapter
listFrag.setListAdapter(adapter);
}
#Override
protected void onStart() {
// TESTING: If device orientation has changed List<ImageBean> was saved
// with a RetainedFragment. Seed the adapter with the retained
// List.
adapter.notifyDataSetChanged();
super.onStart();
}
#Override
protected void onSaveInstanceState(Bundle outState) {
// Android automatically saves visible fragments here. (?)
super.onSaveInstanceState(outState);
}
/*
* ImageBean.
*/
public static class ImageBean{
private String collectionName; // Title of image collection
private String imageUri; // Image URI as a string
private String imageTitle; // Title given to image
public ImageBean(String name, String uri, String title){
collectionName = name;
imageUri = uri;
imageTitle = title;
}
public String getCollectionName() {
return collectionName;
}
public String getImageUri() {
return imageUri;
}
public String getImageTitle() {
return imageTitle;
}
}
/*
* Called when user is finished selecting images.
*
* Performs a bulk insert to the Provider.
*/
private void saveToDatabase() {
int arraySize = beanList.size();
final ContentValues[] valuesArray = new ContentValues[arraySize];
ContentValues values;
String imageuri;
String title;
int counter = 0;
for(ImageBean image : beanList){
imageuri = image.getImageUri();
title = image.getImageTitle();
values = new ContentValues();
values.put(CollectionsTable.COL_NAME, nameOfCollection);
values.put(CollectionsTable.COL_IMAGEURI, imageuri);
values.put(CollectionsTable.COL_TITLE, title);
values.put(CollectionsTable.COL_SEQ, counter +1);
valuesArray[counter] = values;
counter++;
}
AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... arg0) {
getContentResolver().bulkInsert(CollectionsContentProvider.COLLECTIONS_URI, valuesArray);
return null;
}
#Override
protected void onPostExecute(Void result) {
// End this activity.
finish();
}
};
task.execute();
}
public ImageHandler getImageHandler(){
return imageHandler;
}
}
class RetainedFragment extends Fragment{
private static final String TAG = "RetainedFragment";
// data to retain
public List<AddActivity.ImageBean> list;
public static RetainedFragment findOrCreateRetainFragment(FragmentManager fm){
RetainedFragment fragment = (RetainedFragment)fm.findFragmentByTag(TAG);
if(fragment == null){
fragment = new RetainedFragment();
fm.beginTransaction().add(fragment, TAG);
}
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
}
ListFragment:
public class ListViewFragment extends ListFragment {
ListFragListener listener;
public interface ListFragListener{
public void listFragListener(Cursor cursor);
}
#Override
public void onCreate(Bundle savedInstanceState) {
// Retain this fragment across configuration change
setRetainInstance(true);
super.onCreate(savedInstanceState);
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// Set listener
if(activity instanceof ListFragListener){
listener = (ListFragListener)activity;
}else{
//Instantiating activity does not implement ListFragListener.
}
}
#Override
public void onListItemClick(ListView listView, View v, int position, long id) {
// no action necessary
}
}
Adapter:
public class AddCollectionAdapter extends BaseAdapter {
// data collection
List<ImageBean> beanList;
// layout inflator
private LayoutInflater inflater;
// context
Context context;
public AddCollectionAdapter(Context context, List<ImageBean> beanList){
this.context = context;
this.beanList = beanList;
inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
return beanList.size();
}
#Override
public Object getItem(int position) {
return beanList.get(position);
}
#Override
public long getItemId(int arg0) {
// collection not from database nor is going directly to database; this is useless.
return 0;
}
// holder pattern
private class ViewHolder{
ImageView imageView;
TextView titleView;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
View xmlTemplate = convertView;
if(xmlTemplate == null){
//inflate xml
xmlTemplate = inflater.inflate(R.layout.frag_listview_row, null);
// initilaize ViewHolder
holder = new ViewHolder();
// get views that are inside the xml
holder.imageView = (ImageView)xmlTemplate.findViewById(R.id.add_lvrow_image);
holder.titleView = (TextView)xmlTemplate.findViewById(R.id.add_lvrow_title);
// set tag
xmlTemplate.setTag(holder);
}else{
holder = (ViewHolder)xmlTemplate.getTag();
}
// Get image details from List<ImageBean>
ImageBean bean = beanList.get(position);
String imageUri = bean.getImageUri();
String title = bean.getImageTitle();
// Set Holder ImageView bitmap; Use parent activity's ImageHandler to load image into Holder's ImageView.
((AddActivity)context).getImageHandler().loadBitmap(imageUri, holder.imageView, Constants.LISTVIEW_XML_WIDTH, Constants.LISTVIEW_XML_HEIGHT);
// Set Holder's TextView.
holder.titleView.setText(title);
// return view
return xmlTemplate;
}
}
Solved. After putting log statements in strategic places I discovered the RetainedFragment's list was always null. After some head scratching noticed this in RetainedFragment:
fm.beginTransaction().add(fragment, TAG);
I'm missing the commit()!
After I added that the state is being preserved now with configuration changes.
More information related to saving ListFragment state that I discovered during my trials and tribulations:
If you add a fragment via:
if(savedInstanceState == null){
listFrag = new ListViewFragment();
// programmatically add fragment to ViewGroup
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.add(R.id.add_fragFrame, listFrag, LVF_TAG);
}
Then either of these will work in the else:
1) This one works because Android takes care of saving the Fragment:
listFrag = (ListViewFragment)getFragmentManager().findFragmentByTag(LVF_TAG);
2) This one works because the fragment was specifically saved into bundle in
onSaveInstanceState:
listFrag = (ListViewFragment)getFragmentManager().getFragment(savedInstanceState, LVF_TAG);
For number 2 to work, this happens in onSaveInstanceState():
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
getFragmentManager().putFragment(outState, LVF_TAG, listFrag);
}

Android - call OnSaveInstanceState from another class than main

I am creating some EditTexts with button, I created a class which implements OnClickListener, and I want to save those EditTexts after screen rotation but they always disappear. I know that
Activity on rotation is destroyed and created again
I cant save EditText in Bundle since it does not implement Parcelable or something like that
So I tried to save at least their ID's and Text they have in them with the onSaveInstanceState(Bundle savedInstanceState)
but it is not called because it is not in main class, but in another called ViewControl. I need to save some private variables (text and ids) so that is the reason why I have this method inside my class. I tried to call this method from another onSaveInstance which is in main but no luck neither. Can you tell me some easy way to programatically create EditTexts one below another and KEEP them after screen rotation? I really cant find any easy way...
Main class
public class MainActivity extends Activity {
private ViewControl vc;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
vc = new ViewControl(this);
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
vc.onSaveInstanceState(savedInstanceState);
}
ViewControl class
import java.util.ArrayList;
public class ViewControl extends Activity implements View.OnClickListener{
private Button btn;
private RelativeLayout rl;
private Activity act;
private ArrayList<Integer> id;
private ArrayList<String> userpath;
private int lastId;
public ViewControl(Activity activity) {
this.act = activity;
this.id = new ArrayList<Integer>();
this.userpath = new ArrayList<String>();
this.rl = (RelativeLayout)act.findViewById(R.id.layout);
this.btn = (Button)act.findViewById(R.id.btn);
this.id.add(Integer.valueOf(R.id.btn));
this.btn.setOnClickListener(this);
}
#Override
public void onClick(View v) {
if(id.size() != 1)
lastId = id.get(id.size() - 1);
else
lastId = R.id.btn;
RelativeLayout.LayoutParams par = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
par.addRule(RelativeLayout.BELOW, lastId);
EditText et = new EditText(act);
et.setId(++lastId);
id.add(Integer.valueOf(lastId));
et.setText(Integer.toString(lastId));
rl.addView(et, par);
lastId = et.getId();
}
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putIntegerArrayList("ids", id);
savedInstanceState.putStringArrayList("userpaths", userpath);
savedInstanceState.putInt("lastId", lastId);
Toast.makeText(act, "Onsave", Toast.LENGTH_LONG).show();
}
#Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
this.id = savedInstanceState.getIntegerArrayList("ids");
this.userpath = savedInstanceState.getStringArrayList("userpaths");
this.lastId = savedInstanceState.getInt("lastId");
Toast.makeText(act, "Onrestore", Toast.LENGTH_LONG).show();
for(int i = 0; i < id.size(); i++)
{
RelativeLayout.LayoutParams par = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
par.addRule(RelativeLayout.BELOW, lastId);
EditText et = new EditText(act);
et.setId(this.id.get(i));
}
}
}
The calls are in the wrong order (both in MainActivity and ViewControl). First you should add your data, then pass the Bundle to super to save it (see the docs here). (Your onRestoreInstanceState is OK)

Android : Saving data in onSavedInstance

I don't know how to save the object of bean class in onSavedInstance and retrieve it back in onCreate by checking savedInstanceState as null.
So far I've been trying this code but not able to either save/retrieve the values:-
public class BookActivity extends Activity{
private ArrayList<Bitmap> bitmaps = null;
private ArrayList<Book> bookData = null; //Book is the bean class
private TextView bookTitle = null;
private ArrayList<Feature> bookIds = null; // Another bean class
#Override
public void onSaveInstanceState(Bundle savedInstanceState) {
savedInstanceState.putSerializable("bookThumb", bookData);
savedInstanceState.putSerializable("bookBitmaps", bitmaps);
savedInstanceState.putSerializable("featureBook", bookIds);
super.onSaveInstanceState(savedInstanceState);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.penguin_main);
if(savedInstanceState!=null){
bookData = (ArrayList<String>) savedInstanceState.getSerializable("bookThumb");
bookData = (ArrayList<String>) savedInstanceState.getSerializable("bookThumb");
bookData = (ArrayList<String>) savedInstanceState.getSerializable("bookThumb");
}
else{
Toast msg = Toast.makeText(getApplicationContext(), "Values Not Restored!!!", Toast.LENGTH_LONG);
msg.setGravity(Gravity.CENTER, msg.getXOffset() / 2, msg.getYOffset() / 2);
msg.show();
}
}
}
What should I do to save my bitmaps and the object of Book so that it doesn't make new instance of the class on orientation change?
You can save them as Parcelable ArrayLists
savedInstanceState.putParcelableArrayList("bookThumb", bookData);
savedInstanceState.putParcelableArrayList("bookBitmaps", bitmaps);
savedInstanceState.putParcelableArrayList("featureBook", bookIds);
Your Book and Feature classes will need to implement Parcelable for this to work, though. Bitmap is already a parcelable class

Categories

Resources