Pin tasks to places. Get reminded when you arrive.
Location-first task manager for Android. Tasks fire when you walk into their geofence radius — never on a clock.
Built from the GeoTasker Design System (Claude-generated tokens, JSX prototypes, six language packs). React Native CLI 0.76, bare workflow, Hermes, Mapbox SDK v11, Transistorsoft background-geolocation, Notifee.
- Node 18+ (20.x LTS recommended)
- JDK 17 (Temurin / Zulu)
- Android Studio with SDK 34+ and an emulator or real device
- A Mapbox account with two tokens:
- Public token (
pk.…) — for runtime map rendering - Downloads token (
sk.…withDownloads:Readscope) — for fetching the SDK at build time
- Public token (
# 1. Install JS deps (already done if you scaffolded — re-run if node_modules is missing)
npm install --legacy-peer-deps
# 2. Mapbox public token (runtime)
cp .env.example .env
# Then edit .env and replace pk.placeholder_replace_me with your real public token.
# 3. Mapbox downloads token (build time)
# Add to ~/.gradle/gradle.properties (NOT committed):
echo 'MAPBOX_DOWNLOADS_TOKEN=sk.your_secret_token_here' >> ~/.gradle/gradle.propertiesThe downloads token must have the
Downloads:Readscope, otherwise gradle will 401 when fetching the Mapbox Maven artifact.
# Start Metro
npm start
# In another terminal — build and install on a connected device or emulator
npm run androidFirst build takes 5–10 min while gradle downloads native deps and compiles them. Subsequent builds are seconds.
src/
app/ root providers, bootstrap
navigation/ RNN root stack + bottom tabs + modal stack
screens/ Onboarding, Map, Tasks, AddTask, TaskDetail, Alerts, Settings
components/
primitives/ GTButton, GTFab, GTChip, GTSurface, GTSwitch, GTSlider
icons/ Phosphor wrappers (Ico namespace)
map/ TaskPin, ArrivalPing
task/ TaskCard, AlertRow
theme/ tokens.ts (CSS port), light.ts, dark.ts, useTheme
i18n/ 6 language packs + i18next init
services/
map/ Mapbox token init, style URLs
geofence/ GeofenceService.sync(), permissions, headlessTask
notifications/ Notifee channels, displayArrival, action handlers
storage/ MMKV singleton
store/ Zustand stores (tasks, settings) with MMKV persist
models/ Task, Alert, ids
utils/ distance (haversine + format), time (quiet hours), logger
- User pins a task →
tasksStore.addTask()→tasksStore.subscribetriggersGeofenceService.sync() GeofenceService.sync()reconciles registered geofences against active (non-done) tasks via the Transistorsoft library- Library runs a foreground service with a low-importance ongoing notification on channel
geotasker_background - When the user crosses a radius, the OS fires the geofence; the lib invokes the JS headless task
headlessTask.tsreads MMKV, checks quiet hours + per-task cooldown, fires Notifee on channelgeotasker_arrivalswithMark done+Snooze 15 minactions- Action presses route through
notifee.onBackgroundEvent(inindex.js) →handleNotifeeEvent→ store mutation
All visual decisions tokenized in src/theme/tokens.ts, mirrored from the design system's colors_and_type.css. Light/dark themes built in light.ts/dark.ts. ThemeProvider watches Appearance for the system theme mode.
6 packs in src/i18n/{en,hi,bn,mr,te,ta}.ts. Initial language = stored choice → device locale → en. Switching language in Settings live-updates everything (including the bottom-nav, since it consumes t()).
Emulator (UI only — geofences are unreliable without real GPS):
adb emu geo fix -73.98566 40.74844 # set lat/lonReal device (recommended for geofences):
- Fresh install → onboarding → grant fine + background location + notifications
- Tap "+" on Map → drop pin ~250 m from your current location → set radius 150 m → "Pin task here"
- Force-stop the app from system Settings (so we test the headless task path)
- Walk to the location
- Heads-up notification fires within ~30 s of crossing the radius
- Tap "Mark done" → app opens → task is checked off
- Reboot device, do not launch app, walk back into a different task's zone → notification still fires (verifies
RECEIVE_BOOT_COMPLETED+startOnBoot)
npm start # Metro bundler
npm run android # build + install Android app
npm run lint # ESLint
npm test # Jest
npx tsc --noEmit # type check| Issue | Fix |
|---|---|
| Gradle 401 on Mapbox Maven repo | Set MAPBOX_DOWNLOADS_TOKEN in ~/.gradle/gradle.properties (with Downloads:Read scope) |
| Map renders but tile fetches fail | MAPBOX_PUBLIC_TOKEN missing or wrong in .env |
| Background notification never fires after kill | Manufacturer battery optimization: add GeoTasker to "no battery restrictions" in Android Settings → Apps |
| Fonts fall back to Roboto | Verify android/app/src/main/assets/fonts/ contains all 8 TTFs |
| Reanimated cryptic build error | Check babel.config.js: react-native-reanimated/plugin MUST be the last plugin |
| Notifee actions don't trigger | All 3 contexts must be wired: onBackgroundEvent (in index.js, before AppRegistry), onForegroundEvent (in App.tsx), and getInitialNotification (in linking.ts) |
| Snooze re-fires while user lingers | Per-task 60-min cooldown in MMKV. Tune COOLDOWN_MS in src/services/geofence/headlessTask.ts |