Reputation: 16187
I've built a simple music player in Android. The view for each song contains a SeekBar, implemented like this:
public class Song extends Activity implements OnClickListener,Runnable {
private SeekBar progress;
private MediaPlayer mp;
// ...
private ServiceConnection onService = new ServiceConnection() {
public void onServiceConnected(ComponentName className,
IBinder rawBinder) {
appService = ((MPService.LocalBinder)rawBinder).getService(); // service that handles the MediaPlayer
progress.setVisibility(SeekBar.VISIBLE);
progress.setProgress(0);
mp = appService.getMP();
appService.playSong(title);
progress.setMax(mp.getDuration());
new Thread(Song.this).start();
}
public void onServiceDisconnected(ComponentName classname) {
appService = null;
}
};
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.song);
// ...
progress = (SeekBar) findViewById(R.id.progress);
// ...
}
public void run() {
int pos = 0;
int total = mp.getDuration();
while (mp != null && pos<total) {
try {
Thread.sleep(1000);
pos = appService.getSongPosition();
} catch (InterruptedException e) {
return;
} catch (Exception e) {
return;
}
progress.setProgress(pos);
}
}
This works fine. Now I want a timer counting the seconds/minutes of the progress of the song. So I put a TextView
in the layout, get it with findViewById()
in onCreate()
, and put this in run()
after progress.setProgress(pos)
:
String time = String.format("%d:%d",
TimeUnit.MILLISECONDS.toMinutes(pos),
TimeUnit.MILLISECONDS.toSeconds(pos),
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(
pos))
);
currentTime.setText(time); // currentTime = (TextView) findViewById(R.id.current_time);
But that last line gives me the exception:
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
Yet I'm doing basically the same thing here as I'm doing with the SeekBar
- creating the view in onCreate
, then touching it in run()
- and it doesn't give me this complaint.
Upvotes: 1209
Views: 832586
Reputation: 24
if your are using the coroutines for and doing in the IO thread and then where ever are post the UI then use
Dispatcher.Main to update the thread.
withContext(Dispatcher.Main){
//update the UI
}
Upvotes: 0
Reputation: 29
If you are using Kotlin Coroutine, please try this code:
GlobalScope.launch {
//Write a background operation
withContext(Dispatchers.Main) {
// write a UI operation
}
`enter code here`}
Upvotes: 3
Reputation: 1246
Kotlin coroutines can make your code more concise and readable like this:
MainScope().launch {
withContext(Dispatchers.Default) {
//TODO("Background processing...")
}
TODO("Update UI here!")
}
Or vice versa:
GlobalScope.launch {
//TODO("Background processing...")
withContext(Dispatchers.Main) {
// TODO("Update UI here!")
}
TODO("Continue background processing...")
}
Consider carefully when using GlobalScope. If possible, create a new scope so you are able to cancel it, for ex.:
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
Upvotes: 52
Reputation: 47
'Esse é meu codigo'
Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
KeyValuePair<String, String> keyValuePair = getCalculateDifferenceBetweenTwoTimes();
CharSequence charSequence = getPresenterUtil().contact(
getPresenterUtil().messageStyle("Check-in", Color.GRAY, Typeface.NORMAL, 0.9f),
UtilPresenter.LINE,
getPresenterUtil().messageStyle(keyValuePair.getKey(), Color.BLACK, Typeface.NORMAL, 1.3f),
UtilPresenter.LINE,
UtilPresenter.LINE,
getPresenterUtil().messageStyle("Tempo de execução", Color.GRAY, Typeface.NORMAL, 0.9f),
UtilPresenter.LINE,
getPresenterUtil().messageStyle(keyValuePair.getValue(), Color.BLACK, Typeface.NORMAL, 1.3f)
);
if (materialDialog.getContentView() != null) {
materialDialog.getContentView().setText(charSequence);
}
}
});
}
}, 0, 1000);
Upvotes: 0
Reputation: 1706
For anyone using fragment:
(context as Activity).runOnUiThread {
//TODO
}
Upvotes: 7
Reputation: 7528
Kotlin Answer
We have to use UI Thread for the job with true way. We can use UI Thread in Kotlin, such as:
runOnUiThread(Runnable {
//TODO: Your job is here..!
})
Upvotes: 28
Reputation: 1367
For a one-liner version of the runOnUiThread()
approach, you can use a lambda function, i.e.:
runOnUiThread(() -> doStuff(Object, myValue));
where doStuff()
can represents some method used to modify the value of some UI Object (setting text, changing colors, etc.).
I find this to be much neater when trying to update several UI objects without the need for a 6 line Runnable definition at each as mentioned in the most upvoted answer, which is by no means incorrect, it just takes up a lot more space and I find to be less readable.
So this:
runOnUiThread(new Runnable() {
@Override
public void run() {
doStuff(myTextView, "myNewText");
}
});
can become this:
runOnUiThread(() -> doStuff(myTextView, "myNewText"));
where the definition of doStuff lies elsewhere.
Or if you don't need to be so generalizable, and just need to set the text of a TextView object:
runOnUiThread(() -> myTextView.setText("myNewText"));
Upvotes: 7
Reputation: 740
You can use Handler to Delete View without disturbing the main UI Thread. Here is example code
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
//do stuff like remove view etc
adapter.remove(selecteditem);
}
});
Upvotes: 27
Reputation: 2056
Well, You can do it like this.
https://developer.android.com/reference/android/view/View#post(java.lang.Runnable)
A simple approach
currentTime.post(new Runnable(){
@Override
public void run() {
currentTime.setText(time);
}
}
it also provides delay
https://developer.android.com/reference/android/view/View#postDelayed(java.lang.Runnable,%20long)
Upvotes: 2
Reputation: 474
RunOnUIThread didn't seem to work for me but the following ended up solving my issues.
_ = MainThread.InvokeOnMainThreadAsync(async () =>
{
this.LoadApplication(Startup.Init(this.ConfigureServices));
var authenticationService = App.ServiceProvider.GetService<AuthenticationService>();
if (authenticationService.AuthenticationResult == null)
{
await authenticationService.AuthenticateAsync(AuthenticationUserFlow.SignUpSignIn, CrossCurrentActivity.Current.Activity).ConfigureAwait(false);
}
});
Within the Startup.Init method there is ReactiveUI routing and this needs to be invoked on the main thread. This Invoke method also accepts async/await better than RunOnUIThread.
So anywhere I need to invoke methods on the mainthread I use this.
Please comment on this if anyone knows something I don't and can help me improve my application.
Upvotes: 0
Reputation: 25491
If you are within a fragment, then you also need to get the activity object as runOnUIThread is a method on the activity.
An example in Kotlin with some surrounding context to make it clearer - this example is navigating from a camera fragment to a gallery fragment:
// Setup image capture listener which is triggered after photo has been taken
imageCapture.takePicture(
outputOptions, cameraExecutor, object : ImageCapture.OnImageSavedCallback {
override fun onError(exc: ImageCaptureException) {
Log.e(TAG, "Photo capture failed: ${exc.message}", exc)
}
override fun onImageSaved(output: ImageCapture.OutputFileResults) {
val savedUri = output.savedUri ?: Uri.fromFile(photoFile)
Log.d(TAG, "Photo capture succeeded: $savedUri")
//Do whatever work you do when image is saved
//Now ask navigator to move to new tab - as this
//updates UI do on the UI thread
activity?.runOnUiThread( {
Navigation.findNavController(
requireActivity(), R.id.fragment_container
).navigate(CameraFragmentDirections
.actionCameraToGallery(outputDirectory.absolutePath))
})
Upvotes: 1
Reputation: 494
In Kotlin simply put your code in runOnUiThread activity method
runOnUiThread{
// write your code here, for example
val task = Runnable {
Handler().postDelayed({
var smzHtcList = mDb?.smzHtcReferralDao()?.getAll()
tv_showSmzHtcList.text = smzHtcList.toString()
}, 10)
}
mDbWorkerThread.postTask(task)
}
Upvotes: 1
Reputation: 2216
If you couldn't find a UIThread you can use this way .
yourcurrentcontext mean, you need to parse Current Context
new Thread(new Runnable() {
public void run() {
while (true) {
(Activity) yourcurrentcontext).runOnUiThread(new Runnable() {
public void run() {
Log.d("Thread Log","I am from UI Thread");
}
});
try {
Thread.sleep(1000);
} catch (Exception ex) {
}
}
}
}).start();
Upvotes: 1
Reputation: 538
For the people struggling in Kotlin, it works like this:
lateinit var runnable: Runnable //global variable
runOnUiThread { //Lambda
runnable = Runnable {
//do something here
runDelayedHandler(5000)
}
}
runnable.run()
//you need to keep the handler outside the runnable body to work in kotlin
fun runDelayedHandler(timeToWait: Long) {
//Keep it running
val handler = Handler()
handler.postDelayed(runnable, timeToWait)
}
Upvotes: 1
Reputation: 7813
In my case, the caller calls too many times in short time will get this error, I simply put elapsed time checking to do nothing if too short, e.g. ignore if function get called less than 0.5 second:
private long mLastClickTime = 0;
public boolean foo() {
if ( (SystemClock.elapsedRealtime() - mLastClickTime) < 500) {
return false;
}
mLastClickTime = SystemClock.elapsedRealtime();
//... do ui update
}
Upvotes: 0
Reputation: 145
If you simply want to invalidate (call repaint/redraw function) from your non UI Thread, use postInvalidate()
myView.postInvalidate();
This will post an invalidate request on the UI-thread.
For more information : what-does-postinvalidate-do
Upvotes: 2
Reputation: 3700
I was facing a similar problem and none of the methods mentioned above worked for me. In the end, this did the trick for me:
Device.BeginInvokeOnMainThread(() =>
{
myMethod();
});
I found this gem here.
Upvotes: 8
Reputation: 1243
Solved : Just put this method in doInBackround Class... and pass the message
public void setProgressText(final String progressText){
Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// Any UI task, example
progressDialog.setMessage(progressText);
}
};
handler.sendEmptyMessage(1);
}
Upvotes: 0
Reputation: 574
When using AsyncTask Update the UI in onPostExecute method
@Override
protected void onPostExecute(String s) {
// Update UI here
}
Upvotes: 4
Reputation: 454
I've been in this situation, but I found a solution with the Handler Object.
In my case, I want to update a ProgressDialog with the observer pattern. My view implements observer and overrides the update method.
So, my main thread create the view and another thread call the update method that update the ProgressDialop and....:
Only the original thread that created a view hierarchy can touch its views.
It's possible to solve the problem with the Handler Object.
Below, different parts of my code:
public class ViewExecution extends Activity implements Observer{
static final int PROGRESS_DIALOG = 0;
ProgressDialog progressDialog;
int currentNumber;
public void onCreate(Bundle savedInstanceState) {
currentNumber = 0;
final Button launchPolicyButton = ((Button) this.findViewById(R.id.launchButton));
launchPolicyButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
showDialog(PROGRESS_DIALOG);
}
});
}
@Override
protected Dialog onCreateDialog(int id) {
switch(id) {
case PROGRESS_DIALOG:
progressDialog = new ProgressDialog(this);
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
progressDialog.setMessage("Loading");
progressDialog.setCancelable(true);
return progressDialog;
default:
return null;
}
}
@Override
protected void onPrepareDialog(int id, Dialog dialog) {
switch(id) {
case PROGRESS_DIALOG:
progressDialog.setProgress(0);
}
}
// Define the Handler that receives messages from the thread and update the progress
final Handler handler = new Handler() {
public void handleMessage(Message msg) {
int current = msg.arg1;
progressDialog.setProgress(current);
if (current >= 100){
removeDialog (PROGRESS_DIALOG);
}
}
};
// The method called by the observer (the second thread)
@Override
public void update(Observable obs, Object arg1) {
Message msg = handler.obtainMessage();
msg.arg1 = ++currentPluginNumber;
handler.sendMessage(msg);
}
}
This explanation can be found on this page, and you must read the "Example ProgressDialog with a second thread".
Upvotes: 22
Reputation: 1595
I was working with a class that did not contain a reference to the context. So it was not possible for me to use runOnUIThread();
I used view.post();
and it was solved.
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
final int currentPosition = mediaPlayer.getCurrentPosition();
audioMessage.seekBar.setProgress(currentPosition / 1000);
audioMessage.tvPlayDuration.post(new Runnable() {
@Override
public void run() {
audioMessage.tvPlayDuration.setText(ChatDateTimeFormatter.getDuration(currentPosition));
}
});
}
}, 0, 1000);
Upvotes: 5
Reputation: 2128
This happened to my when I called for an UI change from a doInBackground
from Asynctask
instead of using onPostExecute
.
Dealing with the UI in onPostExecute
solved my problem.
Upvotes: 5
Reputation: 7636
This is explicitly throwing an error. It says whichever thread created a view, only that can touch its views. It is because the created view is inside that thread's space. The view creation (GUI) happens in the UI (main) thread. So, you always use the UI thread to access those methods.
In the above picture, the progress variable is inside the space of the UI thread. So, only the UI thread can access this variable. Here, you're accessing progress via new Thread(), and that's why you got an error.
Upvotes: 7
Reputation: 851
I use Handler
with Looper.getMainLooper()
. It worked fine for me.
Handler handler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
// Any UI task, example
textView.setText("your text");
}
};
handler.sendEmptyMessage(1);
Upvotes: 7
Reputation: 7273
In my case,
I have EditText
in Adaptor, and it's already in the UI thread. However, when this Activity loads, it's crashes with this error.
My solution is I need to remove <requestFocus />
out from EditText in XML.
Upvotes: 1
Reputation: 3635
I had a similar issue, and my solution is ugly, but it works:
void showCode() {
hideRegisterMessage(); // Hides view
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
showRegisterMessage(); // Shows view
}
}, 3000); // After 3 seconds
}
Upvotes: 8
Reputation: 452
If you do not want to use runOnUiThread
API, you can in fact implement AsynTask
for the operations that takes some seconds to complete. But in that case, also after processing your work in doinBackground()
, you need to return the finished view in onPostExecute()
. The Android implementation allows only main UI thread to interact with views.
Upvotes: 3
Reputation: 991
My solution to this:
private void setText(final TextView text,final String value){
runOnUiThread(new Runnable() {
@Override
public void run() {
text.setText(value);
}
});
}
Call this method on a background thread.
Upvotes: 89
Reputation: 29463
You have to move the portion of the background task that updates the UI onto the main thread. There is a simple piece of code for this:
runOnUiThread(new Runnable() {
@Override
public void run() {
// Stuff that updates the UI
}
});
Documentation for Activity.runOnUiThread
.
Just nest this inside the method that is running in the background, and then copy paste the code that implements any updates in the middle of the block. Include only the smallest amount of code possible, otherwise you start to defeat the purpose of the background thread.
Upvotes: 2304
Reputation: 1563
Use this code, and no need to runOnUiThread
function:
private Handler handler;
private Runnable handlerTask;
void StartTimer(){
handler = new Handler();
handlerTask = new Runnable()
{
@Override
public void run() {
// do something
textView.setText("some text");
handler.postDelayed(handlerTask, 1000);
}
};
handlerTask.run();
}
Upvotes: 8