[ 개인플젝 ] Android 푸시 알림 (React Native + OneSignal + Supabase)

김보람's avatar
Jan 21, 2026
[ 개인플젝 ] Android 푸시 알림 (React Native + OneSignal + Supabase)

기술 스택

  • React Native

  • OneSignal (Push Notification)

  • Supabase

    • Database

    • RPC Function

    • Edge Function (Deno)

  • Android (실제 디바이스 테스트)


전체 구조 요약

[React Native App]
  └─ OneSignal SDK
      └─ External User ID = Supabase user.id

[Supabase]
  ├─ records 테이블 (오늘 인증 여부)
  ├─ RPC: get_users_without_today_record()
  └─ Edge Function
      └─ OneSignal REST API 호출

1. OneSignal 기본 설정 (Android)

(1) OneSignal App 생성

  • Android App 추가

  • Firebase 설정 (FCM JSON 업로드)

  • Android Platform 활성화 확인

(2) OneSignal App ID 확인

Settings → Keys & IDs → OneSignal App ID

2. React Native에 OneSignal 연동

(1) 설치

yarn add react-native-onesignal
cd ios && pod install

(2) App.tsx 초기화

useEffect(() => {
  OneSignal.initialize(ONESIGNAL_APP_ID);
  OneSignal.Notifications.requestPermission(true);
}, []);

(3) 로그인 성공 시 External ID 연결

OneSignal.login(user.id);
  • initialize앱 실행 시 1회

  • login로그인 성공 직후 1회

  • render 중 호출 / 여러 번 호출 안되도록 주의


3. Supabase: 오늘 인증 안 한 유저 조회 RPC

create or replace function get_users_without_today_record(today_date date)
returns table (id uuid)
language sql
as $$
  select u.id
  from users u
  where not exists (
    select 1
    from records r
    where r.user_id = u.id
      and r.record_date = today_date
  );
$$;

4. Supabase Edge Function에서 푸시 발송

(1) Edge Function 생성

supabase functions new send_daily_reminder

(2) Edge Function Secrets 설정

Supabase Dashboard → Edge Functions → Secrets

SUPABASE_URL
SUPABASE_SERVICE_ROLE_KEY
ONESIGNAL_APP_ID
ONESIGNAL_API_KEY   ← REST API Key (os_v2_...)

Edge Function 코드

import { serve } from "https://deno.land/std/http/server.ts";
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";

serve(async () => {
  const supabase = createClient(
    Deno.env.get("SUPABASE_URL")!,
    Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
  );

  const appId = Deno.env.get("ONESIGNAL_APP_ID")!;
  const apiKey = Deno.env.get("ONESIGNAL_API_KEY")!;

  // KST 기준 오늘
  const now = new Date();
  now.setHours(now.getHours() + 9);
  const today = now.toISOString().slice(0, 10);

  const { data: users } = await supabase.rpc(
    "get_users_without_today_record",
    { today_date: today }
  );

  if (!users || users.length === 0) {
    return new Response("No users to notify");
  }

  const res = await fetch("https://onesignal.com/api/v1/notifications", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Basic ${apiKey}`,
    },
    body: JSON.stringify({
      app_id: appId,
      include_external_user_ids: users.map(u => u.id),
      target_channel: "push",
      headings: {
        en: "Daily workout reminder",
        ko: "오늘 인증 아직이에요",
      },
      contents: {
        en: "Don't forget to log your workout 💪",
        ko: "지금 운동 인증하고 기록 남겨요 💪",
      },
    }),
  });

  const text = await res.text();
  console.log("OneSignal response:", text);

  return new Response(text);
});

배포:

supabase functions deploy send_daily_reminder --use-api

5.있었던 이슈

“All included players are not subscribed”

{
  "errors": ["All included players are not subscribed"]
}

External ID는 존재하지만
푸시를 받을 수 있는 “Subscription”이 없음

➡ OneSignal 사용자 상태 문제라고 지피티가 알려줌

해결 방법

(1) OneSignal Dashboard → Audience → Subscriptions

  • Android / Subscribed 항목이 떠야 함

  • Users 화면만 보면 안 됨

(2) 앱 완전 삭제 + 재설치

  • 앱 삭제

  • 디바이스 재부팅

  • 재설치

  • 알림 권한 허용

  • 로그인 → OneSignal.login(user.id)

(3) Subscriptions에 Android가 생긴 뒤에 Edge Function 호출

성공 시 응답:

{
  "id": "notification-id",
  "recipients": 1
}

Messages → Push 목록에 기록 생성됨

Share article

김보람 | 930802qhfka@gmail.com