I am loading an HTTP request in the background using loopj HTTP CLIENT and when it is done, I want to display a "success" notification (dialog, toast, etc.)
I have the code in a seperate (non-activity) class with a static method that executes the background request. In the end, the response is in a AsyncHttpResponseHandler under a onSuccess method. In this method, I can print out the response, confirm that the request went through, save data to the sd card/ Shared Preferences, but how do I access the UI thread to display a notification?
Thanks in advance.
you can do it using a Handler, or by calling Activity.runOnUiThread(). so you either pass a Handler, or an Activity object to your static method, then in your onSuccess() method, do,
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
// i'm on the UI thread!
}
}
);
or,
handler.post(new Runnable() {
#Override
public void run() {
// i'm on the UI thread!
}
}
);
I guess you mean a service as a background process. Service has many built in methods like onCreate, onStartCommand, onDestroy, etc. I suggest using a Notification, because notifications do not require a UI thread to do the job.
Create a method to generate a notification and call it after your HTML read is over.
private static void generateNotification(Context context, String message) {
int icon = R.drawable.ic_stat_gcm;
long when = System.currentTimeMillis();
NotificationManager notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = new Notification(icon, message, when);
String title = context.getString(R.string.app_name);
Intent notificationIntent = new Intent(context, MainActivity.class);
// set intent so it does not start a new activity
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent intent = PendingIntent.getActivity(context, 0, notificationIntent, 0);
notification.setLatestEventInfo(context, title, message, intent);
notification.flags |= Notification.FLAG_AUTO_CANCEL;
notificationManager.notify(0, notification);
}
You could fire a local broadcast with the message, and show a toast with a receiver.
Do this in the class doing the updates:
Intent intent = new Intent("ACTION_TOAST");
intent.putExtra("message", "Success!");
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
Then in any activity that might want to know about the update, do this:
BroadcastReceiver receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if ("ACTION_TOAST".equals(intent.getAction()) {
Toast.makeText(MyActivity.this, intent.getStringExtra("message"),
Toast.LENGTH_SHORT).show();
}
}
}
#Override
protected void onStart() {
super.onStart();
LocalBroadcastManager.getInstance(this).registerReceiver(
receiver, new IntentFilter("ACTION_TOAST"));
}
#Override
protected void onStop() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver);
}
You still need to pass a context into your static method, but this works even if that context is a Service or some other context that can't show Toasts / create UI.
Related
I'm developing a countdown app, and currently trying to show a notification when you exit the app while the countdown is running. Correspondingly, I want the notification to disappear when the user returns to the app.
So far I've managed to make it work for a simple notification with static text, do the following: in MainActivity.java, in onStop(), I create an intent and initiate the service with startService(intent). Symmetrically, in onStart() I run stopService(intent) so that when you return to the app the service gets canceled. This works like a charm, the notification appears and disappears when it must.
The next step has been trying to make the notification show a text that varies (it will say "X minutes remaining"). According to the info out there, to update an existing notification you have to create a new one, give it the same ID as the existing one, and call .notify of a NotificationManager. When I do this the notification indeed gets updated correctly (the text changes as expected), BUT: now, returning to the main activity does not cancel the notification. The icon stays up there and doesn't get interrupted.
I've been trying to solve this for hours and hours. I've also tried hacks like sending signals via shared preferences to tell the service to stop, but for some reason, it seems to completely ignore the command stopself() too.
Does anybody have a suggestion of what could be the cause? Any help would be greatly appreciated. Here is the relevant code:
MainActivity.java:
#Override
protected void onStart() {
super.onStart();
Intent serviceIntent = new Intent(getBaseContext(), CounterService.class);
stopService(serviceIntent);
}
protected void onStop() {
super.onStop();
Intent serviceIntent = new Intent(getBaseContext(), CounterService.class);
startService(serviceIntent);
}
CounterService.java:
public class CounterService extends Service {
Notification notification;
NotificationManager notificator;
Intent intentNoti;
CountDownTimer counter;
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onCreate() {
super.onCreate();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
intentNoti = new Intent(this, MainActivity.class);
final PendingIntent pending = PendingIntent.getActivity(this, 0, intentNoti, 0);
final Bitmap icon = BitmapFactory.decodeResource(getResources(),
R.drawable.common_full_open_on_phone);
//Countdown
counter = new CountDownTimer (30000, 1000) {
public void onTick(long millisUntilFinished) {
String time = String.valueOf(System.currentTimeMillis());
notification = new Notification.Builder(CounterService.this)
.setContentTitle("Name")
.setContentText(time)
.setSmallIcon(R.drawable.icon_start)
.setLargeIcon(Bitmap.createScaledBitmap(icon, 128, 128, false))
.setContentIntent(pending)
.setOngoing(true).build();
notificator = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificator.notify(1001, notification);
}
public void onFinish() {
}
}.start();
return START_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
counter.cancel();
}
}
First create a Timer like this
private Timer timer;
private TimerTask timerTask;
public void startTimer() {
timer = new Timer();
timerTask = new TimerTask() {
public void run() {
// Add your code
}
};
timer.schedule(timerTask, 1000, 1000); //
}
Also you need to stop your timer.
So
public void stoptimertask() {
if (timer != null) {
timer.cancel();
timer = null;
}
}
Call StartTimer and StopTimer in OnStartCommand() and onDestroy() respectively. Add these lines in onDestroy()
Intent broadcastIntent = new Intent();
broadcastIntent.setAction("restartservice");
broadcastIntent.setClass(this, Restarter.class);
this.sendBroadcast(broadcastIntent);
it can be handled in multiple ways, you have not stopped your timer
Note:- posting code in Kotlin
1)
override fun onDestroy() {
counter.cancel()
}
in your activity
override fun onResume() {
super.onResume()
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.cancelAll()
}
I have created a Notification channel In the onCreate method of MapsActivity and called the method startService to start the service.
App stay froze until service execution ends
In logcat Errors caused by is not mentioned.
I have also mentioned using FOREGROUND_SERVICE in ManifestFile
Main Class
public class MapsActivity extends AppCompatActivity {
public static final String CHANNEL_ID = "FenceCalculateChannel";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_maps);
createNotificationChannel();
Intent serviceIntent = new Intent(this,BackGroundDistanceCalculate.class);
startService(serviceIntent);
}
public void createNotificationChannel() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationChannel serviceChannel = new NotificationChannel(CHANNEL_ID,
"Fence Service Channel",
NotificationManager.IMPORTANCE_DEFAULT);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(serviceChannel);
}
}
}
BackGroundDistanceCalculate Service Class
public class BackGroundDistanceCalculate extends Service {
final public static String TAG = "BackGroundDistance";
#Override
public void onCreate() {
super.onCreate();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Intent notificationIntent = new Intent(this, MapsActivity.class);
PendingIntent pendingIntent =
PendingIntent.getActivity(this, 0, notificationIntent, 0);
int i;
for(i=0;i<1000;i++)
{
Log.d(TAG,"Value of i is : "+ i);
SystemClock.sleep(1000);
}
Notification notification =
new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Calculating Distance")
.setContentText("Distance Calculation is running")
.setSmallIcon(R.drawable.ic_favorite)
.setContentIntent(pendingIntent)
.build();
//startForeground(1, notification);
return START_NOT_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
}
This code should create a Notificationchannel on App start but not working.
What changes may I make to make this App run?
Service runs works on main thread (UI thread) according to official doc, so it blocked UI, that's why your app freezed.
You should use IntentService which already has a worker thread to run your work.
Or you can manually create an AsyncTask or a Thread inside your service to handle your work.
I can see two things that you can fix to make it work.
You are making the BackGroundDistanceCalculate go to sleep for 1000 seconds which makes the Main thread to hang on you as instances of Service class runs on the Main thread. You can either start up a new thread or extend from IntentService (might not work in Pie+ if not using WorkManager)
You can try starting the service using ContextCompat.startForegroundService() to start your service and from within your service you will have to post the notification before you start doing your stuff.
Ok, so I have a main activity called 'Main.java'. This main activity starts an AlarmManager which fires an intent leading to 'AlarmReceiver.java'.
This 'AlarmReceiver.java' then creates a notification which has two buttons on it. One of the buttons is a deletion button, and so when the user clicks on that button, another intent is fired, leading it to 'DelPair.java'.
In DelPair.java, I modify a table in a Database, but then I need the UI of Main.java to reflect this change. I have created two functions in Main.java called updateArrayFromDB() and updateUIFromArray() to do this for me:
updateArrayFromDB() will sync an ArrayList created in Main.java to a
certain table in the DB.
updateUIFromArray() will change the UI of
Main.java to represent the ArrayList that has just been changed.
The problem is that I cannot call these two functions from DelPair.java (they don't exist in that space). I have come across Serializables in trying to find an answer but I don't know enough to know if they apply here or exactly how to implement them across the AlarmManager and the NotificationManager.
How can I access these methods from DelPair.java?
In Main.java:
public void updateArrayFromDB(){
//... The code for this is long and irrelevant
}
public void updateUIFromArray(){
//... The code for this is long and irrelevant
}
private void SendNotification() {
Intent intent = new Intent(this, AlarmReceiver.class);
//...
PendingIntent sender = PendingIntent.getBroadcast(this, 2 , intent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
am.setRepeating(AlarmManager.RTC_WAKEUP, 5000, notif_freq, sender);
}
In AlarmReceiver.java:
Intent delPairI = new Intent(context, DelPair.class);
PendingIntent delPairPI = PendingIntent.getService(context, 0, delPairI, PendingIntent.FLAG_UPDATE_CURRENT);
Notification noti;
noti = new Notification.Builder(context)
//...
.addAction(R.drawable.ic_delete_icon, "Delete the thing", delPairPI)
.build();
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0, noti);
and then in DelPair.java:
public class DelPair extends IntentService {
//...
#Override
protected void onHandleIntent(final Intent intent) {
//...
Intent it = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
getApplicationContext().sendBroadcast(it);
handler.post(new Runnable() {
#Override
public void run() {
//... here is where I update the database, which works perfectly
//now need to update the UI and array in Main.java
updateArrayFromDB(); //these lines
updateUIFromArray(); //obviously don't work
}
});
}
}
Why not use broadcasts ? in onHandleIntent just send a broadcast
Intent i = new Intent();
i.setAction(CUSTOM_INTENT);
//put relevant data in intent
getApplicationContext().sendBroadcast(i);
The broadcast receiver:
public class IncomingReceiver extends BroadcastReceiver {
private MainActivity act;
public IncomingReceiver(MainActivity main){
this.act = act;
}
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(CUSTOM_INTENT)) {
System.out.println("GOT THE INTENT");
// call the method on act
}
}
}
In your activity onResume - register new IncomingReceiver, onPause unregister
private IncomingReceiver receiver;
public void onCreate(Bundle bOs){
//other codes
receiver = new IncomingReceiver(this);
}
#Override
protected void onResume() {
IntentFilter filter = new IntentFilter();
filter.addAction(CUSTOM_INTENT);
registerReceiver(receiver, filter);
super.onResume();
}
#Override
protected void onPause() {
unregisterReceiver(receiver);
super.onPause();
}
Since you need to have an updated UI based on database changes, you can call updateArrayFromDB() and updateUIFromArray() in the onResume() method of your activity so the UI gets updated each time the user enters the activity.
The main activity has an AlarmManager which calls a Service every X minutes. I need a way that the methods inside the service's class can update a TextView in the main activity, but I dont know how to get the TextView's object in the service. Is there any way?
Here is part of the code:
Intent myIntent = new Intent(MainActivity.this, MyAlarmService.class);
pintent = PendingIntent.getService(MainActivity.this, 0, myIntent, 0);
AlarmManager alarmManager = (AlarmManager)getSystemService(ALARM_SERVICE);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), 60000, pintent);
In your alarm service you have a onReceive method
public void onReceive(Context context, Intent arg1) {
String data = "haha";
if (data.isEmpty() == false && data.contentEquals("") == false) {
nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
CharSequence from = "sing";
CharSequence message = data;
//get the activity
PendingIntent contentIntent = PendingIntent.getActivity(context, 0,
new Intent(), 0);
Notification notif = new Notification(R.drawable.icon,
data, System.currentTimeMillis());
notif.defaults |= Notification.DEFAULT_SOUND;
notif.setLatestEventInfo(context, from, message, contentIntent);
nm.notify(1, notif);
}
Method call:
AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(this, ReminderReceiverActivity.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0,
intent, PendingIntent.FLAG_CANCEL_CURRENT);
am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(),
100000, pendingIntent);
}
I had the same need, in an accelerometer-reading service and an app that would start/stop the service and display the current average accelerometer magnitude.
I used Service Binding to allow the Service to send a Message to the Activity, containing the accelerometer value.
See http://developer.android.com/guide/components/bound-services.html#Messenger for background on Binding. For a good example, see the API Samples' MessengerServiceActivities and MessengerService classes.
I did the following. I've left out the mundane details (such as Synchronization to avoid races) for clarity. Notice that I use bind() as well as StartService(). The bind() is for sending messages between the Activity and Service; the StartService() is so the Service keeps running after the Activity exits.
Basically, the Activity and Service exchange Messengers that will allow each to send Messages to the other. The Activity sends custom Messages to the Service in order to Subscribe to Service Messages or Unsubscribe from Service Messsages. When the Service wants to send data to the Activity, it sends a custom Message to the Activity's Messenger. The Activity, on receiving such a message, displays the new value to the user.
The answer that suggested using Notifications uses a simple way to get data onto the screen. This more complex Message-passing answer is needed if you want to display data in your Activity (vs. the Notification bar).
I apologize for the length of the example code below, but there are a lot of necessary details to convey.
In my Activity, named AccelServiceControl:
private FromServiceHandler handler; // My custom class for Handling Messages from my Service.
private Messenger fromService; // For receiving messages from our Service
...
public void onCreate(Bundle savedInstanceState) {
...
handler = new FromServiceHandler(this);
fromService = new Messenger(handler);
...
}
protected void onResume() {
...
// While we're in the foreground, we want to be bound to our service.
// onServiceConnected() will return the IBinder we'll use to communicate back and forth.
bindService(new Intent(this, AccelService.class), this, Context.BIND_AUTO_CREATE);
}
protected void onPause() {
...
// Note: By itself, this doesn't stop the Service from sending messages to us.
// We need to send a custom Unsubscribe Message to stop getting messages from the Service.
unbindService(this);
}
public void onClick(View v) {
// Send a custom intent to start or stop our Service.
if (buttonStart == v) {
startService(new Intent(AccelService.ACTION_START));
} else if (buttonStop == v) {
startService(new Intent(AccelService.ACTION_STOP));
}
...
}
public void onServiceConnected(ComponentName name, IBinder service) {
...
// Ask our Service to send us updates.
toService = new Messenger(service);
Message msg = Message.obtain(null, FromClientHandler.MSG_SUBSCRIBE); // our custom Subscribe message
msg.replyTo = fromService;
try {
toService.send(msg);
} catch (RemoteException e) {
// Failed because the Service has died.
// We handle this in onServiceDisconnected().
}
}
In the Activity's custom Message Handler:
....
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_ACCEL_UPDATE:
...
Bundle bundle = msg.getData();
double accelValue = bundle.getDouble(ACCEL_UPDATE_VALUE);
...then display the new accelValue in, for example, a TextView.
...
}
}
In the Service, named AccelService:
...
private Messenger fromClient; // For receiving messages from our Client(s).
private FromClientHandler handler; // needed just for unlinking at in onDestroy().
// Since we have only one Client, we store only one Activity's Messenger
private Messenger subscribedMessenger;
public void onCreate() {
...
handler = new FromClientHandler(this);
fromClient = new Messenger(handler);
}
public void onDestroy() {
// Unlink ourselves from our Handler, so the Garbage Collector can get rid of us. That's a topic in itself.
handler.unlink();
....
}
public int onStartCommand(Intent intent, int flags, int startId) {
...
int returnValue = START_NOT_STICKY;
String action = intent.getAction();
if (ACTION_START.equals(action)) {
doActionStart();
returnValue = START_STICKY;
} else if (ACTION_STOP.equals(action)) {
...
// Our Service is done
stopSelf();
}
...
return returnValue;
}
public IBinder onBind(Intent intent) {
// Hand back a way to send messages to us.
return fromClient.getBinder();
}
...when we want to send data to the Activity:
Message msg = Message.obtain(null, FromServiceHandler.MSG_ACCEL_UPDATE);
Bundle bundle = new Bundle();
bundle.putDouble(FromServiceHandler.ACCEL_UPDATE_VALUE, avgAccel);
msg.setData(bundle);
try {
subscribedMessenger.send(msg);
} catch (RemoteException e) {
// Failed because the Client has unbound.
subscribedMessenger = null;
}
I have a service that create a notification when it is started.
And then ondestroy() i want it to be removed.
I just use .cancel(NOTIFICATION_ID);
It works great when it is a normal notification but when i am using ongoing event it just won't cancel it.
I did read something about that services doesn't stop if android have the resources for it, but how to overcome this?
I use this code to start the service
final Intent bg_service = new Intent(BackgroundService.class.getName());
btn_start = (Button)findViewById(R.id.btn_start);
btn_stop = (Button)findViewById(R.id.btn_stop);
btn_start.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
startService(bg_service);
}
});
btn_stop.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
stopService(bg_service);
}
});
I use this in on create
notificationManager = (NotificationManager)
getSystemService(NOTIFICATION_SERVICE);
Notification notification = new Notification(R.drawable.ic_launcher,
"A new notification", System.currentTimeMillis());
notification.flags |= Notification.FLAG_FOREGROUND_SERVICE;
Intent intent = new Intent(this, mainapp.class);
PendingIntent activity = PendingIntent.getActivity(this, 0, intent, 0);
notification.setLatestEventInfo(this, "...",
"...", activity);
notificationManager.notify(19314, notification);
And this in on destroy
noficationManager.cancel(19314);
I would look at doing 'ongoing' notifications a bit differently if you can. There are methods that live on Service that are dedicated to adding and removing an 'ongoing' notification.
Service.startForeground(int, Notification)
Service.stopForeground(boolean)
Perhaps you could just try calling stopForegroud(boolean) from your onDestroy() method first...