Android RecyclerView not refreshing on adapter.notifyDataSetChanged - java

Classic problem here: I'm fetching some data from a database into a recyclerView using an ArrayList of custom objects (and this happens in a Fragment, not in the Main Activity). Everything works like a charm until I try to refresh the recyclerView using a spinner that changes how the data is sorted. I know the data is fed to the recyclerView correctly. What am I doing wrong? API level is 19.
This is how the fetch is done:
public ArrayList<LEGOSet> getSets(String conditions) {
ArrayList<LEGOSet> sets = new ArrayList<>();
Cursor cursor = database.rawQuery("SELECT * FROM Sets " + conditions, null);
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
LEGOSet set = new LEGOSet(cursor.getInt(0), cursor.getString(1), cursor.getInt(2), cursor.getInt(3), cursor.getString(4), cursor.getDouble(5),
cursor.getDouble(6), cursor.getDouble(7), cursor.getDouble(8), cursor.getDouble(9), cursor.getDouble(10), cursor.getString(11),
cursor.getString(12), cursor.getString(13), cursor.getString(14), cursor.getInt(15), cursor.getInt(16), cursor.getInt(17));
sets.add(set);
cursor.moveToNext();
}
cursor.close();
return sets;
}
The fragment with the data being pulled, the recyclerView being set up and with the spinner onItemSelected code:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_catalog, container, false);
// pull data from database
sets = dbManager.getSets("order by pieces desc");
// set up the RecyclerView
final RecyclerView recyclerView = root.findViewById(R.id.rvSets);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
adapter = new MyRecyclerViewAdapter(getActivity(), sets, portrait);
adapter.setClickListener(this);
recyclerView.setAdapter(adapter);
// add dividers
DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(), 1);
dividerItemDecoration.setDrawable(new ColorDrawable(getResources().getColor(R.color.colorSubtle)));
recyclerView.addItemDecoration(dividerItemDecoration);
// spinner Product Sorting
spinnerProductSorting = root.findViewById(R.id.spinnerProductSorting);
spinnerProductSorting.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
String s = spinnerProductSorting.getSelectedItem().toString();
switch(s)
{
case "Biggest first":
sets = dbManager.getSets("order by pieces desc");
Toast.makeText(getContext(),sets.get(0).getName(), Toast.LENGTH_SHORT).show();
break;
case "Smallest first":
sets = dbManager.getSets("order by pieces asc");
Toast.makeText(getContext(),sets.get(0).getName(), Toast.LENGTH_SHORT).show();
break;
default:
}
adapter.notifyDataSetChanged();
}
#Override
public void onNothingSelected(AdapterView<?> parent) {}
});
disclaimer = root.findViewById(R.id.disclaimer);
disclaimer.setMovementMethod(LinkMovementMethod.getInstance());
return root;
}
What I've noticed is that I can get refreshed recyclerView if I replace:
adapter.notifyDataSetChanged();
with
adapter = new MyRecyclerViewAdapter(getActivity(), sets, portrait);
recyclerView.setAdapter(adapter);
But that doesn't seem right. I should be able to refresh the existing adapter with new data instead of creating a brand new adapter, correct?

This is a classic issue faced by many developers frequently.
Quick Fix:
Replace
sets = dbManager.getSets("order by pieces desc");
with
sets.clear()
sets.addAll(dbManager.getSets("order by pieces desc"));
And same for Ascending Order also.
Explanation:
When you initialize the Adapter, you pass an Arraylist whose instance is stored by the Adapter. When you call notifyDataSetChanged(), Adapter reads the instance and refreshes the layout as per the new ArrayList. However, when you reinitialize the ArrayList with sets = dbManager.getSets("order by pieces desc");, the adapter loses the reference to the new list and is unable to refresh the layout. This can be fixed by keeping the instance the same and replacing the values which are done using clear() and addAll(list).
Feel free to ask for any doubts in comments and please mark this answer correct if I am able to solve your problem.

Inside your adapter class create method:
setData(ArrayList<LEGOSet> sets) { // new list here
this.sets = sets; // assing ArrayList from database to your list which is inside adapter class
notifyDataSetChanged();
}
and then just replace adapter.notifyDataSetChanged() with adapter.setData(sets)

Related

Getting a button to work in the list view

