|
| 1 | +# leaderboard_app |
| 2 | + |
| 3 | +Flutter application with backend integration using `dio` + `retrofit` for typed HTTP APIs. |
| 4 | + |
| 5 | +## Backend Integration |
| 6 | + |
| 7 | +We use: |
| 8 | + |
| 9 | +* `dio` for HTTP transport, interceptors, timeouts. |
| 10 | +* `retrofit` for declarative REST interface generation (`lib/services/core/rest_client.dart`). |
| 11 | +* `build_runner` + `retrofit_generator` (and `json_serializable` if/when model code generation is added). |
| 12 | + |
| 13 | +### Generating code |
| 14 | + |
| 15 | +Run code generation after updating API interface annotations: |
| 16 | + |
| 17 | +```bash |
| 18 | +dart run build_runner build --delete-conflicting-outputs |
| 19 | +``` |
| 20 | + |
| 21 | +### Using the REST client |
| 22 | + |
| 23 | +```dart |
| 24 | +import 'package:leaderboard_app/services/core/dio_provider.dart'; |
| 25 | +import 'package:leaderboard_app/services/core/rest_client.dart'; |
| 26 | +
|
| 27 | +final dio = await DioProvider.getInstance(); |
| 28 | +final api = RestClient(dio); |
| 29 | +
|
| 30 | +final start = await api.startVerification({'leetcodeUsername': 'someUser'}); |
| 31 | +final status = await api.getVerificationStatus('someUser'); |
| 32 | +``` |
| 33 | + |
| 34 | +Auth tokens (JWT) are automatically attached from `SharedPreferences` via an interceptor in `DioProvider`. |
| 35 | + |
| 36 | +### Environment / Base URL |
| 37 | + |
| 38 | +Centralized in `lib/config/api_config.dart`. |
| 39 | + |
| 40 | +Default baked-in base URL (when no override is supplied): |
| 41 | + |
| 42 | +``` |
| 43 | +http://140.238.213.170:3002/api |
| 44 | +``` |
| 45 | + |
| 46 | +Override at build/run time: |
| 47 | + |
| 48 | +```bash |
| 49 | +flutter run --dart-define=API_BASE_URL=https://your.api.host/api |
| 50 | +``` |
| 51 | + |
| 52 | +Release / CI example: |
| 53 | + |
| 54 | +```bash |
| 55 | +flutter build apk --dart-define=API_BASE_URL=https://prod.api.host/api |
| 56 | +``` |
| 57 | + |
| 58 | +Trailing slashes are trimmed automatically. Keep `/api` if your backend routes are under that prefix. |
| 59 | + |
| 60 | +### Adding new endpoints |
| 61 | + |
| 62 | +1. Edit `lib/services/core/rest_client.dart` – add a method with appropriate HTTP verb annotation. |
| 63 | +2. Run the build command above to regenerate `rest_client.g.dart`. |
| 64 | +3. Consume the new method from services or providers. |
| 65 | + |
| 66 | +### Logging & Retry |
| 67 | + |
| 68 | +`DioProvider` adds a lightweight log interceptor and simple retry (only once) for idempotent GET requests on connection errors. |
| 69 | + |
| 70 | +--- |
| 71 | + |
| 72 | +Generated code (`rest_client.g.dart`) should not be manually edited. |
| 73 | + |
| 74 | + |
| 75 | +## Building a Release APK / Sharing the App |
| 76 | + |
| 77 | +1. (Optional) Override the API base URL at build time (recommended for different envs): |
| 78 | + |
| 79 | +```bash |
| 80 | +flutter build apk --release --dart-define=API_BASE_URL=https://prod.api.host/api |
| 81 | +``` |
| 82 | + |
| 83 | +If you omit `--dart-define` the baked-in default from `ApiConfig` is used. |
| 84 | + |
| 85 | +2. The unsigned release APK will be at: |
| 86 | + |
| 87 | +``` |
| 88 | +build\app\outputs\flutter-apk\app-release.apk |
| 89 | +``` |
| 90 | + |
| 91 | +3. (Recommended) Create a keystore and configure signing in `android/key.properties` + `build.gradle` to avoid Play Store rejection and to allow in-place upgrades. |
| 92 | + |
| 93 | +### Example keystore creation (run once) |
| 94 | + |
| 95 | +```bash |
| 96 | +keytool -genkey -v -keystore my-release-key.keystore -alias upload -keyalg RSA -keysize 2048 -validity 10000 |
| 97 | +``` |
| 98 | + |
| 99 | +Place the keystore under `android/` (never commit to VCS) and add a `key.properties`: |
| 100 | + |
| 101 | +``` |
| 102 | +storePassword=YOUR_STORE_PASSWORD |
| 103 | +keyPassword=YOUR_KEY_PASSWORD |
| 104 | +keyAlias=upload |
| 105 | +storeFile=../my-release-key.keystore |
| 106 | +``` |
| 107 | + |
| 108 | +Then update `android/app/build.gradle` signingConfigs + buildTypes (if not already present). |
| 109 | + |
| 110 | +### Distributing for quick tests |
| 111 | + |
| 112 | +You can directly share `app-release.apk` with testers (they must enable install from unknown sources). For Play Store publishing prefer an AAB: |
| 113 | + |
| 114 | +```bash |
| 115 | +flutter build appbundle --dart-define=API_BASE_URL=https://prod.api.host/api |
| 116 | +``` |
| 117 | + |
| 118 | +## Troubleshooting: "Cannot reach server. Check BASE_URL..." |
| 119 | + |
| 120 | +This message originates from `ErrorUtils.fromDio` when the `DioExceptionType.connectionError` occurs. Common causes: |
| 121 | + |
| 122 | +| Cause | Fix | |
| 123 | +|-------|-----| |
| 124 | +| Device has no internet | Ensure Wi‑Fi/data works (open a website) | |
| 125 | +| Backend URL wrong or down | Open the URL in mobile Chrome to verify response | |
| 126 | +| Using `localhost` / private IP not reachable externally | Use a public/stable host or expose via tunneling (ngrok, Cloudflare) | |
| 127 | +| HTTP blocked (if you switch to HTTPS only) | Ensure correct scheme in `API_BASE_URL` | |
| 128 | +| Missing INTERNET permission | Manifest now includes `<uses-permission android:name="android.permission.INTERNET" />` | |
| 129 | + |
| 130 | +To quickly verify the URL the app is using, add a temporary log: |
| 131 | + |
| 132 | +```dart |
| 133 | +print('API base URL: ' + ApiConfig.baseUrl); |
| 134 | +``` |
| 135 | + |
| 136 | +Or run with an override: |
| 137 | + |
| 138 | +```bash |
| 139 | +flutter run --release --dart-define=API_BASE_URL=https://your-temp-api/api |
| 140 | +``` |
| 141 | + |
| 142 | +If the backend uses a self-signed certificate, Android may reject it—use a valid cert (Let's Encrypt) for production. |
| 143 | + |
| 144 | +## Future Enhancements (Optional) |
| 145 | + |
| 146 | +* Add build flavors: dev / staging / prod with per-flavor `--dart-define` presets. |
| 147 | +* Add environment banner in-app for non-prod. |
| 148 | +* Implement exponential backoff retries for transient network errors. |
| 149 | +* Add Sentry or similar for error monitoring. |
| 150 | + |
| 151 | +## Splash Screen & Offline Handling |
| 152 | + |
| 153 | +The app shows a native splash (configured via `flutter_native_splash`) while core services initialize. For returning users (flag stored in `SharedPreferences` as `returningUser`), dashboard data is preloaded (daily question, submissions if verified, leaderboard) before removing the splash to deliver a populated home view quickly. |
| 154 | + |
| 155 | +If there's no network connectivity at launch, a dedicated offline screen (`NoInternetPage`) is displayed. Connectivity is monitored with `connectivity_plus` through `ConnectivityProvider`; once a connection becomes available the app automatically proceeds with initialization and dismisses the splash. |
| 156 | + |
| 157 | +Update splash assets/colors in `pubspec.yaml` under `flutter_native_splash:` then regenerate: |
| 158 | + |
| 159 | +```bash |
| 160 | +flutter pub run flutter_native_splash:create |
| 161 | +``` |
| 162 | + |
| 163 | +Key files: |
| 164 | + |
| 165 | +* `lib/main.dart` – splash preservation & initialization logic (`_AppInitializer`). |
| 166 | +* `lib/provider/connectivity_provider.dart` – connectivity listener. |
| 167 | +* `lib/pages/no_internet_page.dart` – offline UI. |
| 168 | + |
| 169 | +To disable preloading behavior simply remove the `dashboardProvider.loadAll()` call in `_preload()`. |
| 170 | + |
0 commit comments