mcpField
A
android-notification-builder

android-notification-builder

github.com/dengineproblem開発63 回インストール
リポジトリ

android-notification-builder について

NotificationCompat.Builder と通知チャンネルを使った Android 通知の実装を支援するスキルです。展開可能な通知、操作可能な通知、進捗・グループ通知、FCM メッセージ処理の例を扱います。

主な機能

  1. 01通知チャンネルの作成
  2. 02基本的な通知の構築
  3. 03テキスト、画像、受信トレイ形式の展開表示
  4. 04操作ボタンの追加
  5. 05進捗通知とグループ通知
  6. 06FCM メッセージの処理

活用シーン

  1. 01Android アプリへのカテゴリ別通知の追加
  2. 02長文または画像通知の表示
  3. 03返信などの通知アクションの提供
  4. 04ダウンロード進捗やグループメッセージの表示

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/dengineproblem/agents-monorepo --skill android-notification-builder

インストール後、Claude Code で/android-notification-builderを入力して読み込まれたか確認します。

詳細

Android 通知ビルダーのエキスパート

NotificationCompat.Builder と通知チャンネルを使用した Android 通知の実装に関するエキスパートです。

通知チャンネル(API 26+)

class NotificationHelper(private val context: Context) {
    companion object {
        const val CHANNEL_ID_MESSAGES = "messages"
        const val CHANNEL_ID_UPDATES = "updates"
        const val CHANNEL_ID_PROMOTIONS = "promotions"
    }

    fun createNotificationChannels() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val messagesChannel = NotificationChannel(
                CHANNEL_ID_MESSAGES,
                "Сообщения",
                NotificationManager.IMPORTANCE_HIGH
            ).apply {
                description = "Уведомления о новых сообщениях"
                enableLights(true)
                lightColor = Color.BLUE
                enableVibration(true)
                vibrationPattern = longArrayOf(0, 250, 250, 250)
            }

            val updatesChannel = NotificationChannel(
                CHANNEL_ID_UPDATES,
                "Обновления",
                NotificationManager.IMPORTANCE_DEFAULT
            ).apply {
                description = "Системные обновления"
            }

            val notificationManager = context.getSystemService(NotificationManager::class.java)
            notificationManager.createNotificationChannels(
                listOf(messagesChannel, updatesChannel)
            )
        }
    }
}

基本的な通知

fun showBasicNotification(title: String, message: String) {
    val intent = Intent(context, MainActivity::class.java).apply {
        flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
    }

    val pendingIntent = PendingIntent.getActivity(
        context, 0, intent,
        PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
    )

    val notification = NotificationCompat.Builder(context, CHANNEL_ID_MESSAGES)
        .setSmallIcon(R.drawable.ic_notification)
        .setContentTitle(title)
        .setContentText(message)
        .setPriority(NotificationCompat.PRIORITY_HIGH)
        .setContentIntent(pendingIntent)
        .setAutoCancel(true)
        .build()

    NotificationManagerCompat.from(context).notify(NOTIFICATION_ID, notification)
}

拡張可能な通知

ビッグテキストスタイル
fun showExpandableTextNotification(title: String, shortText: String, longText: String) {
    val bigTextStyle = NotificationCompat.BigTextStyle()
        .bigText(longText)
        .setBigContentTitle(title)
        .setSummaryText("Подробности")

    val notification = NotificationCompat.Builder(context, CHANNEL_ID_MESSAGES)
        .setSmallIcon(R.drawable.ic_notification)
        .setContentTitle(title)
        .setContentText(shortText)
        .setStyle(bigTextStyle)
        .setPriority(NotificationCompat.PRIORITY_DEFAULT)
        .build()

    NotificationManagerCompat.from(context).notify(NOTIFICATION_ID, notification)
}
ビッグピクチャースタイル
fun showImageNotification(title: String, message: String, bitmap: Bitmap) {
    val bigPictureStyle = NotificationCompat.BigPictureStyle()
        .bigPicture(bitmap)
        .bigLargeIcon(null as Bitmap?)
        .setBigContentTitle(title)
        .setSummaryText(message)

    val notification = NotificationCompat.Builder(context, CHANNEL_ID_MESSAGES)
        .setSmallIcon(R.drawable.ic_notification)
        .setContentTitle(title)
        .setContentText(message)
        .setLargeIcon(bitmap)
        .setStyle(bigPictureStyle)
        .build()

    NotificationManagerCompat.from(context).notify(NOTIFICATION_ID, notification)
}
インボックススタイル
fun showInboxNotification(title: String, messages: List<String>) {
    val inboxStyle = NotificationCompat.InboxStyle()
        .setBigContentTitle(title)
        .setSummaryText("${messages.size} новых сообщений")

    messages.take(5).forEach { message ->
        inboxStyle.addLine(message)
    }

    val notification = NotificationCompat.Builder(context, CHANNEL_ID_MESSAGES)
        .setSmallIcon(R.drawable.ic_notification)
        .setContentTitle(title)
        .setContentText("${messages.size} новых сообщений")
        .setStyle(inboxStyle)
        .setNumber(messages.size)
        .build()

    NotificationManagerCompat.from(context).notify(NOTIFICATION_ID, notification)
}

インタラクティブな通知

