I've made a simple application with a recyclerview in a fragment. In OnCreateView I get a list from SharedPreferences (from a separate class), then I save it into a private list like this:
mModel = AnotherClass.GetListFromSharedPreferences();
The problem is when I try to add an element to the RecyclerView (and in SharedPreferences) by clicking a button. This is my code when the button is pressed:
AnotherClass.saveInSharedPreferences(itemAdded);
mModel.add(ItemAdded);
saveInSharedPreferences get the saved list from SharedPreferences:
public boolean saveInSharedPreferences(#NonNull final Item item) {
List<item> currentSaved = GetListFromSharedPreferences();
if (currentSaved.size() == 0) {
currentSaved = new LinkedList<>();
}
currentSaved.add(item);
mCache = currentSaved ;
return save();
}
And it calls the save method, that save the list edited in SharedPreferences:
private boolean save() {
if (mCache != null) {
try {
final JSONArray array = new JSONArray();
for (Item item : mCache) {
JSONObject item = item.toJson(); //Just put everthing in a JSON Object
array.put(item);
}
final String arrayAsString = array.toString();
mSharedPreferences.edit().putString(KEY, arrayAsString).apply();
return true;
} catch (JSONException e) {
e.printStackTrace();
return false;
}
}
return false;
}
It works correctly(it adds the new item in SharedPreferences), but after the calling of this methods, seems like that my mModel already has the new Item added! Before calling mModel.add.
Since OnCreateView is called just 1 time when I call the fragment, and the mModel is valorized only in that time, I don't see how is possible that my mModel is modified just after I edit my SharedPreferences...?
to listen to newly added items use registerOnSharedPreferenceChangeListener it will keep called when items added
use it on onCreateView
SharedPreference.registerOnSharedPreferenceChangeListener (SharedPreferences.OnSharedPreferenceChangeListener listener)
Related
This is the activity where I defined the checkboxes and Array list.
I have save the result in the arraylist and want to display the same arraylist in another activity.
public ArrayList<String> Result;
Result = new ArrayList<>();
public void statuscheck(){
bcg.setOnClickListener(v -> {
if(bcg.isChecked()){
Result.add("BCG(Bacillus Calmette and Guérin)");
}
else {
Result.remove("BCG(Bacillus Calmette and Guérin)");
}
});
bopv0.setOnClickListener(v -> {
if(bopv0.isChecked()){
Result.add("b Oral Polio vaccine(bOPV)-0 ");
}
else {
Result.remove("b Oral Polio vaccine(bOPV)-0 ");
}
});
hepatitis0.setOnClickListener(v -> {
if(hepatitis0.isChecked()){
Result.add("Hepatitis B birth dose ");
}
else {
Result.remove("Hepatitis B birth dose ");
}
});
}
the activity where I want to show the arraylist in a text view
TextView vac_result;
ArrayList<String> vc_Res;
vac_result = findViewById(R.id.vac_result);
vac_result.setEnabled(false);
vc_Res = vaccineone.Result;
StringBuilder stringBuilder = new StringBuilder();
for(String s : vc_Res)
stringBuilder.append("\n");
vac_result.setText(stringBuilder.toString());
vac_result.setEnabled(true);
There are many ways to achieve what you want. You can either pass your data as a Bundle when you're trying to call startActivity() or use a static global variable across your application.
The snippet below will demonstrate how to use a static variable:
class Example {
public static ArrayList<String> Result = new ArrayList<>();
}
Fill the static arraylist with your data and simply get the data in the other activity.
Save the "Result" ArrayList with SharedPreferences and in the other activity load the data before trying to work with it.
is realm have something like listener for commiting data?
my code is
...
RealmDatabase.submitNewUser(mainActivity.getMyRealm(), userModel);
mainActivity.closeKeyboard();
mainActivity.onBackPressed();
...
public static void submitNewUser(Realm myRealm, UserModel user) {
myRealm.beginTransaction();
UserDb userDb = myRealm.createObject(UserDb.class);
userDb.setUserid(getNextUserId(myRealm));
userDb.setName(user.name);
....
myRealm.commitTransaction();
}
private static int getNextUserId(Realm myRealm) {
try {
return myRealm.where(UserDb.class).max("userid").intValue() + 1;
} catch (ArrayIndexOutOfBoundsException e) {
return 1;
}
}
after save data, i closed fragment and back to previous fragment.
on start function, checking if have data
#Override
public void onStart() {
super.onStart();
if (loading.isShowing()) loading.dismiss();
if (reloadList && checkContent()) {
....
}
reloadList = true;
}
private boolean checkContent() {
users = RealmDatabase.loadUserList(mainActivity.getMyRealm());
if (users.size() > 0 && users.get(0).userid > 0) {
// note:
// user id is auto increament while saved
// if no data, return dataset with userid = 0 for other purpose
return true;
} else {
....
return false;
}
}
public static List<UserModel> loadUserList(Realm myRealm) {
List<UserModel> list = new ArrayList<>();
RealmResults<UserDb> results = myRealm.where(UserDb.class).findAll();
results = results.sort("userid", Sort.DESCENDING);
for (UserDb result : results) {
UserModel userModel = new UserModel();
userModel.userid = result.getUserid();
....
userModel.note = result.getNote();
list.add(userModel);
}
if (list.size() == 0) {
UserModel userModel = new UserModel();
userModel.userid = 0;
userModel.note = "You still have no user at this time";
list.add(userModel);
}
return list;
}
checkContent(), user.size detected as 1 (new data is added) but userid still 0.
am i miss something in this logic? because everything is working well if i reopen app after add new user.
update
after using listener, i got my dataset but still not showing my content. after some trial i found that my list view is not showing the data even after i re-input data and do notifydataset on adapter.
users = RealmDatabase.loadUserList(mainActivity.getMyRealm());
homeAdapter.reloadList(users);
....
public void reloadList(List<UserModel> users) {
this.users = users;
notifyDataSetChanged();
}
update 2
everything going well for the 2nd, 3rd, and later item except the first one
is realm have something like listener for commiting data?
Yes
realmResults.addChangeListener(realmChangeListener);
One must keep a field reference to the RealmResults.
everything is working well if i reopen app after add new user.
Probably the ArrayList you build from the RealmResults is not updated.
as suggested by #epicPandaForce answer, i use listener to my code.
and to solved my problem as i mentioned in the last comment in #epicPandaForce answer, i change my code like this
getMyRealm().addChangeListener(new RealmChangeListener<Realm>() {
#Override
public void onChange(Realm element) {
// previous code in my activity
// getFragmentManager().popBackStackImmediate();
// new code in my activity
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.container, fragment);
}
});
those code not really inside the listener instead calling function on my activity. those placement just for where its called.
public void setSearch(ArrayList<Search> ListSearch){
search=ListSearch;
removeInActiveClasses(search);
notifyItemRangeChanged(0,search.size());
}
public void removeInActiveClasses(ArrayList<Search> data){
for(int i=0;i<data.size();i++){
boolean isActive=Boolean.parseBoolean(data.get(i).getActive());
System.out.println("The course at Not Removed "+search.get(i).getName()+" is set to "+search.get(i).getActive());
if(!isActive){
System.out.println("The course at Removed"+search.get(i).getName()+" is set to "+search.get(i).getActive());
search.remove(i);
}
}
}
A list is passed through as listSearch and it contains a list of courses, if the courses are set to active which is a string that either true or false, and parsed as a boolean, then the item should be removed. I am certain I did the parsing correctly so I am wondering what is going on here? How come it does not delete all the false courses?
You might wanna create another instance of ArrayList and set your search to that one because your are accessing and modifying your ArrayList at simultaneously.
Other notes:
Please use camelCase for your argument names. So instead of ListSearch, use searchList.
For your class variable, try adding m in front so you won't get confused. So instead of search, use mSearchList
Lastly, you are mixing some variables within one method. Try unifying them for better maintenance.
Here's the full code.
public void setSearchList(ArrayList<Search> searchList) {
mSearchList = removeInactiveClasses(searchList);
notifyDataSetChanged();
}
private ArrayList<Search> removeInactiveClasses(ArrayList<Search> data) {
ArrayList<Search> list = new ArrayList<>();
for (int i = 0; i < data.size(); i++){
boolean isActive = Boolean.parseBoolean(data.get(i).getActive());
if (isActive){
list.add(data.get(i));
}
}
return list;
}
I have an activity that in onCreate() does the following:
Creates an empty ArrayList
Creates a new ArrayAdapter associated with the above ArrayList
Sets ListView to use the above ArrayAdapter
Uses Volley to send a GET request to my API to fetch some JSON data to load into the ListView
Once the data is fetched I add it to my ArrayList and the ListView is populated as expected
My problem is that when the activity is restarted (i.e. the screen is rotated via the emulator or the activity is restarted through Android Studio) the ListView no longer populates.
I am not saving any state. I expect the activity to return to its initial default state so I don't think onSaveInstanceState() is the answer.
I've verified that the data is returned successfully from the API and that the adapter's hashcode is the same before and after the volley request and that it equals the ListView's set adapter. I've also verified that onDestroy() and then onCreate() are called when the activity is restarted so I know it is going through a full life cycle.
If I rotate the screen programmatically with setRequestedOrientation() I don't experience this issue. If I add items to my ArrayList outside of the GET request callback, I don't experience this issue.
Here is my activity onCreate()
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
//The data to be displayed
descriptions = new ArrayList<>();
listView = (ListView)this.findViewById(R.id.myListView);
//Link 'descriptions' to the adapter
adapter = new ArrayAdapter<>(this, R.layout.list_json_text_view, descriptions);
listView.setAdapter(adapter);
this.addTextFilter();
this.addListViewClickListener();
//See my ApiGetRequest class below
request = new ApiGetRequest();
request.send(this.getContext(), getDataUrl(), this, "", REQUEST_TYPES.TEXT);
}
And my activity GET request callback
public void onSuccess(DescriptiveJSONArray items, REQUEST_TYPES type) {
descriptions.clear();
try {
for (int i = 0; i < items.length(); ++i) {
JSONObject obj = items.getJSONObject(i);
String desc = obj.optString("name", "") + " " + obj.optString("description", "");
//TODO: Remove debug code
System.out.println("Adding: "+desc);
descriptions.add(desc);
}
}
catch(JSONException e) {
e.printStackTrace();
//getJSONObject failed
}
}
And my ApiGetRequest methods
//My activity implements ApiGetCallback
public void send(Context context, String url, ApiGetCallback callback, String tag, REQUEST_TYPES type) {
StringRequest stringRequest = getStringRequest(url, callback, tag, type);
//Singleton wrapper for RequestQueue
AppRequestQueue queue = AppRequestQueue.getInstance(context);
queue.add(stringRequest);
}
//Inner class inside ApiGetCallback
class SuccessListener implements Response.Listener<String> {
ApiGetCallback callback;
REQUEST_TYPES type;
public SuccessListener(ApiGetCallback callback, REQUEST_TYPES type) {
this.callback = callback;
this.type = type;
}
#Override
public void onResponse(String response) {
try {
DescriptiveJSONArray jsonResp = new DescriptiveJSONArray(response);
callback.onSuccess(jsonResp, type);
}
catch(JSONException e) {
callback.onJsonException(e);
}
}
}
Any ideas what is happening?. I'm testing on Marshmallow and Nougat
You are missing a call to notifyDataSetChanged, after the onSuccess function is done.
you may need to override onStart and do update anything in it
adapter = new ArrayAdapter<>(this, R.layout.list_json_text_view, descriptions);
listView.setAdapter(adapter);
//See my ApiGetRequest class below
request = new ApiGetRequest();
request.send(this.getContext(), getDataUrl(), this, "", REQUEST_TYPES.TEXT);
use this part of code in onResume method.
Here is my issue. I can save an object, but if I save another object, it will erase the previous item. I'm using gson lib to save my items. After some researches I've seen this How to use SharedPreferences to save more than one values?
But I can't use it because of my custom objects, if I use .toString(), I will not be able to get back my original item. I know that's it's the same key used to save object that will erase the previous one but I dont really know how to give a different key every time I will save an item.
Code to add :
addFav.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if (currentProduit.getIsAdded() ==0) {
SharedPreferences.Editor prefsEditor = mPrefs.edit();
Gson gson = new Gson();
String myJson = gson.toJson(currentProduit);
Log.i("INFO", "Value of saved data" + myJson);
prefsEditor.putString("myproduct", myJson);
prefsEditor.apply();
Toast.makeText(getApplicationContext(), "Data saved !", Toast.LENGTH_SHORT).show();
addFav.setText(R.string.delete_fav);
currentProduit.setIsAdded(1);
} else {
addFav.setText(R.string.add_fav);
currentProduit.setIsAdded(0);
SharedPreferences.Editor editor = mPrefs.edit();
editor.remove("myproduct").apply();
Toast.makeText(getApplicationContext(), "Data removed !", Toast.LENGTH_SHORT).show();
}
}
});
Code to get back from other activity:
String myJson = mPrefs.getString("myproduct", "");
Log.i("INFO", "Value of loaded data" + myJson);
if (myJson.isEmpty() && favProductList.isEmpty()) {
listview_R.setAdapter(null);
Log.i("INFO", "No items");
title.setText(getString(R.string.fav));
} else if (myJson.isEmpty() && favProductList != null) {
myCustomAdapterVersionR = new CustomAdapter_VersionR(getApplicationContext(), favProductList);
listview_R.setAdapter(myCustomAdapterVersionR);
} else {
Product savedProduct = gson.fromJson(myJson, Product.class);
favProductList.add(savedProduct);
Log.i("INFO", "Favorite was added");
myCustomAdapterVersionR = new CustomAdapter_VersionR(getApplicationContext(), favProductList);
listview_R.setAdapter(myCustomAdapterVersionR);
}
Thanks for helping ! Btw, since it's not saving a lot of items, I didnt use sqlite db, cheers !
EDIT: I tried Juan Cortés solution, but I have this error after getting back the shared preferences --> error: incompatible types: CustomProduct[] cannot be converted to List, here is the code
if (fromPrefs.isEmpty() && favProductList.isEmpty()) {
listview_R.setAdapter(null);
Log.i("INFO", "No items");
title.setText(getString(R.string.fav));
} else {
//Product savedProduct = gson.fromJson(fromPrefs, Product.class);
//favProductList.add(savedProduct);
//Get the Object array back from the String `fromPrefs`
CustomProduct[] reInflated = gson.fromJson(fromPrefs,CustomProduct[].class);
Log.i("INFO", "Favorite was added");
myCustomAdapterVersionR = new CustomAdapter_VersionR(getApplicationContext(), reInflated); //error
listview_R.setAdapter(myCustomAdapterVersionR);
}
Thanks !
As an overly simplified app for example, you could define a custom class as follows (of course you'll have to adapt it to your particulars). The concept is create an array of custom objects, convert it to json, store it. It's really straightforward once you see it.
The code
Gson gson = new Gson();
//Create an array to work with it, dummy content
CustomProduct[] exampleList = new CustomProduct[10];
for(int i=0;i<10;i++){
exampleList[i] = new CustomProduct("string","number:"+i);
}
//Get a String representation of the objects
String forStoring = gson.toJson(exampleList);
//HERE you can store and retrieve to SharedPreferences
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit().putString("myarrayofcustomobjects", forStoring).commit();
//Get the string back from the SharedPreferences
String fromPrefs = prefs.getString("myarrayofcustomobjects","");
//Get the Object array back from the String `fromPrefs`
CustomProduct[] reInflated = gson.fromJson(fromPrefs,CustomProduct[].class);
Notes
If you already have a set of objects in an array, you'll need to inflate the array as shown above, create a new array with those elements + the one you want to add, convert them to a string again, and store them. Once this becomes too much of a hassle, you'll move to another means of persisting data for you app, but for as long as there are not that many, it should be ok.
Assuming
To get this to work, I'm assuming you have a Custom object named CustomProduct with the following definition:
public class CustomProduct {
String field1,field2;
public CustomProduct(String field1, String field2){
super();
this.field1 = field1;
this.field2 = field2;
}
#Override
public String toString() {
return "CustomProduct [field1="+field1+",field2="+field2+"]";
}
}
Update
User wants to show the results in a listview. You can define a custom adapter like the following to get it to work. Let this be the time for me to advise you to soon move towards RecyclerView instead of ListView but first tackle the problem you have, make it work, then improve upon it
public class CustomAdapter extends BaseAdapter{
private CustomProduct[] mProducts;
private LayoutInflater mInflater;
public CustomAdapter(Context context, CustomProduct[] products){
mProducts = products;
mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public int getCount() {
return mProducts.length;
}
public CustomProduct getItem(int i) {
return mProducts[i];
}
public long getItemId(int i) {
return i;
}
public View getView(int i, View convertView, ViewGroup parent) {
//Purposely not doing view recycling for sake of clarity
View row = mInflater.inflate(R.layout.custom_row,parent,false);
//Set the data from the row
((TextView)row.findViewById(R.id.field1)).setText(getItem(i).field1);
((TextView)row.findViewById(R.id.field2)).setText(getItem(i).field2);
//Return the view
return row;
}
}
By setting this adapter to your ListView and creating the layout (which simply consists in two textviews with the given ids) you will get the following result. You can try removing the part where it creates the data after it's run the first time and leaving only the part where it fetches the data to ensure it's persisted.