πΉ Watch the demo walkthrough β the fastest way to see Loop working end to end.
Real-time geo-matching platform connecting cargo owners with vehicle drivers in Rwanda. Monorepo:
loop/
βββ mobile/ Flutter app (cargo owner + driver) β see mobile/README.md
βββ api/ NestJS REST API + PostgreSQL/PostGIS β see api/README.md
βββ admin/ Next.js admin (verification + metrics) β see admin/README.md
βββ docs/ engineering spec (BUILD_SPEC.md)
The API is the system of record (PostgreSQL/PostGIS). The mobile app and admin are clients of it.
- Try it
- Install the Android app (Android APK)
- Quick start (local development)
- Deployment
- Walkthrough video
- Screenshots
- Testing
- Status
- Pricing
- Technical report
- Roadmap (Future Works)
- Contributing
- License
| Link | |
|---|---|
| Admin (web) | https://loop-admin-prod.up.railway.app β sign in with the demo admin admin@loop.rw / Admin@2026, a throwaway account seeded for evaluation since there is no public admin signup at the moment. |
| API | https://loop-api-prod.up.railway.app Β· Swagger at /docs |
| Mobile app (Android APK) | Download and install β see Install the Android app below. Or build/run the Flutter app against the deployed API. |
| Demo video | https://drive.google.com/drive/folders/19V9SplFLdkcaqbnbVAdXDNMCyUUCHTqn?usp=drive_link |
The mobile app runs against the deployed API with no local backend:
cd mobile && flutter pub get
flutter run --dart-define=API_BASE_URL=https://loop-api-prod.up.railway.appAn Android emulator (with Google Play services, for push) or a physical device works β see mobile/README.md.
The release APK is already configured to talk to the hosted API β no local backend or setup is needed. It targets Android (Android 6.0 / API 23 and newer).
-
Download the APK to your Android phone from the latest release: Loop release APK β download the
loop-*.apkasset from the latest release. Or scan this QR code with the phone's camera:
(If you download it on a computer, transfer the file to the phone.)
-
Allow install from this source. Open the downloaded file. Android will ask to permit installs from your browser or file manager: tap Settings β allow from this source (the standard prompt for any app installed outside the Play Store), then go back and continue.
-
Install. If a "App blocked" or "Unsafe app blocked" message appears (Play Protect scanning an app it doesn't recognise), do not tap OK or Got it β that cancels the install. Instead tap More details (or the small expander), then Install anyway. Open Loop.
-
Grant permissions when asked β location (so nearby matching works) and notifications (for job/proposal push).
-
Create an account in the app (pick Cargo owner or Driver), or sign in. Everything runs against the live hosted backend.
To try the full owner β driver loop on one phone, register one account, sign out, and register the other role β or use two devices. The admin side (verification approval, metrics) is the web console linked in Try it.
β οΈ Gotcha β if the app times out on mobile data: on some Rwandan mobile networks (observed on MTN) the app can time out at login/registration while working fine on Wi-Fi. This is not an app bug β it is a routing problem between the mobile carrier and the host's (Railway's) edge IP, a documented Railway pattern that Railway staff attribute to the carrier's network, not the app. Workaround: use Wi-Fi or a different carrier. Durable fix: front the API with a Cloudflare-proxied custom domain β the plan and diagnosis are in DEPLOYMENT.md section 12. It was found on 10 July 2026, late in testing, because emulators, the dev network, and Wi-Fi all reach the API normally.
To build the APK yourself instead, see DEPLOYMENT.md section 6 (Mobile APK).
Prerequisites: Docker, Node.js 20+, and the Flutter SDK.
# 1. Database β PostgreSQL 17 + PostGIS 3.5 (host port 5433)
docker compose up -d
# 2. API β NestJS on http://localhost:3000 (Swagger at /docs)
cd api
cp .env.example .env # dev defaults: stub mail/storage/push
npm install
npm run migration:run # creates the schema + PostGIS extension
npm run seed # seeds the admin account + pricing/size config
npm run start:dev
# 3. Admin β Next.js on http://localhost:3001
cd ../admin
cp .env.example .env.local # NEXT_PUBLIC_API_BASE_URL=http://localhost:3000
npm install
PORT=3001 npm run dev # http://localhost:3001 (login: admin@loop.rw)
# 4. Mobile β Flutter (iOS simulator / Android emulator / device)
cd ../mobile
flutter pub get
flutter run --dart-define=API_BASE_URL=http://localhost:3000
# Android emulator: use http://10.0.2.2:3000 instead of localhost
# Deployed API (no local backend): use https://loop-api-prod.up.railway.appTo run the mobile app against the deployed backend, just point API_BASE_URL at the hosted API β see mobile/README.md and, for the release APK, DEPLOYMENT.md section 6 (Mobile APK).
The seeded admin credentials come from ADMIN_EMAIL / ADMIN_PASSWORD in api/.env (defaults: admin@loop.rw / change-me-admin). The seed also provisions a fixed demo admin β admin@loop.rw / Admin@2026 β so the deployed admin console can be signed into for evaluation without a public admin signup (DEMO_ADMIN_EMAIL / DEMO_ADMIN_PASSWORD override it). It is a throwaway evaluation login and will be archived (rotated or removed) once the defence is complete. Backend details and the full endpoint list are in api/README.md; the admin app is documented in admin/README.md.
Loop deploys to Railway as one project with three services (PostGIS DB, API, admin) plus external Firebase Storage/FCM and SendGrid. Full, reproducible steps β architecture, environment matrix, prerequisites, deploy commands, going-live, the mobile APK build, secrets handling, and a hosted-stack verification checklist β are in DEPLOYMENT.md.
πΉ Watch the walkthrough β the core loop end to end: driver verification, posting a job with a cost estimate, nearby matching, sending and accepting a proposal, in-app messaging, completing and rating, and the admin metrics dashboard.
The core flow, captured across both mobile apps and the admin console. More views (welcome, permissions) are in screenshots/.
Each package has an automated suite (API β Jest; admin β Vitest; mobile β Flutter), plus a manual end-to-end matrix and a post-deploy verification checklist. How to run each, the strategies, and the device/environment constraints are in TESTING.md.
cd api && npm test # pricing formula, proposal state machine, auth
cd admin && npm test # pagination + metrics formatters
cd mobile && flutter test # model / screen / widget testsAll milestones M1βM7 are built (the milestone plan is section 6 of docs/BUILD_SPEC.md):
- M1 β Foundation: monorepo, database schema for all core entities, NestJS-issued JWT auth (argon2, access + rotating refresh), driver verification + admin review.
- M2 β Matching: availability + location capture, PostGIS nearby-driver query (approved and online, nearest first),
flutter_map/OpenStreetMap map view + vehicle-type filter, vehicle CRUD. - M3 β Pricing + jobs: rule-based cost-estimate endpoint + editable config, pin-based job creation and posting (both the estimated cost and the owner-set price are persisted).
- M3.5 β Location: OpenStreetMap place/landmark search + reverse-geocoding + "Open in Maps" navigation hand-off.
- M4 β Transaction loop: proposals (accept/decline), in-app messaging (REST + WebSocket),
tel:call button, FCM push (stub-safe). - M5 β Trust: two-way ratings + portable reputation.
- M6 β Admin: Next.js verification queue + server-computed metrics dashboard + read-only drivers/users/jobs directory.
- M7 β Routing + pricing v2 + navigation: OSRM road routing (with a great-circle fallback), a distance-and-time cost estimate (see Pricing below), and driver in-app turn-by-turn navigation (route line, follow-me camera, voice, off-route rerouting), with "Open in Maps" kept as a secondary option.
The cost estimate is rule-based and transparent (not ML β there is no transaction history at cold start), computed server-side and rounded to whole RWF (a zero-decimal currency):
estimated_price = max( min_fare(vt),
base_fare(vt) + rate_per_km(vt) Γ distance_km
+ rate_per_min(vt) Γ duration_min
+ rate_per_kg(vt) Γ weight_kg )
Γ size_factor(size)
distance_km and duration_min come from OSRM road routing; if the router is unavailable the estimate falls back to the PostGIS great-circle distance and drops the time term (the weight term is dropped when weight is unknown). Every parameter is DB config (editable without a redeploy); the current v3 values (placeholders pending field research, whole RWF) are:
| Vehicle type | base_fare |
rate_per_km |
rate_per_min |
rate_per_kg |
min_fare |
|---|---|---|---|---|---|
| moto | 800 | 280 | 25 | 6 | 1,000 |
| pickup | 2,500 | 550 | 50 | 8 | 3,000 |
| van | 3,500 | 700 | 60 | 6 | 4,500 |
| small_truck | 5,000 | 1,000 | 90 | 4 | 7,000 |
| large_truck | 8,000 | 1,600 | 150 | 3 | 12,000 |
size_factor is 1.0 (small), 1.3 (medium), 1.6 (large), and reflects bulk/awkwardness, not weight β weight is priced separately via rate_per_kg. The estimate is a reference β the owner sets the final price, and both are stored on the job. Recent changes beyond this README are logged in docs/CHANGES_LOG.md.
The full write-up (how each proposal objective was met, a requirements-to-code traceability table, the analysis of results, discussion of the milestones, and future work notes) is in docs/TECHNICAL_REPORT.md.
Loop currently runs as a single Railway project (PostGIS DB + API + admin), chosen for pilot simplicity. The architecture is deliberately portable β Dockerised services, a standard DATABASE_URL, and an env-driven DB_SSL flag β so the planned production moves below are each a configuration change, not a rewrite:
- Database β managed Postgres + PostGIS (Supabase): automated backups + point-in-time recovery and a management dashboard, versus the pilot's self-managed container.
- Admin β Vercel: Next.js-native hosting with per-branch preview deployments.
- API β Fly.io (Johannesburg region): lower latency to users in Rwanda than EU-region hosting.
A product-side item is admin user management: today the single admin is seeded (no public admin signup, by design), and a later phase adds a super-admin who can create and manage other admin accounts from the admin console.
See DEPLOYMENT.md section 11 (Future / production migration) for the infrastructure detail. Other product/feature future work (payments, live driver tracking, traffic-aware routing and alternate routes) is tracked in docs/BUILD_SPEC.md.
main is protected and always deployable, so work happens on short-lived branches and lands through a pull request.
- Branch from
main:feat/<area>-<desc>,fix/<desc>, orchore/<desc>(e.g.feat/api-road-distance). - Commit in the conventional style β
feat(api): β¦,fix(mobile): β¦,docs: β¦. Keep messages clean, with no AI-attribution or co-author trailers. - Test the package you touched before opening the PR:
Per-package setup is in each package's README; conventions live in
cd api && npm test # NestJS (Jest) cd admin && npm test # Next.js (Vitest) cd mobile && flutter test # Flutter
docs/BUILD_SPEC.mdsection 9. - Open a pull request against
main. A review is required before merge; keep the change scoped to the MVP feature set (check it against "What we are building" and "Out of scope" in the docs).
Secrets never go in git β commit .env.example, never a real .env or a service-account key.
Proprietary β Β© 2026 Habib Josue Ahadi, all rights reserved. The source is available in this repository for reference and academic evaluation only; it may not be copied, modified, redistributed, or used without prior written permission. See LICENSE. Third-party dependencies remain under their own licenses.














