I have a shopping cart where I add products, but I have a problem with it. When I push the button to delete an item, recycler view is not updated after calling also notifydatachanged(). When I navigate again to my shopping cart, the item is not there because is deleted. But I wan't to see direclty when I'm in my shopping cart activity. Here is my adapter:
holder.removeProduct.setOnClickListener(v -> {
SharedPreferences preferences = mContext.getSharedPreferences(ITEMS_PREF, Context.MODE_PRIVATE);
SharedPreferences.Editor mEditor = preferences.edit();
Gson gson = new Gson();
String json = preferences.getString("artikujtShporta", "");
ArrayList<Artikujt> artikullObject = gson
.fromJson(json, new TypeToken<ArrayList<Artikujt>>(){}.getType());
if (artikullObject != null) {
int indexToRemove;
for(int i = 0 ; i < artikullObject.size(); i++){
if(artikullObject.get(i).getId().equals(artikulli.getId())) {
indexToRemove = i;
artikullObject.remove(indexToRemove);
notifyItemRemoved(indexToRemove);
String jsonString = gson.toJson(artikullObject);
mEditor.putString("artikujtShporta", jsonString);
mEditor.apply();
}
}
}
});
and in my fragment:
SharedPreferences preferences = Objects.requireNonNull(getContext())
.getSharedPreferences(ITEMS_PREF, Context.MODE_PRIVATE);
Gson gson = new Gson();
String json = preferences.getString("artikujtShporta", "");
cartItems = gson.fromJson(json, new TypeToken<ArrayList<Artikujt>>(){}.getType());
basketAdapter = new BasketAdapter(cartItems, getContext());
mRecyclerView = mView.findViewById(R.id.basket_recycler_view);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this.getActivity()));
mRecyclerView.setAdapter(basketAdapter);
//basketAdapter.clear();
//basketAdapter.addAll(cartItems);
basketAdapter.notifyDataSetChanged();
notifyDataSetChanged here in the for loop:
for(int i = 0 ; i < artikullObject.size(); i++){
if(artikullObject.get(i).getId().equals(artikulli.getId())) {
indexToRemove = i;
artikullObject.remove(indexToRemove);
//here
notifyDataSetChanged();
String jsonString = gson.toJson(artikullObject);
mEditor.putString("artikujtShporta", jsonString);
mEditor.apply();
}
}
UPDATE
keep what I told you and make sure that the list that you call on the method .remove(indexToRemove) is the actual list that you passed to your adapter to populate data, meaning the list that you used in getItemCount()
in other words:
I am not sure if artikullObject is the list that you must remove the object from, you must use the main list you used to populate the adapter.
Create an IShoppingCart Interface which will be implemented by your Fragment. We want to keep the logic in the Fragment. (I personally would move this logic to the ViewModel but that will be going on a tangent.)
IShoppingCart
interface IShoppingCart {
fun addItemToCart(item: CartItem): Boolean
fun removeItemToCart(itemId: String): Boolean
/**
* Feel free to add more functionality
*/
}
Now, pass your fragment through composition to your RecyclerViewAdapter.
RecyclerViewAdapter
ShoppingCartAdapter(val contract: IShoppingCart) : RecyclerView.Adapter<ViewHolder>() {
.
.
.
}
Now you can use this reference in your ViewHolder class as they probably are defined as Inner Classes.
ViewHolder
holder.removeProduct.setOnClickListener(v -> {
// This will kick in the code that lies in the Fragment
contract.removeItemToCart(viewHolder.itemView.id)
});
Now in your Fragment, inside the removeItemToCart(val itemId), just remove the item with the given itemId and call notifyDataSetChanged().
Fragment
fun DeleteItemToCart(itemId: String) {
dataset.removeItemWithItemId(itemId)
notifyDataSetChanged()
}
Following this approach, you do not need to notify the adapter that the whole data set changed, as it did not. You can simply tell the adapter that only a single element's position changed (as it was removed) by using notifyItemRemoved(position: Int).
You can delete an item within the adapter on within a ViewHolder view onClick() listener of a certain row in the RecyclerView by using getAdapterPosition() which returns the position of the clicked row; so you'd add:
artikullObject.remove(getAdapterPosition());
notifyItemRemoved(getAdapterPosition());
So changes in your code:
holder.removeProduct.setOnClickListener(v -> {
SharedPreferences preferences = mContext.getSharedPreferences(ITEMS_PREF, Context.MODE_PRIVATE);
SharedPreferences.Editor mEditor = preferences.edit();
Gson gson = new Gson();
String json = preferences.getString("artikujtShporta", "");
ArrayList<Artikujt> artikullObject = gson
.fromJson(json, new TypeToken<ArrayList<Artikujt>>(){}.getType());
// remove current row of the clicked item
artikullObject.remove(getAdapterPosition());
notifyItemRemoved(getAdapterPosition());
});
Update:
getAdapterPosition() is not found as a method
The getAdapterPosition() is a ViewHolder method that can not be accessed from the adapter class, so you can just transfer the View.OnLongClickListener to be within the custom ViewHolder instead of the custom RecyclerView.Adapter adapter.
class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
View removeProduct;
MyViewHolder(#NonNull View listItem) {
super(listItem);
// caching views
removeProduct = listItem.findViewById(...);
// Listen to removing a product
removeProduct.setOnClickListener(v -> {
SharedPreferences preferences = mContext.getSharedPreferences(ITEMS_PREF, Context.MODE_PRIVATE);
SharedPreferences.Editor mEditor = preferences.edit();
Gson gson = new Gson();
String json = preferences.getString("artikujtShporta", "");
ArrayList<Artikujt> artikullObject = gson
.fromJson(json, new TypeToken<ArrayList<Artikujt>>(){}.getType());
// remove current row of the clicked item
artikullObject.remove(getAdapterPosition());
notifyItemRemoved(getAdapterPosition());
});
}
}
Note: your approach of removing a single item by iterating the entire list of items is not that good on performance, so you just need to target a certain index to be deleted.
Related
I'm trying to make favorites module in my app. If user click favorite button for a radio, this radio must displayed in Favorites screen. But just last clicked radio shown in Favorites screen. I want to save more than one radios in Favorites. Where I'm doing wrong? thanks in advance.
This is favorite button in RadioFragment
add_favorites_button= (Button) view.findViewById(R.id.add_favorites_button);
add_favorites_button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
SharedPreferences settings = getActivity().getSharedPreferences("PREFS", 0);
SharedPreferences.Editor editor = settings.edit();
editor.putString("radio_link", radio_play_link);
editor.putString("radio_namee", radio_name);
editor.commit();
}
});
And I'm trying to get these values and put in ArrayList in FavoritesFragment. To display received values, I sent them in textview to try.
public class FavoritesFragment extends Fragment {
public FavoritesFragment() {
// Required empty public constructor
}
TextView radio_name_txt, radio_link_txt;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view= inflater.inflate(R.layout.fragment_favorites, container, false);
List<String> radio_name_list = new ArrayList<>();
List<String> radio_link_list = new ArrayList<>();
SharedPreferences settings = getActivity().getSharedPreferences("PREFS",0);
radio_name_list.add(settings.getString("radio_namee", ""));
radio_link_list.add(settings.getString("radio_link", ""));
radio_name_txt = (TextView) view.findViewById(R.id.radio_name_txt);
radio_link_txt = (TextView) view.findViewById(R.id.radio_link_txt);
String a= "";
String b= "";
for (int i =0; i<radio_name_list.size(); i++) {
a = a +radio_name_list.get(i);
b = b +radio_link_list.get(i);
}
radio_name_txt.setText(a);
radio_link_txt.setText(b);
return view;
}
}
You have a lot of data and Shared Preferences is not the write option to store your data for 7000 items. Shared preference is good for easy and less frequent data storage, For your case you need to make a SQLite Database. If its totally new to your then pay a visit in Androids only Documentation Training in this link.
since you are using same key for every radio button insert it will overwrite the previous values.
You need to store values in an array and then store array in the preferneces.
Or the better way is to use sqllite database in the android to store the likes in a table.
Try displaying the values from the Lists radio_name_list and radio_link_list in a ListView:
Save the values in an Array List.
Create a ListView instead of a TextView.
Create an ArrayAdapter and set it as the adapter for your ListView.
As far as I can see you only have one TextView. Try creating a ListView instead of a TextView. It will be more organized.
editor.putString("radio_link", radio_play_link);
editor.putString("radio_namee", radio_name);
You are overwriting the favorite every time you try to add a new one, so instead of doing the above, do this:
editor.putString(radio_play_link, radio_play_link);
editor.putString(radio_name, radio_name);
So now you have a key-value pair, and you can iterate over your favorites like this:
String radioLink = "", radioKey = "";
for (Map.Entry<String, ?> entry : getSharedPreferences("PREFS",0).getAll().entrySet()) {
radioKey = entry.getKey();
radioLink = (String) entry.getValue();
}
In my App I have crated a MyList.class which looks like this:
public class MyList {
private ArrayList<Obj> objList = new ArrayList<>();
//adds object to list
public void addObjToList(Obj obj) {
objList.add(obj);
}
//gets the whole list
public static ArrayList getObjList() {return objList;}
//gets the size of the list
public static int getObjListSize() {return objList.size();}
//removes obj from list based on his position
public void removeObj(int pos) {
objList.remove(pos);
}
}
From the CreateObj.class where I create the Obj I have this code to add it to the objList:
// creates the new object
Obj newObj = new Obj("Name", 3 /*int*/ );
// creates a new List
MyList myList = new MyList();
// adds the obj into the list
myList.addObjToList(newObj);
It successfully adds the obj to the list. Now from my Main_Activity.class I need to retrieve it and inflate it into a recyclerView, which I do in the onCreate() method like this:
currentObjList = MyList.getObjList();
//puts list into recycler
recyclerView = (RecyclerView) findViewById(R.id.recycler);
recyclerView.setLayoutManager(new LinearLayoutManager(this,
LinearLayoutManager.VERTICAL, false));
adapter = new RecyclerAdapter(this, currentObjList);
recyclerView.setAdapter(adapter);
Notice that I do not set MyList myList = new MyList() in my Main_Activity because I want the list to be the one created in the CreateObj class.
Obviously this is not the right way to do it, because if, let's say I want to delete an element from the recyclerView, I need to remove it from the objList (in the MyList.class) too, and that's not possible since I cannot access the MyList.class methods without setting a new MyList(), and if I do set it to new, It would not keep the Obj added from the CreateObj class.
In short: How can I have the same objList to be both accessible and modifiable from both, CreateObj.class and Main_Activity.class.
Following my comment this is a draft of what I was suggesting.
Please note I haven't ran this code so it must have errors and typos, it is just to reflect the idea of what I was proposing.
The Activity that receives the input holds a referenct to the class that creates the object and to the class that holds the ArrayList.
Upon user input, the activity asks the object creator to create an ojbect and passes it back to the activity. Then the activity adds it to the list.
And finally it notifies the recycler adapter that the data has changed.
In MainActivity:
private CreateObj createObj;
private MyList myList;
//Other memeber variables for Input elements on the screen
//used in createObje.create() to build the new object.
public void onCreate(...){
...
createObj = new CreateObj();
myList = new MyList();
currentObjList = MyList.getObjList();
//puts list into recycler
recyclerView = (RecyclerView) findViewById(R.id.recycler);
recyclerView.setLayoutManager(new LinearLayoutManager(this,
LinearLayoutManager.VERTICAL, false));
adapter = new RecyclerAdapter(this, currentObjList);
recyclerView.setAdapter(adapter);
...
aUserConfirmInputElement.setOnClickListener(new OnClickListener()){
public void onClick(){
Obj obj = createObj.create();
myList.addObjectToList(obj);
adapter.notifyDataSetChanged();
}
}
...
}
In my code, with the help of context menu I'm able to delete a particular item from Listview but as I'm using sharedpreferences to save arraylist called "places" then it restores the sharedpreference when the app is launched back again. Now how should I implement my sharedpreferences such that when a particular item is deleted from listview, the same item also gets deleted from arraylist "places" of shared preferences.
Below is my code snippet
static ArrayList<String> places = new ArrayList<String>();
static ArrayList<LatLng> locations = new ArrayList<>(); //to save lat and long
static ArrayAdapter arrayAdapter;
public ListView listView;
SharedPreferences sharedPreferences;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) findViewById(R.id.listView);
sharedPreferences = this.getSharedPreferences("com.starprojects.memorableplaces", Context.MODE_PRIVATE);
registerForContextMenu(listView);
//tricker locations
ArrayList<String> latitudes = new ArrayList<>();
ArrayList<String> longitudes = new ArrayList<>();
//initially set
places.clear();
latitudes.clear();
longitudes.clear();
locations.clear();
//to restore
try {
places = (ArrayList<String>) ObjectSerializer.deserialize(sharedPreferences.getString("places", ObjectSerializer.serialize(new ArrayList<>())));
latitudes = (ArrayList<String>) ObjectSerializer.deserialize(sharedPreferences.getString("latitudes", ObjectSerializer.serialize(new ArrayList<>())));
longitudes = (ArrayList<String>) ObjectSerializer.deserialize(sharedPreferences.getString("longitudes", ObjectSerializer.serialize(new ArrayList<>())));
Log.i("palces",places.toString());
} catch (IOException e) {
e.printStackTrace();
}
#Override
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
// sharedPreferences = getSharedPreferences("places",0);
SharedPreferences.Editor editor = sharedPreferences.edit();
if((item.getTitle()).equals("Delete"))
{
places.remove(info.position);
editor.remove("places"); //problem is here, how to get particular index to be removed from arraylist places and save it.
editor.commit();
arrayAdapter.notifyDataSetChanged();
return true;
}
return super.onContextItemSelected(item);
}
}
you can get index by
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
int index = info.position;
it means you must a list in you shared preference or you have a different key for your shared preference.
Case 1: if you have a list in your shared preference than update the shared preference with the remove of data of data from the listview.
Case 2: if you assigned different key_names for each of the list item then you can simply remove or clear that key_name when the data is removed from the shared preference.
If I'm getting the point of your question you are trying to keep the shared preferences copy up to date with the one you display and vice-versa.
To accomplish this I think that you just need to put the updated places array list into shared preferences, like this:
#Override
public boolean onContextItemSelected(MenuItem item) {
AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
SharedPreferences.Editor editor = sharedPreferences.edit();
if ((item.getTitle()).equals("Delete")) {
// Update local list
places.remove(info.position);
// Set list into shared preferences
// Or if you use a JSON string you could serialize and use putString()
editor.putStringSet("places", places);
// Use apply it's async
editor.apply();
arrayAdapter.notifyDataSetChanged();
return true;
}
return super.onContextItemSelected(item);
}
Please use apply() in place of commit(). It's faster and asynchronous
I'm working on an app in which i need to show some data in listview for that i've created an activity which is connected to the layout containing listview , for that listview i've created custom listitem. Now i need to access the components( Imageview) from custom list item in activity. Can anyone please tell me how to achieve this ?
This is the activity
package com.example.manishnegi.sharemyride;
public class RideMatched extends Activity {
int commentCount = 0;
private List<GetRidesSummaryDetails> oslist = new ArrayList<GetRidesSummaryDetails>();
ListView rides_matchedListview;
ImageView hrate1,hrate2,hrate3,hrate4,hrate5;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.ride_matched);
hrate1=(ImageView)findViewById(R.id.hrate1);
hrate2=(ImageView)findViewById(R.id.hrate2);
hrate3=(ImageView)findViewById(R.id.hrate3);
hrate4=(ImageView)findViewById(R.id.hrate4);
hrate5=(ImageView)findViewById(R.id.hrate5);
int l=0;
String arr;
JSONArray array=new JSONArray();
Bundle b=getIntent().getExtras();
if(b!=null)
{
l=b.getInt("array_length");
arr=b.getString("rides_array");
try {
array=new JSONArray(arr);
Log.e("String to array ",array.toString());
} catch (JSONException e) {
e.printStackTrace();
Log.e("String to array",e.getMessage());
}
Log.e("Number of matched",l+"");
}
rides_matchedListview = (ListView)findViewById(R.id.rides_matchedListview);
Second scnd = new Second();
List<GetRidesSummaryDetails> detailsofrides = scnd.getallcomments(l,array);
for (GetRidesSummaryDetails values :detailsofrides){
String driver_imageget = values.getDriverimage();
String driver_nameget = values.getDrivername();
String pickup_get = values.getPickuptime();
String ride_idget= values.getRideId();
String rating_get = values.getRating();
String vehicle_imageget = values.getVehicleImage();
GetRidesSummaryDetails vehi = new GetRidesSummaryDetails();
vehi.setDriverimage(driver_imageget);
vehi.setDrivername(driver_nameget);
vehi.setPickuptime(pickup_get);
vehi.setRideId(ride_idget);
vehi.setRating(rating_get);
setRating(Integer.parseInt(rating_get));
vehi.setVehicleImage(vehicle_imageget);
oslist.add(vehi);
commentCount++;
rides_matchedListview.setAdapter(new RidesMatchedAdapter(RideMatched.this, 0, oslist));
new RidesMatchedAdapter(RideMatched.this ,0, oslist).notifyDataSetChanged();
}
}
public void setRating(int rate)
{
if (rate == 1) {
hrate1.setImageResource(R.drawable.star);
}
if (rate == 2) {
hrate1.setImageResource(R.drawable.star);
hrate2.setImageResource(R.drawable.star);
}
if (rate == 3) {
hrate1.setImageResource(R.drawable.star);
hrate2.setImageResource(R.drawable.star);
hrate3.setImageResource(R.drawable.star);
}
if (rate == 4) {
hrate1.setImageResource(R.drawable.star);
hrate2.setImageResource(R.drawable.star);
hrate3.setImageResource(R.drawable.star);
hrate4.setImageResource(R.drawable.star);
}
if (rate == 5) {
hrate1.setImageResource(R.drawable.star);
hrate2.setImageResource(R.drawable.star);
hrate3.setImageResource(R.drawable.star);
hrate4.setImageResource(R.drawable.star);
hrate5.setImageResource(R.drawable.star);
}
}
}
This is the listview layout(ride_matched.xml)
This is the custom list item
You must've created the adapter for the listview using that adapter declare the images inside viewHolder it should work.
Use any adapter (according to your need) to inflate data in the ListView. In the adapter itself, you can access the components of custom listitem.
You cannot access the components of custom listitem inside the activity. They can only be accessed inside the adapter inflating the data in the ListView.
We need to pass adapter to the listview. Adapters contain list of model object to be shown in list . You can create a custom adapter extending the base adapter. You can follow the below tutorial
http://androidexample.com/How_To_Create_A_Custom_Listview_-_Android_Example/index.php?view=article_discription&aid=67&aaid=92
p[ay attention to getView method in the adapter and change the layout name as per you custom layout in the following line of get view
vi = inflater.inflate(R.layout.tabitem, null);
Create a holder class where it has all the view item required to display in your list.
You need not to access Image view from activity as it can be accessed in the getView method for each row iteration.
My application is returning the latest data from firebase to the buttom of the ListView. But I want it to be on the top! I have thought about it and I think there is only two possible ways to do it.
1. Invert the Listview.
I think that this way is how it should be done but I couldn't figure it out. I have searched a lot on the web but no suitable solution for my case
This is my adapter code
public void onStart() {
super.onStart();
// Setup our view and list adapter. Ensure it scrolls to the bottom as data changes
final ListView listView = getListView();
// Tell our list adapter that we only want 50 messages at a time
mChatListAdapter = new ChatListAdapter(mFirebaseRef.limit(50), this, R.layout.chat_message, mUsername);
listView.setAdapter(mChatListAdapter);
}
And this is the code for the ChatListAdapter constructor for a custom list class ChatListAdapter which extends special list adapter class FirebaseListAdapter:
public ChatListAdapter(Query ref, Activity activity, int layout, String mUsername) {
super(ref, Chat.class, layout, activity);
this.mUsername = mUsername;
}
[Edit] This is some of the code for FirebaseListAdapter which extends BaseAdapter class
public FirebaseListAdapter(Query mRef, Class<T> mModelClass, int mLayout, Activity activity) {
this.mRef = mRef;
this.mModelClass = mModelClass;
this.mLayout = mLayout;
mInflater = activity.getLayoutInflater();
mModels = new ArrayList<T>();
mModelKeys = new HashMap<String, T>();
// Look for all child events. We will then map them to our own internal ArrayList, which backs ListView
mListener = this.mRef.addChildEventListener(new ChildEventListener() {
#Override
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
T model = dataSnapshot.getValue(FirebaseListAdapter.this.mModelClass);
mModelKeys.put(dataSnapshot.getKey(), model);
// Insert into the correct location, based on previousChildName
if (previousChildName == null) {
mModels.add(0, model);
} else {
T previousModel = mModelKeys.get(previousChildName);
int previousIndex = mModels.indexOf(previousModel);
int nextIndex = previousIndex + 1;
if (nextIndex == mModels.size()) {
mModels.add(model);
} else {
mModels.add(nextIndex, model);
}
}
}
2. Descending query the data.
The second way seams impossible to me, because when I searched on Firebase API documentation and on the web, I couldn't find anyway to order retraived data on descending way.
My data on firebase look like the following:
glaring-fire-9714
chat
-Jdo7-l9_KBUjXF-U4_c
author: Ahmed
message: Hello World
-Jdo71zU5qsL5rcvBzRl
author: Osama
message: Hi!
Thank you.
A simple solution would be to manually move the newly added data to the top of the listview. As you rightly noticed, new data added to a listview will automatically be appended to the bottom of the list, but you may freely move entries once they are added. Something like the following would help you manually move the newest entry to the top of the list:
int iSwapCount = listView.getCount() - 1;
int iPosition = listView.getCount() - 1;
for (int j = 0; j < iSwapCount; j++)
{
Collections.swap(yourlistobject, iPosition, iPosition - 1);
iPosition = iPosition - 1;
}
The above code will begin by calculating the number of swaps that will be required to move last list entry to the top of the list, which is determined by the number of elements in the list - 1. The same is true for calculating the last position in the list. From there Collections.swap will be used to swap the last element in the list with the element before it; this will be repeated until the last element is now the first element, with the rest of the entries in the list remaining in the same order. This code would have to be called each time a new entry is added so that the overall order of the list is maintained.
I realize it has been a while since you asked but I had the same issue. It does not appear that there is a direct answer here.
Here's the change to the firebase adapter to get new items on the top of the list.
Notice the change from add(...) to add(0,...) and add(next...) to add(prev...)
Look for comments:
// prepend instead append
Example:
...
// Insert into the correct location, based on previousChildName
if (previousChildName == null) {
mModels.add(0, model);
mKeys.add(0, key);
} else {
int previousIndex = mKeys.indexOf(previousChildName);
int nextIndex = previousIndex + 1;
if (nextIndex == mModels.size()) {
//mModels.add(model);
//mKeys.add(key);
// prepend instead append
mModels.add(0,model);
mKeys.add(0,key);
} else {
//mModels.add(nextIndex, model);
//mKeys.add(nextIndex, key);
// prepend instead append
mModels.add(previousIndex, model);
mKeys.add(previousIndex, key);
}
}
...
Here is a simple way to invert a FirebaseUI list using a RecyclerView:
boolean reverseList = true;
LinearLayoutManager manager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, reverseList);
if (reverseList) {
manager.setStackFromEnd(true);
}
mRecyclerView.setLayoutManager(manager);