notifyDataSetChanged only refreshes GridView once, despite being called multiple times - android

I will attempt to summarize my code as follows:
I have a TileAdapter which extends from BaseAdapter to populate a GridView
My TileAdapter contains an array of Tiles that is used to maintain state
My TileUpdate class performs the following operations:
Changes Tile A's colour to green
Calls TileAdapter.notifyDataSetChanged()
Changes Tile B's colour to yellow
Calls TileAdapter.notifyDataSetChanged()
Changes Tile B's colour to red
Calls TileAdapter.notifyDataSetChanged()
I would have expected to see my GridView refresh 3 times, once for each call to notifyDataSetChanged().
However, I only see it refresh after step 6, when and never see Tile B turn yellow.
What is happening here? I presume there is some part of the API I'm not aware of.
Thanks
Update
I still have not been successful in implementing this, even after studying the Handler approaches described below.
So in my UI thread I've created a new GridOperationQueue class which extends from Thread:
public class GridOperationQueue extends Thread {
private Handler handler;
#Override
public void run() {
Looper.prepare();
handler = new Handler();
Looper.loop();
}
public void addTaskToQueue(final GridUpdateTask task)
{
Log.d(this.getClass().toString(), "Current thread is ID " + Thread.currentThread().getId());
handler.post(new Runnable()
{
#Override
public void run() {
Log.d(this.getClass().toString(), " Handler task's current thread is ID " + Thread.currentThread().getId());
task.run();
}
});
}
public void clearQueue(){
handler.removeCallbacksAndMessages(null);
}
}
So my UI thread invokes addTaskToQueue providing a new Task object. The idea is that the task's processing will be performed on a separate thread and after it completes invokes a notifyDataSetChanged() on the UI thread.
However, I've added some logging and it seems that when my tasks run they are still running on the main thread....how would this be? See the following logging:
GridOperationQueue(4393): Current thread is ID 1
GridOperationQueue(4393): Current thread is ID 1
GridOperationQueue(4393): Current thread is ID 1
GridOperationQueue$1(4393): Handler task's current thread is ID 9
TileOperationService$1(4393): Current thread is ID 9
MyActivity(4393): Current thread is ID 9
GridOperationQueue$1(4393): Handler task's current thread is ID 9
TileOperationService$2(4393): Current thread is ID 9
MyActivity(4393): Current thread is ID 9
GridOperationQueue$1(4393): Handler task's current thread is ID 9
TileOperationService$3(4393): Current thread is ID 9
MyActivity(4393): Current thread is ID 9
How come the tasks are still running on the main thread?

I assume you're making all of those changes in your onClick method. The onClick method runs in the UI thread... so, while the code in onClick is running, the UI thread cannot change and tile colors since it's busy. in fact, notifyDataSetChanged simply sets a flag that tells Android to update the changes to the view whenever it can; notifyDataSetChanged does not force an update, but simple tells android one is needed. Thus, you are simply telling android it needs to update the view three times... but, by the time android can actually make the update, which is after your onClick method is done, it can only see the most recent change to the tile color.
How do you get around this? Well, it depends on what you really want to do. for instance, if you want tile color A to change to tile color B when you click the view, and then change to tile color C 500 ms later, do something like this
Handler handler; // instance var
public onCreate(Bundle savedInstanceState) {
...
handler = new Handler();
}
// in your onClick method, wherever it may be (pseudocode)
public void onClick(View v) {
1) set tile color to color B
2) call notifyDataSetChanged
3) schedule a color change in 500 ms:
handler.postDelayed(new Runnable() {
public void run() {
1) set tile color to color C
2) call notifyDataSetChanged
}
}), 500);

You should take a look at Updating the UI from a Timer. The trouble is that when you call notifyDataSetChanged() you only trigger a flag to the GUI. When the adapter gets a repaint it checks if the flag is set and takes action. It does not take any action directly when you call notify.
If you want to update the GUI directly you should learn about the class Handler (see the previous link) so you can post updates to the interface.