I am new to android and my code has got a bit messy. I have successfully created a list view extended from item_layout.xml. When I click on the list view It works exactly how I want it. However in each item of the list view I have a button that when clicked I want the item of the list to delete.
When researching I have come across that you need to create a customer adapter to do this however I have come so far in the project that I wouldn't even know where to start.
This code it used successfully to when the list items are clicked it works. This is just put in the Main Activity class
mylist.setOnItemClickListener(
new AdapterView.OnItemClickListener(){
#Override
public void onItemClick(AdapterView<?> parent, View view, final int position, long id) {
}
}
);
I populate the list using this function just outside the main activity class. It is needed to be written like this as It gets the items from a database and has to be called depending on different circumstances
private void populatelistView() {
Cursor res = userDb.getAllRows();
String[] fromFeildnames = new String[]{ DatabaseUser.KEY_1, DatabaseUser.KEY_2};
int[] toViewIds = new int[]{R.id.textViewNum, R.id.textViewItem};
SimpleCursorAdapter myCursorAdaptor;
myCursorAdaptor = new SimpleCursorAdapter(getBaseContext(), R.layout.item_layout, res, fromFeildnames, toViewIds, 0);
//ListView mylist = (ListView) findViewById(R.id.listViewID);
mylist.setAdapter(myCursorAdaptor);
}
I would like to be able to get the button on each items to work by not changing much of what I have already written. I have tried just using the following code. But because it is in a different xml layout it display an error of null reference towards the item button
delete.setOnClickListener(
new View.OnClickListener() {
#Override
public void onClick(View v) {
View parentRow = (View) v.getParent();
ListView listView = (ListView) parentRow.getParent();
final int position = listView.getPositionForView(parentRow);
Toast.makeText(getApplicationContext(), "Button " + position, Toast.LENGTH_SHORT).show();
}
}
);
Please could someone help me make the button work without changing much code or give me a step by step tutorial on how to add an adapter but make my populateListView function do the same thing.
Ps. I have looked at so many tutorials about list adapters but can't find ones that are for my specific need

Android ArrayList: Store Multiple Values Into A Profile

