Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 

Repository files navigation

GreenStride — Eco-Rewards Android App

Rewards urban commuters with real discount coupons for walking, cycling, and using public transit. Built as an end-to-end production project — Android app + FastAPI backend + CI/CD — by a solo developer.

Android Backend Deploy Live


What is it?

GreenStride is a full-stack Android application that:

  • Tracks eco-friendly commutes (walking, cycling, public transit) via GPS speed analysis
  • Awards GreenCoins per 100 m of verified eco travel, with streak bonuses, daily challenges, and a spin wheel
  • Redeems coins for brand coupons (Zalando, Decathlon, Myntra, Nykaa…) via Awin / Admitad / VCommission affiliate networks
  • Saves coupons to Google Wallet with a single tap
  • Operates in two markets — EU (Germany-first) and India — with auto-detected regional coupon catalogues

Live landing page: greenstride-app.netlify.app
Live API: com-greenstride-android.fly.dev/health


Architecture

┌──────────────────────────────────────────────────────────────────┐
│                        Android App (Kotlin)                       │
│                                                                    │
│  ┌──────────────┐   ┌──────────────┐   ┌──────────────────────┐  │
│  │  UI Layer    │   │ Domain Layer │   │    Data Layer         │  │
│  │  Compose     │◄──│  Use Cases   │◄──│  Repositories        │  │
│  │  ViewModels  │   │  Domain Models│   │  RemoteDataSource    │  │
│  └──────────────┘   └──────────────┘   │  (Retrofit + OkHttp) │  │
│                                         └──────────┬───────────┘  │
└────────────────────────────────────────────────────┼─────────────┘
                                                      │ HTTPS/JWT
┌────────────────────────────────────────────────────▼─────────────┐
│                    FastAPI Backend (Python 3.12)                   │
│                                                                    │
│  /v1/auth   /v1/me   /v1/challenges   /v1/trips   /spin           │
│  /referral  /coupons  /offsets  /admin  /postback                 │
│                                                                    │
│  ┌──────────────┐   ┌───────────────┐  ┌──────────────────────┐  │
│  │  SQLAlchemy  │   │ Fraud Engine  │  │  Affiliate Sync      │  │
│  │  ORM + SQLite│   │ (GPS analysis)│  │  Awin · Admitad · VC │  │
│  └──────────────┘   └───────────────┘  └──────────────────────┘  │
│                                                                    │
│  Deployed on Fly.io (Frankfurt) · Auto-deployed via GitHub Actions │
└───────────────────────────────────────────────────────────────────┘

Android layers

Layer Pattern Key files
UI Jetpack Compose + ViewModel HomeScreen, WalletScreen, ChallengesScreen
Domain Use cases + Repository interfaces GetProfileUseCase, RedeemCouponUseCase
Data Repository impls + DTOs ApiRemoteDataSource, GreenStrideApi
DI Hilt (SingletonComponent) NetworkModule, RepositoryModule
Auth JWT stored in SessionManager + AuthInterceptor Injected into OkHttp chain

Backend layers

