I've to develop a widget (AppWidget) that contains a button.
When the button is pressed an http request is done and, if the request is successful, the widget notify the fact through a Toast.
Since the http request can lock the UI, I run it in a thread.
All it's working fine (I can see "All right" in my logat), except for the Toast notification.
What am I doing wrong?
In the AppWidgetProvider subclass:
#Override
public void onReceive(Context c, Intent intent){
super.onReceive(c, intent);
if(intent.getAction().equals(CLICK_ACTION)){
performRequest(c);
}
}
(I think onUpdate method is not necessary for the question)
private void performRequest(final Context c){
new Thread(new Runnable(){
public void run() {
try {
if(Http.get(URL).contains(SUCCESS)){
Toast.makeText(c, "All right", Toast.LENGTH_SHORT).show();
Log.i("Widget","All right");
}
} catch (Exception e) {
e.printStackTrace();
}}}).start();
}
I think that the problem is the Context argument of performRequest, but if I remove the final keyword, I have errors.
Thanks in advice.
[EDIT]
I "solved" the problem in this way:
-I declare Toast t as a global variable;
-Inside the onReceive method I initialize it with Toast.makeText(Context,String,int)
-When I need to show it, I simply call t.show()
I hope that there are better solutions.
Since the http request can lock the UI, I run it in a thread. All it's working fine (I can view "All right" in my logat), except for the Toast notification. What am I doing wrong?
The problem is you cannot alter the UI from another Thread. You must pass this message back to the main Thread in order to display your Toast.
How can I do it?
There are a lot of different ways to do this, try saving the Context in onReceive() as a field variable:
Context context;
#Override
public void onReceive(Context c, Intent intent){
super.onReceive(c, intent);
context = c;
...
Next create a simple method in your Activity:
public void httpGetSucceeded() {
Toast.makeText(context, "All right", Toast.LENGTH_SHORT).show();
Log.i("Widget","All right");
}
Now call this inside your Runnable:
try {
if(Http.get(URL).contains(SUCCESS)){
httpGetSucceeded();
}
} //etc
Better yet, why don't you have a handler that dispatches a message after the thread completes its task. When you receive that message in your broadcast receiver just consume the message and display the toast by using the post runnable method from the Handler object.
Because the handler will belong to your broadcast receiver, you should be running on the UI thread when you call the post for the runnable.
Ref
http://developer.android.com/reference/android/os/Handler.html
http://developer.android.com/reference/android/os/Handler.html#post(java.lang.Runnable)
Easiest way is to call
activity.runOnUiThread(new Runnable() {
public void run() {
// launch toast here
}
});
Or, given a specific widget:
view.post(new Runnable() {
public void run() {
// launch toast here
}
});
Related
I have a legacy IntentService that attempts to use Toast messages to display error messages.1 I'd like the messages to be displayed, and have added code to get them on the correct thread. The simplest change would be to pass in the constructed Toast object and then display it on the UI thread. However, the Toast only displays if I make it in the posted runnable, not if I pass in a pre-made Toast.
This works:
#Override
protected void onHandleIntent(Intent intent) {
showToast("Error", Toast.LENGTH_LONG);
}
private void showToast(final String msg, final int duration) {
new Handler(getMainLooper()).post(new Runnable() {
#Override
public void run() {
// Make and show the toast in the posted runnable
Toast.makeText(getApplicationContext(), msg, duration).show();
}
});
}
This doesn't work:
#Override
protected void onHandleIntent(Intent intent) {
// Make the toast here
Toast myToast = Toast.makeText(getApplicationContext(), "Error", Toast.LENGTH_LONG);
showToast(myToast);
}
private void showToast(final Toast toast) {
new Handler(getMainLooper()).post(new Runnable() {
#Override
public void run() {
// Show the toast here
toast.show();
}
});
}
In both cases, the context is the application context, and I didn't see anything in the source that would cause one version to work, but the other not. Instead the latter has the same problems as if the Toast was shown directly in the IntentService: "Handler (android.os.Handler) {...} sending message to a Handler on a dead thread", Toast not disappearing, etc.
Why does the Toast have to be made on the main thread instead of just shown there?
1. Legacy = I don't think displaying error messages in Toasts is great UI, and I don't think services displaying messages to users directly is a good idea, but that's the code I was handed and I'd like to make it this little bit better.
In the second code that you've posted, the Toast is created in the background thread which has a looper and handler set up (that is the point of IntentService).
The toast uses the current thread's looper to create a handler, but once the IntentService is finished processing the work in onHandleIntent it stops itself (if there aren't other intents to process) - destroying the thread that your Toast's handler is relying on.
line 327: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/java/android/widget/Toast.java
Making the toast in the runnable works because at that point, the current thread is the UI thread.
Hi I want to make Toast available to me no-matter-what and available from any thread whenever I like within my application. So to do this I extended the Activity class:
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.widget.Toast;
public class MyActivity extends Activity{
private Handler mHandler;
#Override
public void onCreate(Bundle savedInstanceState) {
mHandler = new Handler();
super.onCreate(savedInstanceState);
}
private class ToastRunnable implements Runnable {
String mText;
public ToastRunnable(String text) {
mText = text;
}
public void run(){
Toast.makeText(getApplicationContext(), mText, Toast.LENGTH_SHORT).show();
}
}
public void doToast(String msg) {
mHandler.post(new ToastRunnable(msg));
}
}
so that all Activity classes in my app are now simply
public class AppMain extends MyActivity {
//blah
}
what I expected to be able to do (in a worker thread) was this:
try{
MyActivity me = (MyActivity) Looper.getMainLooper().getThread();
me.doToast("Hello World");
}
catch (Exception ex){
Log.e("oh dear", ex.getMessage());
}
and so long as the Activity was a "MyActivity" it should work - but the problem is ---> the Looper.getMainLooper().getThread(); isn't returning the MyActivity to me and it's making me cry - what am I doing wrong?
: EDIT :
some background to explain "why" I am stuck with this type of implmentation.
I need to be able to confirm to the user that a "HTTP POST" event has been a success. Now. If the User clicks "OK" on the UI Form it MAY or MAY NOT have internet at that time.. If it has Internet - all well and good - it posts the form via HTTP POST all well and good.. but if there is NO Internet most (99.999% of Android apps lame /pathetic / mewling at this, and basically offer the user no plan "b" assuming that at all times the internet is there - when it is NOT)
My App will not "go lame (as I call it)" - it does have a plan "b" instead it "Queues" the post event and retries every x minutes.. now this is a silent thread in the background.. I have plenty of user interaction all over the app I don't know where the user will "be" but eventually when the HTTP POST that queue/retries/queue/retries returns "! Success! " I want to Toast that as a message to the user (EG: "your form was sent")
What's wrong with runOnUiThread?
http://developer.android.com/reference/android/app/Activity.html#runOnUiThread(java.lang.Runnable)
activity.runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(activity, "Hello, world!", Toast.LENGTH_SHORT).show();
}
});
use below code. create activity object which contains your activity instance..
activity.runOnUiThread(new Runnable() {
public void run() {
Toast.makeText(activity.getApplicationContext(),"Toast text",Toast.LENGTH_SHORT).show();
}
);
This will allow you to display the message without needing to rely on the context to launch the toast, only to reference when displaying the message itself.
runOnUiThread was not working from an OpenGL View thread and this was the solution. Hope it helps.
private Handler handler = new Handler();
handler.post(new Runnable() {
public void run() {
Toast.makeText(activity, "Hello, world!", Toast.LENGTH_SHORT).show();
}
});
You can't just cast the result of getThread() to an instance of your MyActivity base class. getThread() returns a Thread which has nothing to do with Activity.
There's no great -- read: clean -- way of doing what you want to do. At some point, your "worker thread" abstraction will have to have a reference to something that can create a Toast for you. Saving off some static variable containing a reference to your Activity subclass simply to be able to shortcut Toast creation is a recipe for memory leaks and pain.
Why don't you send an intent that is captured by a BroadCastReceiver, then the broadcast receiver can create a notification in the notification tray. It's not a toast, but its a way to inform the user that his post has been successful.
If it's within your own activity, why can't you just call doToast()?
if you have the context with you, you can call the ui thread like this from non activity class.
((Activity)context).runOnUiThread(new Runnable() {
public void run() {
// things need to work on ui thread
}
});
I am trying to update my UI in FirstActivity when I receive a notification but is confused by runOnUiThread , Runnable and Handler. Here is what I have: I am running FirstActivity and NotificationService. When NotificationService reeives a notification, it will update FirstActivity UI.
I also have another service AlarmService running.
First Activity
#Override
public void onResume() {
super.onResume();
//some other code for alarm service
}
NotificationService
//on receiving notification
private void showNotification(String text) {
//Get activity
Class<?> activityClass = null;
try {
activityClass = Class.forName("com.pakage.FirstActivity");
contextActivity = (Activity) activityClass.newInstance();
//Update UI on FirstActivity not working
contextActivity.runOnUiThread(new Runnable() {
public void run()
{
Looper.prepare();
TextView tv = (TextView ) contextActivity.findViewById(R.id.notifyTest);
Looper.loop();
}
});
} catch (Exception e) {
e.printStackTrace();
}
//Shows the notification
Notification n = new Notification();
//... etc
}
I keep getting looper.prepare error. Do I need to put extra codes in my FirstActivity?
My 1st instinct is that you should instead have the Activity bind to your service and handle the UI update on its side instead of the Service directly modifying the Activity.
See more info here:
http://developer.android.com/reference/android/app/Service.html#LocalServiceSample
And an example here:
Example: Communication between Activity and Service using Messaging
I've always just had the service fire off a Broadcast and then in my Activity I have a BroadcastReciever listening for the Broadcast. It's an approach that is much simpler than the one you outlined above.
I have no idea why you are putting a Looper in
contextActivity.runOnUiThread(new Runnable() {
public void run()
{
Looper.prepare();
TextView tv = (TextView ) contextActivity.findViewById(R.id.notifyTest);
Looper.loop();
}
});
because the UI (main) thread already has a Looper/Handler etc..
Even if it did work Looper.loop() is going to block and since you are running it on the UI thread, it will block the UI thread which is not what you want.
What you really want to do is
contextActivity.runOnUiThread(new Runnable() {
public void run()
{
TextView tv = (TextView ) contextActivity.findViewById(R.id.notifyTest);
tv.setText("do something that must be on UI thread") // or whatever
}
});
You don't really need to do all this fancy stuff to get the Activity
activityClass = Class.forName("com.pakage.FirstActivity");
contextActivity = (Activity) activityClass.newInstance();
assuming the Service and Activity are both running in the same process you can just save a reference to the Activity but be careful to update the reference when the Activity gets destroyed.
I've a class named by MyService which extends Service below. Everything will be ran until
I remove the Toast.makeText... line in the run method of Thread.
Why? And how can I get access to the Activity components from the run method of Thread class?
public class MyService extends Service {
#Override
public IBinder onBind(Intent intent) { return null; }
#Override
public void onCreate() {
Toast.makeText(this, "This msg will be shown", Toast.LENGTH_LONG).show();
Log.d("Bilgi", "This msg will be shown.");
super.onCreate();
}
#Override
public void onStart(Intent intent, int startId) {
Toast.makeText(this, "This msg will be shown", Toast.LENGTH_LONG).show();
super.onStart(intent, startId);
timer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
try {
Log.d("This msg will ","be shown"); //if I remove next line
Toast.makeText(this, "This msg will NOT be shown", Toast.LENGTH_LONG).show();
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, 5000, 8000);
}
And how can I get access to the Activity components from the run method of Thread class?
You don't. Use Messenger to send Message objects from the service to the activity's Handler. The activity -- and only the activity -- can update its widgets, and that only from the main application thread.
Here is a sample application demonstrating this.
Don't use Threads - use AyncTasks. Also, you shouldn't be accessing the Activity methods/UI through threads/tasks. Take a look at the first link to get an idea of how the Activity and its "threads" work together.
The only method I know is to use broadcast receiver inside your activity, which will catch you messages and update UI or whatever you want.
When creating the Toast, pass in the ApplicationContext which you can get through getApplicationContext()
The UI widgets are not thread-safe so you can not update the the UI widget unless in the Main(UI) Thread , in your case, making Toast is in another thread which is forbidden.
You may need to use something like Handler, and use Messenger to send message to the handler created in the activity UI thread. And then deal with the widgets in method handleMessage(Message msg).
I need to display a message to the user "Communicating to the Server...Please wait for few seconds" when a call to a webservice is made. Currently I'm using Toast.makeText to display the message. For some reason, I don't see the message pop-up. But interestingly when I comment the web service method call, I see the Toast message.
Toast.makeText(this, "Communicating to the Server...Please wait for few seconds",
Toast.LENGTH_SHORT).show();
//webservice code goes here...
Or any other alternative to satisfy this requirement is also fine.
Have you looked at using AysncTask. Using AsyncTask you can show a dialog with your message on onPreExecute().
Do NOT mix UI code and network code. See: http://developer.android.com/resources/articles/painless-threading.html
You can use AsyncTask to run your service and show Toast in onPreExecute.
Or you can use normal Thread but, you'll need to use Handler. Here is how:
class MyActivity extends Activity
{
final Handler mHandler;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(...);
mHandler = new Handler();
...
}
void showToast(final String text)
{
mHandler.post(new Runnable()
{
#Override
public void run()
{
Toast.makeText(MyActivity.this, text, Toast.LENGTH_LONG).show();
}
});
}
class MyThread implements Runnable
{
#Override
public void run()
{
showToast("your custom text");
//your service code
}
}
}
And here is how you start the thread:
Thread thread = new Thread(new MyThread());
thread.run();
The problem is that the UI thread is blocked as soon as you make the blocking web service call, so it never updates with the toast message. By the time it returns, the time for toast message has expired.
Run your web service call in a thread, using AsyncTask, or just create a thread like,
new Thread(new Runnable() {
public void run() {
// WS call here
}
}).start();
Take care that if you create your own thread, you can only update the UI from the UI thread, so you'll need to use Handler.post() or sendMessage() to run the UI update on the UI thread.
http://developer.android.com/reference/android/os/AsyncTask.html
http://developer.android.com/reference/android/os/Handler.html