Android Java app for F1 fans: MVVM architecture, JWT authentication, post CRUD, admin moderation panel, geolocation, and race data from an external XML API.
Mobile front-end of the FormulaFan project. See also FormulaFan Backend for the Spring Boot REST API that powers this app.
flowchart TD
subgraph PRESENTATION["PRESENTATION"]
style PRESENTATION fill:#dbeafe,stroke:#3b82f6
A["Activities\n(Login · Profile · CreatePost · EditPost)"]
F["Fragments\n(Home · Feed · UserPosts · Race · Location)"]
VM["ViewModels\n(RaceViewModel · CameraViewModel · LocationViewModel)"]
end
subgraph DATA["DATA"]
style DATA fill:#dcfce7,stroke:#22c55e
REPO["UserRepository\n(SharedPreferences + Gson)"]
AC["ApiClient\n(Retrofit + AuthorizationInterceptor)"]
SVC["Services\n(Auth · Post · Moderation · Profile · GrandPrix)"]
end
subgraph REMOTE["REMOTE / PERSISTENCE"]
style REMOTE fill:#fef9c3,stroke:#eab308
DB["SQLite + SharedPreferences"]
BE["FormulaFan Backend :8080"]
F1["F1 XML API"]
end
A & F --> VM
A & F --> REPO
REPO --> DB
VM --> AC
A & F --> AC
AC --> SVC
SVC --> BE
VM --> F1
| Layer | Package | Responsibilities |
|---|---|---|
| PRESENTATION | ui/ |
Activities, Fragments, ViewModels, Adapters. View Binding throughout. |
| DATA / NETWORK | data/api/ |
Retrofit service interfaces per domain, ApiClient, OkHttp interceptor |
| REPOSITORY | data/repository/ |
UserRepository: single source of truth for auth state (token + User) |
| MODELS | data/ |
DTOs: PostRequest/Response, Auth, ModerationRequest/Response, ProfileStat |
Navigation: 5-tab bottom nav via NavController / mobile_navigation.xml. Screens requiring full Activity lifecycle (login, post creation/editing, profile) are launched via Intent.
Context. Every request to the FormulaFan backend requires a Bearer token. Repeating the injection manually across 5 Retrofit service interfaces would be fragile and duplicated.
Decision.
ApiClient.AuthorizationInterceptor reads the token from UserRepository and automatically injects it into every request through the OkHttp chain.
Consequence.
Zero Bearer header duplication across Services. Any change to the authentication strategy is isolated to ApiClient.
Context. F1 race data (schedule, results) comes from a third-party XML API, not from the FormulaFan backend.
Decision.
HttpConnectAPI + F1ApiXmlParser consume the XML API independently from the Retrofit REST interfaces.
Consequence.
RaceViewModel orchestrates two heterogeneous sources. The FormulaFan backend stays focused on social logic (posts, users, moderation).
Context. Admins need access to a moderation panel. Regular users see a different home screen.
Decision.
HomeFragment checks user.role == "ADMIN" and renders either the hidden-posts list via ModerationService, or the default view.
Consequence.
A single Fragment handles two distinct user experiences. Adding a new role requires a targeted change in HomeFragment.isAdminUser().
Context. Two types of local data with distinct needs: authentication state (small, structured, must survive restarts) and photo file paths (a list that benefits from SQL queries).
Decision.
UserRepository uses SharedPreferences + Gson for the token and User object. FormulaFanDatabaseHelper (SQLite) stores camera photo paths.
Consequence. Each mechanism is sized for its use case. No oversized ORM for straightforward mobile persistence.
Context.
Some Fragments (RaceFragment, LocationFragment) manage rich UI state and complex lifecycles. Others (FeedFragment, HomeFragment) make simple one-shot Retrofit calls.
Decision.
Fragments with complex state use ViewModel + LiveData. Simpler Fragments call Retrofit directly via Callback.
Consequence. Less boilerplate for simple cases. New code should prefer ViewModel + LiveData to ensure long-term consistency.
Prerequisites:
- Android Studio Hedgehog+ (AGP 8.x)
- JDK 17+
- Android emulator API 30+ or a physical device
Backend required: start FormulaFan Backend on localhost:8080 before launching the app. The emulator reaches the host via 10.0.2.2 (configured in BuildConfig.BASE_URL).
git clone <repo> cd FormulaFan
Open in Android Studio and hit Run (Shift+F10), or from the command line:
./gradlew assembleDebug adb install app/build/outputs/apk/debug/app-debug.apk
| Concern | Technology |
|---|---|
| Language | Java (Android source/target compat: 1.8) |
| Platform | Android min SDK 30 / compile SDK 34 |
| Build | Gradle (Kotlin DSL) |
| HTTP | Retrofit 2.9.0 + OkHttp |
| Serialization | Gson |
| Image loading | Glide 4.16.0 |
| UI | Material 3 · ConstraintLayout · View Binding |
| Navigation | AndroidX Navigation 2.7.7 |
| State management | AndroidX ViewModel + LiveData 2.7.0 |
| Maps / Location | Google Maps SDK 18.2.0 + Location Services 21.2.0 |
| Local persistence | SQLite (photos) + SharedPreferences (auth) |
| Testing | JUnit 4.13.2 + Espresso 3.5.1 |
./gradlew test # Unit tests ./gradlew connectedAndroidTest # Instrumented tests (emulator required) ./gradlew build # Full build (assemble + test)