A mobile app that lets students sign attendance only while physically inside the classroom using GPS geofencing, prevents proxy sign-ins, and generates attendance reports instantly.
Built for Jomo Kenyatta University of Agriculture and Technology (JKUAT).
- JKUAT ID login + real email OTP via Supabase + optional biometric (fingerprint / Face ID)
- Dashboard showing today's classes with live session indicator
- GPS geofenced attendance with live map and distance status
- One sign-in per session (DB-enforced; proxy sign-ins blocked)
- Attendance history with percentage, risk level, filters
- Profile, FAQ, help screens
- Dashboard of today's units with "Start Session" / "View Report"
- Live session setup: pick classroom GPS pin, adjust geofence radius (10–100 m), set duration
- Real-time monitor: students appear live as they sign in
- Reports: bar chart per session, present/absent donut, student risk table, per-session drill-down
- Export-ready data model
- GPS verified at sign-in (Haversine distance)
- Session must be explicitly opened by the lecturer
unique(session_id, student_id)DB constraint + Supabase RLS policy rejects duplicate / out-of-class sign-ins- Account lockout after 3 failed OTP attempts
- Biometric second factor
- Go to https://supabase.com → New project (free tier is fine).
- In the dashboard, open SQL Editor → New query, paste the contents of
supabase/schema.sql, and click Run. This creates tables, RLS policies, and seeds demo users + units. - Open Authentication → Providers → Email and make sure it is enabled. Under Email auth uncheck "Confirm email" (so OTPs work on first login without clicking a link).
- Open Authentication → URL Configuration → Redirect URLs and add
gpsattendance://auth-callback(matches the appschemeand Google OAuth callback path). - Open Settings → API — copy the Project URL and the anon public key.
- Lecturer Gmail exception (enforced in the app): The only non-@jkuat.ac.ke address that may sign in as a lecturer is eugenegabriel.ke@gmail.com (
LECTURER_GMAIL_EXCEPTIONinsrc/lib/auth-helpers.ts). All other lecturers must use @jkuat.ac.ke. - Profiles RLS (required for course picker + linking seeded users): After
schema.sql/supabase/courses-migration.sql, runsupabase/fix-profiles-rls-link-and-update.sqlonce if your project was created before that policy existed. Without it, savingcourse_idor linkingauth_user_idon rows that started withauth_user_idnull will fail. For Google sign-in errors like Database error saving new user, check Authentication → Hooks (disable custom DB hooks temporarily) and ensure Google is not restricted to a domain that blocks personal Gmail.
Supabase uses the same signInWithOtp call for both flows. What the user receives depends on the email template, not the app code.
- In the dashboard go to Authentication → Email Templates.
- Open the Magic Link template (this is the one used for passwordless email sign-in).
- Include the OTP token in the body, for example:
Your login code is: {{ .Token }}
If the template only contains{{ .ConfirmationURL }}, users get a clickable link instead of a code. - A full HTML example (JKUAT logo + OTP only, no magic link) lives in
supabase/email-template-magic-link.html— copy its inner body/table content into the dashboard editor if it expects HTML fragments only. - Under Authentication → Providers → Email, set OTP length to 6 digits (Supabase default) so codes match the app. Optionally adjust Email OTP expiration.
The app cannot set the sender address. Configure it in Supabase:
- Project Settings → Auth → SMTP Settings — enable custom SMTP and enter host, port, username, and password from your mail provider.
- Set Sender email (and Sender name) to the address your provider allows you to send as (e.g.
eugene@technetium.co.ke). It must match SPF/DKIM or the provider’s “verified sender” rules, or mail will fail or go to spam.
cp .env.example .env
# edit .env and paste the two values from Settings → APIIf you build a standalone APK, these values are embedded at build-time.
After changing .env, rebuild and reinstall the APK.
npm install --legacy-peer-deps
npx expo startScan the QR with Expo Go on Android, or press a for an emulator.
The seed in schema.sql inserts placeholder emails like s001@demo.local. Before you can actually log in, update those rows to emails you can receive OTPs on:
update profiles set email = 'your.name+s001@gmail.com' where id = 'S001';
update profiles set email = 'your.name+l001@gmail.com' where id = 'L001';
-- repeat for any accounts you want to useThen in the app, log in by typing the student/staff ID (e.g. S001) → Supabase emails you a 6-digit code → enter it on the OTP screen.
- Log in as L001 (
Dr. Mwangi) — tap Start Session on any unit. - On session setup, your current GPS is the geofence centre. Adjust radius, tap Start Session.
- On another device (or after logging out), log in as S001. The live session appears on the dashboard with a red Go Sign Attendance button.
- Tap through to the GPS screen — if you are inside the geofence, Sign Attendance activates.
- Sign in → success screen → record appears instantly in the lecturer's live monitor (tab shows a gold pulsing dot).
- Lecturer taps End Session → report is generated on the Reports tab.
Demo IDs seeded:
| Role | ID | Name |
|---|---|---|
| Student | S001 | Brian Otieno |
| Student | S002 | Aisha Mohamed |
| Student | S003 | Kevin Kamau |
| Student | S004 | Faith Wanjiru |
| Lecturer | L001 | Dr. Mwangi |
| Lecturer | L002 | Prof. Njoroge |
GPS-Based-Student-Attendance-App/ # repo root — everything lives here now
├── logo.png # JKUAT logo (used as app icon/splash)
├── .env / .env.example # Supabase keys
├── app.json # Expo config, Android permissions
├── package.json
├── app/ # expo-router routes
│ ├── index.tsx # Splash
│ ├── (auth)/ # 10 auth screens
│ ├── (student)/ # 11 student screens (tabs + detail)
│ └── (lecturer)/ # 9 lecturer screens (tabs + detail)
├── src/
│ ├── theme.ts # JKUAT colour palette
│ ├── store.tsx # Auth context
│ ├── lib/
│ │ ├── geo.ts # Haversine geofence
│ │ └── supabase.ts # Supabase client
│ ├── data/ # Types + Supabase repo
│ └── components/ # Button, Card, Input, Pill, TopBar
├── supabase/
│ └── schema.sql # Run in Supabase SQL editor
└── assets/ # Icons / splash
No build needed — npx expo start + Expo Go app.
npx expo prebuild --platform android
cd android && ./gradlew assembleRelease
# APK lives in android/app/build/outputs/apk/release/Requires Android SDK (Android Studio) + JDK 17.
npm install -g eas-cli
eas login
eas build -p android --profile previewEAS emails you a signed APK in ~10 min.
- Expo SDK 54 + React Native 0.81 + TypeScript
- expo-router — file-based navigation (stacks + tabs)
- react-native-maps — live map + geofence circle
- expo-location — foreground GPS
- expo-local-authentication — biometric
- expo-notifications — push
- Supabase — Postgres, Row-Level-Security, email-OTP auth, realtime-ready
AttendEase is a single Expo client talking to Supabase for authentication and data. Geofence checks use on-device GPS and Haversine distance against the session geofence stored in the database; Postgres enforces uniqueness and RLS enforces who can read or write which rows.
flowchart LR
subgraph actors["People"]
STU["Students"]
LEC["Lecturers"]
end
subgraph device["Expo app on phone"]
APP["AttendEase\nReact Native + expo-router"]
end
subgraph supa["Supabase project"]
AUTH["Auth\nemail OTP, Google"]
PG[("Postgres\nRLS policies")]
SMTP["Email delivery\nbuilt-in or custom SMTP"]
end
STU --> APP
LEC --> APP
APP -->|"REST + auth session"| AUTH
APP -->|"SQL via PostgREST\nanon key + JWT"| PG
AUTH -->|"OTP / magic link mail"| SMTP
flowchart TB
subgraph routes["app/ routes"]
R_AUTH["(auth)"]
R_STU["(student)"]
R_LEC["(lecturer)"]
end
subgraph state["State & helpers"]
CTX["AuthProvider\nsrc/store.tsx"]
REPO["repo + types\nsrc/data/"]
GEO["geo.ts\nHaversine"]
end
subgraph native["Device APIs"]
LOC["expo-location"]
MAPS["react-native-maps"]
BIO["expo-local-authentication"]
STORE["AsyncStorage"]
end
SB["Supabase JS client\nsrc/lib/supabase.ts"]
R_AUTH --> CTX
R_STU --> CTX
R_LEC --> CTX
R_STU --> GEO
R_STU --> LOC
R_STU --> MAPS
R_AUTH --> BIO
CTX --> REPO
REPO --> SB
CTX --> SB
REPO --> STORE
erDiagram
profiles ||--o{ units : "lecturer teaches"
profiles ||--o{ sessions : "lecturer opens"
profiles ||--o{ attendance : "student signs"
units ||--o{ sessions : "scheduled as"
units ||--o{ attendance : "for unit"
sessions ||--o{ attendance : "per session"
profiles {
string id
string auth_user_id
string role
}
sessions {
string id
string status
string geofence_json
}
attendance {
string id
string coords_json
}
On GitHub and many Markdown viewers, the diagrams above render automatically. If your viewer does not support Mermaid, use the Mermaid Live Editor and paste the fenced blocks to export PNG or SVG.
| Token | Hex |
|---|---|
| Forest green | #1B5E20 |
| Crimson red | #B71C1C |
| Gold / amber | #F9A825 |
| White | #FFFFFF |
| Dark text | #1A1A1A |