I am using a RecyclerView that show results that come from GCM callbacks. The RecyclerView has a custom adapter a method add, there is also a progress bar that updates using an asynctask.
Message recieving over GCM that works fine:
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
runOnUiThread(new Runnable() {
#Override
public void run() {
mAdapter.add(new ResultRecord("asf", 89, 1000));
}
});
}
};
Add method in the custom adapter:
public void add(final ResultRecord result) {
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
results.add(0, result);
notifyItemInserted(0);
}
});
}
The problem is that the method add called and nothing happens on the UI. The method add called and then onBindViewHolder and the recycler view does not update. Only when the progress bar is finished the RecylcerView is getting update with all the ViewHolders that has been added before.
I have checked if the add method works from the onCreate method and it worked fine. Maybe this problem is related to threading.
You have a Threading problem here.
Your code is based on ArrayList, which isn't Thread-Safe. You are calling the "Add" method from event, which called probably from multiple threads.
You have to synchronize your code. Something like this:
private final ReentrantLock lock = new ReentrantLock();
public void add(final ResultRecord result) {
lock.lock();
try {
AddNotThreadSafe(result); // Only one thread add in same time. Now is safe for executing.
} finally {
lock.unlock();
}
}
Now, move your original Add code to separated method called AddNotThreadSafe.
This should work. :)
Related
I've got game based on CountDownTimer, which is continuously repeating countdowns. This countDown is counting time for user to react on some action related to number, if user reacts onFinish() is called by some clickListener or by itself if the time was up. Depending of succesCondition(), method success or fail is called and those methods are defining if game is still running.
OnCreate
loop = gameLoop(time).start();
MainActivity
public CountDownTimer gameLoop(int time){
return new CountDownTimer(time, time+100) {
#Override
public void onTick(long millisUntilFinished) {
}
#Override
public void onFinish() {
if (!Conditions.succesCondition(number)) {
success();
} else {
fail();
}
}
};
}
public void success() {
loop.cancel();
scoreCount++;
animation.start();
}
public void fail(){
loop.cancel();
}
However this timer runs on Main thread and that provides well known issue skipped xx frames, your app might be doing too much work on its main thread and I found that this is common issue for CountDownTimer and replacing it with Handler is a solution.
I can't put this timer in AsyncTask because it performs mainly UI related tasks (TextViews, TextSwitcher, some progressBar etc. in success() method. I didn't put that in code in those methods for more clean view of the main problem. I'm trying to reconstruct CountDownTimer- like concept with handler and runnable to replace my Timer, but I'm actually stuck with nothing. As you can see I'm using only onFinish method, onTick is not necessary.
I suggest using a combination of java.util.Timer, java.util.TimerTask and Activity.runOnUiThread(). First create a Timer and call one of its schedule...()methods. Any action that needs to be done on the main (ui) thread can be wrapped in runOnUiThread(() -> { ...}). Be sure to call cancel() on TimerTask and Timer if those objects are no longer needed. Cancelling the Timer cancels the TimerTask as well.
Here is how this may look like:
public class TimerTaskActivity extends Activity {
Timer timer;
TimerTask timerTask;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.timertask);
...
}
#Override
protected void onStart() {
super.onStart();
timer = new Timer();
timerTask = new TimerTask() {
#Override
public void run() {
runOnUiThread(() -> {
....
});
}
};
timer.scheduleAtFixedRate(timerTask, 2000, 2000);
}
#Override
protected void onPause() {
super.onPause();
timer.cancel();
}
}
You may handle this situation using AsyncTask as well overriding the onProgressUpdate method.
Here's an example about how you can achieve the behaviour to interact with your main thread from AsyncTask. The example shows the update of a download which can be easily transformed to your specific problem of timer.
Update
In my case almost all code would be in onProgressUpdate, would it
still make any sense?
No, your code will not be in onProgressUpdate. The onProgressUpdate method will only be updating your timer in your UI. As far as I could understand, the success and the fail will be triggered based on user action as well. Then those actions are triggered, you can stop the AsyncTask to update your timer as well. You just need to AsyncTask to update the timer value time to time.
You will get a callback in your Activity when the AsyncTask finishes. See the mNotificationHelper.completed(); function in the above example. When you are notified in your Activity when the timer finishes, you might then execute the following task there.
public void completed() {
if (!Conditions.succesCondition(number)) {
success();
} else {
fail();
}
}
OK. I finally figured out how to handle it with handler (hehe):
public void startGameAction() {
//My game actions
handler = new Handler();
runnable = () -> {
if (!Conditions.succesCondition(number)) {
success();
} else {
fail();
}
};
handler.postDelayed(runnable,time);
}
public void success(){
handler.removeCallbacks(runnable);
handler = null;
scoreCount++;
//other stuff
startGameAction();
}
private void fail() {
handler.removeCallbacks(runnable);
//other stuff
}
onCreate only startGame call, handler and runnable defined as class fields
startGameAction();
Update
My small showcase is stored on Bitbucket
https://bitbucket.org/solvapps/animationtest
I have an Activity with a view in it. Contentview is set to this view.
public class MainActivity extends AppCompatActivity {
private MyView myView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myView = new MyView(this);
setContentView(myView);
startMovie();
}
public void startMovie(){
MovieTask movieTask = new MovieTask(myView, this);
movieTask.doInBackground(null);
}
}
A MovieTask is an Asynctask and refreshes the view periodically.
But invalidate() doesn't refresh the view.
public class MovieTask extends AsyncTask<String, String, String> {
MyView drawingView;
MainActivity mainActivity;
public MovieTask(MyView view, MainActivity mainActivity){
this.mainActivity = mainActivity;
this.drawingView =view;
}
#Override
protected String doInBackground(String... strings) {
for(int i=20;i<100;i++){
drawingView.myBall.goTo(i,i);
publishProgress();
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
#Override
protected void onProgressUpdate(String... values) {
super.onProgressUpdate(values);
mainActivity.runOnUiThread(new Runnable() {
#Override
public void run() {
Log.v("DEBUG_DRAW","in onProgressUpdate()");
drawingView.invalidate();
}
});
}
}
Can someone help ?
See how you are launching the AsyncTask:
public void startMovie() {
MovieTask movieTask = new MovieTask(myView, this);
movieTask.doInBackground(null);
}
You are manually calling a method inside some class called MovieTask, thus you are running a code on the same thread. Obviously, that is not your intention, you intended to run the computation code on a background thread.
Correct way to launch AsyncTask is using execute(Params...):
public void startMovie() {
MovieTask movieTask = new MovieTask(myView, this);
movieTask.execute("");
}
Now you will get the desired effect.
P.S.
Please, do not use that code: you do not need to launch a background thread in order to do that kind of stuff. As an alternative consider Animators API.
Declare setBall(int pos) method inside MyBall class:
public class MyView extends View {
...
public void setBall(int pos) {
myBall.setX(pos);
myBall.setY(pos);
invalidate();
}
}
Then change startMovie() to following:
public void startMovie() {
// "ball" means, that Animators API will search for `public setBall(int)` method inside MyView.java and call that method
ObjectAnimator ball = ObjectAnimator.ofInt(myView, "ball", 20, 100);
ball.setDuration(1000);
ball.start();
}
You'll get the same animation without a nasty code.
There is two possible case, first as described in documents:
void invalidate ()
Invalidate the whole view. If the view is visible,
onDraw(android.graphics.Canvas) will be called at some point in the
future.
So try to run your code in onResume, there is a chance that View is not visible yet.
Secondly View#invalidate tells the system to redraw the view as soon as the main UI thread goes idle. That is, calling invalidate schedules your view to be redrawn after all other immediate work has finished.
If you'd like to have your view updated periodically use Handler#postDelay or run it in a separate thread and use View#postInvalidate to update the View and trigger the call to onDraw.
My problem is pretty simple. I am creating a card based on the result of a HTTP query performed inside a separate thread. The card also has an onclick method and is defined inside a runOnUiThread() located inside the separate thread. However, when the device is tapped, the onclick event isn't fired.
Here is my code:
private void login() {
Runnable r = new Runnable() {
#Override
public void run() {
// irrelevant code
runOnUiThread(new Runnable() {
#Override
public void run() {
setContentView(buildError(code));
}
}
}
Thread t = new Thread(r);
t.start();
}
private View buildError(String code) {
CardBuilder card = new CardBuilder(this, CardBuilder.Layout.ALERT);
card.setIcon(R.drawable.ic_warning_150);
if (code.equals("1"))
card.setText("Incorrect credientals");
else
card.setText("Unexpected error");
card.setFootnote("Tap to try again");
View cView = card.getView();
cView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.i("Event", "Clicked"); // This is what isn't triggering
}
});
cView.setFocusable(true);
cView.setFocusableInTouchMode(true);
return cView;
}
Even though the snippet of code contains an error (can't be compiled, missing ; at the Runnable statement), you were on the right track.
The View simply needs to request the focus in order to be clickable right away. Otherwise you'll have to move the focus manually.
cView.setFocusable(true);
cView.setFocusableInTouchMode(true);
cView.requestFocus();
Reference
I've seen some similar questions and got some information but they stop shy of telling me enough to get it working.
What I'm trying to do is make a simple rhythm game where the player taps a button at regular intervals (ie. beats). I wanted to set up a way of signalling when to tap by having the button change colour, and since this would be a repeated task at regular intervals I want to use a timer object with a schedule method.
But when I try calling on this method it tells me that I can't change the UI in a non UI thread. I've tried a few ways to write a method in the main thread that I can call from the timer object but I get the same error every time. I'm assuming that I just have the wrong idea about what counts as being from the UI thread, so I was hoping someone could clear it up.
Here's a snippet of one way I tried it, just to show what my code looks like:
OnClickListener clickButton = new View.OnClickListener() {
public void onClick(View v) {
if (startBeat == 0){
startBeat = System.nanoTime();
timerStart.scheduleAtFixedRate((new TimerTask()
{
public void run()
{
flashButton();
}
}), 0, beatTime);
timerEnd.schedule(new TimerTask()
{
public void run()
{
unflashButton();
}
}, beatTolerance*2, beatTime);
return;
}
};
public void flashButton(){
beatPrompt.setBackgroundColor(getResources().getColor(R.color.primary1transparent_very));
}
public void unflashButton(){
beatPrompt.setBackgroundColor(getResources().getColor(R.color.primary1));
}
To be clear, this is all contained within my MainActivity class along with the OnCreate class.
if you are in an activity all you need to do is use runOnUiThread() and then place the code to change the ui element in there
public void flashButton(){
runOnUiThread(new Runnable() {
#Override
public void run() {
beatPrompt.setBackgroundColor(getResources().getColor(R.color.primary1transparent_very));
}
});
}
You cannot, under any circumstances, touch a UI object from a non UI thread.
You can accomplish your intent using Handler.sendMessageDelayed
UI can only be touched by the main thread. You should post the actions you are performing on the ui thread via handler or via runOnUiThread
Try something similar to this
timerStart.scheduleAtFixedRate((new TimerTask()
{
public void run()
{
//replace MainActivity with your activity
//if inside a fragment use getActivity()
MainActivity.this.runOnUiThread(new Runnable() {
public void run() {
flashButton();
}
});
}
}), 0, beatTime);
If you are in an Activity you could surround flashButton() with an runOnUiThread.
...
runOnUiThread(new Runnable(){
public void run(){
flashButton();
}
});
...
use android.os.Handler Class. Change your code as follows:
private Handler handler=new Handler();
public void flashButton(){
handler.post(new Runnable(){
public void run(){
beatPrompt.setBackgroundColor(getResources().getColor(R.color.primary1transparent_very));
}
});
}
public void unflashButton(){
handler.post(new Runnable(){
public void run(){
beatPrompt.setBackgroundColor(getResources().getColor(R.color.primary1));
}
});
}
clipProgress resets a progress bar to zero:
public class Whatever extends Activity implements ListenerA, ListenerB {
private Handler mHandler = new Handler();
ProgressBar myProgressBar;
int myProgress = 0;
public void clipProgress() {
myProgressBar = (ProgressBar)findViewById(R.id.progressbar);
myProgressBar.setProgress(myProgress);
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
clipProgress();
public void onClick(View view) {
Duration.duration = 0;
startRecording();
new Thread(new Runnable(){
#Override
public void run() {
while(myProgress<100){
try{
myHandle.sendMessage(myHandle.obtainMessage());
Thread.sleep(50);
}catch(Throwable t){
}
}
}
}).start();
}
Handler myHandle = new Handler(){
#Override
public void handleMessage(Message msg) {
myProgress++;
myProgressBar.setProgress(myProgress);
}
};
});
}
I have two different methods, below, where I suppose I could attempt to reset the progress bar to zero. But they both reside in a different class entirely:
#Override protected void onPostExecute(Void result) {
Whatever.clipProgress(); //...which throws all kinds of red lines if I use this statement.
}
and
public void stopThis(){
thing.stop();
try {
Whatever.clipProgress(); //...which throws all kinds of red lines if I use this statement.
dos.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Eclipse wants to apply "static" to various initializations in the Whatever class. But those fixes just leave findViewById(R.id.progressbar) redlined.
I just want to reset the progress bar (which was started from within class 1) from class 2. It's too much to ask for.
UPDATE:
Whatever is the starting point, the Android Activity. Its handler, which I should have included in the above snip, right underneath onCreate is included in the snip below. So more completely, and invoked from a button, is thus:
public void onClick(View view) {
new Thread(new Runnable(){
#Override
public void run() {
while(myProgress<100){
try{
myHandle.sendMessage(myHandle.obtainMessage());
Thread.sleep(87);
}catch(Throwable t){
}
}
}
}).start();
}
Handler myHandle = new Handler(){
#Override
public void handleMessage(Message msg) {
myProgress++;
myProgressBar.setProgress(myProgress);
}
};
Once that button is pressed a listener is produced:
myTask = new Task();
myTask.addTaskListener(this);
myTask.execute(this);
It is inside the Task class:
public class Task extends AsyncTask<Whatever, ArrayList<Float>, Void>{
...
public void addTaskListener(TaskListener rl){
this.rl = rl;
}
...
that those two functions reside, one of which I'm hoping to use to stop the progress bar.
A task is designed elsewhere to shut down at exactly a constant number of seconds, and so I can hardcode 50 into that progress bar value on the sleep(). I just want to be able to shut the bar down back to zero earlier if I stop the task earlier.
Eclipse warns you because you are accessing the clipProgress method in a static way (by calling Whatever.clipProgress()).
Also, you should only assign your progress bar once, so move
myProgressBar = (ProgressBar)findViewById(R.id.progressbar);
into your onCreate method.
You should probably pass the ProgressBar into the other class that needs to update it, and then do something like
myProgressBar.post(new Runnable() {
public void run() {
myProgressBar.setProgress(myProgress);
}
});
this post method updates the ProgressBar on the UI thread, which is the only safe way to do it. Make sure you are only doing any of this if the activity with the ProgressBar is currently active.
You should also question whether you need to update the progress bar in this other class at all. I only see you updating the myProgress variable inside your handler, where you set the progress on the bar immediately after. Could you explain a bit more about the relationship between this other class and the activity with your progress bar?
This has more to do with Java programming and instance / static methods than with Android:
When you call Whatever.clipProgress() you're attempting to make a call to a static method call to clipProgress on the Whatever class, which is why Eclipse wants to add the static modifier.
However, findViewById() is an instance method (read the link above) on Activity, which is presumably some ancestor of your Whatever class. Static methods can't call instance methods statically, so even if you let Eclipse make clipProgress static, you need to call clipProgress on an instance of your activity.
Your other class needs to have a reference to the instance of the class on which you want to call clipProgress(). You should either pass in that reference to the other class, or as another poster noted, pass in a reference to the ProgressBar itself.
Then you can call e.g. mWhatever.clipProgress() or progressBar.setProgress(0).