NotifyDataSetChanged not working with Xamarin - android

I'm using RecyclerView with Xamarin.Android. I'm filling the adapter with a List<Article>. For adding more items to list when user scrolls down I'm using RecyclerView.OnScrollListener. It triggers an even and inside the event handler I'm retrieving the data and adding it to the old list with and then call adapter.NotifyDataSetChanged ();. It retrieves the data and triggers the event but doesn't update the recyclerview on the device.
What am I doing wrong??
RecyclerView recyclerview;
NewsAdapter adapter;
private List<Article> listofarticles;
var onScrollListener = new XamarinRecyclerViewOnScrollListener (layoutmanager);
onScrollListener.LoadMoreEvent += async (object sender, EventArgs e) => {
//Load more stuff here
Console.WriteLine ("*** loading more stuff ***");
var newarticles = await GetData ("2");
listofarticles = listofarticles.Concat (newarticles).ToList ();
adapter.NotifyDataSetChanged ();
};
Here's my adapter: (The clickhandler and itemcount methods are omitted)
// Adapter
public class NewsAdapter : RecyclerView.Adapter
{
// Event handler for item clicks:
public event EventHandler<int> ItemClick;
private Context globalContext = null;
// Underlying data set:
public List<Article> _listofarticles;
// Load the adapter with the data set (articles) at construction time:
public NewsAdapter (List<Article> listofarticle, Context context)
{
this._listofarticles = listofarticle;
globalContext = context;
}
// Create a new article CardView (invoked by the layout manager):
public override RecyclerView.ViewHolder OnCreateViewHolder (ViewGroup parent, int viewType)
{
// Inflate the CardView for the photo:
View itemView = LayoutInflater.From (parent.Context).Inflate (Resource.Layout.latestnews_recycler_article, parent, false);
// Create a ViewHolder to find and hold these view references, and
// register OnClick with the view holder:
NewsViewHolder vh = new NewsViewHolder (itemView, OnClick);
return vh;
}
// Fill in the contents of the article card (invoked by the layout manager):
public override void OnBindViewHolder (RecyclerView.ViewHolder holder, int position)
{
NewsViewHolder vh = holder as NewsViewHolder;
// Set the ImageView and TextView in this ViewHolder's CardView
// from this position in the photo album:
//vh.Thumbnail.SetImageResource (_listofarticles[position].Thumbnail);
// We use Picasso to load images efficiently
Picasso.With (globalContext).Load (_listofarticles[position].Thumbnail)
//.Placeholder(R.drawable.ic_placeholder) // optional
//.Error(R.drawable.ic_error_fallback) // optional
//.Resize(250, 200) // optional
//.Rotate(90) // optional
.Into (vh.Thumbnail);
vh.Title.Text = _listofarticles[position].Title;
}
}

I needed to add new items to the list inside adapter (not the one for the activity class) that is _listofarticles and then call NotifyDataSetChanged inside the adapter class like this.NotifyDataSetChanged ();. So to achieve that I added a method called AddToList to the adapter:
public void AddToList(List<Article> newitemslist)
{
_listofarticles = _listofarticles.Concat (newitemslist).ToList ();
this.NotifyDataSetChanged ();
}
and I call it inside the event handler for scrolling like:
var newarticles = await GetData ("2");
adapter.AddToList (newarticles);
This properly updates the adapter and adds items to the end of recyclerview.

In Xamarin because of memory management and garbage collection when using an ArrayAdapter, the implementation makes a copy of your backing list when you create it.
In order to be able to use the NotifyDataSetChanged, you need to use the Android.Runtime.JavaList instead of the .Net IList.
Here is a snippet :
ArrayAdapter adapter;
Android.Runtime.JavaList<string> messages = new Android.Runtime.JavaList<string>();
Then either on your OnCreate or in the OnResume, add the following code :
adapter = new ArrayAdapter(this, Android.Resource.Layout.SimpleListItem1, Android.Resource.Id.Text1, messages);
listView.Adapter = adapter;
You can now add items to your list and call the NotifyDataSetChanged and you should be able to get the changes.
In my case, i have a click handler on the button that updates the listview :
void BtnSend_Click(object sender, System.EventArgs e)
{
var message = editText.Text;
messages.Add(message);
adapter.NotifyDataSetChanged();
}

