Loading...
Gym Management
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
- On login: call
/schedule, register local alarms viaAlarmManager+BroadcastReceiverfor gym reminder and water reminder. Schedule one-shot alarms for upcoming subscription expiry. - On app resume (foreground): call
/sync?since={last_synced_at}to get all missed notifications. Update local state and badge count. - While foregrounded: poll
/poll?since_id={latest_id}every 30–60 s. Whenhas_new: true, fetch new items and display in-app notification banner. - On notification tap: call
POST /notifications/{id}/readto mark it read. - WorkManager: (optional) schedule a periodic
SyncWorkerwith a 15-minute interval for background polling — this is the minimum WorkManager allows and does not require FCM.