ভবিষ্যৎ নির্দিষ্ট তারিখে একক নোটিফিকেশন শিডিউলিং
এই টিউটোরিয়ালটি দেখাবে কীভাবে ব্যবহারকারী একটি ভবিষ্যৎ তারিখ (এবং সময়) বেছে নিয়ে একবারেই নোটিফিকেশন শিডিউল করতে পারেন। এটি অ্যাপয়েন্টমেন্ট, ডেডলাইন, বা ইভেন্ট রিমাইন্ডারের জন্য আদর্শ।
কোয়ালিটি অফ লাইফ উন্নতি
মূল উদাহরণে শুধু DatePicker ছিল। আমরা এখানে DatePicker + TimePicker উভয়ই যোগ করব যাতে ব্যবহারকারী পুরো ডেট-টাইম বেছে নিতে পারে।
ধাপ ১: প্রজেক্ট সেটআপ
Package: com.example.futurenotification
ধাপ ২: লেআউট (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="20sp"
android:textStyle="bold"
android:layout_marginBottom="24dp"/>
<Button
android:id="@+id/btnDate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="তারিখ বেছে নিন"
android:onClick="showDatePicker"/>
<Button
android:id="@+id/btnTime"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="সময় বেছে নিন"
android:onClick="showTimePicker"/>
<Button
android:id="@+id/btnSchedule"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:text="নোটিফিকেশন শিডিউল করুন"
android:onClick="scheduleNotification"
android:backgroundTint="#4CAF50"/>
<TextView
android:id="@+id/tvSelectedDateTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="নির্বাচিত: কোনো তারিখ/সময় নেই"
android:textSize="16sp"/>
</LinearLayout>
ধাপ ৩: MainActivity.java
package com.example.futurenotification;
import android.app.AlarmManager;
import android.app.DatePickerDialog;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.TimePickerDialog;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationCompat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
public class MainActivity extends AppCompatActivity
implements DatePickerDialog.OnDateSetListener, TimePickerDialog.OnTimeSetListener {
public static final String CHANNEL_ID = "future_notif_channel";
private static final String DEFAULT_CHANNEL = "default";
private TextView tvDateTime;
private final Calendar selectedCalendar = Calendar.getInstance();
private AlarmManager alarmManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvDateTime = findViewById(R.id.tvSelectedDateTime);
alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
// ডিফল্ট: পরের দিন সকাল ৯:০০
selectedCalendar.add(Calendar.DAY_OF_MONTH, 1);
selectedCalendar.set(Calendar.HOUR_OF_DAY, 9);
selectedCalendar.set(Calendar.MINUTE, 0);
selectedCalendar.set(Calendar.SECOND, 0);
updateDisplay();
}
public void showDatePicker(View view) {
new DatePickerDialog(this, this,
selectedCalendar.get(Calendar.YEAR),
selectedCalendar.get(Calendar.MONTH),
selectedCalendar.get(Calendar.DAY_OF_MONTH)).show();
}
public void showTimePicker(View view) {
new TimePickerDialog(this, this,
selectedCalendar.get(Calendar.HOUR_OF_DAY),
selectedCalendar.get(Calendar.MINUTE), true).show();
}
@Override
public void onDateSet(android.widget.DatePicker view, int year, int month, int dayOfMonth) {
selectedCalendar.set(Calendar.YEAR, year);
selectedCalendar.set(Calendar.MONTH, month);
selectedCalendar.set(Calendar.DAY_OF_MONTH, dayOfMonth);
updateDisplay();
}
@Override
public void onTimeSet(android.widget.TimePicker view, int hourOfDay, int minute) {
selectedCalendar.set(Calendar.HOUR_OF_DAY, hourOfDay);
selectedCalendar.set(Calendar.MINUTE, minute);
selectedCalendar.set(Calendar.SECOND, 0);
updateDisplay();
}
public void scheduleNotification(View view) {
long triggerTime = selectedCalendar.getTimeInMillis();
long now = System.currentTimeMillis();
if (triggerTime <= now) {
Toast.makeText(this, "ভুল: অতীতের তারিখ/সময় বেছে নেওয়া যাবে না", Toast.LENGTH_LONG).show();
return;
}
Notification notification = buildNotification("আপনার শিডিউল্ড ইভেন্টের সময় হয়েছে!");
scheduleExactNotification(notification, triggerTime);
Toast.makeText(this, "নোটিফিকেশন শিডিউল করা হয়েছে: " +
new SimpleDateFormat("dd/MM/yyyy hh:mm a", Locale.getDefault()).format(new Date(triggerTime)),
Toast.LENGTH_LONG).show();
}
private Notification buildNotification(String content) {
Intent notificationIntent = new Intent(this, MainActivity.class);
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
PendingIntent contentIntent = PendingIntent.getActivity(
this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, DEFAULT_CHANNEL)
.setContentTitle("শিডিউল্ড নোটিফিকেশন")
.setContentText(content)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentIntent(contentIntent)
.setAutoCancel(true)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setChannelId(CHANNEL_ID);
return builder.build();
}
private void scheduleExactNotification(Notification notification, long triggerTimeMillis) {
Intent intent = new Intent(this, NotificationPublisher.class);
intent.putExtra(NotificationPublisher.EXTRA_NOTIFICATION_ID, 1001);
intent.putExtra(NotificationPublisher.EXTRA_NOTIFICATION, notification);
PendingIntent pendingIntent = PendingIntent.getBroadcast(
this, 0, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT);
if (alarmManager != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP, triggerTimeMillis, pendingIntent);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTimeMillis, pendingIntent);
} else {
alarmManager.set(AlarmManager.RTC_WAKEUP, triggerTimeMillis, pendingIntent);
}
}
}
private void updateDisplay() {
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy hh:mm a", Locale.getDefault());
tvDateTime.setText("নির্বাচিত: " + sdf.format(selectedCalendar.getTime()));
}
}
ধাপ ৪: NotificationPublisher.java
package com.example.futurenotification;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
public class NotificationPublisher extends BroadcastReceiver {
public static final String EXTRA_NOTIFICATION_ID = "notification-id";
public static final String EXTRA_NOTIFICATION = "notification";
@Override
public void onReceive(Context context, Intent intent) {
NotificationManager manager = (NotificationManager)
context.getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = intent.getParcelableExtra(EXTRA_NOTIFICATION);
int id = intent.getIntExtra(EXTRA_NOTIFICATION_ID, 0);
if (notification == null) return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
MainActivity.CHANNEL_ID, "শিডিউল্ড নোটিফিকেশন",
NotificationManager.IMPORTANCE_HIGH);
manager.createNotificationChannel(channel);
}
manager.notify(id, notification);
}
}
ধাপ ৫: AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
package="com.example.futurenotification">
<uses-permission android:name="android.permission.VIBRATE" />
<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.FutureNotification">
<receiver
android:name=".NotificationPublisher"
android:enabled="true"
android:exported="true" />
<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>
Android 12+ (API 31+) Exact Alarm Permission Handling
// MainActivity.onCreate-এ বা scheduleNotification-এ শুরুতে
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
AlarmManager alarmManager = (AlarmManager) getSystemService(ALARM_SERVICE);
if (alarmManager != null && !alarmManager.canScheduleExactAlarms()) {
Intent intent = new Intent(android.provider.Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM);
startActivity(intent);
Toast.makeText(this, "দয়া করে 'Exact Alarm' অনুমতি দিন", Toast.LENGTH_LONG).show();
return;
}
}
রিবুট Efter রিস্টোর (BootCompleteReceiver)
শিডিউল করা অ্যালার্ম রিবুটে 클리ার হয়ে যায়। রিস্টোর করার জন্য:
// BootCompleteReceiver.java
public class BootCompleteReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
// SharedPreferences বা Database থেকে সব পেন্ডিং নোটিফিকেশন লোড করুন
// প্রতিটির জন্য scheduleExactNotification() কল করুন
}
}
}
// Manifest-এ যোগ করুন:
<receiver android:name=".BootCompleteReceiver" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
পরীক্ষা পদ্ধতি
- অ্যাপ চালু করুন → তারিখ বেছে নিন → সময় বেছে নিন → "শিডিউল করুন" চাপুন
- ১-২ মিনিট পরের সময় সেট করে টেস্ট করুন
- নির্দিষ্ট সময়ে নোটিফিকেশন আসছে কিনা চেক করুন
- নোটিফিকেশন ট্যাপ করলে অ্যাপ খুলছে কিনা
সেরা অনুশীলন (Best Practices)
- WorkManager ব্যবহার করুন যদি এক্স্যাক্ট টাইমিং বাধ্যতামূলক না হয় (ব্যাটারি-ফ্রেন্ডলি)
- Notification Channel সব সময় তৈরি করুন (API 26+)
- PendingIntent.FLAG_IMMUTABLE ব্যবহার করুন (API 23+)
- ব্যবহারকারীকে Exact Alarm Permission দিতে গাইড করুন (Android 12+)
- শিডিউল করা নোটিফিকেশন ক্যাঞ্চেল করার অপশন দিন (AlarmManager.cancel())