Related

Initial data from ListAdapter gets erased when using DiffUtil.ItemCallback

In my app I have two LiveData objects, one for getting items from 0-10 and second to get the items from 11-20. I'm trying to load the data in a RecyclerView but instead of having 20 items, the first 10 (0-10) are replaces with new 10 (11-20). This is what I have tried:
recyclerView = findViewById(R.id.recycler_view);
adapter = new ItemsAdapter();
recyclerView.setAdapter(adapter);
viewModel = new ViewModelProvider(this).get(ItemListViewModel.class);
To get items from 0-10 I use this method:
private void getInitialItems() {
ItemListLiveData liveData = viewModel.getItemsLiveData();
liveData.observe(this, itemtList -> adapter.submitList(itemtList));
}
To get items from 11-20 I use this method:
private void getNextlItems() {
ItemListLiveData liveData = viewModel.getItemsLiveData();
liveData.observe(this, itemtList -> adapter.submitList(itemtList));
}
This is my ViewModel class:
public class ItemListViewModel extends ViewModel {
private ItemListRepository repository = new ItemListRepository();
ItemListLiveData getItemsLiveData() {
return repository.getItemListLiveData();
}
}
In the repository I only get the items from a back-end server. This is my adapter class:
public class ItemsAdapter extends ListAdapter<Item, ItemsAdapter.ItemViewHolder> {
ItemsAdapter() {
super(diffCallback);
}
#NonNull
#Override
public ItemViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
//Inflate the view
}
#Override
public void onBindViewHolder(#NonNull final ItemViewHolder holder, int position) {
//Bind the Item according to position
}
private static DiffUtil.ItemCallback<Item> diffCallback = new DiffUtil.ItemCallback<Item>() {
#Override
public boolean areItemsTheSame(#NonNull Item oldItem, #NonNull Item newItem) {
return oldItem.id.equals(newItem.id);
}
#Override
public boolean areContentsTheSame(#NonNull Item oldItem, #NonNull Item newItem) {
return oldItem.equals(newItem);
}
};
}
My expectation is when using DiffUtil.ItemCallback to get both lists as a cumulative list since all the objects are different. Even if I pass both lists to the same adapter, I end up having only ten items (11-20). How to use submit list so I can have 20 items in my list and not only 10 (11-20)?
DiffUtil.ItemCallback is used for animating smoothly changes in dataset in adapter.
For example if you have have 10 items, than submit list with 9 items that were contained in previous 10, DiffUtil.ItemCallback will determine difference between old and new list, which position that element was and animate changes accordingly. What you are looking for in your case is Pagination where you can expand/show items while scrolling.
You don't need two LiveData for this one, you cast fetch data from some source add it to LiveData of Pagination. First it will be showed 10 items, then if you scroll to end another 10, and so on. You can adjust type of pagination by your needs with provided Configuration.
To do all that without Pagination.
liveData.observe(this, itemtList -> adapter.submitList(adapter.getCurrentList().addAll(itemtList)));
Get previous data, on top of that data add new data and it will all be shown.

Android Room Pagination not working