アクションボタン
fun showNotificationWithActions(title: String, message: String) {
    // Reply action
    val replyIntent = Intent(context, ReplyReceiver::class.java)
    val replyPendingIntent = PendingIntent.getBroadcast(
        context, 0, replyIntent,
        PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
    )

    val remoteInput = RemoteInput.Builder("key_text_reply")
        .setLabel("Ответить")
        .build()

    val replyAction = NotificationCompat.Action.Builder(
        R.drawable.ic_reply,
        "Ответить",
        replyPendingIntent
    ).addRemoteInput(remoteInput).build()

    // Archive action
    val archiveIntent = Intent(context, ArchiveReceiver::class.java)
    val archivePendingIntent = PendingIntent.getBroadcast(
        context, 1, archiveIntent,
        PendingIntent.FLAG_IMMUTABLE
    )

    val archiveAction = NotificationCompat.Action.Builder(
        R.drawable.ic_archive,
        "Архивировать",
        archivePendingIntent
    ).build()

    val notification = NotificationCompat.Builder(context, CHANNEL_ID_MESSAGES)
        .setSmallIcon(R.drawable.ic_notification)
        .setContentTitle(title)
        .setContentText(message)
        .addAction(replyAction)
        .addAction(archiveAction)
        .build()

    NotificationManagerCompat.from(context).notify(NOTIFICATION_ID, notification)
}

進行状況の通知

fun showProgressNotification(title: String, maxProgress: Int, currentProgress: Int) {
    val notification = NotificationCompat.Builder(context, CHANNEL_ID_UPDATES)
        .setSmallIcon(R.drawable.ic_download)
        .setContentTitle(title)
        .setContentText("Загрузка: $currentProgress%")
        .setProgress(maxProgress, currentProgress, false)
        .setOngoing(true)
        .build()

    NotificationManagerCompat.from(context).notify(PROGRESS_NOTIFICATION_ID, notification)
}

fun showIndeterminateProgress(title: String) {
    val notification = NotificationCompat.Builder(context, CHANNEL_ID_UPDATES)
        .setSmallIcon(R.drawable.ic_sync)
        .setContentTitle(title)
        .setContentText("Синхронизация...")
        .setProgress(0, 0, true)
        .setOngoing(true)
        .build()

    NotificationManagerCompat.from(context).notify(PROGRESS_NOTIFICATION_ID, notification)
}

fun completeProgressNotification(title: String) {
    val notification = NotificationCompat.Builder(context, CHANNEL_ID_UPDATES)
        .setSmallIcon(R.drawable.ic_done)
        .setContentTitle(title)
        .setContentText("Загрузка завершена")
        .setProgress(0, 0, false)
        .build()

    NotificationManagerCompat.from(context).notify(PROGRESS_NOTIFICATION_ID, notification)
}

グループ化された通知

fun showGroupedNotifications(messages: List<Message>) {
    val GROUP_KEY = "com.example.MESSAGE_GROUP"

    // Individual notifications
    messages.forEachIndexed { index, message ->
        val notification = NotificationCompat.Builder(context, CHANNEL_ID_MESSAGES)
            .setSmallIcon(R.drawable.ic_message)
            .setContentTitle(message.sender)
            .setContentText(message.text)
            .setGroup(GROUP_KEY)
            .build()

        NotificationManagerCompat.from(context).notify(index, notification)
    }

    // Summary notification
    val summaryNotification = NotificationCompat.Builder(context, CHANNEL_ID_MESSAGES)
        .setSmallIcon(R.drawable.ic_message)
        .setContentTitle("${messages.size} новых сообщений")
        .setStyle(NotificationCompat.InboxStyle()
            .setBigContentTitle("${messages.size} новых сообщений")
            .setSummaryText("Сообщения"))
        .setGroup(GROUP_KEY)
        .setGroupSummary(true)
        .build()

    NotificationManagerCompat.from(context).notify(SUMMARY_ID, summaryNotification)
}

FCM との統合

class MyFirebaseMessagingService : FirebaseMessagingService() {

    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        remoteMessage.notification?.let { notification ->
            showNotification(
                notification.title ?: "Уведомление",
                notification.body ?: ""
            )
        }

        remoteMessage.data.isNotEmpty().let {
            handleDataMessage(remoteMessage.data)
        }
    }

    override fun onNewToken(token: String) {
        sendTokenToServer(token)
    }

    private fun showNotification(title: String, body: String) {
        val notification = NotificationCompat.Builder(this, CHANNEL_ID_MESSAGES)
            .setSmallIcon(R.drawable.ic_notification)
            .setContentTitle(title)
            .setContentText(body)
            .setPriority(NotificationCompat.PRIORITY_HIGH)
            .setAutoCancel(true)
            .build()

        NotificationManagerCompat.from(this).notify(
            System.currentTimeMillis().toInt(),
            notification
        )
    }
}

ベストプラクティス

セキュリティ
// Скрытие контента на lock screen
.setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
.setPublicVersion(publicNotification)

// Безопасные PendingIntent
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
パフォーマンス
// Оптимизация bitmap
fun scaleBitmap(bitmap: Bitmap, maxSize: Int = 256): Bitmap {
    val ratio = minOf(
        maxSize.toFloat() / bitmap.width,
        maxSize.toFloat() / bitmap.height
    )
    return Bitmap.createScaledBitmap(
        bitmap,
        (bitmap.width * ratio).toInt(),
        (bitmap.height * ratio).toInt(),
        true
    )
}
ユーザーエクスペリエンス
  • 適切な重要度レベルを使用します
  • 関連する通知をグループ化します
  • すばやく対応できるアクションを追加します
  • コンテンツ説明を通じてアクセシビリティを確保します
  • さまざまなバージョンの Android でテストします

コメント