প্রতিদিন নিশ্চিত সময়ে নোটিফিকেশন (Daily Recurring)
এই গাইডটি দেখাবে কীভাবে প্রতিদিন একটি নির্দিষ্ট সময়ে (যেমন সকাল ৯:০০) নোটিফিকেশন দেখাতে হয়। আমরা AlarmManager.setRepeating() ব্যবহার করব দৈনিক রিপিটের জন্য।
প্রধান পার্থক্য: Expiration vs Daily
| ফিচার | Expiration (একবার) | Daily Recurring |
|---|---|---|
| Alarm Type | setExact / setExactAndAllowWhileIdle | setRepeating |
| ট্রিগার | একবার | প্রতিদিন (interval = 24h) |
| Use Case | অ্যাপয়েন্টমেন্ট, ডেডলাইন | দৈনিক রিমাইন্ডার, হ্যাবিট ট্র্যাকার |
ধাপ ১: প্রজেক্ট সেটআপ
Package: com.example.dailynotification
ধাপ ২: লেআউট (activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="দৈনিক নোটিফিকেশন সময় সেট করুন"
android:textSize="18sp"
android:layout_marginBottom="16dp"/>
<Button
android:id="@+id/btnTimePicker"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="সময় বেছে নিন"
android:onClick="showTimePicker"/>
<Button
android:id="@+id/btnEnable"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="দৈনিক নোটিফিকেশন চালু করুন"
android:onClick="enableDailyNotification"/>
<TextView
android:id="@+id/tvTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="নির্বাচিত: ০৯:০০ AM"
android:textSize="16sp"/>
</LinearLayout>
ধাপ ৩: MainActivity.java
package com.example.dailynotification;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.TimePickerDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.util.Calendar;
public class MainActivity extends AppCompatActivity
implements TimePickerDialog.OnTimeSetListener {
private static final String PREFS = "DailyNotifPrefs";
private static final String KEY_HOUR = "hour";
private static final String KEY_MINUTE = "minute";
private static final String KEY_ENABLED = "enabled";
private TextView tvTime;
private int hour = 9, minute = 0;
private AlarmManager alarmManager;
private PendingIntent pendingIntent;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvTime = findViewById(R.id.tvTime);
alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
loadPrefs();
updateTimeDisplay();
}
public void showTimePicker(View view) {
new TimePickerDialog(this, this, hour, minute, true).show();
}
@Override
public void onTimeSet(android.widget.TimePicker view, int hourOfDay, int minute) {
this.hour = hourOfDay;
this.minute = minute;
savePrefs();
updateTimeDisplay();
}
public void enableDailyNotification(View view) {
scheduleDailyAlarm();
saveEnabled(true);
findViewById(R.id.btnEnable).setEnabled(false);
findViewById(R.id.btnTimePicker).setEnabled(false);
}
private void scheduleDailyAlarm() {
Intent intent = new Intent(this, DailyNotificationService.class);
pendingIntent = PendingIntent.getService(
this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);
calendar.set(Calendar.SECOND, 0);
// যদি আজকের সময় পাস হয়ে গেছে, পরের দিন থেকে শুরু
if (calendar.getTimeInMillis() <= System.currentTimeMillis()) {
calendar.add(Calendar.DAY_OF_MONTH, 1);
}
long interval = AlarmManager.INTERVAL_DAY; // 24 ঘণ্টা
if (alarmManager != null) {
alarmManager.setRepeating(
AlarmManager.RTC_WAKEUP,
calendar.getTimeInMillis(),
interval,
pendingIntent
);
}
}
private void loadPrefs() {
SharedPreferences prefs = getSharedPreferences(PREFS, MODE_PRIVATE);
hour = prefs.getInt(KEY_HOUR, 9);
minute = prefs.getInt(KEY_MINUTE, 0);
}
private void savePrefs() {
getSharedPreferences(PREFS, MODE_PRIVATE).edit()
.putInt(KEY_HOUR, hour)
.putInt(KEY_MINUTE, minute)
.apply();
}
private void saveEnabled(boolean enabled) {
getSharedPreferences(PREFS, MODE_PRIVATE).edit()
.putBoolean(KEY_ENABLED, enabled).apply();
}
private void updateTimeDisplay() {
String amPm = hour >= 12 ? "PM" : "AM";
int displayHour = hour % 12 == 0 ? 12 : hour % 12;
tvTime.setText(String.format("নির্বাচিত: %02d:%02d %s", displayHour, minute, amPm));
}
}
ধাপ ৪: DailyNotificationService.java
package com.example.dailynotification;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import androidx.core.app.NotificationCompat;
public class DailyNotificationService extends Service {
private static final String CHANNEL_ID = "daily_notif_channel";
private static final String DEFAULT_CHANNEL = "default";
private static final int NOTIFICATION_ID = 1001;
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
showNotification();
stopSelf();
return START_NOT_STICKY;
}
private void showNotification() {
Intent notificationIntent = new Intent(this, MainActivity.class);
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(
this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
NotificationManager manager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, DEFAULT_CHANNEL)
.setContentTitle("দৈনিক রিমাইন্ডার")
.setContentText("আপনার দৈনিক কাজের সময়! ⏰")
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_REMINDER);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID, "দৈনিক নোটিফিকেশন",
NotificationManager.IMPORTANCE_HIGH);
channel.setDescription("প্রতিদিন নির্দিষ্ট সময়ে আসবে");
manager.createNotificationChannel(channel);
builder.setChannelId(CHANNEL_ID);
}
manager.notify(NOTIFICATION_ID, builder.build());
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
ধাপ ৫: BootCompleteReceiver.java (রিবুট Efter রিস্টার্ট)
package com.example.dailynotification;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Build;
import java.util.Calendar;
public class BootCompleteReceiver extends BroadcastReceiver {
private static final String PREFS = "DailyNotifPrefs";
private static final String KEY_HOUR = "hour";
private static final String KEY_MINUTE = "minute";
private static final String KEY_ENABLED = "enabled";
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
SharedPreferences prefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
if (prefs.getBoolean(KEY_ENABLED, false)) {
int hour = prefs.getInt(KEY_HOUR, 9);
int minute = prefs.getInt(KEY_MINUTE, 0);
scheduleDaily(context, hour, minute);
}
}
}
private void scheduleDaily(Context context, int hour, int minute) {
Intent serviceIntent = new Intent(context, DailyNotificationService.class);
PendingIntent pendingIntent = PendingIntent.getService(
context, 0, serviceIntent, PendingIntent.FLAG_IMMUTABLE);
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY, hour);
calendar.set(Calendar.MINUTE, minute);
calendar.set(Calendar.SECOND, 0);
if (calendar.getTimeInMillis() <= System.currentTimeMillis()) {
calendar.add(Calendar.DAY_OF_MONTH, 1);
}
if (alarmManager != null) {
alarmManager.setRepeating(
AlarmManager.RTC_WAKEUP,
calendar.getTimeInMillis(),
AlarmManager.INTERVAL_DAY,
pendingIntent
);
}
}
}
ধাপ ৬: AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
package="com.example.dailynotification">
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.DailyNotification">
<service
android:name=".DailyNotificationService"
android:enabled="true"
android:exported="false" />
<receiver
android:name=".BootCompleteReceiver"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
গুরুত্বপূর্ণ: setRepeating() এর লিমিটেশন (API 19+)
Android 4.4 (KitKat) থেকে `setRepeating()` ইনএক্সেক্ট হয়ে গেছে — সিস্টেম ব্যাটারি বাঁচাতে একাধিক অ্যাপের অ্যালার্ম বাচ করে একসাথে ফায়ার করে। এক্স্যাক্ট টাইমিং দরকার হলে:
বিকল্প ১: setExact + Reschedule (প্রতি এক্সিকিউশনে)
// Service-এ onStartCommand-এ
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
showNotification();
rescheduleNextDay(); // পরবর্তী দিনের জন্য Exact সেট করুন
return START_NOT_STICKY;
}
private void rescheduleNextDay() {
AlarmManager am = (AlarmManager) getSystemService(ALARM_SERVICE);
Intent intent = new Intent(this, DailyNotificationService.class);
PendingIntent pi = PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_IMMUTABLE);
Calendar cal = Calendar.getInstance();
cal.add(Calendar.DAY_OF_MONTH, 1);
cal.set(Calendar.HOUR_OF_DAY, 9); // ফিক্সড আওর
cal.set(Calendar.MINUTE, 0);
cal.set(Calendar.SECOND, 0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), pi);
} else {
am.setExact(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), pi);
}
}
বিকল্প ২: WorkManager (সুপারিশ করা, আধুনিক)
// build.gradle
implementation "androidx.work:work-runtime-ktx:2.9.0"
// Worker
public class DailyNotifyWorker extends Worker {
public DailyNotifyWorker(@NonNull Context context, @NonNull WorkerParameters params) {
super(context, params);
}
@NonNull
@Override
public Result doWork() {
// নোটিফিকেশন দেখান
return Result.success();
}
}
// Schedule (MainActivity-এ)
PeriodicWorkRequest request = new PeriodicWorkRequest.Builder(
DailyNotifyWorker.class, 24, TimeUnit.HOURS)
.setInitialDelay(calculateInitialDelay(), TimeUnit.MILLISECONDS)
.build();
WorkManager.getInstance(this).enqueue(request);
পরীক্ষা চেকলিস্ট
- সময় সেট করুন → "চালু করুন" চাপুন
- ডিভাইস রিবুট → পরের দিন সেট সময়ে নোটিফিকেশন আসছে কিনা
- ব্যাটারি সেভার অন থাকলে ও কাজ করছে কিনা (Exact alarm permission দরকার)
- অ্যাপ ফোর্স স্টপ করলে রিবুটে রিস্টোর হচ্ছে কিনা