I have multiple variables, we'll call them varA, varB, varC, and varD. When a button is pressed these 4 variables are to be stored in an ArrayList, together. This creates a single entry which can be viewed by variable varA. All the entries can be viewed in a ListView which is done through an ArrayAdapter.
onCreate
ArrayAdapter<String> profileAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, arrayList);
profileList.setAdapter(profileAdapter);
When a button is pressed
// Add data to a new profile in #+id/profileList ListView
ArrayList<String> arrayList= new ArrayList<>();
arrayList.add(varA);
System.out.println("varA's");
System.out.println(arrayList);
((ArrayAdapter)profileList.getAdapter()).notifyDataSetChanged();
Layout
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/overviewLayout">
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/profileList"/>
</RelativeLayout>
I am having issues with the ListView showing the items that were added. Also, I was trying to use TinyDB to store these items so they can be referenced later since that ArrayList used on button press is just temporary. I also I'm having trouble figuring out how to set this up so that each time the button is pressed those 4 vars will stay together in a "profile" so they don't get mixed up with some other data that might be added later.
To avoid repeated values from getting stored check if it already exists :
if(!arrayList.contains(varA)){
arrayList.add(varA);
}
For listview updation problem ,use notifyItemInserted(position) instead of notifyDataSetChanged()
yourAdapter.notifyItemInserted(position);
As I understand, you want to display a list which contains 4 datas in one entry and showing just the first of them.
If it is, you could create a custom Model, it will be easier. This will store your datas in one object:
ProfileModel
|
|-- varA
|-- varB
|-- varC
'-- varD
Your custom model could look like something like this:
public class ProfileModel {
private String varA;
private float varB;
private int varC;
private String varD;
// getters
...
// setters
...
public ProfileModel() { }
}
Initialize the list of profiles at start:
ArrayList<ProfileModel> list = new ArrayList<>();
To populate the main list do as follows:
// create a list of object to the datas
ProfileModel profile = new ProfileModel();
profile.setVarA(varA);
profile.setVarB(varB);
profile.setVarC(varC);
profile.setVarD(varD);
// then fill the first list
list.add(profile);
Since you want to fill the adapter with other datas than a simple String but a custom model, I'd prefer to use a custom adapter. It's not that hard to create one, and because sometimes, you maybe want a different behavior on an update, I'd also prefer to use a custom notifyDataSetChanged method.
So, here's a simple adapter with the same layout as the one you used:
public class ProfileAdapter extends BaseAdapter {
private Context context;
private ArrayList<ProfileModel> listProfiles;
public ProfileAdapter(Context context, ArrayList<ProfileModel> listProfiles) {
super();
this.context = context;
this.listProfiles = listProfiles;
}
private class ViewHolder {
private TextView textVarA;
}
#Override
public int getCount() {
return listProfiles != null ? listProfiles.size() : 0;
}
#Override
public Object getItem(int i) {
return listProfiles.get(i); // will return the profile model from position
}
#Override
public long getItemId(int i) {
return i;
}
#Override
public View getView(int position, View view, ViewGroup viewGroup) {
ViewHolder holder;
if (view == null) {
holder = new ViewHolder();
view = LayoutInflater.from(context)
.inflate(android.R.layout.simple_list_item_1, null);
holder.textVarA = (TextView) view.findViewById(android.R.id.text1);
view.setTag(holder);
} else {
holder = (ViewHolder) view.getTag();
}
ProfileModel profile = listProfiles.get(i);
holder.textVarA.setText(profile.getVarA()); // get varA value
return view;
}
public void addEntry(ProfileModel newEntry) {
listProfiles.add(newEntry);
this.notifyDataSetChanged();
}
}
Then, you can set the adapter as follows:
ProfileAdapter profileAdapter = new ProfileAdapter(this, list);
profileList.setAdapter(profileAdapter);
To update this adapter:
// create a list of object to the datas
ProfileModel profile = new ProfileModel();
profile.setVarA(varA);
profile.setVarB(varB);
profile.setVarC(varC);
profile.setVarD(varD);
profileAdapter.addEntry(profile); // this will call notifyDataSetChanged
Finally, I'm not very aware about TinyDB, but I saw it's using some JSON file to store data. So you could create a method to convert your datas to JSONArray which its name is varA and has values varB, varC, varD. There is many examples how to convert datas to JSON (as using Gson to convert your model to a JSON format).
Hope this will be useful.

add row to section in recyclerView

I have a RecyclerView inside that recyclerView I added two Sections Favorites, All Contacts. For adding the section I used SimpleSectionedRecyclerViewAdapter
now I have rows with section which is hardcoded (the index of rows and section), I want to add rows to favorite section by selecting the rows since there's nothing documented about what I want, I don't know where to start or what I have to do ?
If anybody knows how can I add a row from All Contacts section (which is default location for rows ) by selecting them. then please give me some hints for what I have to do
what I want is something like this :
add to favorite by pressing the button
any guidance will be so helpful for me , thanks
You can achieve it with the library SectionedRecyclerViewAdapter without having to hardcode header/rows indexes.
First create a Section class to group your items:
class MySection extends StatelessSection {
String title;
List<String> list;
public MySection(String title, List<String> list) {
// call constructor with layout resources for this Section header, footer and items
super(R.layout.section_header, R.layout.section_item);
this.title = title;
this.list = list;
}
#Override
public int getContentItemsTotal() {
return list.size(); // number of items of this section
}
#Override
public RecyclerView.ViewHolder getItemViewHolder(View view) {
// return a custom instance of ViewHolder for the items of this section
return new MyItemViewHolder(view);
}
#Override
public void onBindItemViewHolder(RecyclerView.ViewHolder holder, int position) {
MyItemViewHolder itemHolder = (MyItemViewHolder) holder;
// bind your view here
itemHolder.tvItem.setText(list.get(position));
}
#Override
public RecyclerView.ViewHolder getHeaderViewHolder(View view) {
return new SimpleHeaderViewHolder(view);
}
#Override
public void onBindHeaderViewHolder(RecyclerView.ViewHolder holder) {
MyHeaderViewHolder headerHolder = (MyHeaderViewHolder) holder;
// bind your header view here
headerHolder.tvItem.setText(title);
}
}
Then you set up the RecyclerView with your Sections:
// Create an instance of SectionedRecyclerViewAdapter
SectionedRecyclerViewAdapter sectionAdapter = new SectionedRecyclerViewAdapter();
// Create your sections with the list of data
MySection favoritesSection = new MySection("Favorites", favoritesList);
MySection contactsSection = new MySection("Add Favorites", contactsList);
// Add your Sections to the adapter
sectionAdapter.addSection(favoritesSection);
sectionAdapter.addSection(contactsSection);
// Set up your RecyclerView with the SectionedRecyclerViewAdapter
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setAdapter(sectionAdapter);
You can find an example of how to listen to the button click here.
You can also add new rows to your sections without having to recalculate indexes, just create a method like this in your Section class:
public void addRow(String item) {
this.list.add(item);
}
Then:
favoritesSection.addRow("new item");
sectionAdapter.notifyDataSetChanged();

