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.
Related
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)
I'm looking for the best implementation pattern in Android to update a list when one of the elements change in a different activity.
Imagine this user journey:
An async process fetches ten (10) contact profiles from a web server. These are placed in an array and an adapter is notified. The ten (10) contact profiles are now displayed in a list.
The user clicks on contact profile five (5). It opens up an activity with details of this contact profile. The user decides they like it and clicks 'add to favourite'. This triggers an async request to the web server that the user has favourited contact profile five (5).
The user clicks back. They are now presented again with the list. The problem is the list is outdated now and doesn't show that profile five (5) is favourited.
Do you:
Async call the web server for the updated data and notify the adapter to refresh the entire list. This seems inefficient as the call for the list can take a couple of seconds.
On favouriting the profile store the object somewhere (perhaps in a singleton service) marked for 'refresh'. OnResume in the List activity do you sniff the variable and update just that element in the list.
Ensure the list array is static available. Update the array from the detail activity. OnResume in the activity always notify the adapter for a refresh.
Ensure the list array and adapter is static available. Update the array and notify the adapter from the detail activity.
Any other options? What is the best design principle for this?
Async call the web server for the updated data and notify the adapter
to refresh the entire list. This seems inefficient as the call for the
list can take a couple of seconds.
As you say, it's very inefficient. Creating an Object is expensive in Android. Creating a List of many object is much more expensive.
On favouriting the profile store the object somewhere (perhaps in a
singleton service) marked for 'refresh'. OnResume in the List activity
do you sniff the variable and update just that element in the list.
This is not a good solution because there is a probability that the app crashes before we refresh the object or the app get killed by the device.
Ensure the list array is static available. Update the array from the
detail activity. OnResume in the activity always notify the adapter
for a refresh.
Updating the array via a static method or variable is not a good solution because it makes your detail Activity get coupled with the list. Also, you can't make sure that only the detail activity that change the list if your project get bigger.
Ensure the list array and adapter is static available. Update the
array and notify the adapter from the detail activity.
Same as the above, static variable or object is a no go.
You better use an Event Bus system like EventBus.
Whenever you clicks 'add to favourite' in detail activity, send the async request to update favourite to the web server and also send Event to the list activity to update the specific profile object. For example, if your profile has id "777" and the profile is favourited in detail activity then you need to send the Event something like this in your :
btnFavourite.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Send event when click favourite.
EventBus.getDefault.post(new RefreshProfileEvent(id, true);
}
});
RefreshProfileEvent is a simple pojo:
public class RefreshProfileEvent {
private String id;
private boolean isFavourited;
public RefreshProfileEvent(String id, boolean isFavourited) {
this.id = id;
this.isFavourited = isFavourited;
}
//getter and setter
}
Then you can receive the Event in your list activity to update the selected profile:
public class YourListActivity {
...
#Override
protected onCreate() {
...
EventBus.getDefault().register(this);
}
#Override
protected onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
#Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(RefreshProfileEvent event) {
// Refresh specific profile
// For example, your profile is saved in List<Profile> mProfiles
// Search for profile by its id.
for(int i = 0; i < mProfiles.size(); i++) {
if(mProfiles.getId().equals(event.getId()) {
// Refresh the profile in the adapter.
// I assume the adapter is RecyclerView adapter named mAdapter
mProfiles.get(i).isFavourited(true);
mAdapter.notifyItemChanged(i);
// Stop searching.
break;
}
}
}
You don't need to wait for AsyncTask request result returned by the server. Just make the profile favourited first and silently waiting for the result. If your request success, don't do anything. But if the request error, make the profile unfavourited and send unobstructive message like SnackBar to inform the user.
Third option is the best when a user changes the data in detail activity the array should be changed and then when the use returns to main activity call Adapter.notifyDataSetChanged(); will do the trick
For an ArrayAdapter , notifyDataSetChanged only works if you use the add() , insert() , remove() , and clear() on the Adapter.
You can do something like this:
#Override
protected void onResume() {
super.onResume();
Refresh();
}
public void Refresh(){
items = //response....
CustomAdapter adapter = new CustomAdapter(MainActivity.this,items);
list.setAdapter(adapter);
}
On every onResume activity it will refresh the list. Hope it helps you.
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 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)
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