Maybe I misunderstood your question, but it seems that you are updating twice the color of Tile B (identical steps 3 and 5 on your question). If that's the case, you will only see the last color (red) and yellow will never be displayed, of course.
Or, you meant "Tile C" on step 5. If this is the case, then the following comments might help:
Even if the UI is changing 3 times, you are probably overriding each color on each call.
1) So your three tiles are 'default' color
2) You update Tile A to green
3) notifyDataSetChanged() is called
(your grid view is updated correctly)
4) You update Tile B to yellow
5) notifyDataSetChanged() is called
(your grid view updates Title B to yellow, but overrides Title A to 'default' color.
6) You update Tile C to red
7) notifyDataSetChanged() is called
(your grid view updates Title C to red, but overrides Title A and B to 'default' color.
That could be the issue.
Also, remember as other guys have mentioned, notifyDataSetChanged() does nothing directly. It only signals the UI to be refreshed when the ideal conditions are met.

Related

Android: Output of thread on UI after it finishes its execution

Basically I have 2 tabs.
In which first tab contains the data, and the second one contains some summary kind of data based on first tab's data.
In the first tab there is list of events. Whenever the there is an add/update/delete operation on events the summary needs to recalculated. The recalculated summary creates the list of rows in the database. And from those rows the summary tab is displayed.
So the problem is summary calculation takes long amount of time. So it is blocking the UI thread. So I moved the calculations in the thread for smooth UI operations.
Now as summary calculation is running in thread, I don't know when the calculation is complete and now I need to update the summary tab data and showing something while calculation is going on.
The current setup which I have is something like this:
My DatabaseHelper Class:
add() {
...
// Do Add operation
...
reCalculateSummary();
}
remove() {
...
// Do remove operation
...
reCalculateSummary();
}
reCalculateSummary() {
new Thread(new Runnable() {
#Override
public void run() {
...
// calculate summary (may call another method)
...
// Add calculated summary to DB
}
}).start();
}
While the reCalculateSummary() is running, the summary to tab should show loading message.
So How can I achieve this whole situation working?
How can I know if thread has completed its execution? So that I can update summary tab data by retrieving new db values of summary data.
So here is some confusing situation. Please feel free to ask if you have any doubts in understanding the situation.
Thanks in advance!
Use an event bus, so you can update the UI, and avoid the limitation on AsyncTasks.
You can create the event "recalculateSummary", and just before job start (think of it as onPreExecute), update the UI with something that shows the user that the information on screen is not up-to-date, and update results when job is done.
Examples on event buses are Otto, EventBus, AutoBus ...
You can use an AsyncTask.
Do the processing in the doInBackground(..) method, and update the UI inside onPostExcecute(..) method

Android GridView and threading wait

I have a method playSoE() that performs an operation on each view and updates the ArrayAdapter and thus GridView after each operation. I want to do is have the code wait for 1 second after the update is done, and then perform the operation on the next view. For example:
public class MainActivity extends Activity{
public void playSoE(){
for(int i = 0; i < primesLE; i++) //iterates to each view
mAdapter.setPositionColor(index, 0xffff0000 + 0x100 * index); //operation performed on view
mAdapter.notifyDataSetChanged(); //updates adapter/GridView
//Code that waits for one second here
}
}
I have tried many threading APIs but none of them seem to work, they've either froze up the application for primesLE seconds and shown all the operations at the end, skip the second wait and just performed all the operations at once, or gotten an Exception related to concurrency.
Assuming the update itself is quick, you should not be creating additional threads at all. Create a Handler and use postDelayed(...) to time your updates in the UI thread.
You may have to take a look at AsyncTask.
Put the wait code at the doInBackground() and then the following that affects the visual on the onPostExecute()
All you do on doInBackground() will not freeze your application.

Posting a runnable to a View that invalidates the View sometimes doesn't work

I been fighting an odd issue these last few days. I have a custom ExpandableListAdapter where each row contains an ImageView, among other things. I have a class that handles the asynchronous loading of images from the multitude of places they may reside (disk cache, app data, remote server, etc). In my adapter's getView method I delegate the responsibility of returning a View to the list Item itself (I have multiple row types for my group list). I request the image load as follows:
final ImageView thumb = holder.thumb;
holder.token = mFetcher.fetchThumb(mImage.id, new BitmapFetcher.Callback() {
#Override
public void onBitmap(final Bitmap b) {
thumb.post(new Runnable() {
#Override
public void run() {
thumb.setImageBitmap(b);
}
});
}
#Override
public void onFailure() {
}
});
Yeah, it's ugly, but I decided against some contract where you have the BitmapFetcher.Callback execute its methods on the UI thread by default.
Anyway, when I load the Activity that contains the ExpandableListView there will often be thumb images missing from different rows in the list. Reloading the Activity may cause some of the missing thumbs to show but others that were previously showing may not be anymore. The behavior is pretty random as far as I can tell. Scrolling the ListView such that the rows with missing images get recycled causes the new thumb images (when the recycled row gets displayed again) to load fine. Scrolling back to rows that previously contained missing images causes the missing images to appear. I can confirm that all the images are loading correctly from my BitmapFetcher (mFetcher) class. I should also mention that I load other images in other places. Every once in awhile they don't appear either.
After pulling most of my hair out, I discovered that changing:
thumb.post(new Runnable() {
to:
mExpListView.post(new Runnable() {
fixes the issue. I originally thought that the issue might be happening because I was using a final reference to a View, but the other locations in the app use non-final references to a view to post messages, and, as I mentioned, sometimes those did not work. I eventually changed everything to use an Activity's runOnUiThread() method (and my own getUiThreadRunner().execute method when inside Fragments) and that seems to fix the issue all around.
So my question remains, in what cases can View.post() to fail to deliver the runnable to the associated ViewRoot's message queue in the proper order? Or, perhaps the invalidate() is happening before the View is returned from getView and thus before it's placed in a ViewGroup that can be reached from the root View. Those are really the only cases I can think of that would prevent the image from showing up. I can guarantee that none of these calls are happening until at least onStart has finished executing. Further, it looks like it's fine to post to a View even if it hasn't been attached to a Window yet:
// Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(attachInfo.mHandler);
(in performTraversal). The only difference between the runOnUiThread and post seems to be that an Activity has a different Handler than the ViewRootImpl.
Activity:
final Handler mHandler = new Handler();
whereas in ViewRootImpl:
final ViewRootHandler handler = new ViewRootHandler();
But, this should not be a problem provided both Handlers were constructed in the same Thread (or using the same Looper). That leaves me wondering if it is, indeed, a problem to invalidate() a View that has not yet been added to the hierarchy. For this to be the case invalidate should either 1. not do anything if it's not visible, or 2. only be valid for the next performTraversal() that happens.
View.invalidate() checks a nice private method that's not documented called skipInvalidate():
/**
* Do not invalidate views which are not visible and which are not running an animation. They
* will not get drawn and they should not set dirty flags as if they will be drawn
*/
private boolean skipInvalidate() {
return (mViewFlags & VISIBILITY_MASK) != VISIBLE && mCurrentAnimation == null &&
(!(mParent instanceof ViewGroup) ||
!((ViewGroup) mParent).isViewTransitioning(this));
}
It looks like number 1 is more accurate! However, I would think this only pertains to a View's VISIBILITY property. So, is it accurate to assume that a View is considered not VISIBLE if it cannot be reached from the ViewRoot? Or is the VISIBILITY property unaffected by the View's container? If the former is the case (which I suspect it is) it raises a concern. My use of Activity.runOnUiThread is not a solution to the problem. It only happens to work because the invalidate() calls are being sent to a different Handler and being executed later (after getView returns and after the row has been added and made visible on the screen). Has anybody else run into this issue? Is there a good solution?
Hey David I ran into a similar issue long time back. The basic requirement for view.post(Runnable r) is that the view should be attached to the window for Runnable to be executed. However, since you are loading images asynchronously in your first case, therefore there is a probability that imageView aren't attached to window when post request is made and hence, some images fail to load.
Quoting earlier version of docs on the same:
View.post() : Causes the Runnable to be added to the message queue. The runnable will be run on the user interface thread. This method can
be invoked from outside of the UI thread only when this View is
attached to a window.
Switching to you next question, what is the best solution to handle this situation ?
Can't comment on the best solution. However, I think both handler.post() and activity.runOnUIThread() are good to go. Since, they basically post runnable in main thread queue irrespective of anything and in general, the request to display list rows would be enqueued prior to our thumb.post(). So, they might work flawlessly for most cases. (Atleast I've never faced a problem with them !). However. if you find a better solution, do share it with me.
Try this : setBitmap() like this :
runOnUiThread(new Runnable() {
#Override
public void run() {
thumb.setImageBitmap(b);
}
});

After loading an activity, it sometimes works, sometimes not, why?

I have a bit of a problem I cannot solve, since it might a bug or something like that. I would like to make a chart with androidplot, and it works really good, but of course it needs some time to draw it (using hundreds of data), so I use a progress dialog to indicate the loading. But what happens is really weird.
I can define the appearance of the activity when it's loading and when it's loaded. When its loading I define a textview in the background setting its text to "loading" and if it is loaded, that textview contains lots of datas, text etc.
onCreate
{
Thread thread = new Thread(new Runnable() {
public void run() {
-------what needs to be appeared after its loaded ----
Textview -> 12,3245,456,78,789
}
----what is on the screen while the progressbar is on---
TextView -> loading..
}
But most of the time after the progress dialog disappears, nothing happens, the textview still says "loading" and rarely it loads the datas and makes the data appear and changes the textview. What I noticed that the more data I would like to appear the rarelier it stays at the loading phase. Of course everytime the loading progessbar appeers then disappears.
Any Suggestion? It is really weird because I use it in a tablayout and other tabs never do this, always make the data appear.
Thanks in advance!
UP: Okay, its always the first tab, whatever it contains, so the first tab is somehow wrong...
The Andoid UI toolkit is not thread-safe. So, you must not manipulate your UI
from a worker thread—you must do all manipulation to your user interface from
the UI thread. Thus, there are simply two rules to Android's single thread model:
1. Do not block the UI thread
2. Do not access the Android UI toolkit from outside the UI thread
read this for more information on how to access UI elements from outside.
edit::
use AsyncTask ::
onCreate
{
new myLoading().execute();
}
class myLoading extends AsyncTask<Void, Void, Void>
{
protected Void doInBackground(Void ... ) {
.......... do all the loading here .........
}
protected void onPostExecute(Void ) {
Textview -> 12,3245,456,78,789
}
}
I figured out some workaround. I dont have any clue about the solution, my only guess is the loading screen somehow overtake the arrival of all the data and so the chart cannot be drawn. or whatever...
I put a thread.sleep(1000) and now it works.

Canvas do not update (invalidate) until whole while loop ends

I'm trying to move a ball on canvas. a and b are similar to x,y coordinate positions. Any way from my code im trying to get different values dynamically. The a,b are global variables. But it seems that "invalidate()" or the refreshing of screen only happens afer end of the whole loop. Do you know why?. And if i have to build this on another thread please suggest me with some simple codes.
private void shootBall(){
while (a>b){
a = getPositionX();
b = getPositionY();
invalidate();
}
}
}
I think it's more correct to say that you can call invalidate() from within a loop, but that that invalidation will not be handled (the canvas won't be redrawn) until after your loop is complete. The problem is that you are calling invalidate on the same thread (the UI toolkit thread) as the one that would call your onDraw() method. So unless/until you hand control back to the toolkit, it cannot possibly do the rendering. So your invalidate() call does actually invalidate the view ... but the view won't be redrawn until after your loop completes and your function returns.
It is more correct to change the position in some function that is called via some timer (which is essentially what the animation classes do). In that function, you would change the values, invalidate() appropriately, and return. Then the toolkit re-renders the scene and your function will get future callbacks and update the position accordingly.
do it like this, and use postInvalidate() instead:
private void shootBall(){
new Thread(new Runnable() {
public void run() {
while (a>b){
a = getPositionX();
b = getPositionY();
postInvalidate();
}
}
}).start();
}
edit: but as mentioned before, don't assume that the invalidate redraws the screen, it marks it as to-be-redrawn and the UI thread will get around to it.
You can put invalidate() at the end of onDraw() like in this example: How can I use the animation framework inside the canvas?
However this works well on some devices while bottlenecks and slows down on other.
To use a thread and SurfaceView go through all of these tutorials: http://www.droidnova.com/playing-with-graphics-in-android-part-i,147.html
UI cant be modified form any new thread..you should use invalidate() in the same thread where your view

Categories

Resources