I am trying to use android Room API to load records from sQlite in pages.
The issue is Paging library is loading entire database into model class and binding it with the adapter which is making UI thread skip frames. It suppose to load 20 records and then keep on adding more when required
This is my view model class
public class UserViewModel extends ViewModel {
public LiveData<PagedList<User>> userList;
public UserViewModel() {
}
public void init(UserDao userDao) {
PagedList.Config pagedListConfig =
(new PagedList.Config.Builder()).setEnablePlaceholders(true)
.setPrefetchDistance(10)
.setPageSize(20).build();
userList = (new LivePagedListBuilder(userDao.usersByFirstName(),
pagedListConfig))
.build();
}
}
Paged adapter
public class UserAdapter extends PagedListAdapter<User, UserAdapter.UserItemViewHolder> {
protected UserAdapter() {
super(User.DIFF_CALLBACK);
}
#Override
public UserItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View view = layoutInflater.inflate(R.layout.item_user_list, parent, false);
return new UserItemViewHolder(view);
}
#Override
public void onBindViewHolder(UserItemViewHolder holder, int position) {
User user= getItem(position);
if(user!=null) {
holder.bindTo(user);
}
}
static class UserItemViewHolder extends RecyclerView.ViewHolder {
TextView userName, userId;
public UserItemViewHolder(View itemView) {
super(itemView);
userId = itemView.findViewById(R.id.userId);
userName = itemView.findViewById(R.id.userName);
}
public void bindTo(User user) {
userName.setText(user.firstName);
userId.setText(String.valueOf(user.userId));
}
}
}
Binding with recycler View:
UserViewModel viewModel =
ViewModelProviders
.of(this)
.get(UserViewModel.class);
viewModel.init(userDao);
final UserAdapter userUserAdapter = new UserAdapter();
viewModel.userList.observe(this, pagedList -> {
Toast.makeText(this, "Page " + pagedList.size(), Toast.LENGTH_SHORT).show();
Log.e("Paging ", "Page " + pagedList.size());
userUserAdapter.setList(pagedList);
});
recyclerView.setAdapter(userUserAdapter);
02-18 10:19:40.409 15310-15310/com.androidkt.archpaging E/Paging: Page
200
Any idea what I am missing.
By the paging implementation, your result count should indeed be the full size of the query (200), as you configured to do so, the RecyclerView will receive placeholders null for the views which data is not ready yet. This is intended for when you want to show the whole list views but only bind the content of it when the data is available. But your RecyclerView should not call onCreateViewHolder and onBindViewHolder for the entire count unless it is visible.
Check (put a breakpoint) the method onMeasure or onLayout on your RecyclerView to see if the method is not returning a bigger height than expected (probably the expected is something around the size of your screen). Sometimes the actual height of RecyclerView is much bigger than the screen and the adapter call onBindViewHolder() for the total number of items because it's "visible" to it instead of the number we can see. This would trigger the DataSource to query the database and bind the views before you want.
try userUserAdapter.setList(pagedList); put out observe . observe use listner list change . You . You need to initialize the list and set up recyclerview normally .
The object list should be included in the pageAdapter in the usual way
You should call userUserAdapter.submitList(pagedList).
setList() is used for RecyclerView.Adapter not for PagedListAdapter.

How to edit RecyclerView data

