A mobile app that helps locals and tourists find legal fishing spots in Singapore, understand regulations, identify fish species, and get practical fishing tips — enhanced with real-time weather, tide, marine conditions, and fish activity data from multiple live APIs.
| Layer | Technology |
|---|---|
| Backend | Spring Boot 3.x + Java 17 |
| Frontend | React Native (Expo SDK 51) |
| Database | H2 (embedded, file-based) |
| HTTP Client | Spring WebClient (async API calls) |
| Caching | Caffeine (TTL-based) + ConcurrentHashMap |
| Build | Gradle (backend), Expo CLI (frontend) |
- 10 fishing spots across Singapore with GPS coordinates, difficulty ratings, and facilities info
- 8 fish species with identification details, best bait, and seasonal advice
- 8 regulations from PUB and NParks with severity indicators
- 8 fishing tips organized by category and difficulty
- Real-time conditions for each spot:
- Weather (temperature, wind, humidity, UV, rainfall) via NEA/Open-Meteo
- Tides (status, next high/low, solunar rating) via TideCheck
- Marine conditions (wave height, sea temperature, currents) via Open-Meteo Marine
- Air quality (PSI, PM2.5) via NEA
- Barometric pressure with fish activity prediction
- Moon phase and sunrise/sunset
- Composite Fishing Score (0-100) calculated from 7 environmental factors
- Fallback API chains — if one data source fails, the next takes over
- Offline-capable — seed data always available, conditions show "Unavailable" when APIs fail
- Pull-to-refresh on all screens
- Sort spots by fishing conditions or name
- Filter spots by type (shore, offshore, reservoir)
- Java 17+ (for the Spring Boot backend)
- Node.js 18+ (for the React Native mobile app)
- Expo CLI (
npm install -g expo-cli) - Expo Go app on your phone (Android/iOS) for testing
cd backend
./gradlew bootRunThe backend starts on http://localhost:8080. The H2 database is auto-created and seeded with fishing spots, species, regulations, and tips on first run.
Verify it's working:
curl http://localhost:8080/api/spots
curl http://localhost:8080/api/conditions/1cd mobile
npm install --legacy-peer-deps
npx expo startScan the QR code with Expo Go (Android/iOS), or press a for Android emulator / i for iOS simulator.
Note: The mobile app connects to http://10.0.2.2:8080 on Android emulator and http://localhost:8080 on iOS simulator. If using a physical device, update the base URL in mobile/services/api.js to your machine's local IP.
The app works out of the box with no API keys — free, keyless APIs (data.gov.sg NEA, Open-Meteo) provide weather, marine, air quality, and pressure data.
For enhanced data (tides, solunar ratings), set environment variables before starting the backend:
# Recommended for tide data (free: 50 req/day)
export TIDECHECK_API_KEY=your_key # Register at tidecheck.com/dashboard/login
# Optional fallbacks (enhance data but not required)
export STORMGLASS_API_KEY=your_key # Free: 10 req/day at stormglass.io/register
export WORLDTIDES_API_KEY=your_key # Free tier at worldtides.info| API | Data Provided |
|---|---|
| data.gov.sg (NEA) | Weather forecasts, temperature, wind, humidity, UV, rainfall, PSI, PM2.5 |
| Open-Meteo | Weather fallback, barometric pressure |
| Open-Meteo Marine | Wave height, sea temperature, ocean currents |
| Open-Meteo Air Quality | PM2.5, PM10 fallback |
| API | Data Provided | Free Tier |
|---|---|---|
| TideCheck | Tide times, solunar rating, moon phase, sunrise/sunset | 50 req/day |
| StormGlass | Marine data fallback, tide fallback | 10 req/day |
| WorldTides | Tide extremes fallback | Limited free |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/spots |
List all fishing spots |
| GET | /api/spots/{id} |
Spot details |
| GET | /api/spots?type=shore |
Filter by type (shore/offshore/reservoir) |
| GET | /api/spots?zone=East |
Filter by zone (East/West/North/South/Central/Northeast) |
| GET | /api/species |
List all fish species |
| GET | /api/species/{id} |
Species details |
| GET | /api/regulations |
List all regulations |
| GET | /api/regulations/{id} |
Regulation details |
| GET | /api/tips |
List all fishing tips |
| GET | /api/tips/{id} |
Tip details |
| GET | /api/conditions/{spotId} |
Full live conditions for a spot |
| GET | /api/conditions/all |
Summary conditions (score + label) for all spots |
| GET | /api/conditions/weather |
Current Singapore-wide weather |
| GET | /api/conditions/tides |
Current Singapore tide status |
Each spot gets a composite score from 0-100 based on real-time environmental conditions:
| Component | Points | Best Condition |
|---|---|---|
| Solunar Rating | 0-25 | 5 stars (peak fish activity) |
| Tide Phase | 0-15 | Moving tide (rising/falling) |
| Barometric Pressure | 0-15 | Falling (fish most active) |
| Weather | 0-15 | Clear or overcast skies |
| Wind Speed | 0-10 | Under 15 km/h |
| Wave Height | 0-10 | Under 1m (calm seas) |
| Air Quality (PSI) | 0-10 | Under 50 (good) |
Score Labels: 85-100 Excellent | 65-84 Good | 40-64 Fair | 0-39 Poor
SGFishing/
├── backend/
│ ├── src/main/java/com/sgfishing/
│ │ ├── SgFishingApplication.java # Spring Boot entry point
│ │ ├── controller/
│ │ │ ├── SpotController.java # /api/spots endpoints
│ │ │ ├── SpeciesController.java # /api/species endpoints
│ │ │ ├── RegulationController.java # /api/regulations endpoints
│ │ │ ├── TipController.java # /api/tips endpoints
│ │ │ └── ConditionsController.java # /api/conditions endpoints
│ │ ├── model/
│ │ │ ├── FishingSpot.java # Spot entity (JPA)
│ │ │ ├── FishSpecies.java # Species entity (JPA)
│ │ │ ├── Regulation.java # Regulation entity (JPA)
│ │ │ ├── FishingTip.java # Tip entity (JPA)
│ │ │ └── FishingConditions.java # Composite conditions DTO
│ │ ├── repository/ # Spring Data JPA repositories
│ │ ├── service/
│ │ │ ├── FishingScoreService.java # Score calculation + orchestration
│ │ │ ├── WeatherService.java # NEA + Open-Meteo weather
│ │ │ ├── TideService.java # TideCheck + WorldTides + StormGlass
│ │ │ ├── MarineService.java # Open-Meteo Marine + StormGlass
│ │ │ ├── AirQualityService.java # NEA PSI/PM2.5 + Open-Meteo
│ │ │ └── PressureService.java # Barometric pressure + fish impact
│ │ ├── config/
│ │ │ ├── DataSeeder.java # Seeds 10 spots, 8 species, 8 regs, 8 tips
│ │ │ ├── CacheConfig.java # Caffeine cache with TTLs
│ │ │ └── ApiKeyConfig.java # Environment variable API keys
│ │ └── scheduler/
│ │ └── DataRefreshScheduler.java # Periodic condition refresh
│ ├── src/main/resources/
│ │ └── application.properties
│ └── build.gradle
├── mobile/
│ ├── App.js # Tab + stack navigation
│ ├── screens/
│ │ ├── SpotsScreen.js # Spot list with scores, filters, sorting
│ │ ├── SpotDetailScreen.js # Spot info + live conditions panel
│ │ ├── SpeciesScreen.js # Species list
│ │ ├── SpeciesDetailScreen.js # Species details
│ │ ├── RegulationsScreen.js # Regulations with severity badges
│ │ └── TipsScreen.js # Expandable tips by category
│ ├── components/
│ │ ├── ConditionsPanel.js # Weather/tide/marine/pressure display
│ │ ├── FishingScoreBadge.js # Animated color-coded score circle
│ │ ├── TideChart.js # Tide visualization bars
│ │ ├── SpotCard.js # Spot summary with score badge
│ │ ├── SpeciesCard.js # Species summary card
│ │ └── RegulationCard.js # Regulation with severity styling
│ ├── services/
│ │ └── api.js # Axios API client
│ ├── constants/
│ │ └── theme.js # Colors, spacing, shadows
│ ├── app.json
│ └── package.json
├── PROJECT_PLAN.md
└── README.md
The app is designed to work gracefully when APIs are unreachable:
- Seed data (spots, species, regulations, tips) is stored in the embedded H2 database and always available
- Live conditions show "Conditions unavailable" with a retry button when APIs fail
- Fallback chains ensure maximum data availability — if NEA is down, Open-Meteo fills in; if TideCheck is down, WorldTides or StormGlass take over
- Cached data is served when APIs are temporarily unreachable
| Data Type | Refresh Interval | Cache TTL |
|---|---|---|
| Weather | 5 minutes | 5 minutes |
| Tides & Solunar | 3 hours | 1 hour |
| Marine Conditions | 30 minutes | 30 minutes |
| Air Quality | 30 minutes | 30 minutes |
| Barometric Pressure | 15 minutes | 15 minutes |