【Kotlin/Android Studio】通知を任意の日時を指定して発行する方法!リマインダー機能
この記事からわかること
- Android Studio/Kotlinで通知機能の実装方法
- 通知のタイミングを任意の時間に指定するには?
- リマインダー機能
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
環境
- Android Studio:Flamingo
- Kotlin:1.8.20
- Android:13
任意の指定した未来の時間に通知を発行する方法
通知はアプリ内から発行して即座に届くより、数時間後や数日後に届くリマインダーのような使い方をすることのほうが多いと思います。ここからは「20XX年のX月XX日のXX時XX分に通知を発行する」方法を紹介していきます。
実装の流れ
- パーミッションの付与
- ブロードキャストを受け取り通知を発行するクラスの定義
- 日時を指定してブロードキャストを発行
実装するためにはAlarmManagerでブロードキャストの仕組みを利用していきます。詳細な使い方や仕組みは以下の記事を参考にしてください。
また通知自体の実装方法は以下の記事を参考にしてください。
1.パーミッションの付与
最初に通知とブロードキャストをアプリから利用するためにマニフェストファイルにパーミッションとレシーバーを追加していきます。
<!-- 追加 -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<uses-permission android:name="android.permission.USE_EXACT_ALARM"/>
<application>
<!-- 省略 -->
<activity>
<!-- 省略 -->
</activity>
<!-- 追加 -->
<receiver android:name=".ReceivedActivity"
android:process=":remote" />
</application>
2.ブロードキャストを受け取り通知を発行するクラスの定義
続いて先ほどレシーバーに設定したクラスを定義していきます。ここでは任意の時間に発行される予定のブロードキャストを受け取り、通知を発行する処理を記述していきます。
const val CHANNEL_ID = "CHANNEL_ID" // 共通のチャンネルID
class ReceivedActivity : BroadcastReceiver() {
val NOTIFY_ID = 1
override fun onReceive(context: Context, intent: Intent) {
// ブロードキャストを受け取る
val receivedData = intent.getStringExtra("message")
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
// 通知オブジェクトの作成
var builder = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle("通知のタイトル")
.setContentText(receivedData)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
// 通知の発行
with(NotificationManagerCompat.from(context)) {
notify(NOTIFY_ID, builder.build())
}
}
}
3.日時を指定してブロードキャストを発行
最後に任意の日時を指定してブロードキャストを発行するためのアラームをセットしていきます。通知のテキストのボタン押下時の時間が入るようにIntent経由でデータを渡してみました。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 許可ダイアログを表示
launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
// チャンネルの生成
createNotificationChannel()
// 通知を発行ボタン
val buttonNotification: Button = findViewById(R.id.button)
buttonNotification.setOnClickListener {
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val notificationIntent = Intent(this, ReceivedActivity::class.java)
// 通知のテキストにボタン押下時の時間を表示させる
val now = LocalDateTime.now()
val df = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")
val fdate = df.format(now)
notificationIntent.putExtra("message", fdate.toString())
val pendingIntent = PendingIntent.getBroadcast(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE)
val timeZone = TimeZone.getTimeZone("Asia/Tokyo")
val calendar = Calendar.getInstance(timeZone)
calendar.timeInMillis = 0
calendar.set(Calendar.YEAR, 2023) // 任意の年を設定
calendar.set(Calendar.MONTH, Calendar.OCTOBER) // 任意の月を設定
calendar.set(Calendar.DAY_OF_MONTH, 23) // 任意の日を設定
calendar.set(Calendar.HOUR_OF_DAY, 12) // 任意の時を設定
calendar.set(Calendar.MINUTE, 50) // 任意の分を設定
calendar.set(Calendar.SECOND, 0) // 任意の秒を設定
val triggerTime = calendar.timeInMillis // 指定した日時のミリ秒表現を取得
// アラームを設定
alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent)
}
}
// 通知チャンネルの作成
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = "チャンネル名"
val descriptionText = "チャンネルの説明"
val importance = NotificationManager.IMPORTANCE_HIGH
val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
description = descriptionText
}
// チャンネルをシステムに登録
val notificationManager: NotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
private val launcher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { result ->
// ダイアログの結果で処理を分岐
if (result) {
Toast.makeText(this, "許可されました", Toast.LENGTH_SHORT)
.show()
} else {
Toast.makeText(this, "否認されました", Toast.LENGTH_SHORT)
.show()
}
}
}
全体のコード
import android.app.AlarmManager
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.Calendar
import android.Manifest
const val CHANNEL_ID = "CHANNEL_ID"
class ReceivedActivity : BroadcastReceiver() {
val NOTIFY_ID = 1
override fun onReceive(context: Context, intent: Intent) {
val receivedData = intent.getStringExtra("message")
val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
// 通知オブジェクトの作成
var builder = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_launcher_foreground)
.setContentTitle("通知のタイトル")
.setContentText(receivedData)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
// 通知の発行
with(NotificationManagerCompat.from(context)) {
notify(NOTIFY_ID, builder.build())
}
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 許可ダイアログを表示
launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
// チャンネルの生成
createNotificationChannel()
// 通知を発行ボタン
val buttonNotification: Button = findViewById(R.id.button)
buttonNotification.setOnClickListener {
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val notificationIntent = Intent(this, ReceivedActivity::class.java)
val now = LocalDateTime.now()
val df = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss")
val fdate = df.format(now)
notificationIntent.putExtra("message", fdate.toString())
val pendingIntent = PendingIntent.getBroadcast(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE)
val timeZone = TimeZone.getTimeZone("Asia/Tokyo")
val calendar = Calendar.getInstance(timeZone)
calendar.timeInMillis = 0
calendar.set(Calendar.YEAR, 2023) // 任意の年を設定
calendar.set(Calendar.MONTH, Calendar.OCTOBER) // 任意の月を設定
calendar.set(Calendar.DAY_OF_MONTH, 23) // 任意の日を設定
calendar.set(Calendar.HOUR_OF_DAY, 13) // 任意の時を設定
calendar.set(Calendar.MINUTE, 4) // 任意の分を設定
calendar.set(Calendar.SECOND, 0) // 任意の秒を設定
val triggerTime = calendar.timeInMillis // 指定した日時のミリ秒表現を取得
alarmManager.setExact(AlarmManager.RTC_WAKEUP, triggerTime, pendingIntent)
}
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = "チャンネル名"
val descriptionText = "チャンネルの説明"
val importance = NotificationManager.IMPORTANCE_HIGH
val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
description = descriptionText
}
// チャンネルをシステムに登録
val notificationManager: NotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
private val launcher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { result ->
// ダイアログの結果で処理を分岐
if (result) {
Toast.makeText(this, "許可されました", Toast.LENGTH_SHORT)
.show()
} else {
Toast.makeText(this, "否認されました", Toast.LENGTH_SHORT)
.show()
}
}
}
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。