I would like to check every so often if exists a new version of my app, and if it´s, show a message to user. I use Firebase, connecting and comparing version from remote config with current version of app. This isn´t the problem, my problem is how to show the dialog at any time, in any activity.
I have a BaseActivity, where I have methods to connect with firebase and to show the message when it answers. Furthermore, I have a method that executes every hour this update checking:
private void checkUpdate() {
handlerCheckUpdate.postDelayed(() -> {
getConfigFromFirebase(this);
checkUpdate();
}, 3600000);
}
And finally I have Activity1 and Activity2 that extends of BaseActivity. In my Activity1 I start the recursive checkUpdate method.
The problem is that, if user is in Activity2 currently, when the message shows, it do it in Activity1 and not in Activity2.
What is the best solution to do this?.
Thank you very much!
You need to create a dialog in your Application class and pass the context to it.
class App : Application() {
fun showAlert(context: Context) {
val builder = AlertDialog.Builder(context)
builder.setTitle(getString(R.string.your_title))
builder.setMessage(getString(R.string.your_message))
builder.setPositiveButton("OK") { dialog, _ ->
dialog.dismiss()
}
val dialog: AlertDialog = builder.create()
dialog.setCancelable(false) // make it true if you want to cancel
dialog.setCanceledOnTouchOutside(false)
dialog.show()
}
}
And you need to add this line in your manifest file under the application tag
<application
android:name=".App"
android:allowBackup="false"
......>
Call that function from any of your activity by passing context:
App().showAlert(YourActivity.this)
You can use Android LiveData check it here
The idea is - you add an observer in each of your Activities (or the BaseActivity). In the observer you add your logic for launching the Dialog. When you're ready with the update-check in the background, you notify the LiveData. The Activity that is currently in a resumed state will handle the update and show the Dialog.
class BaseActivity : AppCompatActivity() {
...
fun onCreate() {
//provide your liveData from app object
application.getVersionUpdateLiveData().(this, Observer {
//start your dialog here
})
}
}
Make sure you use the latest androidx lib.
Finally, I´ve found a solution. I use AlarmManager and BroadcastReceiver, and I´ve removed the recursive method checkUpdate and set an alarm that repeat every hour, and does the action getConfigFromFirebase(this):
Create broadcast receiver with the action of alarm:
public class CheckUpdateAppAlarmReceiver extends BroadcastReceiver {
public static CheckUpdateReceiverListener checkUpdateReceiverListener;
public static Boolean dialogShown = false;
#Override
public void onReceive(Context context, Intent intent) {
//Alarm!!
if (checkUpdateReceiverListener != null) {
checkUpdateReceiverListener.onCheckUpdateListenerChanged();
}
}
public interface CheckUpdateReceiverListener {
void onCheckUpdateListenerChanged();
}
}
In BaseActivity, add create and cancel alarm methods:
public void createAlarmCheckUpdate() {
try {
long firstAlarmTime = new Date().getTime();
Intent intentAlarmCheckUpdate = new Intent(this, CheckUpdateAppAlarmReceiver.class);
AlarmManager alarmMgr = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
PendingIntent alarmIntent = PendingIntent.getBroadcast(this, 2000,
intentAlarmCheckUpdate,
PendingIntent.FLAG_UPDATE_CURRENT);
alarmMgr.setInexactRepeating(AlarmManager.RTC, firstAlarmTime,
AlarmManager.INTERVAL_HOUR, alarmIntent);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void cancelAlarmCheckUpdate(Context context) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, CheckUpdateAppAlarmReceiver.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 2000, intent, PendingIntent.FLAG_NO_CREATE);
if (pendingIntent != null) {
alarmManager.cancel(pendingIntent);
}
}
Finally, implements the broadcastReceiver in BaseActivity, and override its method
public class BaseActivity extends AppCompatActivity implements CheckUpdateAppAlarmReceiver.CheckUpdateReceiverListener {
...
#Override
protected void onResume() {
super.onResume();
CheckUpdateAppAlarmReceiver.checkUpdateReceiverListener = this;
}
...
#Override
public void onCheckUpdateListenerChanged() {
if (this instanceof Activity1 || this instanceof Activity2) {
getConfigFromFirebase(this);
}
}
Use create and cancel alarm methods where necessary.
Related
I am trying to set an alarm to call a method in MainActivity. I have used the method described here; the alarm fires but once it does it repeats about once a second.
I am using setExactAndAllowWhileIdle since the alarm is needed only every hour or so (actually it doesn't need to be exact, I could use setAndAllowWhileIdle instead but that gives the same problem).
My Alarm class is pretty simple:
public class Alarm extends BroadcastReceiver
{
static MainActivity main = null;
public Alarm()
{
}
public Alarm(MainActivity ctx)
{
main = ctx;
}
#Override
public void onReceive(Context context, Intent intent)
{
if (main != null)
main.alarmAction();
}
}
In OnCreate() for MainActivity I have
alarmReceiver = new Alarm(this);
IntentFilter alarmFilter = new IntentFilter("Alarm");
registerReceiver(alarmReceiver,alarmFilter);
and then I have methods:
public void SetAlarm() {
alarmStarted = true;
Intent i = new Intent(this, Alarm.class);
i.setAction("Alarm");
PendingIntent pi = PendingIntent.getBroadcast(this, 1001, i, 0);
AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
am.cancel(pi); // tried this to solve the problem but probably not needed
am.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME, 1000 * 60 * 10, pi);
}
public void alarmAction() {
if (!alarmStarted)
return;
SetAlarm();
// will be more stuff here but this is minimum required to show problem
}
The flag alarmStarted is set from a button press.
Android Studio is giving me a memory-leak warning about the use of static in static MainActivity main = null, but without this main is always null in onReceive. I don't know if the repeating alarm issue is connected with this or not.
After login i want to show dialog using AlertDialog for every 2 hours.
Tried below code :
private void ShowDialog() {
new SweetAlertDialog(getContext())
.setTitleText("Attention!")
.setContentText("Have You Checked Followup list? Check Here !!")
.setConfirmText("Followup List")
.setConfirmClickListener(new SweetAlertDialog.OnSweetClickListener() {
#Override
public void onClick(SweetAlertDialog sweetAlertDialog) {
ShowFollowupList();
sweetAlertDialog.dismissWithAnimation();
}
})
.setCancelButton("Cancel", new SweetAlertDialog.OnSweetClickListener() {
#Override
public void onClick(SweetAlertDialog sDialog) {
sDialog.dismissWithAnimation();
}
})
.show();
}
By using Work Manager you can display it.
Refer link: https://developer.android.com/topic/libraries/architecture/workmanager
Schedule tasks with WorkManager
API makes it easy to schedule deferrable, asynchronous tasks that are expected to run even if the app exits or device restarts.
You can use AlarmManager to achieve it.
First, create your Receiver class that extends BroadcastReceiver as below.
public class YourReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
// show your dialog here
}
}
After that, create an AlarmManager and PendingIntent instance to set the alarm, as below.
public void setAlarm(Context context, int requestCode) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, YourReceiver.class);
// different request code enable you to set alarm more than one
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT);
final long everyTwoHours = 2 * 60 * 60 * 1000; // in milliseconds
alarmManager.setInexactRepeating(
AlarmManager.RTC, calendar.getTimeInMillis(), everyTwoHours, pendingIntent);
}
Use WorkManager for Scheduling task in background and foreground
Example for Periodic Request
PeriodicWorkRequest request= new PeriodicWorkRequest.Builder(WorkerClass.class,
2, TimeUnit.HOURS).addTag("TAG").build()
WorkManager.getInstance().enqueueUniquePeriodicWork("TAG", ExistingPeriodicWorkPolicy.KEEP, request);
Create a worker class
public class WorkerClass extends Worker {
#Override
public Worker.WorkerResult doWork() {
// Do the work here
// Indicate success or failure with your return value:
return WorkerResult.SUCCESS;
// (Returning RETRY tells WorkManager to try this task again
// later; FAILURE says not to try again.)
}
}
add this in build.gradle
implementation 'android.arch.work:work-runtime:2.1.0-alpha01'
check the latest release doc https://developer.android.com/jetpack/androidx/releases/work
I created a Broadcast Receiver (BR) in a service that will react to incoming SMS with specific number and body. I only need it to receive for a few seconds/minutes after user action, that's why I didn't registered it in manifest or activity (user may close it). BR has two parts, automatic (which works fine) and manual which should launch MainActivity and start a Dialog. I know that Dialog can't be started from BR and thats why I created a Listener, but my problem is that it is always null after service starts. It has value in onCreate of my MainActivity, but when service starts it changes to null, and I understand why (serivce re-initalize the Listener listener). I even tryed to put initialised listener value to SharedPrefs and restore it after, but when I try to store it with json it only stores null again. So how do I make my listener != null??? These are the relevant parts of my code:
MainActivity
onCreate {
SMSService smsReceiver = new SMSService();
smsReceiver.setListener(new SMSService.Listener() { //here listener from service is != null
#Override
public void onTextReceived(String s) {
dialogS(s); // totaly different dialog
}
});
...
mDialogBuilder = new AlertDialog.Builder(this);
...
.setPositiveButton(new OnClick...
Intent servisIntent = new Intent(MainActivity.this, SMSService.class);
startService(servisIntent);
...
}
SMSService
private Listener listener; // and here it get null which is the problem
public int onStartCommand(Intent intent, int flags, int startId) {
...
SMSReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
Intent i = new Intent(context, MainActivity.class);
context.startActivity(i);
if (listener != null) {
listener.onTextReceived("4333");
}
}
void setListener(Listener listener) {
this.listener = listener; }
interface Listener {
void onTextReceived(String text);
}
Btw I also tried to put smsReceiver.setListener block of code in my Dialog .setPossitive onClickListener after calling startService hoping it would initiate after service but nothing
Installing a listener mechanism with setter method in service is bad practice. You can use ResultReceiver to receive callback results from service. It is Parcelable, so it can be passed in an intent before service started
I have read every question there is about Android, AlarmManager and cancelling.
I currently use an Activity starting a receiver through:
long msInterval = 1;
Intent intent = new Intent(this, Updater.class);
intent.setAction("theAction");
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 12, intent, 0);
Updater.origin = pendingIntent;
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + (msInterval), msInterval, pendingIntent);
This starts the receiver Updater one millisecond after this code was called, with request code 12 (chosen arbitrarily, using 0 produces the same incorrect behaviour). It also sets the origin of Updater to the currently scheduled PendingIntent, which is later used to cancel the alarm.
Updater looks like this:
public class Updater extends BroadcastReceiver {
public static int flaggedClose = 0;
public static PendingIntent origin;
#Override
public void onReceive(Context context, Intent intent) {
// Do some work
Log.w("running", "run");
if (Updater.flaggedClose != 0) {
if(flaggedClose == 1) {
Toast.makeText(context, "Finished!", Toast.LENGTH_SHORT).show();
}
flaggedClose++; // Only show Toast once
Log.w("running", "close");
origin.cancel();
AlarmManager alarms = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
alarms.cancel(origin);
}
}
}
What it does at the moment is just to log the message "run", which is done ~1000 times/s. When the Activity's onStop() is called, Updater.flaggedClose is set to 1. I can be see this in Logcat since it starts printing the log warning "close". However, the alarm is still on, so every other logged message is "run" and every other is "close". In best case, the alarm is closed after a few seconds. Worst case I need to restart the phone. In the description of AlarmManager, it specifically states that close closes "Any alarm, of any type, whose Intent matches this one (as defined by filterEquals(Intent)), will be canceled". Why are there still alarms being triggered?
As ci_ mentioned in the comments, it is possible that "those 100 "extra" alarms already triggered before the cancel happens". For anyone else who has the same problem, here is a solution. I tested the AlarmManager and it seems to work best if you have a delay of at least 200 ms. for a lower delay use a Handler. Example from the question using Handler:
public class MainActivity extends Activity {
private boolean pressed = false;
private boolean done = false;
private Handler worker;
private Runnable method;
long msInterval = 1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
worker = new Handler();
method = getMethod();
Button toggle = (Button)(findViewById(R.id.toggle));
toggle.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if(!pressed) {
worker.post(method);
pressed = true;
} else {
done = true;
}
}
});
}
#Override
protected void onDestroy() {
super.onDestroy();
done = true;
}
private Runnable getMethod() {
return new Runnable() {
public void run() {
Log.w("running", "run");
if (done) {
Toast.makeText(getApplicationContext(), "Finished!", Toast.LENGTH_SHORT).show();
Log.w("running", "close");
} else {
worker.postDelayed(method, msInterval);
}
}
};
}
}
On first button press the handler starts the runnable, and on each call the runnable calls itself. On second button press condition done is set to true, thus the runnable finishes after one (cleanup) round.
I have a startApplicationService method in an activity. Therefore I have an alarm manager. startApplicationService method is calling background service. I want to use/call that method in onReceive method from my alarm manager. How to do it? Please help me. :)
EDITED: Added alarm manager class:
public class WatcherAlarm extends BroadcastReceiver
{
private final static String LOG_TAG = WatcherAlarm.class.getSimpleName();
private AccessActivity activity = null;
#Override
public void onReceive(Context context, Intent intent)
{
Log.d(LOG_TAG, "-------------------------- WATCHER ALARM ------ ON RECEIVE");
if(activity != null) {
activity.startApplicationService(intent.getExtras());
}
}
public void startAlarm(AccessActivity activity, Bundle bundle)
{
this.activity = activity;
AlarmManager alarmManager = (AlarmManager) activity.getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent(activity, WatcherAlarm.class); // explicit
i.putExtras(bundle);
PendingIntent pi = PendingIntent.getBroadcast(activity, 0, i, 0);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 1000 * 20, pi); // Millisec * Second * Minute
}
public void stopAlarm(Context context)
{
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent i = new Intent(context, WatcherAlarm.class); // explicit
PendingIntent pi = PendingIntent.getBroadcast(context, 0, i, 0);
alarmManager.cancel(pi);
}
}
And here is the my startApplicationService method: It's starting AsyncTask.
public void startApplicationService(Bundle bundle)
{
try {
task = new ApplicationServiceTask(this);
requestId = task.execute(bundle).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
I'm tried get activiy from context. But it's not possible. :(. Is there any way to say call startApplicationService method to activity from alarm manager?
Thank you for every advice.
Zeck
No... because you have no guarantee that the Activity you are trying to call will even exist when your onReceive() is called.
What you can do is start that Activity using an Intent with an Extra indicating that the the caller is you BroadcastReceiver. The Activity can then check that Extra and call the method.
However, considering what you appear to want to do. I would recommend going with a Service. Since you are doing the work in the background anyways, I don't see a reason for wanting to do it in an Activity (unless, of course, you have a valid reason that does not show here).
See my answer here for an example of something similar.