I have a RecyclerView which displays two pieces of data in each row.
Both are from a List Players.
What I need is to update the second piece of data (which is a counter, an int) each time an element is clicked.
Basically I have this players List modified, but don't know how to put it back in to the RecyclerView edited.
My Adapter Code
TextView name;
TextView counter;
CoursesViewHolder(View itemView) {
super(itemView);
itemView.setOnClickListener(this);
name = (TextView) itemView.findViewById(R.id.textName);
counter = (TextView) itemView.findViewById(R.id.textCounter);
}
[...]
#Override
public void onBindViewHolder(CoursesViewHolder holder, int position) {
Player player = mArrayCourses.get(position);
holder.name.setText(player.getName());
holder.counter.setText(String.valueOf(player.getCount()));
}
What I've tried
adapter = new CoursesAdapter(players);
myList.setAdapter(adapter);
myList.invalidate();
Edit:
// Piece of code of the activity where my RecyclerView is
myList = (RecyclerView) findViewById(R.id.playersVote);
myList.setLayoutManager(new LinearLayoutManager(this));
adapter = new CoursesAdapter(players);
myList.setAdapter(adapter);
// RecyclerView with a click listener
CoursesAdapter clickAdapter = new CoursesAdapter(players);
clickAdapter.setOnEntryClickListener(new CoursesAdapter.OnEntryClickListener() {
#Override
public void onEntryClick(View view, int position) {
// Let each player vote (ghosts too)
Player player = players.get(position);
player.incrementCount();
//Toast.makeText(ListPlayersVote.this, String.valueOf(player.getCount()), Toast.LENGTH_SHORT).show();
votes++;
Every time that your data change you should notify those changes in the adapter, using notifyDataSetChanged()
notifyDataSetChanged(): Notifies the attached observers that the underlying data has been
changed and any View reflecting the data set should refresh itself.
like this:
adapter = new CoursesAdapter(players);
myList.setAdapter(adapter);
....
....
adapter.notifyDataSetChanged();
Use the below code to change the particular Item in a recyclerview
public void updatedelivact(String value,int index) {
ChatMessage chatMessage = getItem(index);
Collection<Integer> readIds = chatMessage.getDeliveredIds();
readIds.add(userid);
chatMessage.setReadIds(readIds);
//To change the perticular item
notifyItemChanged(index);
}

Multiple header in recyclerview android

I have created a recyclerview for displaying data which is fetching from the server. I had used only single layout for displaying the data.
Now my requirement is like when I upload images or videos, then the uploading status should be displayed on top of the data which is displaying from the server. i-e on 0th position of recyclerview. I can add any number of images or videos.
after image or video successfully upload i also want to remove that row from recyclerview. I thought of doing using getItemViewType(). In this using two layout. I don't know this method is correct or not. I am not getting any solution to this,
Please.....
any help...
You can easily achieve that using the library SectionedRecyclerViewAdapter.
You should first create a section class:
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_footer, 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();
MySection uploadsSection = new MySection("Uploads", uploadList);
MySection downloadsSection = new MySection("Downloads", downloadList);
// Add your Sections
sectionAdapter.addSection(uploadsSection);
sectionAdapter.addSection(downloadsSection);
// Set up your RecyclerView with the SectionedRecyclerViewAdapter
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
recyclerView.setAdapter(sectionAdapter);
This way you manage the items from your upload and download lists separately by removing them from uploadList/downloadList then notifying the changes to the adapter. The upload items will always be displayed at the top of the RecyclerView because they are in the first section added to the adapter.
If you have different layouts for uploads and downloads you can create a different Section class for each.
Make two viewTypes like:
private static final int REGULAR_HOLDER = 1;
private static final int LOADING_HOLDER = 2;
Override getItemViewType and return LOADING_HOLDER for position 0, and REGULAR for all others. Also have state if you are loading or not. If you are not loading anything you will return REGULAR_HOLDER for all rows (positions).
Then in onCreate check if you have REGULAR or LOADING viewType, and create proper Holder. Important: make your Adapter implement RecyclerView.Adapter<VH> not your custom implementation of ViewHolder.
onBind executes next. There you will have to check if your viewHolder object you get is instance of RegularViewHolder or instance of LoadingViewHolder, like:
if (holder instance of RegularViewHolder) {
holder.doStuff();
} else if (holder instance of LoadingViewHolder) {
holder.showLoading();
}
Now, before this you should made two layouts. One is for your regular rows, and other is for row that will show loading. Make two classes that implement ViewHolder, in example above i called them RegularViewHolder and LoadingViewHolder.
EDIT: few things to keep in mind. I told you to keep a loading state (loading or not loading), so if you want to remove LOADING row, you could make that change and call notifyDataSetChanged();. Now, getItemViewType should return all REGULAR rows if you did it right.
Also you should keep in mind that if you want to show 10 rows of your data. Your getItemCount() should return 11 (10 + loading row) if there is loading happening. Also, in that case your data rows start from second row (position 1).

How to refresh Android listview?

How to refresh an Android ListView after adding/deleting dynamic data?
Call notifyDataSetChanged() on your Adapter object once you've modified the data in that adapter.
Some additional specifics on how/when to call notifyDataSetChanged() can be viewed in this Google I/O video.
Also you can use this:
myListView.invalidateViews();
Please ignore all the invalidate(), invalidateViews(), requestLayout(), ... answers to this question.
The right thing to do (and luckily also marked as right answer) is to call notifyDataSetChanged() on your Adapter.
Troubleshooting
If calling notifyDataSetChanged() doesn't work all the layout methods won't help either. Believe me the ListView was properly updated. If you fail to find the difference you need to check where the data in your adapter comes from.
If this is just a collection you're keeping in memory check that you actually deleted from or added the item(s) to the collection before calling the notifyDataSetChanged().
If you're working with a database or service backend you'll have to call the method to retrieve the information again (or manipulate the in memory data) before calling the notifyDataSetChanged().
The thing is this notifyDataSetChanged only works if the dataset has changed. So that is the place to look if you don't find changes coming through. Debug if needed.
ArrayAdapter vs BaseAdapter
I did find that working with an adapter that lets you manage the collection, like a BaseAdapter works better. Some adapters like the ArrayAdapter already manage their own collection making it harder to get to the proper collection for updates. It's really just an needless extra layer of difficulty in most cases.
UI Thread
It is true that this has to be called from the UI thread. Other answers have examples on how to achieve this. However this is only required if you're working on this information from outside the UI thread. That is from a service or a non UI thread. In simple cases you'll be updating your data from a button click or another activity/fragment. So still within the UI thread. No need to always pop that runOnUiTrhead in.
Quick Example Project
Can be found at https://github.com/hanscappelle/so-2250770.git. Just clone and open the project in Android Studio (gradle). This project has a MainAcitivity building a ListView with all random data. This list can be refreshed using the action menu.
The adapter implementation I created for this example ModelObject exposes the data collection
public class MyListAdapter extends BaseAdapter {
/**
* this is our own collection of data, can be anything we
* want it to be as long as we get the abstract methods
* implemented using this data and work on this data
* (see getter) you should be fine
*/
private List<ModelObject> mData;
/**
* our ctor for this adapter, we'll accept all the things
* we need here
*
* #param mData
*/
public MyListAdapter(final Context context, final List<ModelObject> mData) {
this.mData = mData;
this.mContext = context;
}
public List<ModelObject> getData() {
return mData;
}
// implement all abstract methods here
}
Code from the MainActivity
public class MainActivity extends Activity {
private MyListAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView list = (ListView) findViewById(R.id.list);
// create some dummy data here
List<ModelObject> objects = getRandomData();
// and put it into an adapter for the list
mAdapter = new MyListAdapter(this, objects);
list.setAdapter(mAdapter);
// mAdapter is available in the helper methods below and the
// data will be updated based on action menu interactions
// you could also keep the reference to the android ListView
// object instead and use the {#link ListView#getAdapter()}
// method instead. However you would have to cast that adapter
// to your own instance every time
}
/**
* helper to show what happens when all data is new
*/
private void reloadAllData(){
// get new modified random data
List<ModelObject> objects = getRandomData();
// update data in our adapter
mAdapter.getData().clear();
mAdapter.getData().addAll(objects);
// fire the event
mAdapter.notifyDataSetChanged();
}
/**
* helper to show how only changing properties of data
* elements also works
*/
private void scrambleChecked(){
Random random = new Random();
// update data in our adapter, iterate all objects and
// resetting the checked option
for( ModelObject mo : mAdapter.getData()) {
mo.setChecked(random.nextBoolean());
}
// fire the event
mAdapter.notifyDataSetChanged();
}
}
More Information
Another nice post about the power of listViews is found here: http://www.vogella.com/articles/AndroidListView/article.html
Call runnable whenever you want:
runOnUiThread(run);
OnCreate(), you set your runnable thread:
run = new Runnable() {
public void run() {
//reload content
arraylist.clear();
arraylist.addAll(db.readAll());
adapter.notifyDataSetChanged();
listview.invalidateViews();
listview.refreshDrawableState();
}
};
i got some problems with dynamic refresh of my listview.
Call notifyDataSetChanged() on your Adapter.
Some additional specifics on how/when to call notifyDataSetChanged() can be viewed in this Google I/O video.
notifyDataSetChanged() did not work properly in my case[ I called the notifyDataSetChanged from another class]. Just in the case i edited the ListView in the running Activity (Thread). That video thanks to Christopher gave the final hint.
In my second class i used
Runnable run = new Runnable(){
public void run(){
contactsActivity.update();
}
};
contactsActivity.runOnUiThread(run);
to acces the update() from my Activity. This update includes
myAdapter.notifyDataSetChanged();
to tell the Adapter to refresh the view.
Worked fine as far as I can say.
If you are using SimpleCursorAdapter try calling requery() on the Cursor object.
if you are not still satisfied with ListView Refreshment, you can look at this snippet,this is for loading the listView from DB, Actually what you have to do is simply reload the ListView,after you perform any CRUD Operation
Its not a best way to code, but it will refresh the ListView as you wish..
It works for Me....if u find better solution,please Share...
.......
......
do your CRUD Operations..
......
.....
DBAdapter.open();
DBAdapter.insert_into_SingleList();
// Bring that DB_results and add it to list as its contents....
ls2.setAdapter(new ArrayAdapter(DynTABSample.this,
android.R.layout.simple_list_item_1, DBAdapter.DB_ListView));
DBAdapter.close();
The solutions proposed by people in this post works or not mainly depending on the Android version of your device. For Example to use the AddAll method you have to put android:minSdkVersion="10" in your android device.
To solve this questions for all devices I have created my on own method in my adapter and use inside the add and remove method inherits from ArrayAdapter that update you data without problems.
My Code: Using my own data class RaceResult, you use your own data model.
ResultGpRowAdapter.java
public class ResultGpRowAdapter extends ArrayAdapter<RaceResult> {
Context context;
int resource;
List<RaceResult> data=null;
public ResultGpRowAdapter(Context context, int resource, List<RaceResult> objects) {
super(context, resource, objects);
this.context = context;
this.resource = resource;
this.data = objects;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
........
}
//my own method to populate data
public void myAddAll(List<RaceResult> items) {
for (RaceResult item:items){
super.add(item);
}
}
ResultsGp.java
public class ResultsGp extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
...........
...........
ListView list = (ListView)findViewById(R.id.resultsGpList);
ResultGpRowAdapter adapter = new ResultGpRowAdapter(this, R.layout.activity_result_gp_row, new ArrayList<RaceResult>()); //Empty data
list.setAdapter(adapter);
....
....
....
//LOAD a ArrayList<RaceResult> with data
ArrayList<RaceResult> data = new ArrayList<RaceResult>();
data.add(new RaceResult(....));
data.add(new RaceResult(....));
.......
adapter.myAddAll(data); //Your list will be udpdated!!!
For me after changing information in sql database nothing could refresh list view( to be specific expandable list view) so if notifyDataSetChanged() doesn't help, you can try to clear your list first and add it again after that call notifyDataSetChanged(). For example
private List<List<SomeNewArray>> arrayList;
List<SomeNewArray> array1= getArrayList(...);
List<SomeNewArray> array2= getArrayList(...);
arrayList.clear();
arrayList.add(array1);
arrayList.add(array2);
notifyDataSetChanged();
Hope it makes sense for you.
If you want to maintain your scroll position when you refresh, and you can do this:
if (mEventListView.getAdapter() == null) {
EventLogAdapter eventLogAdapter = new EventLogAdapter(mContext, events);
mEventListView.setAdapter(eventLogAdapter);
} else {
((EventLogAdapter)mEventListView.getAdapter()).refill(events);
}
public void refill(List<EventLog> events) {
mEvents.clear();
mEvents.addAll(events);
notifyDataSetChanged();
}
For the detail information, please see Android ListView: Maintain your scroll position when you refresh.
Just use myArrayList.remove(position); inside a listener:
myListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override public void onItemClick(AdapterView<?> parent, android.view.View view, int position, long id) {
myArrayList.remove(position);
myArrayAdapter.notifyDataSetChanged();
}
});
You need to use a single object of that list whoose data you are inflating on ListView. If reference is change then notifyDataSetChanged() does't work .Whenever You are deleting elements from list view also delete them from the list you are using whether it is a ArrayList<> or Something else then Call
notifyDataSetChanged() on object of Your adapter class.
So here see how i managed it in my adapter see below
public class CountryCodeListAdapter extends BaseAdapter implements OnItemClickListener{
private Context context;
private ArrayList<CountryDataObject> dObj;
private ViewHolder holder;
private Typeface itemFont;
private int selectedPosition=-1;
private ArrayList<CountryDataObject> completeList;
public CountryCodeListAdapter(Context context, ArrayList<CountryDataObject> dObj) {
this.context = context;
this.dObj=dObj;
completeList=new ArrayList<CountryDataObject>();
completeList.addAll(dObj);
itemFont=Typeface.createFromAsset(context.getAssets(), "CaviarDreams.ttf");
}
#Override
public int getCount() {
return dObj.size();
}
#Override
public Object getItem(int position) {
return dObj.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View view, ViewGroup parent) {
if(view==null){
holder = new ViewHolder();
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = inflater.inflate(R.layout.states_inflator_layout, null);
holder.textView = ((TextView)view.findViewById(R.id.stateNameInflator));
holder.checkImg=(ImageView)view.findViewById(R.id.checkBoxState);
view.setTag(holder);
}else{
holder = (ViewHolder) view.getTag();
}
holder.textView.setText(dObj.get(position).getCountryName());
holder.textView.setTypeface(itemFont);
if(position==selectedPosition)
{
holder.checkImg.setImageResource(R.drawable.check);
}
else
{
holder.checkImg.setImageResource(R.drawable.uncheck);
}
return view;
}
private class ViewHolder{
private TextView textView;
private ImageView checkImg;
}
public void getFilter(String name) {
dObj.clear();
if(!name.equals("")){
for (CountryDataObject item : completeList) {
if(item.getCountryName().toLowerCase().startsWith(name.toLowerCase(),0)){
dObj.add(item);
}
}
}
else {
dObj.addAll(completeList);
}
selectedPosition=-1;
notifyDataSetChanged();
notifyDataSetInvalidated();
}
#Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
Registration reg=(Registration)context;
selectedPosition=position;
reg.setSelectedCountryCode("+"+dObj.get(position).getCountryCode());
notifyDataSetChanged();
}
}
Consider you have passed a list to your adapter.
Use:
list.getAdapter().notifyDataSetChanged()
to update your list.
After deleting data from list view, you have to call refreshDrawableState().
Here is the example:
final DatabaseHelper db = new DatabaseHelper (ActivityName.this);
db.open();
db.deleteContact(arg3);
mListView.refreshDrawableState();
db.close();
and deleteContact method in DatabaseHelper class will be somewhat looks like
public boolean deleteContact(long rowId) {
return db.delete(TABLE_NAME, BaseColumns._ID + "=" + rowId, null) > 0;
}
I was not able to get notifyDataSetChanged() to work on updating my SimpleAdapter, so instead I tried first removing all views that were attached to the parent layout using removeAllViews(), then adding the ListView, and that worked, allowing me to update the UI:
LinearLayout results = (LinearLayout)findViewById(R.id.results);
ListView lv = new ListView(this);
ArrayList<HashMap<String,String>> list = new ArrayList<HashMap<String,String>>();
SimpleAdapter adapter = new SimpleAdapter( this, list, R.layout.directory_row,
new String[] { "name", "dept" }, new int[] { R.id.name, R.id.dept } );
for (...) {
HashMap<String, String> map = new HashMap<String, String>();
map.put("name", name);
map.put("dept", dept);
list.add(map);
}
lv.setAdapter(adapter);
results.removeAllViews();
results.addView(lv);
while using SimpleCursorAdapter can call changeCursor(newCursor) on the adapter.
I was the same when, in a fragment, I wanted to populate a ListView (in a single TextView) with the mac address of BLE devices scanned over some time.
What I did was this:
public class Fragment01 extends android.support.v4.app.Fragment implements ...
{
private ListView listView;
private ArrayAdapter<String> arrayAdapter_string;
...
#Override
public void onActivityCreated(Bundle savedInstanceState)
{
...
this.listView= (ListView) super.getActivity().findViewById(R.id.fragment01_listView);
...
this.arrayAdapter_string= new ArrayAdapter<String>(super.getActivity(), R.layout.dispositivo_ble_item, R.id.fragment01_item_textView_titulo);
this.listView.setAdapter(this.arrayAdapter_string);
}
#Override
public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord)
{
...
super.getActivity().runOnUiThread(new RefreshListView(device));
}
private class RefreshListView implements Runnable
{
private BluetoothDevice bluetoothDevice;
public RefreshListView(BluetoothDevice bluetoothDevice)
{
this.bluetoothDevice= bluetoothDevice;
}
#Override
public void run()
{
Fragment01.this.arrayAdapter_string.add(new String(bluetoothDevice.toString()));
Fragment01.this.arrayAdapter_string.notifyDataSetChanged();
}
}
Then the ListView began to dynamically populate with the mac address of the devices found.
I think it depends on what you mean by refresh. Do you mean that the GUI display should be refreshed, or do you mean that the child views should be refreshed such that you can programatically call getChildAt(int) and get the view corresponding to what is in the Adapter.
If you want the GUI display refreshed, then call notifyDataSetChanged() on the adapter. The GUI will be refreshed when next redrawn.
If you want to be able to call getChildAt(int) and get a view that reflects what is what is in the adapter, then call to layoutChildren(). This will cause the child view to be reconstructed from the adapter data.
I had an ArrayList which I wanted to display in a listview. ArrayList contained elements from mysql.
I overrided onRefresh method and in that method I used tablelayout.removeAllViews(); and then repeated the process for getting data again from the database.
But before that make sure to clear your ArrayList or whatever data structre or else new data will get appended to the old one..
If you want to update the UI listview from a service, then make the adapter static in your Main activity and do this:
#Override
public void onDestroy() {
if (MainActivity.isInFront == true) {
if (MainActivity.adapter != null) {
MainActivity.adapter.notifyDataSetChanged();
}
MainActivity.listView.setAdapter(MainActivity.adapter);
}
}
If you are going by android guide lines and you are using the ContentProviders to get data from Database and you are displaying it in the ListView using the CursorLoader and CursorAdapters ,then you all changes to the related data will automatically be reflected in the ListView.
Your getContext().getContentResolver().notifyChange(uri, null); on the cursor in the ContentProvider will be enough to reflect the changes .No need for the extra work around.
But when you are not using these all then you need to tell the adapter when the dataset is changing. Also you need to re-populate / reload your dataset (say list) and then you need to call notifyDataSetChanged() on the adapter.
notifyDataSetChanged()wont work if there is no the changes in the datset.
Here is the comment above the method in docs-
/**
* Notifies the attached observers that the underlying data has been changed
* and any View reflecting the data set should refresh itself.
*/
I was only able to get notifyDataSetChanged only by getting new adapter data, then resetting the adapter for the list view, then making the call like so:
expandableAdapter = baseFragmentParent.setupEXLVAdapter();
baseFragmentParent.setAdapter(expandableAdapter);
expandableAdapter.notifyDataSetChanged();
on other option is onWindowFocusChanged method, but sure its sensitive and needs some extra coding for whom is interested
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
// some controls needed
programList = usersDBHelper.readProgram(model.title!!)
notesAdapter = DailyAdapter(this, programList)
notesAdapter.notifyDataSetChanged()
listview_act_daily.adapter = notesAdapter
}
If I talked about my scenario here, non of above answers will not worked because I had activity that show list of db values along with a delete button and when a delete button is pressed, I wanted to delete that item from the list.
The cool thing was, I did not used recycler view but a simple list view and that list view initialized in the adapter class. So, calling the notifyDataSetChanged() will not do anything inside the adapter class and even in the activity class where adapter object is initialized because delete method was in the adapter class.
So, the solution was to remove the object from the adapter in the adapter class getView method(to only delete that specific object but if you want to delete all, call clear()).
To you to get some idea, what was my code look like,
public class WordAdapter extends ArrayAdapter<Word> {
Context context;
public WordAdapter(Activity context, ArrayList<Word> words) {}
//.......
#NonNull
#Override
public View getView(final int position, View convertView, ViewGroup group) {
//.......
ImageButton deleteBt = listItemView.findViewById(R.id.word_delete_bt);
deleteBt.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (vocabDb.deleteWord(currentWord.id)) {
//.....
} else{
//.....
}
remove(getItem(position)); // <---- here is the trick ---<
//clear() // if you want to clear everything
}
});
//....
Note: here remove() and getItem() methods are inherit from the Adapter class.
remove() - to remove the specific item that is clicked
getItem(position) - is to get the item(here, thats my Word object
that I have added to the list) from the clicked position.
This is how I set the adapter to the listview in the activity class,
ArrayList<Word> wordList = new ArrayList();
WordAdapter adapter = new WordAdapter(this, wordList);
ListView list_view = (ListView) findViewById(R.id.activity_view_words);
list_view.setAdapter(adapter);
After adding/deleting dynamic data in your "dataArray" do:
if you use an ArrayAdapter
adapter.notifyDataSetChanged();
if you use a customAdapter that extends ArrayAdapter
adapter.clear();
adapter.addAll(dataArray);
adapter.notifyDataSetChanged();
if you use a customAdapter that extends BaseAdapter
adapter.clear();
adapter.getData().addAll(dataArray);
adapter.getData().notifyDataSetChanged();
The easiest is to just make a new Adaper and drop the old one:
myListView.setAdapter(new MyListAdapter(...));

Categories

Resources