I was wondering what the best way is to store variables of a complex data type in Android. onSaveInstanceState() can only be used for simple data types like string, integer etc., but if I wanted so save something else, what could I do?
These are the methods I know of so far:
Save it in the database and restore it
Create a json and save the json-string in the database or shared preferences.
Use static variables (not recommended due to possible memory leaks and other unwanted behaviour)
Making it parceable
I am not satisfied with either one of the methods mentioned above, so I am looking for a different, more generic approach. For example if I wanted to save an ArrayList containing fragments, what could I do (let's not discuss why I would do that, and let's also pretend there's no such such as a fragmentmanager).
I believe if you can make a class parcelable you can store the same as below in the same OnsaveInstanceState
#Override
protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
try
{
outState.putParcelable("ListViewFragment", (Parcelable) mLeftMenuFragment);
outState.putParcelable("ProductsGridFragment", (Parcelable) mProductsGridFragment);
outState.putParcelable("ProductDetailsFragment", (Parcelable) mProductDetailsFragment);
outState.putParcelable("FeaturedFragment", (Parcelable) mFeaturedFragment);
outState.putParcelable("CategoryFragment", (Parcelable) mCategoryFragment);
}
catch(Exception e)
{
mLogger.error(Logger.ERROR_CODE_API, e);
}
}
Get them back as below
if(savedInstanceState != null)
{
isFeaturedProduct = savedInstanceState.getBoolean("IsFeaturedProduct");
mLeftMenuFragment = (LeftMenuFragment) savedInstanceState.getParcelable("ListViewFragment");
mProductsGridFragment = (ProductListFragment) savedInstanceState.getParcelable("ProductsGridFragment");
mProductDetailsFragment = (ProductDetailsFragment) savedInstanceState.getParcelable("ProductDetailsFragment");
if(isFeatured)
mFeaturedFragment = (FeaturedFragment) savedInstanceState.getParcelable("FeaturedFragment");
else
mCategoryFragment = (CategoryFragment) savedInstanceState.getParcelable("CategoryFragment");
// mActionBar.setTitle(savedInstanceState.getString("ActionBarTitle"));
}
else
{
mLeftMenuFragment = new LeftMenuFragment();
mProductsGridFragment = new ProductListFragment();
mProductDetailsFragment = new ProductDetailsFragment();
if(isFeatured)
{
mFeaturedFragment = new FeaturedFragment();
}
else
{
mCategoryFragment = new CategoryFragment();
}
mIsFeaturedClicked = true;
}
Related
Lets say, I have an activity that does a network query, recieves data and does something with that data, how fo I perserve that data on config changes not using livemodel, persistant presenters, DB or shared preferences. One variant is actually a new frgment with a bundle, after config changes the activity will restore the fragment with the data, any more sugestions?
following the answer bellow, might be usefull, dont use key and bundle, because bundle lso holds a key. Here's how u can do this in Kotlin
object DataHolder {
private val data = HashMap<String, Any>()
fun setData(key: String, data: Any) {
this.data[key] = data
}
fun getData(key: String): Any? {
return data[key]
}
fun removeData(key: String) {
data.remove(key)
}
}
It depends how long you want to preserve the data. If you want it to just be persistent for the current app session, you could use a singleton. Here's an example using a Bundle, but you could use any object that holds the data you care about. Here's an example with a map to let you set different persistent data entries with unique String keys.
public class MyDataHolder {
private static MyDataHolder instance = new MyDataHolder();
private final HashMap<String,Bundle> data = new HashMap<>();
private MyDataHolder() {};
public static MyDataHolder getInstance() {
return instance;
}
public void setData(String key, Bundle data) {
this.data.put(key,data)
}
public Bundle getData(String key) {
return data.getOrDefault(key, null);
}
}
then in your activity, to save some data
MyDataHolder holder = MyDataHolder.getInstance();
holder.setData("ActivityB",data);
and to get data (for example, in onCreate)
MyDataHolder holder = MyDataHolder.getInstance();
Bundle data = holder.getData("ActivityB");
if( data != null ) {
// use saved config
}
else {
// use default config
}
If you need it to persist beyond the current app session or persist even if the app is killed, you'll need to save it to a local file or network location. That can be either a built-in method (DB, SharedPreferences) but you don't want to use those, so you'd have to write your own method to read and write the data.
When i start my android app, it loads large amount of data from server in mainactivity and adapt it with listView and when listview is clicked , new activity will launch . When return back to mainactivity from current activity , my app again load data from server that was previously loaded? How can i use 1st time loaded data after returning back to same activity next time??
There are several possible approaches to your problem depending on what you want to do. First of all, in the activity where you download and process the data, you should override the onSaveInstanceState and onRestoreInstanceState methods so that your data is persisted and survives orientation changes (and not loose the work you did processing the data).
If your application simply opens a detail activity, from the main activity, then when you press back, your data will be available. You do not have to reload anything. If this is not the case, then there may be some problem with how you load the data in the activity's lifecycle (e.g. avoid loading and processing data in the onStart/onResume methods).
If you want to persist data after your application has died, you can either use http caching (e.g. with OkHttp) or use an Sqlite database, as others have pointed out. These approaches can also be combined for additional performance gains.
You can cache the server response in shared preference in form of JSON and store to avoid making server calls each time. (However this is recommended only when data is small)
Example :
public <T> void putJsonIntoSharedPreferences(String key, T object) {
SharedPreferences sharedPreferences = getSharedPreferences();
SharedPreferences.Editor edit = sharedPreferences.edit();
String json = convertToJson(object);
edit.putString(key, json);
edit.commit();
}
public <T> T getFromSharedPreferences(String key, Class<T> clazz) {
SharedPreferences sharedPreferences = getSharedPreferences();
final String fromSharedPreferences = sharedPreferences.getString(key, "");
T object = null;
if (!fromSharedPreferences.equals("")) {
try {
object = JsonConverter.fromJson(fromSharedPreferences, clazz);
} catch (IOException e) {
return null;
}
} else {
try {
return clazz.newInstance();
} catch (Exception e) {
return null;
}
}
return object;
}
private <T> T fromJson(String json, Class<T> clazz) throws IOException {
ObjectMapper objectMapper = getObjectMapper();
if(json!=null)
return objectMapper.readValue(json,clazz);
return null;
}
private String convertToJson(Object object) {
ObjectMapper objectMapper = new ObjectMapper();
String string = null;
try {
string = objectMapper.writeValueAsString(object);
} catch (JsonProcessingException e) {
Log.d(ComponentConstants.JSON_PARSE_ERROR_TAG,e.getMessage(),e);
}
return string;
}
You can simply use an http client with caching like OkHttp. more complicated yet more appropriate in some cases is to manage an internal Sqlite Database.
As a Side note You can save all your data and load it when activity is reloaded but you should not. as no metter what way you choose, IO will be involved and its going to be a heavy operation if your data is big enough.
Its good practice to load exactly what the activity needs to show when its being loaded, and not the full data. You should use CursorLoader or CursorAdapter to load dynamically what your activity needs as user interacts with it...
Is it a good idea to pass a list of object from one Activity to another for Android ?
It is quiet troublesome to pass a list of object in an intent, and I wonder whether it affect the performance if the object list is too large or the object is too complicated.
Is it a better solution to get the list of object from other place, for example, query the DB once more , or save the list of object in a temporary class and fetch it in new Activity?
As long as you are passing Parcelable objects' list, nothing's wrong when passing through Intent. Regarding performance, that is up to you and the amount of data you pass.
As per my experience, If you are passing data up to 1MB of size, it should be fine. Anything above that will fail as this seems to be the limit. Read this.
Besides, you are welcome to use preferences, SQLite, files or static-object-referencing methodologies to fetch your data anytime and anywhere.
Solution1 : Use Intent
Send :
Intent data = new Intent(Activity1.this,Activity2.class);
data.putParcelableArrayListExtra(Constant.LIST_OBJECT,
(ArrayList<? extends Parcelable>) getObjects());
receive :
List<YOUR_OBJECT> objects = data.getParcelableArrayListExtra(Constant.LIST_OBJECT);
Solution2 :
public class SessionData {
private static SessionData instance;
private final List< YOUR_OBJECT > listSessionObjects;
private SessionData() {
listSessionObjects = new ArrayList<>();
}
public static final SessionData getInstance() {
if (instance == null) {
instance = new SessionData();
}
return instance;
}
public List<YOUR_OBJECT> getListSessionObjects() {
return listSessionObjects;
}
public void setListSessionObjects(List<YOUR_OBJECT > objects) {
listSessionObjects = objects
}
}
to use it :
SessionData.getInstance().getListSessionObjects();
SessionData.getInstance(). setListSessionObjects(objects);
I have an activity that contains two fragments. I would like to pass data (an ArrayAdapter and an ArrayList) between the two fragments. User operations in Fragment 1 modifies both datatypes, which then need to be passed onto Fragment 2. Similarly, user operations in Fragement 2 also modify the two datatypes, which then need to be passed back to Fragment 1.
Can you please guide on the most elegant way to do it? I have been looking into parcelable and interface. Since, I do not have much experience with Java (let alone android) I was not able to discern the limitations of two approach.
I'd suggest holding a reference to your data object in each fragment (as I'm sure you are) and do something like the following:
public void onResume()
{
mDataObject = getFragmentManager.getFragmentByTag("Fragment1").getDataObject1();
super.onResume();
}
you can run this in Frag 1 and Frag 2 and it should update the model. If you have sub objects you will need to compare them and determine if the sub-objects are different in a function something like this.
public void determineIfDifferent(DataObject mData1)
{
Field mData1Fields[] = mData1.getClass().getFields();
Field mData2Fields[] = mData2.getClass().getFields();
for (int i = 0; i < mData1Fields.length; i++)
{
try
{
if (mDataFields[i].get(mData) != null && tempFields[i].get(PS)!= null)
{
String mDataValue = mDataFields[i].get(mData).toString().trim();
String tempValue = tempFields[i].get(PS).toString().trim();
if (!mDataValue.equals(tempValue))
{
differenceList.add(tempValue);
}
}
}
catch (IllegalArgumentException e)
{
Logger.logStackTrace(getClass().getSimpleName(), e);
}
catch (IllegalAccessException e)
{
Logger.logStackTrace(getClass().getSimpleName(), e);
}
}
}
This obviously can be modified if the type is not a String - this is just what I had at hand
You can put them into Intent.putExtra() and vice versa
I'm hitting an external API that's returning JSON data (new dvd titles). I'm able to parse out the JSON and list each dvd title and other dvd information into a ListView just fine. I was also able to setup an onListItemClick method just fine for primitive data (title string). I ended up writing something like so for the onListItemClick method:
Just to note, the productArray is a class var that's being set by another method that holds an array of JSONObjects.
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
Intent i = new Intent(DvdListingActivity.this, MovieProductActivity.class);
try {
JSONObject jsonObj = productArray.getJSONObject(position);
i.putExtra("mTitle", jsonObj.getJSONObject("Title").opt("val").toString());
i.putExtra("mRelDate", jsonObj.getJSONObject("RelDate").opt("val").toString());
i.putExtra("mDesc", jsonObj.getJSONObject("Desc").opt("val").toString());
i.putExtra("mRating", jsonObj.getJSONObject("MPAA").getJSONObject("Rating").opt("val").toString());
i.putExtra("mActors", jsonObj.getJSONObject("Actors").opt("val").toString());
i.putExtra("mImage", jsonObj.getJSONObject("Image").opt("val").toString());
startActivity(i);
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
The above code all works, but I'm thinking there's GOTTA be a better way for me to pass in data to another Activity. I was thinking that I would be able to pass a JSONObject that contains all the data for a dvd movie instead of setting each data point individually.
I tried for a week and a half to figure out how to use Parcelable. I tried instantiating a new JSONObject jsonObj that implements Parcelable with no luck. I kept getting an error in my LogCat that said that the object was un-parcelable.
I've tried reading the Android developer site and other blogs, but I couldn't apply their examples to what I needed to do.
Any help would be much appreciated
You can simply put an entire JSONObject as a string. Something like this:
i.putString("product", jsonObj.toString);
And then in the MovieProductActivity you could
JSONObject jsonObj = new JSONObject(getIntent().getStringExtra("product"));
From Current Activity
Intent mIntent = new Intent(CurrentActivity.this, TargetActivity.class);
mIntent.putExtra("ITEM_EXTRA", json_object.toString());
startActivity(mIntent);
From Target Activity's onCreate
try {
json_object = new JSONObject(getIntent().getStringExtra("ITEM_EXTRA"));
Log.e(TAG, "Example Item: " + json_object.getString("KEY"));
} catch (JSONException e) {
e.printStackTrace();
}
You can just encapsulate all of the information about a movie into a Movie object, which implements Parcelable.
The code will look similar to above, but instead of passing 6 different extras you can just pass one extra that is the movie.
Movie movie = new Movie();
movie.setTitle(jsonObj.getJSONObject("Title").opt("val").toString());
movie.setRelDat(jsonObj.getJSONObject("RelDate").opt("val").toString());
.
.
.
i.putExtra("movie", movie);
For information on implementing a Parcelable object, see Parcelable docs. You basically just write out each string in 'writeToParcel', and read in each string in 'readFromParcel' in the correct order.
The answer posted here by Cheryl Simon is completely correct however this might not explain it too clearly for someone who is new to android or java. Please let me build on her answer.
The best and cleanest way to do this is with the Parcelable interface.
Declare a class as below that implements Parcelable. This one uses an accessor and mutator which isn't actually necessary.
public class JSonArrayParser implements Parcelable {
private JSONArray jsonArray;
public JSonArrayParser() {}
public JSonArrayParser(JSONArray jsonArray) {
this.jsonArray = jsonArray;
}
public void setJsonArray(JSONArray jsonArray) {
this.jsonArray = jsonArray;
}
public JSONArray getJsonArray() {
return jsonArray;
}
#Override
public int describeContents() {
return 0;
}
#Override
public void writeToParcel(Parcel dest, int flags) {
}
}
The next step would be to create an instance of that class (in this case JSonArrayParser) and pass it through the Bundle as in the example below.
//This could be an Activity, Fragment, DialogFragment or any class that uses the Bundle Type such as savedInstanceState.
jobItemsPopUp = new JobItemsPopUp();//This example uses a DialogFragment.
Bundle bundle = new Bundle();
JSonArrayParser jSonArrayParser = new JSonArrayParser(jsonArray);
bundle.putParcelable("jsonArray", jSonArrayParser);
jobItemsPopUp.setArguments(bundle);
jobItemsPopUp.show(getActivity().getSupportFragmentManager(), "JobItemsPopUp");
The the way your would retrieve the value would be as follows (I've used the onCreate but you can retrieve the value anywhere):
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
this.jsonArray = ((JSonArrayParser) getArguments().getParcelable("jsonArray")).getJsonArray();
}
}
Just as an additional note: you can do this with custom constructors, but this method has been deprecated by Google.
The old deprecated method (associated with app compat v4 NOT RECOMMENDED) would be:
private Fragment MyFragment;
MyFragment = new MyFragment(jsonArray)
Hope this help and clears a few things up.
The Parcelable interface can be used with any data class with or without mutators and/or accessors.
Are you storing the information in a DB? If you are, you can simply pass the ID of the desired title (via the intent).
Have a look at gson. It allows you to sereialise and deserialise JSON blobs to entire class instances.
Just create a Parcel like this:
public class Parcel implements Serializable {
private JSONObject obj;
public Parcel(JSONObject obj) {
this.obj = obj;
}
public JSONObject getObj() {
return obj;
}}
And put into the bundle:
Parcel p = new Parcel(data);
Bundle args = new Bundle();
args.putSerializable("events", p);
Finally, you can get the JSONObject back using:
JSONObject obj = ((Parcel) ((Bundle) getArguments()).getSerializable("events")).getObj();