Background notifikasi berhasil dibuat
Add: -components/Notification/BackgroundNotificationHandler.tsx ### No Issue
This commit is contained in:
@@ -103,9 +103,9 @@ export default function Application() {
|
||||
}
|
||||
>
|
||||
<StackCustom>
|
||||
<ButtonCustom onPress={() => router.push("./test-notifications")}>
|
||||
{/* <ButtonCustom onPress={() => router.push("./test-notifications")}>
|
||||
Test Notif
|
||||
</ButtonCustom>
|
||||
</ButtonCustom> */}
|
||||
|
||||
<Home_ImageSection />
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { BackButton } from "@/components";
|
||||
import BackgroundNotificationHandler from "@/components/Notification/BackgroundNotificationHandler";
|
||||
import NotificationInitializer from "@/components/Notification/NotificationInitializer";
|
||||
import { NotificationProvider } from "@/hooks/use-notification-store";
|
||||
import { HeaderStyles } from "@/styles/header-styles";
|
||||
@@ -9,6 +10,7 @@ export default function ApplicationLayout() {
|
||||
<>
|
||||
<NotificationProvider>
|
||||
<NotificationInitializer />
|
||||
<BackgroundNotificationHandler />
|
||||
<ApplicationStack />
|
||||
</NotificationProvider>
|
||||
</>
|
||||
|
||||
@@ -6,7 +6,8 @@ import {
|
||||
StackCustom,
|
||||
TextCustom,
|
||||
} from "@/components";
|
||||
import { AccentColor } from "@/constants/color-palet";
|
||||
import { IconPlus } from "@/components/_Icon";
|
||||
import { AccentColor, MainColor } from "@/constants/color-palet";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { useNotificationStore } from "@/hooks/use-notification-store";
|
||||
import { apiGetNotificationsById } from "@/service/api-notifications";
|
||||
@@ -17,7 +18,9 @@ import { useCallback, useState } from "react";
|
||||
import { RefreshControl, View } from "react-native";
|
||||
|
||||
const selectedCategory = (value: string) => {
|
||||
const category = listOfcategoriesAppNotification.find((c) => c.value === value);
|
||||
const category = listOfcategoriesAppNotification.find(
|
||||
(c) => c.value === value
|
||||
);
|
||||
return category?.label;
|
||||
};
|
||||
|
||||
@@ -104,7 +107,12 @@ export default function AdminNotification() {
|
||||
options={{
|
||||
title: "Admin Notifikasi",
|
||||
headerLeft: () => <BackButton />,
|
||||
headerRight: () => <></>,
|
||||
headerRight: () => (
|
||||
<IconPlus
|
||||
color={MainColor.yellow}
|
||||
onPress={() => router.push("/test-notifications")}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
140
components/Notification/BackgroundNotificationHandler.tsx
Normal file
140
components/Notification/BackgroundNotificationHandler.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
// src/components/BackgroundNotificationHandler.tsx
|
||||
import { useNotificationStore } from "@/hooks/use-notification-store";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import {
|
||||
FirebaseMessagingTypes,
|
||||
getInitialNotification,
|
||||
getMessaging,
|
||||
onNotificationOpenedApp,
|
||||
} from "@react-native-firebase/messaging";
|
||||
import { router } from "expo-router";
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
const HANDLED_NOTIFICATIONS_KEY = "handled_notifications";
|
||||
|
||||
export default function BackgroundNotificationHandler() {
|
||||
const { addNotification, markAsRead } = useNotificationStore();
|
||||
const messaging = getMessaging();
|
||||
const unsubscribeRef = useRef<(() => void) | null>(null); // 🔑 cegah duplikasi
|
||||
|
||||
useEffect(() => {
|
||||
const init = async () => {
|
||||
// 1. Handle (cold start)
|
||||
const initialNotification = await getInitialNotification(messaging);
|
||||
if (initialNotification) {
|
||||
handleNotification(initialNotification);
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Handle background
|
||||
if (unsubscribeRef.current) {
|
||||
unsubscribeRef.current();
|
||||
}
|
||||
|
||||
const unsubscribe = onNotificationOpenedApp(
|
||||
messaging,
|
||||
(remoteMessage) => {
|
||||
handleNotification(remoteMessage);
|
||||
}
|
||||
);
|
||||
|
||||
unsubscribeRef.current = unsubscribe;
|
||||
};
|
||||
|
||||
init();
|
||||
|
||||
// Cleanup saat komponen unmount
|
||||
return () => {
|
||||
if (unsubscribeRef.current) {
|
||||
unsubscribeRef.current();
|
||||
unsubscribeRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [addNotification, messaging]);
|
||||
|
||||
const isNotificationHandled = async (
|
||||
notificationId: string
|
||||
): Promise<boolean> => {
|
||||
const handled = await AsyncStorage.getItem(HANDLED_NOTIFICATIONS_KEY);
|
||||
const ids = handled ? JSON.parse(handled) : [];
|
||||
return ids.includes(notificationId);
|
||||
};
|
||||
|
||||
const markNotificationAsHandled = async (notificationId: string) => {
|
||||
const handled = await AsyncStorage.getItem(HANDLED_NOTIFICATIONS_KEY);
|
||||
const ids = handled ? JSON.parse(handled) : [];
|
||||
if (!ids.includes(notificationId)) {
|
||||
ids.push(notificationId);
|
||||
// Simpan maksimal 50 ID terakhir untuk hindari memori bocor
|
||||
await AsyncStorage.setItem(
|
||||
HANDLED_NOTIFICATIONS_KEY,
|
||||
JSON.stringify(ids.slice(-50))
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleNotification = async (
|
||||
remoteMessage: FirebaseMessagingTypes.RemoteMessage
|
||||
) => {
|
||||
const { notification, data } = remoteMessage;
|
||||
if (!notification?.title) return;
|
||||
|
||||
console.log(
|
||||
"🚀 Notification received:",
|
||||
JSON.stringify(remoteMessage, null, 2)
|
||||
);
|
||||
|
||||
const notificationId = data?.id;
|
||||
if (!notificationId || typeof notificationId !== "string") {
|
||||
console.warn("Notification missing notificationId, skipping navigation");
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ Cek apakah sudah pernah ditangani
|
||||
if (await isNotificationHandled(notificationId)) {
|
||||
console.log("Notification already handled, skipping:", notificationId);
|
||||
return;
|
||||
}
|
||||
|
||||
// ✅ Tandai sebagai ditangani
|
||||
await markNotificationAsHandled(notificationId);
|
||||
|
||||
// ✅ Normalisasi deepLink: pastikan string
|
||||
let deepLink: string | undefined;
|
||||
if (data?.deepLink) {
|
||||
if (typeof data.deepLink === "string") {
|
||||
deepLink = data.deepLink;
|
||||
} else {
|
||||
// Jika object (jarang), coba string-kan
|
||||
deepLink = JSON.stringify(data.deepLink);
|
||||
}
|
||||
}
|
||||
|
||||
// Tambahkan ke UI state (agar muncul di daftar notifikasi & badge)
|
||||
addNotification({
|
||||
title: notification.title,
|
||||
body: notification.body || "",
|
||||
type: "announcement",
|
||||
data: data as Record<string, string>, // aman karena di-normalisasi di useNotificationStore
|
||||
});
|
||||
|
||||
markAsRead(data?.id as any);
|
||||
|
||||
// Navigasi
|
||||
if (
|
||||
data?.deepLink &&
|
||||
typeof data.deepLink === "string" &&
|
||||
data.deepLink.startsWith("/")
|
||||
) {
|
||||
setTimeout(() => {
|
||||
try {
|
||||
router.push(data.deepLink as any);
|
||||
} catch (error) {
|
||||
console.warn("Navigation failed:", error);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user