I'm currently using a custom list adapter and modifying the rows of the listview at runtime (changing text, buttons etc).
I want to find a way to save/restore the changes made to the listview at runtime when I start another fragment and then come back to the fragment containing the listview.
Currently the listview is being reloaded every time and all the runtime changes are lost.
The following shows how I am setting the list adapter.
public void setAdapterToListView(ArrayList<item> list) {
ItemList = list;
ItemAdapter adapter = new MenuItemAdapter(list, getActivity(), this);
ItemListView.setAdapter(adapter);
}
I am then using a custom list adapter to make the changes (mentioned above) at runtime. Here's a snippet of how I am changing things at runtime:
if (holder.qty.getText().toString().equals("Qty")) {
relativeLayout.setBackgroundColor(row.getResources().getColor(R.color.navDrawerL ightBlue));
holder.qty.setText("1");
holder.qty.startAnimation(anim);
holder.removeItem.setBackgroundResource(R.drawable.remove_item_red);
holder.removeItem.setEnabled(true);
...
Is there a recommended way to approach this issue?
Adapter
You will want your custom adapter to implement some sort of getter for it's internal data. For instance
public ArrayList<YourDataType> getList() {
return new ArrayList<YourDataType>(mAdapterData);
}
Activity
Then in your activity/fragment, you'll need to save and restore that data.
private static final String STATE_LIST = "State Adapter Data"
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelableArrayList(STATE_LIST, getAdapter().getList());
}
While there is an onRestoreInstanceState() method you could override, I typically restore during onCreate(). Usually more convenient to when other things are getting instantiated. Either or is viable.
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//If restoring from state, load the list from the bundle
if (savedInstanceState != null) {
ArrayList<YourDataType> list = savedInstanceState.getParcelableArrayList(STATE_LIST);
ItemAdapter adapter = new MenuItemAdapter(list, getActivity(), this);
} else {
//Else we are creating our Activity from scratch, pull list from where ever you initially get it from
ArrayList<YourDataType> list = getInitData();
ItemAdapter adapter = new MenuItemAdapter(list, getActivity(), this);
}
}
YourDataType
You didn't mention what YourDataType was but I'm assuming it's a custom class. In order to work with the bundled savedstate, it must implement Parcelable. Android's dev link on Parcelable and a StackOverFlow post explaining how to write your own custom class with Parcelable.
Update
Depending on what you are doing with the fragment will dictate if the onSavedInstanceState() method will be called. From the way your question is asked, I'm assuming you are pushing one fragment onto the backstack to load another on top. Then hitting the back button will reloaded that fragment from the backstack...and pass along the bundled state to reload from.
Of course that is just one of many scenarios. It's perfectly possible for the fragment in question to only perform onPause() followed by onStop()...then when redisplaying the fragment, only seeing it do onStart() followed by onResume(). In this case, there would not be a saved state because the fragment was never fully destroyed, so there is nothing to restore. It should be in the same state. If it's not and instead you are seeing it reload the initial data...then you are probably reloading the initial data in or after onStart(). You'll want to move all that initializing data to onCreate() instead.
Another possible case is that you completely destroy the fragment without ever putting it on the backstack. Re-initializing it would not have a saved state to pull from. If you wanted to "restore" this fragment back to the state beforehand, then you can not rely upon onSavedInstanceState(). You will need to persist that information manually somewhere in memory, to disk, or a DB yourself. Then pull from it accordingly.
Life cycles with fragments are unfortunately really complex and depend greatly on usage and even between the support library vs native fragments.
After doing some reading/research I ended up solving this by saving the adapter data to Saved Preferences by using Gson (https://code.google.com/p/google-gson/).
I used Gson to first convert my array of objects into Json and then stored the Json strings in the Saved Preferences.
Subsequently I was able to retrieve these as required and read the Json into Java objects again, store these in an array list and pass this to my listview adapter.
Here's a brief overview of the process for anyone who is looking to do something similar.
Saving to Shared Preferences:
SharedPreferences settings;
Editor editor;
settings = c.getSharedPreferences(PREFS_NAME,Context.MODE_PRIVATE);
editor = settings.edit();
Gson gson = new Gson();
String ItemsJson = gson.toJson(list);
editor.putString(categoryId, ItemsJson);
editor.apply();
Reading from Shared Preferences:
if (settings.contains(categoryId) {
String jsonItems = settings.getString(categoryId, null);
Gson gson = new Gson();
Item[] favoriteItems = gson.fromJson(jsonItems, Item[].class);
list = Arrays.asList(favoriteItems);
ItemsFromSharedPrefs = new ArrayList<>(list);
AllCategories.addAll(ItemsFromSharedPrefs);
} etc...
Related
In my app , at a particular screen there is Arraylist which is a source of recycler view . There are many buttons on that screen which takes you to next screen , next screen may be a single plain activity or activity with view pager and tablayout and that fragment may contain buttons which takes you to next screen .In some screen i can edit the Song class field too . My problem is that i am confused whether the send the list to next screen and further next fragment or next screens through intent or should i make that static and access it anywhere . Again and again i have to parcel wrap and then unwrap then send it to fragment then wrap for the fragment then unwarp it then send it to adpater attached to fragment , this is long process and i am afraid that anyone can change that list in any screen and secondly this whole process is cumbersome every time sending intent and receiving intent .
Passing the Values from Intent have chances of data loss so do not pass the multiple Values with the Intent. So it will be better to access the values from a Static class if the values are not changing. If sometimes values are changing then pass these with Intent.
You can also go with the SharedPreferences, it will be more feasible in your case.
You can shift to flux architecture. Redux store kind of state management.
Who ever needs data queries to store. And data changes automatically dispatched to listeners.
SharedPreferences are NOT made to pass data between Activities/Fragments. They are here to store data that need to persist when the app is closed.
An option could be to use some kind of "cache" class that will store your data. So let's say you display the list of whatever data you want on the first screen, then the user selects one of the items to see the details/modify it. So you give the position of this data (in the array stored in the cache) to your next fragment and this next fragment asks the cache to give to it the data, based on the position it has received.
Example
Cache class
public class Cache{
List<Object> data;
// ... Implementation
public List<Object> getData(){
return this.data;
}
public setData(List<Object> data){
this.data = data;
}
public Object getObject(int position){
return data.get(position);
}
}
List Activity
public class ListDataActivity extends ListActivity{
public void onCreate(...){
// get the data
...
// Set the data to the cache
Cache.getInstance().setData(data);
// Display the list
...
}
public void onItemClicked(...){
Intent intent =....
intent.put(ITEM_POSITION, pos);
startActivity(intent);
}
}
Details Activity
public class DetailsActivity extends Activity{
public void onCreate(...){
//...
// get data from the cache
int pos = getIntent.getInt(ITEM_POSITION);
Object obj = Cache.getInstance().getObject(pos);
// Display the details
...
}
}
There are a lot of questions about fragment communication here, but they are normally question about getting data from activity and sending data back to activity, normally starting from fragment.
But I wonder what what is best approach for sending data from activity to fragment, when you cannot do it when creating fragment? For clarification, Lets assume that an app has 2 fragments that can use (can not must) some data to improve user experience, but obtaining this data is costly. So obtain this data in activity using a Loader or AsyncTask in main activity while creating Fragments themselves. Now when data is ready asynchronously in Activity, we need to send this data to Fragments. What is best approach for this? I thought of a way for doing this, and I like to know if there is any problem with this approach.
1-In fragment we use onAttach to send fragment to activity and check if any data is already read:
#Override
public void onAttach (Activity activity) {
MyActivity act = (MyActivity)activity;
act.addFragment(this);
Data data = act.getData();
if (data != null) {
setAdditionData(data)
}
}
2-and in activity store a WeakReference to Fragment:
private ArrayList<WeakReference<Fragment>> mFragments = new ArrayList<>();
...
public void addFragment(Fragment frag) {
WeakReference<Fragment> f = new WeakReference<Fragment>(frag);
mFragments.add(f);
}
public Data getData() {
return mData;
}
public void updateFragmentsData() {
for (Iterator<WeakReference<Fragment>> iterator = mFragments.iterator(); iterator.hasNext();) {
WeakReference<Fragment> wf = iterator.next();
Fragment f = wf.get();
if (f != null) {
f.setAdditionData(mData);
} else {
iterator.remove();
}
}
}
Now when fragments attaches, it adds itself to list of fragments in activity and checks if data is already ready and if ready it will use that data. On the other hand, when data is ready asynchronously in activity, it can call updateFragmentsData() to update all fragments data.
I wonder if this approach is correct or it can be incorrect in some situations? Any idea? Is there any better approach for notifying fragments from main activity?
Btw, is it possible to use Handler/Message for communicating between fragments too or not? As another approach?
Best Regards
I can think of three ways.
Use a listener. Write an interface in the activity to use it as a listener. The fragment implements the interface and registers and unregister as a listener at appropriate time.(say at onCreateView and onDestroyView).
This one is my favorite. I hope DataBinding is gaining popularity and it can be used to solve this. Say you define a particular model for the fragment layout. Now you use ObservableFields in the model. Pass this model to your databinding variable. Now change this object from either the activity or the fragment itself, changes will be reflected in the view.
The newly introduced ViewModels. I will be using them from my next project.
I'm looking for the best implementation pattern in Android to update a list when one of the elements change in a different activity.
Imagine this user journey:
An async process fetches ten (10) contact profiles from a web server. These are placed in an array and an adapter is notified. The ten (10) contact profiles are now displayed in a list.
The user clicks on contact profile five (5). It opens up an activity with details of this contact profile. The user decides they like it and clicks 'add to favourite'. This triggers an async request to the web server that the user has favourited contact profile five (5).
The user clicks back. They are now presented again with the list. The problem is the list is outdated now and doesn't show that profile five (5) is favourited.
Do you:
Async call the web server for the updated data and notify the adapter to refresh the entire list. This seems inefficient as the call for the list can take a couple of seconds.
On favouriting the profile store the object somewhere (perhaps in a singleton service) marked for 'refresh'. OnResume in the List activity do you sniff the variable and update just that element in the list.
Ensure the list array is static available. Update the array from the detail activity. OnResume in the activity always notify the adapter for a refresh.
Ensure the list array and adapter is static available. Update the array and notify the adapter from the detail activity.
Any other options? What is the best design principle for this?
Async call the web server for the updated data and notify the adapter
to refresh the entire list. This seems inefficient as the call for the
list can take a couple of seconds.
As you say, it's very inefficient. Creating an Object is expensive in Android. Creating a List of many object is much more expensive.
On favouriting the profile store the object somewhere (perhaps in a
singleton service) marked for 'refresh'. OnResume in the List activity
do you sniff the variable and update just that element in the list.
This is not a good solution because there is a probability that the app crashes before we refresh the object or the app get killed by the device.
Ensure the list array is static available. Update the array from the
detail activity. OnResume in the activity always notify the adapter
for a refresh.
Updating the array via a static method or variable is not a good solution because it makes your detail Activity get coupled with the list. Also, you can't make sure that only the detail activity that change the list if your project get bigger.
Ensure the list array and adapter is static available. Update the
array and notify the adapter from the detail activity.
Same as the above, static variable or object is a no go.
You better use an Event Bus system like EventBus.
Whenever you clicks 'add to favourite' in detail activity, send the async request to update favourite to the web server and also send Event to the list activity to update the specific profile object. For example, if your profile has id "777" and the profile is favourited in detail activity then you need to send the Event something like this in your :
btnFavourite.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Send event when click favourite.
EventBus.getDefault.post(new RefreshProfileEvent(id, true);
}
});
RefreshProfileEvent is a simple pojo:
public class RefreshProfileEvent {
private String id;
private boolean isFavourited;
public RefreshProfileEvent(String id, boolean isFavourited) {
this.id = id;
this.isFavourited = isFavourited;
}
//getter and setter
}
Then you can receive the Event in your list activity to update the selected profile:
public class YourListActivity {
...
#Override
protected onCreate() {
...
EventBus.getDefault().register(this);
}
#Override
protected onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
#Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(RefreshProfileEvent event) {
// Refresh specific profile
// For example, your profile is saved in List<Profile> mProfiles
// Search for profile by its id.
for(int i = 0; i < mProfiles.size(); i++) {
if(mProfiles.getId().equals(event.getId()) {
// Refresh the profile in the adapter.
// I assume the adapter is RecyclerView adapter named mAdapter
mProfiles.get(i).isFavourited(true);
mAdapter.notifyItemChanged(i);
// Stop searching.
break;
}
}
}
You don't need to wait for AsyncTask request result returned by the server. Just make the profile favourited first and silently waiting for the result. If your request success, don't do anything. But if the request error, make the profile unfavourited and send unobstructive message like SnackBar to inform the user.
Third option is the best when a user changes the data in detail activity the array should be changed and then when the use returns to main activity call Adapter.notifyDataSetChanged(); will do the trick
For an ArrayAdapter , notifyDataSetChanged only works if you use the add() , insert() , remove() , and clear() on the Adapter.
You can do something like this:
#Override
protected void onResume() {
super.onResume();
Refresh();
}
public void Refresh(){
items = //response....
CustomAdapter adapter = new CustomAdapter(MainActivity.this,items);
list.setAdapter(adapter);
}
On every onResume activity it will refresh the list. Hope it helps you.
I was thinking. What is the best way to save a custom adapter before activity onDestroy() is called? I want to populate an adapter with items (texts and images) and set it to listView. However, I don't want to repopulate the adapter again when the user navigate away from that activity and comes back as repopulation is too time consuming. I want to save the adapter value somewhere before the activity inDestroy() is called and check if it empty on activity onCreate.
Well, adapter is a pretty complex object and its persistent saving may be a difficult task (if possible at all). The more common approach is saving persistently your dataset.
You worry about the population time, but serialization-deserialization of the adapter is going to take time as well, and apparently much more time then the dataset alone, because it includes the dataset in it.
EDIT
Small conceptual example on saving your dataset to SharedPreferences using Gson library (more on it) (just one of the ways to persistently save your data):
public void saveData(ArrayList<YourDataType> data) {
Gson gson = new Gson();
String dataJson = gson.toGson(data);
getSharedPreferences("your_prefs", Context.MODE_PRIVATE).edit()
.putString("key_data", dataJson)
.apply();
}
public ArrayList<YourDataType> restoreData() {
String dataJson =
getSharedPreferences("your_prefs", Context.MODE_PRIVATE)
.getString("key_data", "empty");
ArrayList<YourDataType> data = null;
if (!dataJson.equals("empty")) {
Gson gson = new Gson();
Type collectionType = new TypeToken<ArrayList<YourDataType>>() {}.getType();
data = gson.fromJson(dataJson, collectionType);
}
return data;
}
You can save adapter data inside application class and when activity is recreated check Application class arraylist is empty or not. If not empty
assign it to adapter.
But storing it inside global variable i.e Application class may make your app heavy on heap memory.
public class GlobalState extends Application {
ArraList<Type> arrayList = new ArrayList<type>();
public void setArraylist(ArraList<Type> arrayList) {
this.arrayList = arrayList;
}
public ArraList<Type> getArrayList() {
return arrayList;
}
public int dataSize() {
return arrayList.size();
}
}
I have a HomeActivity which extends Activity that contains Actionbar items. The HomeActivity has 1 fragment (StatusFragment which extends Fragment). In the Fragment there is a ListView which uses a custom ArrayAdapter and a method call to supply the data.
private ParseUser[] GetUsers(){
final ParseQuery<ParseUser> query = ParseUser.getQuery();
ParseUser[] usersArray;
try {
List<ParseUser> users = query.find();
usersArray = users.toArray(new ParseUser[users.size()]);
} catch (ParseException e) {
usersArray = null;
e.printStackTrace();
}
return usersArray;
}
I'm having trouble getting the ListView to update from the OnOptionsItemSelected callback.
case R.id.home_ab_refresh:
StatusFragment pFrag = (StatusFragment) getFragmentManager().findFragmentByTag("mFragment");
pFrag.users = pFrag.GetUsers();
pFrag.mAdapter.notifyDataSetChanged();
return true;
1) Is this an appropriate way to access the Fragment from the Actionbar items (HomeActivity)?
2) Is there a better way to design this code?
Thanks much!
Re 1) I probably wouldn't do a findFragmentByTag() every time, and instead just stick the fragment into a member variable of the activity during the activity's onCreate().
The main issue with the code is something else:
pFrag.users = pFrag.GetUsers();
pFrag.mAdapter.notifyDataSetChanged();
Here you violate the object-oriented design principle of loose coupling. The HomeActivity is too intimately bound to the implementation details of the StatusFragment. What you should do instead is move that code into the fragment and expose it as a single, public method that is named for the intent (goal, purpose) of the action, not its implementation.
// In HomeActivity
pFrag.reloadData();
// In the fragment
public void reloadData() {
this.users = pFrag.GetUsers();
this.mAdapter.notifyDataSetChanged();
}
This way, it's easier to reuse the status fragment elsewhere. More importantly, it's easier to evolve that fragment, you can now completely change the internals without having to change the host activity. This is cleaner from a design perspective.
Re 2) Aside from the issue I already mentioned, you should consider returning an empty array rather than null when an exception occurs. It's generally a better idea to return empty array/collection from finder methods, because people tend to immediately use the result for an iterator or an addAll() or something like that, without null-checking it first.
First of all, you dont make a nullpointer check, since you cant be certain that the FragmentManager will actually return a validFragment.
you can however just catch the onOptionsMenuSelected event in the fragment itself, which will result in a much more capsulated code.
Besides that, when do you refresh the ListView? Wouldnt it make sense to update the listview automatically once the new data has arrived?