In my app the user can choose a time and a date when a notification will be sent out on. However, the notifcations are not being sent out on the right time. If I choose a notification to be sent out on the same day, it will be sent out on a random time during the next day. and If I choose a notification to be sent out on a day in the future, it will be sent out on a random time during that day. So in summary, it seems to be ignoring the time and sending out notifications based on the date but it can't even get the date right when I choose a notification for the current day.
The code worked fine when the the user only had the ability to choose the time. It broke after I added the ability to choose a date for the notification.
MeeldetuletusActivity.java
public class MeeldetuletusActivity extends AppCompatActivity implements TimePickerDialog.OnTimeSetListener, DatePickerDialog.OnDateSetListener {
String timeText;
TextView textTime;
Button nupp_vali, nupp_katkesta, nupp_kuupaev, nupp_alarm;
Calendar c;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_meeldetuletus);
nupp_vali = findViewById(R.id.button2);
nupp_katkesta = findViewById(R.id.nupp_katkesta);
nupp_kuupaev = findViewById(R.id.button);
nupp_alarm = findViewById(R.id.button3);
nupp_alarm.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startAlarm(c);
}
});
nupp_vali.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
DialogFragment timePicker = new TimePickerFragment();
timePicker.show(getSupportFragmentManager(), "time picker");
}
});
nupp_kuupaev.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
DialogFragment kuupaevaValija = new KuupaevaFragment();
kuupaevaValija.show(getSupportFragmentManager(), "date picker");
}
});
nupp_katkesta.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
katkestaAlarm();
Toast.makeText(MeeldetuletusActivity.this, "Meeldetuletus unustatud", Toast.LENGTH_SHORT).show();
}
});
}
@Override
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
c = Calendar.getInstance();
c.set(Calendar.HOUR_OF_DAY, hourOfDay);
c.set(Calendar.MINUTE, minute);
c.set(Calendar.SECOND, 0);
}
public void onDateSet (DatePicker view, int aasta, int kuu, int paev) {
c = Calendar.getInstance();
c.set(Calendar.YEAR, aasta);
c.set(Calendar.MONTH, kuu);
c.set(Calendar.DAY_OF_MONTH, paev);
}
private void startAlarm(Calendar c) {
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(this, NotificationPublisher.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 1, intent, 0);
if (c.before(Calendar.getInstance())) {
c.add(Calendar.DATE, 1);
}
Objects.requireNonNull(alarmManager).setExact(AlarmManager.RTC_WAKEUP,
c.getTimeInMillis(), pendingIntent);
}
private void katkestaAlarm() {
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent myIntent = new Intent(getApplicationContext(), NotificationPublisher.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
getApplicationContext(), 1, myIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.cancel(pendingIntent);
}
}
KuupaevaFragment.java
public class KuupaevaFragment extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Calendar c = Calendar.getInstance();
int aasta = c.get(Calendar.YEAR);
int kuu = c.get(Calendar.MONTH);
int paev = c.get(Calendar.DAY_OF_MONTH);
return new DatePickerDialog(getActivity(), (DatePickerDialog.OnDateSetListener) getActivity(),
aasta, kuu, paev);
}
}
TimePickerFragment.java
public class TimePickerFragment extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Calendar c = Calendar.getInstance();
int hour = c.get(Calendar.HOUR_OF_DAY);
int minute = c.get(Calendar.MINUTE);
return new TimePickerDialog(getActivity(), (TimePickerDialog.OnTimeSetListener) getActivity(),
hour, minute, DateFormat.is24HourFormat(getActivity()));
}
}
NotificationPublisher.java
public class NotificationPublisher extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
NotificationHelper notificationHelper = new NotificationHelper(context);
NotificationCompat.Builder nb = notificationHelper.getChannelNotification();
notificationHelper.getManager().notify(1, nb.build());
Notification notification = nb.build();
notification.defaults |= Notification.DEFAULT_VIBRATE;
notification.defaults |= Notification.DEFAULT_SOUND;
}
}
NotificationHelper.java
class NotificationHelper extends ContextWrapper {
public static final String channelID = "channelID";
public static final String channelName = "Channel Name";
private NotificationManager notificationManager;
public NotificationHelper(Context base) {
super(base);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createChannel();
}
}
@TargetApi(Build.VERSION_CODES.O)
private void createChannel() {
NotificationChannel channel = new NotificationChannel(channelID, channelName,
NotificationManager.IMPORTANCE_HIGH);
getManager().createNotificationChannel(channel);
}
public NotificationManager getManager() {
if (notificationManager == null) {
notificationManager = (NotificationManager)
getSystemService(Context.NOTIFICATION_SERVICE);
}
return notificationManager;
}
public NotificationCompat.Builder getChannelNotification() {
return new NotificationCompat.Builder(getApplicationContext(), channelID)
.setContentTitle("Meeldetuletus")
.setContentText("Teie valitud aeg on käes")
.setSmallIcon(R.drawable.ic_baseline_notifications_active_24);
}
}
CodePudding user response:
This is most likely happenning because in Your onTimeSet
and onDateSet
callback methods You are initializing the c
variable - the one that is supposed to represent the date on which the alarm should be fired and thus whenever You are setting a new date or time the c
variable loses the previously set data:
@Override
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
c = Calendar.getInstance(); // This code is causing the trouble
c.set(Calendar.HOUR_OF_DAY, hourOfDay);
c.set(Calendar.MINUTE, minute);
c.set(Calendar.SECOND, 0);
}
public void onDateSet (DatePicker view, int aasta, int kuu, int paev) {
c = Calendar.getInstance(); // This code is also causing the trouble
c.set(Calendar.YEAR, aasta);
c.set(Calendar.MONTH, kuu);
c.set(Calendar.DAY_OF_MONTH, paev);
}
Now, the straightforward solution would be to initialize the c
in Your onCreate
lifecycle method once:
Calendar c;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_meeldetuletus);
c = Calendar.getInstance() // Initialize 'c' here!
...
}
And remove the redundant initialization in the callback methods:
@Override
public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
c.set(Calendar.HOUR_OF_DAY, hourOfDay);
c.set(Calendar.MINUTE, minute);
c.set(Calendar.SECOND, 0);
}
public void onDateSet (DatePicker view, int aasta, int kuu, int paev) {
c.set(Calendar.YEAR, aasta);
c.set(Calendar.MONTH, kuu);
c.set(Calendar.DAY_OF_MONTH, paev);
}
Now, whenever Your startAlarm(c)
method will be called it will use the data that was set from both onDateSet
and onTimeSet
or from only one of them - basically, the changes made in those methods won't be overriden by redundantly reinitializing the c
variable from those methods themselves.
CodePudding user response:
The Answer by sweak seems correct about you needlessly instantiating new Calendar
objects.
In addition, the Calendar
class is terrible, and should be avoided. Sun, Oracle, and the JCP gave up on the legacy date-time classes when they appointed JSR 310 defining the modern java.time classes.
I’ve not studied all your code in detail, but at a glance it looks like you need to end up with simply a count of milliseconds from the epoch reference of the first moment of 1970 in UTC (an offset from UTC of zero hours-minutes-seconds). We can easily do that using only java.time classes.
Your code seems to be ignoring the crucial issue of time zone. If omitted from your code, the JVM’s current default time zone is implicitly applied. I suggest it is better to be explicit. Furthermore, the JVM’s current default time zone can be changed at any moment by any code in any app within the same JVM.
ZoneId z = ZoneId.systemDefault() ; // Or ZoneId.of( "Europe/Paris" )
LocalDate ld = LocalDate.of( 2022 , 1 , 23 ) ;
LocalTime lt = LocalTime.of( 15 , 30 ) ;
ZonedDateTime zdt = ZonedDateTime.of( ld , lt , z ) ;
Instant instant = zdt.toInstant() ;
long millis = instant.toEpochMilli() ;