You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

电商APP基于FCM推送:如何存储通知并在RecyclerView中展示

实现FCM通知本地存储与RecyclerView展示方案

1. 定义通知数据模型

先创建结构化的通知实体类,包含展示和存储所需的核心字段:

// Kotlin示例,Java可直接转换为对应实体类
data class NotificationModel(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    val title: String,
    val content: String,
    val timestamp: Long,
    val isRead: Boolean = false
)

2. 本地持久化存储(Room数据库)

推荐用Room处理结构化通知数据,比SharedPreferences更适合多数据的查询、排序和更新操作:

2.1 创建Dao接口

@Dao
interface NotificationDao {
    @Insert
    suspend fun insertNotification(notification: NotificationModel)

    @Query("SELECT * FROM NotificationModel ORDER BY timestamp DESC")
    fun getAllNotifications(): Flow<List<NotificationModel>> // 用Flow监听数据变化,自动更新列表

    @Update
    suspend fun updateNotification(notification: NotificationModel)
}

2.2 构建Room数据库实例

@Database(entities = [NotificationModel::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun notificationDao(): NotificationDao

    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getDatabase(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "app_notification_db"
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }
}

3. 处理FCM消息接收与存储

在自定义的FirebaseMessagingService中捕获通知,并存入Room:

class MyFirebaseMessagingService : FirebaseMessagingService() {
    private lateinit var notificationDao: NotificationDao

    override fun onCreate() {
        super.onCreate()
        val db = AppDatabase.getDatabase(this)
        notificationDao = db.notificationDao()
    }

    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        super.onMessageReceived(remoteMessage)
        remoteMessage.notification?.let { fcmNotification ->
            val title = fcmNotification.title ?: "促销通知"
            val content = fcmNotification.body ?: ""
            val timestamp = System.currentTimeMillis()

            // 用协程异步插入,避免阻塞主线程
            CoroutineScope(Dispatchers.IO).launch {
                notificationDao.insertNotification(
                    NotificationModel(
                        title = title,
                        content = content,
                        timestamp = timestamp
                    )
                )
            }

            // 可选:弹出系统通知,点击跳转至通知列表页
            showSystemNotification(title, content)
        }
    }

    private fun showSystemNotification(title: String, content: String) {
        val channelId = "promotion_notification_channel"
        val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        // Android O及以上需创建通知渠道
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                channelId,
                "促销通知",
                NotificationManager.IMPORTANCE_DEFAULT
            )
            notificationManager.createNotificationChannel(channel)
        }

        val intent = Intent(this, NotificationListActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
        }
        val pendingIntent = PendingIntent.getActivity(
            this, 0, intent,
            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
        )

        val builder = NotificationCompat.Builder(this, channelId)
            .setSmallIcon(R.drawable.ic_notification)
            .setContentTitle(title)
            .setContentText(content)
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setContentIntent(pendingIntent)
            .setAutoCancel(true)

        notificationManager.notify(System.currentTimeMillis().toInt(), builder.build())
    }
}

别忘了在AndroidManifest.xml中注册该Service:

<service
    android:name=".MyFirebaseMessagingService"
    android:exported="false">
    <intent-filter>
        <action android:name="com.google.firebase.MESSAGING_EVENT" />
    </intent-filter>
</service>

4. 用RecyclerView展示通知列表

4.1 创建列表Adapter

class NotificationAdapter(
    private val notifications: List<NotificationModel>,
    private val onItemClick: (NotificationModel) -> Unit
) : RecyclerView.Adapter<NotificationAdapter.NotificationViewHolder>() {

    inner class NotificationViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val tvTitle: TextView = itemView.findViewById(R.id.tv_notification_title)
        val tvContent: TextView = itemView.findViewById(R.id.tv_notification_content)
        val tvTime: TextView = itemView.findViewById(R.id.tv_notification_time)
        val unreadDot: View = itemView.findViewById(R.id.view_unread_dot)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NotificationViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_notification, parent, false)
        return NotificationViewHolder(view)
    }

    override fun onBindViewHolder(holder: NotificationViewHolder, position: Int) {
        val notification = notifications[position]
        holder.tvTitle.text = notification.title
        holder.tvContent.text = notification.content
        holder.tvTime.text = formatTimestamp(notification.timestamp)
        // 未读显示红点,已读隐藏
        holder.unreadDot.visibility = if (notification.isRead) View.GONE else View.VISIBLE

        holder.itemView.setOnClickListener {
            onItemClick(notification)
        }
    }

    override fun getItemCount() = notifications.size

    // 格式化时间显示
    private fun formatTimestamp(timestamp: Long): String {
        val now = System.currentTimeMillis()
        val diff = now - timestamp
        val minutes = diff / (1000 * 60)
        return when {
            minutes < 1 -> "刚刚"
            minutes < 60 -> "${minutes}分钟前"
            minutes < 1440 -> "${minutes/60}小时前"
            else -> SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(Date(timestamp))
        }
    }
}

4.2 列表项布局(item_notification.xml)

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:padding="12dp"
    android:gravity="center_vertical">

    <View
        android:id="@+id/view_unread_dot"
        android:layout_width="8dp"
        android:layout_height="8dp"
        android:background="@drawable/circle_red"
        android:layout_marginEnd="12dp"/>

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_notification_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="16sp"
            android:textStyle="bold"/>

        <TextView
            android:id="@+id/tv_notification_content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textSize="14sp"
            android:textColor="@color/gray_600"
            android:layout_marginTop="4dp"/>

        <TextView
            android:id="@+id/tv_notification_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="12sp"
            android:textColor="@color/gray_400"
            android:layout_marginTop="4dp"
            android:layout_gravity="end"/>
    </LinearLayout>
</LinearLayout>

4.3 通知列表页逻辑

class NotificationListActivity : AppCompatActivity() {
    private lateinit var notificationDao: NotificationDao

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_notification_list)

        val db = AppDatabase.getDatabase(this)
        notificationDao = db.notificationDao()
        val recyclerView = findViewById<RecyclerView>(R.id.recycler_view_notifications)
        recyclerView.layoutManager = LinearLayoutManager(this)

        // 监听数据库变化,自动更新列表
        lifecycleScope.launch {
            notificationDao.getAllNotifications().collect { notifications ->
                val adapter = NotificationAdapter(notifications) { notification ->
                    // 点击后标记为已读
                    lifecycleScope.launch(Dispatchers.IO) {
                        notificationDao.updateNotification(notification.copy(isRead = true))
                    }
                }
                recyclerView.adapter = adapter
            }
        }
    }
}

内容的提问来源于stack exchange,提问作者Tayyba Rani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 07:33:53