Is it possible to save a reference to an object in a bundle?
Not the whole object, only the reference?
For example, Bundle class has method to get an object but I can't find the put or set method.
I need it because in an activity, I create an instance of my timer class, and then I start the timer: myTimer.start()
If I have to restart the activity, I want to restore the timer to it's previous value.
Hopefully you cannot do this. You can only store primitives in bundle. As Marcin Orlowski mentioned "storing" the whole object is achieveable through implementing Parcelable interface. By "storing" I meant storing object state. Implementing this interface helps you persisting your object state in different code sections without putting its all attributes to Bundle object over and over again.
When activity goes to pause state sooner or later all objects used by your activity will be removed by garbage collector, so storing references to them would be silly.
The official docs recommend using fragments for storing references during "configuration changes" (no, I don't think this means you need to repose your activity as a fragment, but to use a fragment as a storage medium (clarification needed)):
http://developer.android.com/guide/topics/resources/runtime-changes.html#RetainingAnObject
Retaining an Object During a Configuration Change
If restarting your activity requires that you recover large sets of data, re-establish a
network connection, or perform other intensive operations, then a full
restart due to a configuration change might be a slow user experience.
Also, it might not be possible for you to completely restore your
activity state with the Bundle that the system saves for you with the
onSaveInstanceState() callback—it is not designed to carry large
objects (such as bitmaps) and the data within it must be serialized
then deserialized, which can consume a lot of memory and make the
configuration change slow. In such a situation, you can alleviate the
burden of reinitializing your activity by retaining a Fragment when
your activity is restarted due to a configuration change. This
fragment can contain references to stateful objects that you want to
retain.
When the Android system shuts down your activity due to a
configuration change, the fragments of your activity that you have
marked to retain are not destroyed. You can add such fragments to your
activity to preserve stateful objects.
To retain stateful objects in a fragment during a runtime
configuration change:
Extend the Fragment class and declare references to your stateful
objects. Call setRetainInstance(boolean) when the fragment is created.
Add the fragment to your activity. Use FragmentManager to retrieve the
fragment when the activity is restarted. For example, define your
fragment as follows:
public class RetainedFragment extends Fragment {
// data object we want to retain
private MyDataObject data;
// this method is only called once for this fragment
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// retain this fragment
setRetainInstance(true);
}
public void setData(MyDataObject data) {
this.data = data;
}
public MyDataObject getData() {
return data;
}
}
Caution: While you can store any object, you should never pass an
object that is tied to the Activity, such as a Drawable, an Adapter, a
View or any other object that's associated with a Context. If you do,
it will leak all the views and resources of the original activity
instance. (Leaking resources means that your application maintains a
hold on them and they cannot be garbage-collected, so lots of memory
can be lost.)
You can't. You can only pass limited set of data types as you can see by looking at method lists in the docs and if you want more than single primitives then your class needs to implement Parcelable interface (or use helper like Parceler). Once you got that done you will be able to pass your object data via the Bundle (but still, not object itself)
One alternate approach is to convert the POJO to json and then to string the GSON library and save in bundle as string. Then retrieve it back from bundle as string and convert it back to object using the same GSON library. Example code below.
Convert JSON to String and save it in bundle
JsonElement json = new JsonElement();
String result = gson.toJson(json);
bunlde.putString("key",result);
Convert String from bundle to object again
JsonElement json = gson.fromJson(bundle.getString("key"), JsonElement.class);
Related
I would like to know what is the proper way to access non-primitive data between activities. Although there are many questions and answers on the topic, I still think that my question hasn't been asked yet.
1) The main activity shows a custom adapter ToDoListAdapter. ToDoManagerActivity loads data for entries in its methods loadItems() and saveItems() in onPause() and onResume():
public class ToDoManagerActivity extends ListActivity {
toDoListAdapter mAdapter;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAdapter = new ToDoListAdapter(ToDoManagerActivity.this);
.....
}
}
2) Another activity TodoChartActivity is fired from ToDoListAdapter view and is an activity that shows all entries, but differently. In order to show the entries, I need the data that is in adapter variable in main activity.
According to sources in the internet, there are different ways to make adapter variable (or any other) accessible in the second activity:
Make mAdapter public and static or make public getter&setter for it. BAD APPROACH
Access it from Application Singleton. BAD APPROACH
Copy loadItems() and saveItems() from ToDoManagerActivity and load them again into memory. Since I use memory much more that I actually need -- BAD APPROACH
Passing all data of one list entry through intent extras. TEDIOUS APPROACH, I was told. Also, let's imagine, that we need all entries, for example. So, it's a general question.
Do you have other suggestions, how to access complex objects and lists between activities?
ToDoManagerActivity is your first Activity, from here you are initiating ToDoListAdapter and from ToDoListAdapter you are inflating TodoChartActivity.
This is a common scenario. Since, your data is same, just share it across the adapters.
From ToDoManagerActivity pass the list of object in your ToDoListAdapter. Now, you have your data in this adapter. Now, pass the same data to TodoChartActivity.
A singleton is not necessarily a bad approach unless the lifetime of its contents is being held longer than they're used by the two activities. You could even put the container object in a WeakReference so that it's garbage collected after the two activities using the data are no longer around and holding references to the data. The major downside to this approach is that your activities can't save and retain they state if they need to be destroyed and recreated.
You could also persist your data to a sqlite database. If you don't want to break out all the fields from all your objects into sqlite fields, you could "cheat" and just serialize them as JSON with something like Gson that will automate this process if your model data can be represented as Java primitive types and basic collections. If you do this, then your activities can also rebuild themselves from the data persisted in sqlite in case they are destroyed by Android. The downside is that you now have to figure out when to remove this data from the DB when it's no longer being used. And if you need to use this scheme more than once at a time in your app, you need to be able to identify each collection of data using some id and make sure that id is shared between activities.
I use the putExtra and getSerializable Methods to pass my object to a second activity. It works fine, however, am I required to return this object in order to maintain the changes made in the second activity?
When I run my app, and launch my second activity then call finish() after makimg a change to the object passed to it, if I relaunch that second activity the old object data previous to the change is displayed, does this mean that using the put/get serializable methods are passing a clone of the object, and that in order to keep the changes made on the second activity I must repass the object back to the main activity ?!
I am not sure why would you require such behaviour.However you can try the following methods.
You can make that object as global static variable(preferably in the application class of the app) so that the object is retained between different instances of the activities.
Also if the state of the object is important across app restarts you must plan to write the state of the object in some persistent storage like db/file/shared preference.Refer this link for storing object,
I have the following flow:
ActivityA/FragmentA passes to ActivityB/FragmentB via intents a custom somewhat large object.
Among the attributes of the object is a List<CustomObject> items
User presses a widget in FragmentB and then FragmentB starts ActivityC/FragmentC passing also that custom object (Parcelable) that is supposed to show details in its UI and also starts a Service to fetch the list that populates the items of that specific object.
When Service fetches the result from a background HTTP call I need to update the list in the UI that is expected to display these items fetched.
This list is in FragmentC which has a copy of the custom object with the items null.
The Service has another copy of the custom object and the items just fetched but can not update the list of the fragment.
Making a static variable of the fragment and assigning this and then exposing a public method in the fragment that the Service can call to pass the items works but is very dirty.
I was wondering what is a clean/standard design for this?
I am using LocalBroadcastManager but the list is fairly large and I am not sure if passing it via the intent is a good idea
Normally, when we use an Intent, it is to cross process boundaries, and so the Intent has to be converted into a byte array (by means of a Parcel), and that gets to be a problem with large data. LocalBroadcastManager does not do that -- it just passes the Intent object around as is.
The downside of LocalBroadcastManager is that the message is an Intent, and because an Intent is usually used for IPC, it has limitations on data types. Personally, I recommend greenrobot's EventBus, or even Square's Otto, over LocalBroadcastManager, for just this reason. That being said, if it is easy enough for you to get your data into an Intent, size should not be an issue.
I need to access a few of my custom objects in different activities of my application. For this purpose and for the sake of accessibility I have been using static properties for moving data from an activity to another.
For example I have the following class:
public class TrackItem {
public String title, imageUrl, mediaUrl, type, artist, desc;
public static TrackItem track;
}
And for starting an activity:
TrackItem.track = items.get(i); // 'items' is an arraylist defined elsewhere
Intent trackActivity = new Intent(c, TrackActivity.class);
startActivity(trackActivity);
And now inside the TrackActivity I can easily access TrackItem.track and use it's properties.
I just need to know if I'm making a mistake or not? Is there any better way to do this?
The android way of dealing with that problem is to make your class
Parcelable
and pass it with the intent from one activity to another.
If you are initializing your static variables in an activity be aware of loosing data, because in android activity can be destroyed at any point after its state changed to pause. Moreover, your static variables can be erased if the entire application is killed by the system, that is happening rather frequently. Then you'll get the
NullPointerException
trying to access your data.
If you really want to use static members handle their initialization in the
Application
class constructor, so they will be recreated on the start of your application, being killed.
But in general it is not a good practice in android.
I would say it is OK in certain cases, but there might be other more suitable solutions.
You could have a central data store class that uses the singleton principle and therefore would be accessible from everywhere. You would add the item id to the Intent for the new Activity. Then, with the id, you could get the item from the data store.
You could also make the item serializable and just add it to the Intent.
One thing to keep in mind when using static members is that it could lead to a memory leak. Static members are related to the class and are therefore only garbage collected if you either set them to null, or the whole app gets killed and the classloader unloads this specific class.
In general, this is an unsafe practice because it is difficult to keep track of who is manipulating its data. It is much safer to use static variables for bookkeeping information, such as an ID which you can use to go look up the appropriate TrackItem (e.g. in an SQLite database), which is its own object and does not have the chance of something else editing it when it shouldn't be. It terms of OOP, using static variables as shared data breaks encapsulation.
If you are looking to send data around the app, it would be much better to do so either with intents, as others are saying, or with SharedPreferences. Both have the advantage that you are dealing with only one instance of the object at any given time, SharedPreferences have the added advantage of keeping the data around after the app has been killed, so that users can resume with the same track that was playing when they closed the app. And both of these are safer than using static members as shared data fields.
The question is simple, if I make an object parcelable, and put it into a bundle and create a fragment using the bundle. Does the object get cloned or is it referenced.
The context.
I have got an object stored/referenced in an ArrayList. Depending on the type of object in the ArrayList (polymorphism is used). I create a fragment suitable for dealing with it.
I need to also pass this object to the fragment. The fragment is used within a custom view pager. I do not wish to have duplicate objects and it seems to me parcelable clones objects.
Another method is to pass the index of the object in the ArrayList. and then get a reference to the arraylist from the fragment using getActivity().myList.get(Integer passed to ). But it doesn't seem very safe to me (ArrayList contents may change, although I simply delete everything and start again). I have also read, that you should avoid passing arguments to a fragments constructor as it may be recreated using the default no-args constructor.
(Although I'm currently destroying any recreated fragments as there are some strange problem with reattaching to the correct view, another post).
New sub-question: is there a way to pass a value by reference to a fragment?
The question is simple, if I make an object parcelable, and put it into a bundle and create a fragment using the bundle. Does the object get cloned or is it referenced.
It may get cloned, if not immediately, at other points in time (e.g., when the fragment's arguments Bundle is included in the instance state).
I have also read, that you should avoid passing arguments to a fragments constructor as it may be recreated using the default no-args constructor.
Correct.
Another method is to pass the index of the object in the ArrayList. and then get a reference to the arraylist from the fragment using getActivity().myList.get(Integer passed to ). But it doesnt seem very safe to me (ArrayList contents may change, although I simply delete everything and start again).
Don't use an ArrayList. Use a HashMap with a durable key. Pass the key to the fragment. Have the fragment get the data via the key. Make sure anyone deleting this object (and thereby removing it from the HashMap) does so only when this fragment does not exist, or notifies this fragment so it knows how to handle this scenario.