Feature Locked

Upgrade your plan to unlock this feature

Current Plan
Basic
Required Plan
Growth
What you'll unlock
Upgrade Now — View Plans

Secure payment · Instant access · Cancel anytime

Loading... Gym Management
U
App Settings

How Notifications Work

This system delivers notifications without FCM — no Firebase, no paid service, no server key required.

Architecture in one sentence: The server writes notifications to the database. The app reads them by polling a lightweight API endpoint every 30–60 seconds, and syncs the full list on open/resume. Scheduled reminders (gym time, water, subscription expiry) are set as local Android alarms by the app using data from /schedule.

Three Delivery Paths

Real-time Polling
App calls /poll?since_id=N every 30–60 seconds while open (foreground). Single DB query — very fast. When has_new: true, the app fetches full details from /notifications?since_id=N.
Works for: manual sends, automatic alerts
Sync on Open / Resume
App calls /sync?since=TIMESTAMP whenever the app is opened or brought to foreground. Returns all new notifications since the last sync (up to 100, last 30 days). Updates local state completely.
Works for: catching up after the app was closed
Local Android Alarms
App calls /schedule on login and on config change. Response contains gym reminder time, water reminder interval, and upcoming subscription dates. App uses AlarmManager to fire these locally — no server needed at notification time.
Works for: gym reminders, water reminders, expiry alerts
Android limitation (honestly stated): Polling only works when the app is in the foreground or background (not killed). If the user force-kills the app, polling stops until the app is reopened — at which point /sync catches up. Local alarms (AlarmManager) survive app kill but require the SCHEDULE_EXACT_ALARM permission and a BroadcastReceiver. There is no instant server-to-device delivery without a push transport (FCM). This is an intentional design choice to avoid third-party dependencies.

Member App — Notification API

All endpoints require Authorization: Bearer {token}.

1. Poll (every 30–60s while foregrounded)

GET /api/v1/member/notifications/poll?since_id={last_known_id}

Response:
{
  "success": true,
  "data": {
    "has_new":      true,
    "latest_id":    47,
    "unread_count": 3
  }
}

2. Fetch new notifications (when has_new = true)

GET /api/v1/member/notifications?since_id={last_known_id}
→ returns up to 50, ordered oldest-first

GET /api/v1/member/notifications?per_page=20
→ full paginated list (on first open)

3. Sync on open / resume

GET /api/v1/member/notifications/sync?since=2024-01-15T10:00:00Z

Response:
{
  "success":   true,
  "synced_at": "2024-01-20T08:30:00+05:30",
  "data":      [ ...notifications... ]
}

4. Schedule — local Android alarm setup

GET /api/v1/member/notifications/schedule

Response:
{
  "success": true,
  "data": {
    "gym_reminder": {
      "enabled": true,
      "time":    "06:30",          // schedule daily AlarmManager at this time
      "title":   "Time for Your Workout! 💪",
      "message": "Your gym session starts now. Get ready to crush your goals!"
    },
    "water_reminder": {
      "enabled":          true,
      "start_time":       "06:30",
      "end_time":         "08:30",
      "interval_minutes": 30,      // repeating alarm every N minutes in window
      "title":   "Hydration Reminder 💧",
      "message": "Time to drink water! Stay hydrated during your workout."
    },
    "upcoming": [
      {
        "id":           "sub_12_d2",
        "type":         "subscription",
        "title":        "Membership Expiring in 2 days",
        "message":      "Your membership expires in 2 days on 20 Jan 2024. Renew now!",
        "scheduled_at": "2024-01-18T08:00:00+05:30"
      }
    ]
  }
}

5. Unread count

GET /api/v1/member/notifications/unread-count
→ { "success": true, "data": { "count": 3 } }

6. Mark read

POST /api/v1/member/notifications/{id}/read
POST /api/v1/member/notifications/read-all

Notification Object Fields

{
  "id":                42,          // notification_recipients.id — use for since_id polling
  "notification_id":   17,          // notifications.id
  "is_read":           false,
  "read_at":           null,
  "created_at":        "2024-01-15T10:30:00+05:30",

  // Notification content
  "notification_type": "subscription",
  "category":          "subscription",
  "title":             "Membership Expiring Soon",
  "message":           "Your membership expires in 3 days. Renew now!",
  "image_url":         null,
  "scheduled_at":      null,             // non-null = app should show at this future time
  "data":              null,             // extra JSON (e.g. product details)
  "gym_id":            1
}

Trainer App — Notification API

Same pattern as member, under /api/v1/trainer/. No /schedule endpoint (trainers don't have gym/water reminders).

GET  /api/v1/trainer/notifications/poll?since_id=N
GET  /api/v1/trainer/notifications/sync?since=TIMESTAMP
GET  /api/v1/trainer/notifications?since_id=N
GET  /api/v1/trainer/notifications/unread-count
POST /api/v1/trainer/notifications/{id}/read
POST /api/v1/trainer/notifications/read-all

Recommended Android / Flutter Implementation

  1. On login: call /schedule, register local alarms via AlarmManager + BroadcastReceiver for gym reminder and water reminder. Schedule one-shot alarms for upcoming subscription expiry.
  2. On app resume (foreground): call /sync?since={last_synced_at} to get all missed notifications. Update local state and badge count.
  3. While foregrounded: poll /poll?since_id={latest_id} every 30–60 s. When has_new: true, fetch new items and display in-app notification banner.
  4. On notification tap: call POST /notifications/{id}/read to mark it read.
  5. WorkManager: (optional) schedule a periodic SyncWorker with a 15-minute interval for background polling — this is the minimum WorkManager allows and does not require FCM.