agentsclimarketplace

Notifications

Skill Shankulkarni/react-native-ai-plugin/plugins/rn-ai/skills/notifications

Write better React Native code from day one - opinionated Claude Code rules, New Architecture ready.

Install
npx -y skills add Shankulkarni/react-native-ai-plugin --skill notifications

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Use when implementing push notifications -- Expo Notifications setup, permissions, FCM/APNs token registration, foreground/background handlers, notification categories.

SKILL.md

5.1 KB, ~1.1k tokens by cl100k_base, as published. Nobody here has run it

Push Notifications -- Expo Notifications

Install

bunx expo install expo-notifications expo-device expo-constants

app.json:

{
  "expo": {
    "plugins": [
      ["expo-notifications", {
        "icon": "./assets/notification-icon.png",
        "color": "#6366f1",
        "sounds": ["./assets/notification.wav"]
      }]
    ],
    "ios": { "bundleIdentifier": "com.myorg.myapp" },
    "android": { "googleServicesFile": "./google-services.json" }
  }
}

Permission + Token Registration

// src/libs/notifications.ts
import * as Device from 'expo-device'
import * as Notifications from 'expo-notifications'
import Constants from 'expo-constants'
import { Platform } from 'react-native'

export async function registerForPushNotifications(): Promise<string | null> {
  if (!Device.isDevice) {
    console.warn('Push notifications require a physical device')
    return null
  }

  const { status: existing } = await Notifications.getPermissionsAsync()
  let finalStatus = existing

  if (existing !== 'granted') {
    const { status } = await Notifications.requestPermissionsAsync()
    finalStatus = status
  }

  if (finalStatus !== 'granted') return null

  const projectId = Constants.expoConfig?.extra?.eas?.projectId
  const token = await Notifications.getExpoPushTokenAsync({ projectId })

  if (Platform.OS === 'android') {
    await Notifications.setNotificationChannelAsync('default', {
      name: 'default',
      importance: Notifications.AndroidImportance.MAX,
      vibrationPattern: [0, 250, 250, 250],
    })
  }

  return token.data
}

Notification Handler

Set in root _layout.tsx before any component renders:

import * as Notifications from 'expo-notifications'

// How to display notifications when app is in foreground
Notifications.setNotificationHandler({
  handleNotification: async () => ({
    shouldShowAlert: true,
    shouldPlaySound: true,
    shouldSetBadge: true,
  }),
})

export default function RootLayout() {
  // ...
}

Hooks (src/hooks/use-notifications.ts)

import { useEffect, useRef } from 'react'
import * as Notifications from 'expo-notifications'
import { router } from 'expo-router'
import type { Href } from 'expo-router'
import { registerForPushNotifications } from '@/libs/notifications'

export function useNotifications() {
  const notificationListener = useRef<Notifications.EventSubscription>()
  const responseListener = useRef<Notifications.EventSubscription>()

  useEffect(() => {
    registerForPushNotifications().then(token => {
      if (token) {
        // Send token to your API
      }
    })

    // Fires when notification received in foreground
    notificationListener.current = Notifications.addNotificationReceivedListener(_notification => {
      // handle foreground notification -- e.g. update badge count or show in-app toast
    })

    // Fires when user taps notification
    responseListener.current = Notifications.addNotificationResponseReceivedListener(response => {
      const data = response.notification.request.content.data
      if (data.screen) {
        // notification payload is untyped -- validate screen values server-side before sending
        router.push(data.screen as Href)
      }
    })

    return () => {
      notificationListener.current?.remove()
      responseListener.current?.remove()
    }
  }, [])
}

Use in root _layout.tsx:

export default function RootLayout() {
  useNotifications()
  // ...
}

Handle App Launch from Notification

import * as Notifications from 'expo-notifications'
import { useEffect } from 'react'
import { router } from 'expo-router'
import type { Href } from 'expo-router'

export default function RootLayout() {
  useEffect(() => {
    // App opened from a notification
    Notifications.getLastNotificationResponseAsync().then(response => {
      const screen = response?.notification.request.content.data.screen
      if (screen) {
        // cold-start navigation from notification -- payload is untyped, validate screen values server-side
        router.push(screen as Href)
      }
    })
  }, [])
}

Local Notifications

// Schedule a local notification
await Notifications.scheduleNotificationAsync({
  content: {
    title: 'Reminder',
    body: 'Don\'t forget to check in!',
    data: { screen: '/(app)/(tabs)/' },
  },
  trigger: { seconds: 60 },  // 1 minute from now
})

// Cancel all scheduled
await Notifications.cancelAllScheduledNotificationsAsync()

Rules

  • Always request permissions before registering -- never assume granted
  • Always clean up listeners in useEffect return
  • Send push token to API after registration -- store server-side for sending
  • Handle getLastNotificationResponseAsync for cold-start navigation
  • Never show notification UI in JS -- use setNotificationHandler to control display
  • google-services.json must be in EAS secrets for CI -- never committed

What ships with it

Read from the repository

Just SKILL.md. No reference files, no scripts.

Keep looking

Skills are one crate of 326,970. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.