Prevent ListView insertion animation execution on existing entries

I have watched Android developer's videos on animating ListView insertion and deletion on YouTube. These have had very complex code in them and I tried searching for something else and found this.
http://karnshah8890.blogspot.fi/2013/04/listview-animation-tutorial.html
It has pretty simple implementation that works in most parts. My problem is that when I insert a new item in the ArrayList that is supplied to the ArrayAdapter, all the existing elements get reinserted due to adapter.notifyDataSetChanged()... I think. When I insert an entry in the ListView, how do I prevent all the entries from getting animated again? I just want the new one to animate in.
I use my ListView in the code this way:
toggles = (ListView) v.findViewById(R.id.toggleListView);
adapter = new ToggleListAdapter(getActivity(), toggleTasks);
toggles.setAdapter(adapter);
When a new item is inserted, I update the ArrayList (toggleTasks) and then call notifyDataSetChanged() on the adapter.
toggleTasks.add(task);
adapter.notifyDataSetChanged();
Here is my getView() method from the ArrayAdapter.
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
ToggleTask task = toggles.get(position);
String startActionName = "";
String finishActionName = "";
int imageDrawableId = -1;
// Deleted the part where I get the values for those three variables above
final Holder holder;
if(convertView == null) {
convertView = inflater.inflate(R.layout.mute_toggles_toggle_view, null);
holder = new Holder();
holder.image = (ImageView) convertView.findViewById(R.id.image);
holder.startAction = (TextView) convertView.findViewById(R.id.startAction);
holder.finishAction = (TextView) convertView.findViewById(R.id.finishAction);
convertView.setTag(holder);
} else {
holder = (Holder) convertView.getTag();
}
holder.image.setImageResource(imageDrawableId);
holder.startAction.setText(startActionName);
holder.finishAction.setText(finishActionName);
Animation animation = AnimationUtils.loadAnimation(getContext(), R.anim.listview_push_left_in); // Third animation example on the site linked earlier
animation.setDuration(500);
convertView.startAnimation(animation);
animation = null;
return convertView;
}
in the ArrayAdatper class add a global integer (I'll call it changedIndex) and create getter and setters for it. Then before you add an item to the list say adapter.setChangedIndex(index) (whatever you name your setter). You are essentially telling it ahead of time which one you want to animate.
Change this in getView
if(changedIndex == position){
Animation animation = AnimationUtils.loadAnimation(getContext(), R.anim.listview_push_left_in); // Third animation example on the site linked earlier
animation.setDuration(500);
convertView.startAnimation(animation);
animation = null;
}
then change
toggleTasks.add(task);
adapter.notifyDataSetChanged();
to
toggleTasks.add(task);
toggles.getPosition(task);
adapter.notifyDataSetChanged();
*I haven't checked any of this code but I believe it should work
Well if you want to animate only the last item inserted, then before animating check if it is last item in the List.
if (position == getCount() - 1) {
Animation animation = AnimationUtils.loadAnimation(getContext(), R.anim.listview_push_left_in); // Third animation example on the site linked earlier
animation.setDuration(500);
convertView.startAnimation(animation);
animation = null;
}
Use the ListViewAnimations library. After setting up your ListView and ListAdapter like this:
MyListAdapter mAdapter = new MyListAdapter(this, getItems());
SwingRightInAnimationAdapter swingRightInAnimationAdapter = new SwingRightInAnimationAdapter(mAdapter);
// Assign the ListView to the AnimationAdapter and vice versa
swingRightInAnimationAdapter.setAbsListView(getListView());
getListView().setAdapter(swingRightInAnimationAdapter);
Call AnimationAdapter.setShouldAnimateFromPosition(int position) somewhat like this:
swingRightInAnimationAdapter.setShouldAnimateFromPosition(swingRightInAnimationAdapter.getCount());
swingRightInAnimationAdapter.add(myItem);
Have a look at the wiki for more information about how to use the library.

Android BaseAdapters & Multiple Listviews

I am currently creating a basic news aggregator app for Android, I have so far managed to create multiple HorizontalListViews derived from this: http://www.dev-smart.com/archives/34
I am parsing all data from live JSON objects and arrays.
The process goes something like this:
1) Start app
2) Grab a JSON file which lists all feeds to display
3) Parse feed titles and article links, add each to an array
4) Get number of feeds from array and create individual HorizontalListView for each. i.e. "Irish Times".
5) Apply BaseAdapter "mAdapter" to each HorizontalListView during creation.
My baseadapter is responsible for populating my HorizontalListViews by getting each title and thumbnail.
My problem is however that all my feeds seem to contain the same articles and thumbnails. Now I am only new to Android so I'm not 100% sure whats going wrong here. See screenshot below.
Do I need to create a new BaseAdaptor for each HorizontalListview or can I use the same one to populate all my listviews with unique data.
Here's some code to help explain what I mean:
1) OnCreate method to get JSON data, parse it, get number of feeds and create each HorizontalListView
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.listviewdemo);
//--------------------JSON PARSE DATA------------------
// Creating JSON Parser instance
JSONParser jParser = new JSONParser();
// getting JSON string from URL
String json = jParser.getJSONFromUrl(sourcesUrl);
//Parse feed titles and article list
getFeeds(json);
//Create Listviews
for(int i = 0; i < feedTitle.size()-1; i++){
//getArticleImage(i);
addHorzListView(i);
articleArrayCount++;//Used to mark feed count for adaptor to know which array position to look at and retrieve data from.
//Each array position i.e. [1] represents a HorizontalListview and its related articles
}
}
2) addHorzListView method, used to create HorizontalListView and apply adaptor
//Method used to dynamically add HorizontalListViews
public void addHorzListView(int count){
LinearLayout mainLayout = (LinearLayout) findViewById(R.id.main_layout);
View view = getLayoutInflater().inflate(R.layout.listview, mainLayout,false);
//Set lists header name
TextView header = (TextView) view.findViewById(R.id.header);
header.setText(feedTitle.get(count));
//Create individual listview
HorizontalListView listview = (HorizontalListView) view.findViewById(R.id.listviewReuse);
listview.setAdapter(mAdapter);
//add listview to array list
listviewList.add(listview);
mainLayout.addView(view, count);
}
3) Baseadaptor itself:
private BaseAdapter mAdapter = new BaseAdapter() {
private OnClickListener mOnButtonClicked = new OnClickListener() {
#Override
public void onClick(View v) {
AlertDialog.Builder builder = new AlertDialog.Builder(HorizontalListViewDemo.this);
builder.setMessage("hello from " + v);
builder.setPositiveButton("Cool", null);
builder.show();
}
};
#Override
public int getCount() {
return noOfArticles.length;
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
//Each listview is populated with data here
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View retval = LayoutInflater.from(parent.getContext()).inflate(R.layout.viewitem, null);
TextView title = (TextView) retval.findViewById(R.id.title);
title.setText(getArticleTitle(position));
new DownloadImageTask((ImageView) retval.findViewById(R.id.ImageView01)) .execute(getArticleImage(position));
Button button = (Button) retval.findViewById(R.id.clickbutton);
button.setOnClickListener(mOnButtonClicked);
return retval;
}
};
The adapter mAdapter is currently displaying the articles from the last HorizontalListView that calls it.
Currently I am using the same BaseAdaptor for each ListView as I figured it populated the listview as soon as its called but i looks as though a BaseAdaptor can only be called once, I really dont know.
I want to dynamically populate feeds though without having to create a new Adaptor manually for each HorizontalListView.
Any help would be much appreciated.
So...you got the same info in 4 listview, right? In that case you only need oneAdapter populating 4 listview.
An adapter just provide the views which are visible in that moment to the listview (if it is implemented in the right way) so you can reuse the adapter if the info contained is the same.

Categories

Resources