Component Technology Responsibility
API framework FastAPI + Pydantic v2 Request validation, OpenAPI docs
ORM SQLAlchemy 2.0 (DeclarativeBase) All DB access
Auth python-jose JWT Device-hash login, Bearer tokens
Scheduler APScheduler Awin/Admitad/VCommission coupon sync every 6 h
Fraud engine Pure Python GPS speed validation server-side
Deployment Fly.io (shared-cpu-1x, Frankfurt) Auto-deploy on backend/** push

Key Engineering Decisions

1. GPS Fraud Detection (server-side)

The most critical system in the app — prevents users from earning coins by driving a car or faking trips. Every trip is re-validated on the server regardless of what the client reports.

# backend/services/fraud_detection.py (excerpt)

_PROFILES: dict[str, _ModeProfile] = {
    "WALKING":     _ModeProfile(co2_per_km=0.21, coins_per_100m=2,  max_avg_kmh=8.0,   max_peak_kmh=15.0),
    "CYCLING":     _ModeProfile(co2_per_km=0.21, coins_per_100m=4,  max_avg_kmh=40.0,  max_peak_kmh=60.0),
    "UBAHN_SBAHN": _ModeProfile(co2_per_km=0.09, coins_per_100m=3,  max_avg_kmh=150.0, max_peak_kmh=200.0),
    "BUS_TRAM":    _ModeProfile(co2_per_km=0.09, coins_per_100m=2,  max_avg_kmh=70.0,  max_peak_kmh=100.0),
}

# Five independent signals checked per trip:
# Signal 1 — mode-specific peak speed cap
# Signal 2 — rolling-average speed cap
# Signal 3 — GPS-zero ratio (U-Bahn underground confirmation)
# Signal 4 — distance sanity (implied vs declared within 30%)
# Signal 5 — sample count vs duration (tamper detection)

Why per-mode profiles? An early version used a single 50 km/h ceiling, which flagged legitimate S-Bahn trips (60–120 km/h) as fraud. The refactor to per-mode profiles eliminated false positives entirely.

2. Zero-PII Authentication

Users register with a SHA-256 hash of their device ID — no email, no name, nothing reversible to a real person. This satisfies GDPR Art. 5(1)(c) data minimisation from first contact.

// AuthInterceptor.kt
override fun intercept(chain: Interceptor.Chain): Response {
    val token = sessionManager.getToken() ?: return chain.proceed(chain.request())
    val request = chain.request().newBuilder()
        .header("Authorization", "Bearer $token")
        .build()
    return chain.proceed(request)
}

3. Affiliate Coupon Pipeline

Three affiliate networks feed the coupon catalogue via scheduled background jobs. The Android app always reads from the backend's unified catalogue — network-specific API differences are invisible to the client.

Awin EU sync  ──┐
Awin IN sync  ──┼──► APScheduler (every 6h) ──► coupon_catalogue table ──► /v1/me/wallet
VCommission   ──┤
Admitad       ──┘

4. Clean Architecture + DI wiring

Switching from mock to real data source required changing one line in NetworkModule.kt:

// NetworkModule.kt
@Provides @Singleton
fun provideRemoteDataSource(api: ApiRemoteDataSource): RemoteDataSource = api
// was: fun provideRemoteDataSource(mock: MockRemoteDataSource): RemoteDataSource = mock

All ViewModels, use cases, and repositories were unaffected — the interface boundary absorbed the change completely.


Feature Set

Feature Android Backend
GPS trip tracking & auto mode detection ✅ Server validates
Coin ledger with streak multipliers
5 user levels (Green Starter → Climate Champion)
13 challenges (personal / city / employer)
Daily spin wheel
Referral programme
Coupon wallet (Awin + Admitad + VCommission)
Google Wallet integration ✅ Signed JWT
GDPR Art. 17 right-to-erasure ✅ In-app ✅ Grace-period deletion
CCPA §1798.120 opt-out
Consent audit log
Firebase Crashlytics + Analytics
GitHub Actions CI (unit tests + JaCoCo)
GitHub Actions CD (Fly.io auto-deploy)
Play Store release pipeline (Fastlane)

Tech Stack

Android

  • Language: Kotlin
  • UI: Jetpack Compose + Material 3
  • Architecture: Clean Architecture (UI → Domain → Data)
  • DI: Hilt
  • Async: Kotlin Coroutines + StateFlow
  • Networking: Retrofit 2 + OkHttp + Gson
  • Local storage: DataStore (preferences)
  • Analytics: Firebase Analytics + Crashlytics
  • Build: Gradle Kotlin DSL, local.properties secrets with CI env-var fallback

Backend

  • Language: Python 3.12
  • Framework: FastAPI 0.115 + Pydantic v2
  • ORM: SQLAlchemy 2.0 (DeclarativeBase)
  • Auth: python-jose JWT + bcrypt device-hash
  • Scheduler: APScheduler (async)
  • HTTP client: aiohttp (affiliate sync)
  • DB: SQLite (dev/staging) · PostgreSQL-ready
  • Deployment: Fly.io (Frankfurt) · Docker multi-stage build
  • CI/CD: GitHub Actions (deploy on backend/** push)

Selected Source Files

File What it shows
GreenStrideApi.kt Clean Retrofit interface covering all 15 backend endpoints
NetworkModule.kt Hilt DI wiring — interceptor chain, Retrofit, RemoteDataSource binding
fraud_detection.py Multi-signal GPS fraud engine with per-mode speed profiles
me.py Android-compatible API wrapper — maps DB schema to Android DTO schema
challenges.py Challenge progress engine — personal, city, and employer challenge types

CI / CD

Push to master (backend/**)
        │
        ▼
GitHub Actions: backend-deploy.yml
        │  flyctl deploy --remote-only
        ▼
Fly.io builds Docker image (python:3.12-slim, multi-stage)
        │
        ▼
Machine starts · lifespan() runs:
  create_all() → column migrations → seed challenges → start scheduler
        │
        ▼
Health check passes → traffic routed
Push to release/x.y.z branch
        │
        ▼
GitHub Actions: deploy.yml
  1. Unit tests (gate) — ./gradlew testDebugUnitTest
  2. Fastlane: build signed AAB → upload to Play Store
  3. Tag release vX.Y.Z

Privacy & Compliance

  • GDPR Art. 5(1)(c) — data minimisation: device hash only, no PII at signup
  • GDPR Art. 7 — explicit consent before affiliate tracking activates
  • GDPR Art. 17 — in-app erasure request: immediate deactivation, hard-delete in 30 days
  • GDPR Art. 20 — data export available in Settings
  • CCPA §1798.120 — opt-out of sale; no personal data sold
  • GPS raw samples never leave the device — only derived speed summary is sent

Screenshots

Onboarding Home / Dashboard Trip Tracking
Challenges Coupon Wallet Profile & Impact

This is a showcase repository. Source code is available to recruiters and hiring managers on request — please open a GitHub issue or email hello@greenstride.app.

About

Showcase: Full-stack eco-rewards Android app — Kotlin/Compose + FastAPI + GPS fraud detection + affiliate coupons

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages