Hi everytime I install app, open it than close it with back button and than again reopen it I get the same error:
05-05 10:49:35.453: ERROR/AndroidRuntime(5118): java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. [in ListView(2131361820, class android.widget.ListView) with Adapter(class com.TVSpored.ChannelItemAdapter)]
I have read that I have to make onRestart function (LINK)... but I have no idea how to handle it...
onCreate function do the following things:
set layout
set all necessary objects (ProgressDialog, ArrayList, ChannelItemAdapter, channelDBAdapter)
Loads data in asynctask: new LoadChannels().execute(channelItem);
As I have said before it work perfectly onStart and when browsing the app... but when leaving the app and restart it, it always crushes...
Thanks for your help!
Added code:
protected ArrayList<ToDoItem> doInBackground(ArrayList<ToDoItem>... passing)
{
cItems = passing[0]; //get passed arraylist
toDoListCursor = channelDBAdapter. getAllToDoItemsCursor();
startManagingCursor(toDoListCursor);
channelItem.clear();
if (toDoListCursor.moveToFirst())
do
{
String task = toDoListCursor.getString(toDoListCursor.getColumnIndex(ToDoDBAdapter.KEY_NAME));
String created = toDoListCursor.getString(toDoListCursor.getColumnIndex(ToDoDBAdapter.KEY_EPG_NAME));
int fav = toDoListCursor.getInt(toDoListCursor.getColumnIndex(ToDoDBAdapter.KEY_FAV));
int id = toDoListCursor.getInt(toDoListCursor.getColumnIndex(ToDoDBAdapter.KEY_EPG_ID));
int ch_type = toDoListCursor.getInt(toDoListCursor.getColumnIndex(ToDoDBAdapter.KEY_CH_CHTYPE_ID));
int country = toDoListCursor.getInt(toDoListCursor.getColumnIndex(ToDoDBAdapter.KEY_CH_COU_ID));
ToDoItem newItem = new ToDoItem(task, created, fav, id, ch_type, country);
channelItem.add(0, newItem);
}
while(toDoListCursor.moveToNext());
return cItems; //return result
}
From the stack trace you have provided, it seems you modify the content of your ListView Adapter outside of the UI thread. I think you should have a look at your ASyncTask to be sure that you perform modifications on your adapter only on methods that are executed in the UI thread (not in doInBackground for example).
See ASyncTask
Related
I used the lifecycle callback onCreate to fetch data like below
mWeOutViewModel.getPlaceListLiveData()
.observe(this, weOutItemViewModels -> {
AppLogger.i(getCustomTag() + "adding items " + weOutItemViewModels.size());
if (weOutItemViewModels != null && weOutItemViewModels.size() > 0)
mWeOutListAdapter.addToExisting(weOutItemViewModels);
});
As you can see the AppLogger output the initial size which is 0 when the fragment is displayed, then I fetch the data and call postValue (setValue crashes the app and it expected because I fetch data from the internet using a background thread). So I call post value like below :
private void updatePlaces(List<WeOutGroupedViewModels> weOutGroupedViewModels) {
List<WeOutGroupedViewModels> oldList = placeMutableLiveData.getValue();
oldList.addAll(weOutGroupedViewModels);
AppLogger.i(TAG +" updating places "+oldList.size());
placeMutableLiveData.postValue(oldList);
}
As you can see the other AppLogger before postValue, the size of the list is displayed(not empty), but nothing happens until the app crashes and nothing is shown in the logs. I have no ways of debugging since even on debug mode nothing happens. The post value doesn't trigger the observer.
I initialize the mutableLivedata like this :
private final MutableLiveData<List<WeOutGroupedViewModels>> placeMutableLiveData = new MutableLiveData<>();
and access like this :
public LiveData<List<WeOutGroupedViewModels>> getPlaceListLiveData() {
return placeMutableLiveData;
}
Event when I make the livedata public to access directly the livedata, there is no change (just in case someone thinks that's is where the issue comes from)
Instead of placeMutableLiveData.postValue(oldList);
I recommend using
placeMutableLiveData.postValue(Collections.unmodifiableList(new ArrayList<>(newList));
That way, the next time you access this list, you won't be able to mutate it in place, which is a good thing. You're not supposed to mutate the list inside a reactive state holder (MutableLiveData).
So theoretically it should look like this:
private void updatePlaces(List<WeOutGroupedViewModels> weOutGroupedViewModels) {
List<WeOutGroupedViewModels> newList = new ArrayList<>(placeMutableLiveData.getValue());
newList.addAll(weOutGroupedViewModels);
AppLogger.i(TAG +" updating places "+newList.size());
placeMutableLiveData.postValue(Collections.unmodifiableList(newList));
}
I'm using Xamarin Android, and in my app I have a recycle view that gets populated from LiteDB.
Now, in OnCreateView, in my fragment that hold the recycle view, I set the adapters, and call SetData(), to populate my list.
It looks like this:
mLayoutManager = new LinearLayoutManager(Context);
mRecyclerView.SetLayoutManager(mLayoutManager);
mAdapter = new PlaylistItemAdapter(new List<YoutubePlaylistSync>());
mRecyclerView.SetAdapter(mAdapter);
SetData();
This works fine, but when I view the fragment the app freezes. That's normal since the call to fetch the data from the database is blocking.
I wanted to offload the work from the UI thread, so since I need to fire of and forget this function, I replaced my code with:
Task.Run(() => SetData());
This didn't work as I expected, I did confirm that the function is called and the function sets the new data, and calls NotifyNewData().
private void SetData()
{
mAdapter.SetData(db.GetData());
}
//In PlaylistItemAdapter
public void SetData(IEnumerable<YoutubePlaylistSync> newData)
{
playlists.Clear();
foreach (var playlist in newData)
{
playlists.Add(playlist);
}
NotifyDataSetChanged();
}
But the list is not getting populated, if I go to another tab and return, the FAB in my layout disappears, and the drawer is stuck with two items selected (navigation still works).
In addition, if I don't use a task, but a new thread, I get the desired behavior. Like so:
Thread t = new Thread(() =>
{
SetData();
});
t.IsBackground = true;
t.Start();
Note that the app behaves normal, with the first method it works fine.
Why? Is there an alternative way to offload the DB call other than opening another thread? (Its quite an expensive operation)
Ok, so ive spend two days on this and I have tried everything under the sun and Google :L
Basically, if I add or delete something from my server, I want to update my listview which is in mylistfragment from my detailfragment to reflect the changes on the server. The changes have occur ed through clicking either a delete or add button in my detail fragment.
I have a callback method which has everything that I need to repopulate the listview with I just cant seem to get the listview to update.
Any help would be much appreciated.
My callback code is as follows :
public final Handler myCallBackAll = new Handler() {
#Override
public void handleMessage(Message msg) {
ListView lv = (ListView) view.findViewById(R.id.listView1);
if (msg.obj == "No Response") {
TextView tv2 = (TextView) view.findViewById(R.id.textView2);
tv2.setText("No Response. Please check your internet connection");
tv2.setVisibility(View.VISIBLE);
lv.setVisibility(View.INVISIBLE);
} else {
Beer beers = new Beer();
ArrayAdapter<Beer> arrayAdapter = new ArrayAdapter<Beer>(
getActivity(), android.R.layout.simple_list_item_1,(List)msg.obj);
lv.setAdapter(arrayAdapterNew);
arrayAdapter.notifyDataSetChanged();
}
}
};
It doesnt throw any errors just doesnt work
How my callback is called is here
if (isBound) {
myBinder.getAllBeer(myCallBackAll);
}
Its a bounded service I have to use, a stipulation of the project. All this code works, just mylistview wont update
You say that you have already have the callback set up, so I am assuming the callback is being fired accordingly. If that is the case, make sure you update the data source (Array, List, etc) you are using in your adapter to reflect the changes you made in detailfragment.
Also, don't forget that every time you add, edit, delete data from your adapter's data source, you must call mAdapter.notifyDataSetChanged() to tell the adapter something in your data changed.
EDIT
Try creating your handler this way instead: public final Handler myCallBackAll = new Handler(Looper.getMainLooper())
notifyDataSetChanged() must be executed on the main UI thread. When you create a new handler it will execute on its own thread, so you need to make sure it does execute on the UI thread. Take a look at here for more information
finally figured it out, may not be the best approach but it does work, i had declare my listview as a static variable in my main activity, i then reference this variable in both fragments and it works a treat :P
I have standard ListView activity linked to the database:
dbHelper = new DbAdapter(this);
dbHelper.open();
recordsCursor = dbHelper.fetchAllRecords();
startManagingCursor(recordsCursor);
String[] from = new String[]{DbAdapter.KEY_1, DbAdapter.KEY_2};
int[] to = new int[]{R.id.text1, R.id.background};
adapter = new SimpleCursorAdapter(this, R.layout.row, recordsCursor, from, to);
User has an option to update the list manually thru the menu item. It runs the task to get updated date from the server:
new SyncTask(getApplicationContext(), true).execute();
(second parameter indicates if this is manually started synchronization or automatic)
Once synchronization finished (onPostExecute), I would like to update the list (fetchAllRecords, changeCursor etc.) if the same activity is still displayed. How could I understand if this is the case?
Unlike fragments you don't have any methods to check the activity lifecycle, but it's simple to add something similar:
private boolean mResumed;
#Override
protected void onResume() {
mResumed = true;
}
#Override
protected void onPause() {
mResumed = false;
}
private void isResumed() {
return mResumed;
}
Simply call isResumed() when your background task completes to see if the Activity is still visible on the screen.
Well, i don't understand in deep the main problem.
You say before:
" what if user just chosen a item in menu item to refresh the list and then waits for its completion? "
If this happens you can update the listview at the end of the task like you say before "onPostExecute" (Without a problem).
But in the other case you can retrive the state of the activity with methods that return flags i you want or if your using fragments you can getActivity() to do something. Ever when the Activity will be displayed the activity do a 'Resumen' step so like Philio explains before.
Conclusion, you can update the listview automatic in onResumen and you can update it manually in onPostExecute in the syncTask. This method will be called in automatic or manual mode doesn't matter.
I have a ListView which I need to populate using a background thread. The list needs to update as each item is retrieved. Below is a very simplified example of how I implement this.
public class DownloadTask extends AsyncTask <MyUserObject, Integer, String>
{
#Override
protected MyUserObject doInBackground(MyUserObject... myUserObj)
{
MyUserObject muo = null;
int nCount = myUserObj.length;
if( nCount > 0 )
muo = myUserObj[0];
muo.DownloadStuff();
return muo.getUserName();
}
protected void onPostExecute(String userName)
{
adapter.names.add(userName);
adapter.notifyDataSetChanged();
}
}
public class MyAdapterClass extends BaseAdapter
{
private ArrayList<String>names;
public MyAdapterClass(Context context)
{
names = new ArrayList<String>();
}
public fillList()
{
for( int i=0; i<users.length; i++ )
{
DownloadTask task = new DownloadTask();
task.execute(users[i]);
}
}
In the above example, 'adapter' is an object of type MyAdapterClass, and its fillList() method is what launches the threads. Calling notifyDataSetChanged() in onPostExecute() is what updates my ListView as data arrives.
The problem is, that I am accessing my sqlite database in "DownloadStuff()' which is called in 'doInBackground', and having multiple threads accessing the DB causes it to crash. (If I comment out all DB activities in here, then it runs fine). Below is how I try to workaround this problem, however it still crashes. Any advice on how I can have my ListView update as data is retrieved from a background thread?
Semaphore semaphore = new Semaphore(1, true);
public synchronized void DownloadStuff()
{
semaphore.acquire(1);
// ... DB operations ... //
semaphore.release(1);
}
I think your approach is wrong from it's beginning. Why do you want to start separate AsyncTask for each item you have to add to your adapter. Use onProgressUpdate to notify the gui for newly added items in the adapter. In this case you want have concurrent access to your db.
I'm not sure (because I'm really tired) but I think your ot using you synchronysed correctly.
you create a different instance of MyUserObject each time you do a async task, this means you never actually call Downloadstuff on the same instance hence no conflict, but on the other hand your database is unique being called by multiple MyUserObject hence conflict.
what you want to do is have the same instance of muo in all your async task, this way they all call downloadstuff on the same instance and then synchronized will work preventing multiple access.
you also don't need the semaphoe here.
edit:
Mojo Risin answer is also very good, if you can save yourself the trouble by centralizing all you async tasks into one you should(less concurrent threads running around you have the better)