This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
FamilyFilmApp is an Android application that helps groups decide what movies to watch together. It tracks watched movies and watchlists across group members, using an algorithm to recommend movies everyone will enjoy.
Tech Stack: Kotlin, Jetpack Compose, MVVM, Hilt, Firebase (Auth/Firestore), Retrofit, Room, WorkManager
DO NOT automatically run tests or build commands after completing tasks unless explicitly requested by the user. The user will request test execution and builds when they consider it appropriate or will include it in specific task instructions.
Only run ./gradlew test or ./gradlew build when:
- The user explicitly asks for it
- The task instructions specifically include testing/building steps
This project uses ktlint for code style enforcement. After completing any task that involves code changes:
- ALWAYS run
./gradlew ktlintFormatto auto-format the code according to project standards - This ensures all code complies with the established style guidelines
- ktlint rules include Compose-specific linting via
io.nlopez.compose.rules:ktlint
Example workflow:
# After making code changes
./gradlew ktlintFormat
# Only if explicitly requested by user
./gradlew test
./gradlew buildWhen the user requests to "Actualice los ficheros whatsnew" (Update whatsnew files):
- Review recent changes: Check the git commit history on the current branch to understand what features, fixes, or improvements have been implemented
- Create a brief summary: Write a concise summary of the changes in no more than 3 lines
- Update all language files: Update the whatsnew files for each supported language in
/distribution/whatsnew/:whatsnew-en-GB(English)whatsnew-es-ES(Spanish)- Any other language files present
Guidelines:
- Keep the summary user-friendly and focused on user-facing changes
- Avoid technical jargon; focus on benefits to the user
- Prioritize the most impactful changes if there are many commits
- Maintain consistent tone across all language versions
Example workflow:
# Review recent commits
git log --oneline -10
# Update whatsnew files based on commits
# Then format if needed
./gradlew ktlintFormatBefore building, create local.properties in the project root with:
WEB_ID_CLIENT=<your-google-web-client-id>
TMDB_ACCESS_TOKEN=<your-tmdb-api-token>
ADMOB_APPLICATION_ID=<your-admob-app-id>
ADMOB_BOTTOM_BANNER_ID=<your-admob-banner-id>
Additionally, add google-services.json to the app/ directory from your Firebase project.
# Build the project
./gradlew build
# Run all tests
./gradlew test
# Run unit tests only
./gradlew testDebugUnitTest
# Run instrumentation tests
./gradlew connectedAndroidTest
# Run ktlint checks
./gradlew ktlintCheck
# Auto-format code with ktlint
./gradlew ktlintFormat
# Build release APK
./gradlew assembleRelease
# Clean build
./gradlew clean# Run a single test class
./gradlew test --tests com.apptolast.familyfilmapp.ui.screens.home.HomeViewModelTest
# Run a single test method
./gradlew test --tests com.apptolast.familyfilmapp.ui.screens.home.HomeViewModelTest.testSpecificMethodThe app follows clean architecture with MVVM:
ui/screens/{feature}/
├── {Feature}ViewModel.kt # @HiltViewModel, manages state
├── {Feature}UiState.kt # Extends BaseUiState (isLoading, errorMessage)
├── {Feature}Screen.kt # Composable UI
└── components/ # Screen-specific components
Key Conventions:
- All UI states extend
BaseUiStateinterface - State management uses
MutableStateFlowandStateFlow .collectAsStateWithLifecycle()in Compose to prevent memory leaks.update {}builder for thread-safe state modifications
Models exist in three forms with extension functions for conversion:
- Remote: API response models (
TmdbMovieRemote) - inmodel/remote/ - Domain: Business logic models (
Movie) - inmodel/local/ - Room: Database entities (
MovieTable) - inmodel/room/
Conversions: .toDomain(), .toRoom(), etc.
The repository layer abstracts three datasources:
- TmdbDatasource: TMDB API via Retrofit
- RoomDatasource: Local SQLite database
- FirebaseDatabaseDatasource: Firestore with real-time sync
Located in /repositories/, all datasources are injected via Hilt and exposed through the Repository interface.
The app uses an offline-first architecture with Repository Mediator pattern and write-through cache. Room is the single source of truth for the UI; Firebase syncs in the background.
Full documentation: See the
firebase-specialistagent (.claude/agents/firebase-specialist.md) for architecture diagrams, code patterns, best practices, and troubleshooting.
Key rules (always follow these):
- ViewModels observe Room Flows, NEVER Firebase directly
- Write to Firebase first, then immediately to Room (write-through)
- Call
startSync()in ViewModel init,stopSync()inonCleared() - Datasources are decoupled: Firebase and Room have NO dependency on each other
- Repository is the ONLY layer that knows about both datasources
Hilt modules in /di/:
- ApplicationModule: DispatcherProvider, CoroutineScope, WorkManager
- NetworkModule: Retrofit, OkHttpClient, TmdbApi
- FirebaseModule: FirebaseAuth, FirebaseFirestore
- GoogleSignInModule: Credential Manager for Google Sign-In
- LocalStoreModule: SharedPreferences, Room database, DAOs
- RepositoryModule: Repository implementations
All modules use @InstallIn(SingletonComponent::class) for app-scoped dependencies.
- Type: Jetpack Compose Navigation with
NavHost - Location:
/navigation/ - Routes: Login, Home, Groups, Profile, Details (defined in
Routes.kt) - Type Safety: Custom
navtypes/for passing complex objects (e.g.,Movie) - Pattern: Single Activity architecture - all navigation via Compose
AuthViewModel in /ui/sharedViewmodel/ provides authentication state across the entire app. It's shared by all screens to determine login status.
- Unit Tests:
/app/src/test/- ViewModel logic, MockK, Truth,runTest,MainDispatcherRule - Instrumentation Tests:
/app/src/androidTest/- Compose UI tests with Hilt test modules (FakeRepository, in-memory Room) - Test Runner:
CustomHiltTestRunner
- Version: 1.4.1
- Rules: Includes
io.nlopez.compose.rules:ktlintfor Compose-specific linting - Baseline:
ktlint-baseline.xmlfor suppressing existing violations - Auto-formatting: Run
./gradlew ktlintFormatbefore committing
- Language Features: Explicit backing fields enabled (
languageSettings.enableLanguageFeature("ExplicitBackingFields")) - Logging: Use Timber for all logging (
Timber.d(),Timber.e(), etc.) - Error Handling: Custom
CustomExceptionsealed class for domain errors - Async: Prefer coroutines + Flow over callbacks (except legacy Firebase SDK)
Use the
firebase-specialistagent for detailed Firebase architecture, sync patterns, and MCP operations.
- Auth (
FirebaseAuthRepository): Email/password, Google OAuth (Credential Manager), email verification, account deletion - Firestore (
FirebaseDatabaseDatasource): Real-time sync to Room. Structure:FFA/{buildType}/users|groups|movies - Crashlytics: Enabled in release, disabled in debug (
FamilyFilmApp.kt) - MCP: Firebase MCP server configured for direct Firestore/Auth queries from Claude Code
Use the
jira-specialistagent (.claude/agents/jira-specialist.md) for ticket creation, sprint management, epic organization, and development workflow tracking.
- Instance:
apptolast.atlassian.net - Project Key:
FFA - Board: FFA board (id: 1)
- MCP: Atlassian MCP server configured for direct Jira operations from Claude Code
Use the
github-specialistagent (.claude/agents/github-specialist.md) for PR management, code reviews, branch operations, and release management.
- Organization:
apptolast - Repository:
apptolast/FamilyFilmApp - Default Branch:
develop - MCP: GitHub MCP server configured for direct repo operations from Claude Code
Jira is the source of truth for task tracking. The
FFA-XXXcodes come from Jira.
- Create Jira ticket → Auto-generates
FFA-XXXkey - Create branch from
develop→feature/FFA-XXX-short-description - Implement → Commit messages reference
FFA-XXX - Create PR → Title:
FFA-XXX Description, target:develop - Code review → Team reviews and approves
- Squash and Rebase to
develop→ Maintains linear history - Cleanup → Delete branch, update local
develop
Branch naming: {type}/FFA-XXX-short-description where type is feature/, fix/, refactor/, test/
Merge strategy: Always squash and rebase to keep develop history linear and traceable via FFA-XXX codes.
build.yml: Runs on PRs and pushes to develop
- Builds project
- Runs unit tests
- Uploads test reports
- Submits dependency graph
Secrets Required:
FIREBASE_JSON: google-services.json contentWEB_ID_CLIENT: Google OAuth client IDTMDB_ACCESS_TOKEN: TMDB API keyADMOB_APPLICATION_ID: AdMob app IDADMOB_BOTTOM_BANNER_ID: AdMob banner ID
- Create
/ui/screens/{feature}/directory - Create
{Feature}ViewModelwith@HiltViewModelannotation - Create
{Feature}UiStatedata class extendingBaseUiState - Create
{Feature}Screen.ktcomposable - Add route to
Routes.kt - Update
AppNavigation()to include the new screen
- Add endpoint to
/network/TmdbApi.kt - Implement in
TmdbDatasourceImpl - Expose through
Repositoryinterface andRepositoryImpl - Call from ViewModel via injected repository
- Create entity in
/model/room/ - Create DAO in
/room/ - Add DAO to
AppDatabase.kt - Provide DAO in
LocalStoreModule.kt - Implement datasource access in
RoomDatasourceImpl
- Min SDK: 26 (Android 8.0)
- Target SDK: 36
- Compile SDK: 36
- JVM Target: 11
- Kotlin: 2.2.20
Release builds use ProGuard with minification and resource shrinking enabled. Rules in proguard-rules.pro.
Room schema directory: app/schemas/ - version controlled for database migrations.
Background synchronization is handled by the Repository layer with real-time Firebase listeners (see Firebase + Room Synchronization Architecture). The app no longer uses WorkManager for sync operations.
For other background tasks (if needed in the future), use WorkManager with Hilt integration via HiltWorker annotation.
Movie lists use Jetpack Paging 3 with MoviePagingSource for efficient data loading from TMDB API.