From afac1b944d77e940a5e9829945aefc7881cef0e8 Mon Sep 17 00:00:00 2001 From: Andres Hernandez Date: Fri, 19 Dec 2025 14:09:41 +0100 Subject: [PATCH] Added Copilot instructions file for the Android project --- .github/copilot-instructions.md | 959 ++++++++++++++++++++++++++++++++ 1 file changed, 959 insertions(+) create mode 100644 .github/copilot-instructions.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..4d8db16 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,959 @@ +# Alfie Android - GitHub Copilot Instructions + +This document provides project-specific context and guidelines for GitHub Copilot when working with the Alfie Android e-commerce application. + +--- + +## Project Overview + +Alfie is a native Android e-commerce application built with Jetpack Compose (minSdk 26) following Clean Architecture with MVVM pattern and modular structure. The app fetches data from a GraphQL BFF API using Apollo Kotlin and includes features like product browsing, search, wishlist, and bag functionality. + +--- + +## Architecture & Code Organization + +### Clean Architecture + MVVM Pattern + +The codebase follows Clean Architecture principles with MVVM for presentation, organized into distinct layers: + +#### Data Layer +- **Location**: `data/src/main/java/au/com/alfie/ecomm/data/` +- **Purpose**: Data sources, repositories implementation, DTOs, and data mapping +- **Pattern**: Repository pattern with protocol-based interfaces +- **Key Components**: + - Repository implementations (`*RepositoryImpl`) + - Services for API communication + - Mappers to convert DTOs to domain models + - DTOs generated by Apollo Kotlin from GraphQL queries + +**Repository Implementation Pattern**: +```kotlin +internal class BrandRepositoryImpl @Inject constructor( + private val brandService: BrandService +) : BrandRepository { + + override suspend fun getBrands(): RepositoryResult> = + brandService.getBrands() + .mapCatching { it.brands.toDomain() } + .toRepositoryResult() +} +``` + +#### Domain Layer +- **Location**: `domain/src/main/java/au/com/alfie/ecomm/domain/` +- **Purpose**: Business logic, use cases, and repository interfaces +- **Pattern**: Use cases as single-responsibility operations +- **Dependencies**: No Android dependencies, pure Kotlin + +**Repository Interface Pattern** (located in `domain/repository/`): +```kotlin +interface BrandRepository { + suspend fun getBrands(): RepositoryResult> +} +``` + +**Use Case Pattern**: +```kotlin +class GetBrandsUseCase @Inject constructor( + private val brandRepository: BrandRepository +) : UseCaseInteractor { + + suspend operator fun invoke(): UseCaseResult> = + run(brandRepository.getBrands()) +} +``` + +#### Presentation Layer (Feature Modules) +- **Location**: `feature//src/main/java/au/com/alfie/ecomm/feature/` +- **Pattern**: MVVM with Jetpack Compose +- **State Management**: Use `StateFlow` for observable state +- **Dependencies**: Inject use cases via Hilt + +**Feature Module Components**: +- **ViewModels**: `@HiltViewModel` classes managing screen state +- **Screens**: Composable functions annotated with `@Destination` +- **UI Models**: Data classes representing screen data (named with `UI` suffix) +- **Factories**: Convert domain models to UI models + +**ViewModel Pattern**: +```kotlin +@HiltViewModel +internal class HomeViewModel @Inject constructor( + private val getBrandsUseCase: GetBrandsUseCase, + private val uiFactory: HomeUIFactory +) : ViewModel() { + + private val _state = MutableStateFlow(HomeUIState.Loading) + val state: StateFlow = _state + + fun loadData() { + viewModelScope.launch { + when (val result = getBrandsUseCase()) { + is UseCaseResult.Success -> _state.value = HomeUIState.Loaded( + uiFactory(result.data) + ) + is UseCaseResult.Error -> _state.value = HomeUIState.Error + } + } + } +} +``` + +**Screen Pattern**: +```kotlin +@Destination +@Composable +internal fun HomeScreen( + topBarState: TopBarState, + bottomBarState: BottomBarState, + navigator: DestinationsNavigator, + directionProvider: DirectionProvider +) { + val viewModel: HomeViewModel = hiltViewModel() + val state by viewModel.state.collectAsStateWithLifecycle() + + when (state) { + is HomeUIState.Loading -> LoadingContent() + is HomeUIState.Loaded -> LoadedContent(state.data) + is HomeUIState.Error -> ErrorContent() + } +} +``` + +### State Management + +**Use sealed interfaces/classes for screen states**: +```kotlin +sealed interface HomeUIState { + data object Loading : HomeUIState + data class Loaded(val homeUI: HomeUI) : HomeUIState + data class Error(val message: String) : HomeUIState +} +``` + +**Use StateFlow for reactive state**: +- Always use `StateFlow` (not `LiveData`) for observable state +- Collect state using `collectAsStateWithLifecycle()` in Composables +- Make state flows private, expose read-only public property + +### Navigation (Compose Destinations) + +The app uses Compose Destinations library for type-safe navigation: + +#### Creating Screens + +**Globally accessible screen**: +1. Create composable with `@Destination` annotation in feature module +2. Create `*NavArgs` data class in `:core:navigation` module (if screen has arguments) +3. Add to `NavGraphs.root.destinationsByRoute` in `:app` module +4. Add case to `Screen` sealed interface (`:core:navigation` module) +5. Map in `DirectionProviderImpl` (`:app` module) + +**Feature-local screen**: +1. Create composable with `@Destination` annotation +2. Create `*NavArgs` in feature module (if needed) +3. Skip steps 3-5 above (not exposed globally) + +**Screen with arguments**: +```kotlin +// In :core:navigation module +data class ProductDetailsNavArgs( + val productId: String, + val colorId: String? = null +) + +// In feature module +@Destination(navArgsDelegate = ProductDetailsNavArgs::class) +@Composable +internal fun ProductDetailsScreen( + navigator: DestinationsNavigator +) { + val viewModel: ProductDetailsViewModel = hiltViewModel() + // Access args via SavedStateHandle in ViewModel using navArgs() +} +``` + +**Navigating to destinations**: +```kotlin +// For global screens +navigator.navigate(directionProvider.fromScreen(Screen.ProductDetails(args))) + +// For feature-local screens +navigator.navigate(ProductDetailsScreenDestination(args)) +``` + +**Bottom sheets**: +```kotlin +@Destination(style = DestinationStyleBottomSheet::class) +@Composable +internal fun FilterBottomSheet() { + // Dismiss with navigator.popBackStack() +} +``` + +#### Navigation Arguments Access + +**In ViewModel**: +```kotlin +@HiltViewModel +class ProductDetailsViewModel @Inject constructor( + savedStateHandle: SavedStateHandle +) : ViewModel() { + private val navArgs: ProductDetailsNavArgs = savedStateHandle.navArgs() +} +``` + +**In Composable (no ViewModel)**: +```kotlin +@Destination +@Composable +fun ProductDetailsScreen(navBackStackEntry: NavBackStackEntry) { + val args = ProductDetailsScreenDestination.argsFrom(navBackStackEntry) +} +``` + +--- + +## Module Structure + +The project uses Gradle with modularization for scalability: + +### Core Modules + +- **:app**: Main application entry point, navigation setup, dependency injection +- **:network**: Apollo Kotlin GraphQL client, API queries, network layer +- **:data**: Repository implementations, services, data mapping +- **:domain**: Business logic, use cases, repository interfaces +- **:designsystem**: Theme system, reusable UI components +- **:feature:***: Feature modules (home, bag, pdp, plp, wishlist, etc.) +- **:core:analytics**: Analytics tracking and event definitions +- **:core:commons**: Common utilities and extensions +- **:core:configuration**: Feature flags and remote configuration +- **:core:deeplink**: Deep link handling +- **:core:navigation**: Navigation interfaces and screen definitions +- **:core:ui**: Shared UI utilities +- **:core:test**: Testing utilities +- **:data:database**: Room database +- **:data:datastore**: DataStore for preferences +- **:debug**: Debug tools and screens +- **:buildconvention**: Gradle convention plugins + +### Module Dependencies + +- **:app** depends on all feature and core modules +- **:domain** has no Android dependencies (pure Kotlin) +- **:data** depends on `:domain` and `:network` +- **:feature:*** depends on `:domain`, `:designsystem`, `:core:*` +- **:network** handles all GraphQL communication + +--- + +## GraphQL & BFF Integration + +### GraphQL Structure + +- **Location**: `network/src/main/graphql/` +- **Queries**: `*-queries.graphql` (e.g., `product-queries.graphql`) +- **Fragments**: `fragments/` subdirectory +- **Schema**: `schema.graphqls` +- **Generated code**: Apollo Kotlin generates DTOs in `au.com.alfie.ecomm.graphql` package + +### Adding a New Query + +1. **Create query file**: `network/src/main/graphql/-queries.graphql` +2. **Define fragments**: `network/src/main/graphql/fragments/.graphql` +3. **Build project**: Apollo codegen runs automatically on build +4. **Create domain models**: Add in `domain/src/main/java/.../model/` +5. **Create repository interface**: Add in `domain/repository/src/main/java/` +6. **Create service**: Add in `data/src/main/java/.../service/` +7. **Create mappers**: Add in `data/src/main/java/.../mapper/` (use `toDomain()` extension) +8. **Create repository impl**: Add in `data/src/main/java/.../repository/` +9. **Register in DI**: Add binding in appropriate Hilt module + +**Query Pattern**: +```graphql +query GetProduct($productId: String!) { + product(productId: $productId) { + ...ProductFragment + } +} +``` + +**Fragment Pattern**: +```graphql +fragment ProductFragment on Product { + id + name + brand { + ...BrandFragment + } + price { + ...PriceFragment + } +} +``` + +**Mapper Pattern**: +```kotlin +internal fun GetProductQuery.Product.toDomain(): Product = Product( + id = id, + name = name, + brand = brand.toDomain(), + price = price.toDomain() +) +``` + +--- + +## Localization + +### String Resources + +- **Location**: Module-specific `res/values/strings.xml` +- **Main strings**: `designsystem/src/main/res/values/strings.xml` +- **Naming convention**: `feature_screen_element` (e.g., `plp_filter_title`) + +### Adding New Strings + +1. **Add to appropriate strings.xml**: +```xml +Welcome, %1$s! +Member since %1$s +``` + +2. **Use in Composables**: +```kotlin +Text(text = stringResource(R.string.home_greeting, userName)) +Text(text = stringResource(R.string.home_member_since, memberDate)) +``` + +**Important**: Never hardcode user-facing strings. Always use string resources. + +--- + +## Design System & UI Components + +### Theme System + +- **Location**: `designsystem/src/main/java/au/com/alfie/ecomm/designsystem/theme/` +- **Access**: Via `Theme` object +- **Components**: + - `Theme.color` - Color palette + - `Theme.typography` - Text styles + - `Theme.spacing` - Spacing values + - `Theme.iconSize` - Icon dimensions + - `Theme.shape` - Corner radius and shapes + +**Example Usage**: +```kotlin +Text( + text = "Hello", + style = Theme.typography.heading1, + color = Theme.color.primary.mono900 +) + +Box( + modifier = Modifier + .padding(Theme.spacing.spacing16) + .background(Theme.color.secondary.darkGrey, Theme.shape.small) +) +``` + +### Reusable Components + +Located in `designsystem/src/main/java/au/com/alfie/ecomm/designsystem/component/`: + +- **Buttons**: Various button styles and states +- **Indicators**: Loading indicators, badges, progress +- **Cards**: Product cards, content cards +- **Toolbars**: Top bar, bottom bar components +- **Input**: Text fields, search bars, filters +- **Navigation**: Bottom navigation, tabs +- **Dialogs**: Alert dialogs, bottom sheets +- **Lists**: Lazy lists, grid layouts + +**Always use existing design system components** instead of creating custom UI from scratch. + +### Compose Best Practices + +- Use `LazyColumn`/`LazyRow` for scrollable lists +- Use `Modifier.fillMaxWidth()`, `fillMaxHeight()`, `fillMaxSize()` appropriately +- Apply modifiers in logical order: size → padding → background → border +- Use `remember` for non-state values computed during composition +- Use `rememberSaveable` for values that survive configuration changes +- Keep Composables pure (no side effects in composition) +- Use `LaunchedEffect` for side effects with lifecycle awareness + +--- + +## Dependency Injection (Hilt) + +### Hilt Setup + +- **Application class**: `@HiltAndroidApp` annotation +- **ViewModels**: `@HiltViewModel` annotation +- **Modules**: `@Module` with `@InstallIn` annotation +- **Injection**: Constructor injection preferred + +### Module Patterns + +**Feature Module**: +```kotlin +@Module +@InstallIn(ViewModelComponent::class) +internal interface HomeModule { + + @Binds + fun bindHomeUIFactory(factory: HomeUIFactory): HomeUIFactory +} +``` + +**Singleton Module**: +```kotlin +@Module +@InstallIn(SingletonComponent::class) +internal interface DataModule { + + @Binds + fun bindBrandRepository(impl: BrandRepositoryImpl): BrandRepository +} +``` + +**Providing Dependencies**: +```kotlin +@Module +@InstallIn(SingletonComponent::class) +internal object NetworkModule { + + @Provides + @Singleton + fun provideApolloClient( + okHttpClient: OkHttpClient + ): ApolloClient = ApolloClient.Builder() + .serverUrl("https://api.example.com/graphql") + .okHttpClient(okHttpClient) + .build() +} +``` + +--- + +## Code Style & Best Practices + +### Detekt Rules + +- **Configuration**: `config/detekt/detekt.yml` +- **Baseline**: `config/detekt/baseline.xml` (for exceptions) +- **Run lint**: `./gradlew detekt` +- **Auto-fix**: `./gradlew detektAutoFix` (formatting issues only) +- **Update baseline**: `./gradlew detektProjectBaseline` + +### Naming Conventions + +- **ViewModels**: `ViewModel.kt` +- **Screens**: `Screen.kt` +- **Repositories**: Interface `Repository.kt`, Impl `RepositoryImpl.kt` +- **Use Cases**: `GetUseCase.kt`, `UpdateUseCase.kt` +- **UI Models**: `UI.kt` or `UIState.kt` +- **Factories**: `UIFactory.kt` +- **Mappers**: Extension functions named `toDomain()`, `toUI()`, `toEntity()` +- **Composables**: PascalCase function names +- **String resources**: `feature_screen_element` (snake_case) + +### Code Organization + +``` +feature// +├── src/ +│ ├── main/ +│ │ └── java/au/com/alfie/ecomm/feature// +│ │ ├── Screen.kt # Composable screen +│ │ ├── ViewModel.kt # ViewModel +│ │ ├── UIFactory.kt # UI model factory +│ │ ├── model/ +│ │ │ ├── UI.kt # UI models +│ │ │ └── UIState.kt # UI state +│ │ └── di/ +│ │ └── Module.kt # Hilt module +│ └── test/ +│ └── java/.../ +│ ├── ViewModelTest.kt +│ └── factory/ +│ └── UIFactoryTest.kt +``` + +### Preview Pattern + +```kotlin +@Preview(showBackground = true) +@Composable +private fun HomeScreenLoadedPreview() { + Theme { + HomeScreenContent( + state = HomeUIState.Loaded( + homeUI = HomeUI( + userName = "John Doe", + membershipDate = "2020" + ) + ) + ) + } +} + +@Preview(showBackground = true) +@Composable +private fun HomeScreenLoadingPreview() { + Theme { + HomeScreenContent(state = HomeUIState.Loading) + } +} +``` + +**Important**: Create multiple previews for different states (Loading, Success, Error, etc.) + +### Coroutines Pattern + +- Use `viewModelScope` for ViewModel coroutines +- Use `suspend` functions for repository/use case methods +- Handle errors with `runCatching` or try-catch +- Use `Flow` for streams of data +- Use `StateFlow` for state management + +```kotlin +fun loadData() { + viewModelScope.launch { + _state.value = HomeUIState.Loading + when (val result = getBrandsUseCase()) { + is UseCaseResult.Success -> { + _state.value = HomeUIState.Loaded(uiFactory(result.data)) + } + is UseCaseResult.Error -> { + _state.value = HomeUIState.Error(result.message) + } + } + } +} +``` + +--- + +## Testing + +### Test Structure + +- **Location**: `/src/test/` for unit tests +- **Location**: `/src/androidTest/` for instrumentation tests +- **Framework**: JUnit 5 with MockK +- **Coroutines**: Use `runTest` from kotlinx-coroutines-test +- **Coverage**: Kover for code coverage reports + +### Testing Patterns + +**ViewModel Test**: +```kotlin +@ExtendWith(MockKExtension::class) +internal class HomeViewModelTest { + + @RelaxedMockK + private lateinit var getBrandsUseCase: GetBrandsUseCase + + @RelaxedMockK + private lateinit var uiFactory: HomeUIFactory + + @InjectMockKs + private lateinit var viewModel: HomeViewModel + + @Test + fun `loadData - success updates state to loaded`() = runTest { + // Given + val brands = listOf(mockk()) + val homeUI = mockk() + coEvery { getBrandsUseCase() } returns UseCaseResult.Success(brands) + coEvery { uiFactory(brands) } returns homeUI + + // When + viewModel.loadData() + + // Then + assertEquals(HomeUIState.Loaded(homeUI), viewModel.state.value) + } +} +``` + +**Use Case Test**: +```kotlin +@ExtendWith(MockKExtension::class) +internal class GetBrandsUseCaseTest { + + @RelaxedMockK + private lateinit var brandRepository: BrandRepository + + @InjectMockKs + private lateinit var useCase: GetBrandsUseCase + + @Test + fun `invoke - returns list of brands`() = runTest { + // Given + val brands = mockk>() + coEvery { brandRepository.getBrands() } returns RepositoryResult.Success(brands) + + // When + val result = useCase() + + // Then + assertEquals(UseCaseResult.Success(brands), result) + } +} +``` + +**Repository Test**: +```kotlin +@ExtendWith(MockKExtension::class) +internal class BrandRepositoryImplTest { + + @RelaxedMockK + private lateinit var brandService: BrandService + + @InjectMockKs + private lateinit var repository: BrandRepositoryImpl + + @Test + fun `getBrands - returns mapped brands`() = runTest { + // Given + val response = mockk() + coEvery { brandService.getBrands() } returns Result.success(response) + + // When + val result = repository.getBrands() + + // Then + assertTrue(result is RepositoryResult.Success) + } +} +``` + +### Testing Libraries + +- **JUnit 5**: Test framework +- **MockK**: Mocking library for Kotlin +- **Turbine**: Testing Flow emissions +- **Fixture**: Generating test data +- **Kover**: Code coverage + +### Code Coverage + +Run coverage report: +```bash +./gradlew :app:koverHtmlReportRelease +``` + +Coverage focus areas: +- **Domain layer**: Use cases, business logic (aim for >80%) +- **Data layer**: Repository implementations, mappers (aim for >70%) +- **Presentation layer**: ViewModels (aim for >70%) +- **UI layer**: Composables (visual testing preferred over coverage) + +--- + +## Feature Development Process + +### Spec-Driven Approach ⭐ + +**For new features, consider creating a specification document** (though not mandatory for this project): + +If creating specs, document in a central location with: +- Feature overview and business goals +- User stories and acceptance criteria +- Data models and API contracts +- UI flows and navigation +- Localization requirements +- Analytics events +- Edge cases and error handling +- Testing strategy + +### Feature Implementation Checklist + +Use this checklist for systematic feature implementation: + +1. ✅ **Define Domain Models** in `domain/src/main/java/.../model/` +2. ✅ **Create Repository Interface** in `domain/repository/src/main/java/` +3. ✅ **Add GraphQL Query** (if API needed): + - Create `*-queries.graphql` in `network/src/main/graphql/` + - Create fragments in `fragments/` subdirectory + - Build project to generate Apollo code +4. ✅ **Create Mappers** in `data/src/main/java/.../mapper/` +5. ✅ **Create Service** in `data/src/main/java/.../service/` +6. ✅ **Implement Repository** in `data/src/main/java/.../repository/` +7. ✅ **Create Use Cases** in `domain/src/main/java/.../usecase/` +8. ✅ **Register in DI**: Add Hilt bindings in modules +9. ✅ **Create UI Models** in `feature//model/` +10. ✅ **Create UI Factory** in `feature//UIFactory.kt` +11. ✅ **Create ViewModel** in `feature//ViewModel.kt` +12. ✅ **Create Screen** in `feature//Screen.kt` +13. ✅ **Add Navigation** (if globally accessible): + - Add `*NavArgs` in `:core:navigation` + - Add to `NavGraphs` in `:app` + - Add to `Screen` sealed interface + - Map in `DirectionProviderImpl` +14. ✅ **Add String Resources** in appropriate `strings.xml` +15. ✅ **Build & Verify** - Run `./gradlew assembleDebug` and verify no errors +16. ✅ **Write Tests** - ViewModel, use case, repository, factory tests +17. ✅ **Run Lint** - Execute `./gradlew detekt` and fix issues +18. ✅ **Test Coverage** - Verify coverage meets standards + +--- + +## Build & Verification + +### Build Commands + +```bash +# Debug build +./gradlew assembleDebug + +# Release build +./gradlew assembleRelease + +# Run all tests +./gradlew test + +# Run lint +./gradlew detekt + +# Run with auto-fix +./gradlew detektAutoFix + +# Generate coverage report +./gradlew :app:koverHtmlReportRelease + +# Clean build +./gradlew clean build +``` + +### Build Types + +- **debug**: Development build with debugging enabled +- **beta**: Pre-release build for internal testing +- **release**: Production build with ProGuard/R8 + +### Verification Process + +1. Run `./gradlew assembleDebug` after code changes +2. Fix any compilation errors +3. Run `./gradlew detekt` to check lint rules +4. Run `./gradlew test` to verify tests pass +5. Only commit after successful verification + +--- + +## CI/CD + +### Workflows + +**Branch Validation** (runs on all PRs): +- Detekt linting +- Unit tests +- Build verification + +**Debug Distribution** (runs on PRs and develop): +- Branch validation +- Debug build +- Firebase App Distribution to QA group + +**Beta Distribution** (runs on release branches): +- Branch validation +- Beta build +- Firebase App Distribution to Mindera group +- Version update if needed + +**Release Distribution** (runs on release tags): +- Branch validation +- Release build +- Firebase App Distribution + +### Branch Strategy + +Follow Gitflow: +- `main` - Production releases +- `develop` - Integration branch +- `feature/*` - New features +- `bugfix/*` - Bug fixes (non-urgent) +- `hotfix/*` - Urgent production fixes +- `release/*` - Release preparation (format: `release/Alfie-M.m.p`) +- `chore/*` - Maintenance tasks + +### Commit Convention + +**Format**: `[TICKET-ID] Description` + +**Examples**: +- `[ALFIE-123] Add product details screen` +- `[ALFIE-456] Fix crash on empty cart` +- `[ALFIE-789] Update dependencies` + +--- + +## Things to AVOID + +❌ Access repositories directly from ViewModels (use Use Cases) +❌ Hardcode user-facing strings (use string resources) +❌ Put business logic in Composables or ViewModels (use Use Cases in Domain layer) +❌ Add Android dependencies to Domain layer +❌ Edit auto-generated GraphQL code +❌ Skip testing for ViewModels and Use Cases +❌ Create custom UI without checking DesignSystem first +❌ Use `LiveData` (use `StateFlow` instead) +❌ Block main thread (use coroutines for async operations) +❌ Ignore Detekt warnings (fix or add to baseline) +❌ Skip code reviews +❌ Commit with lint errors +❌ Use `!!` (null assertion) without proper justification + +--- + +## Quick Reference + +### Key Directories + +``` +Alfie-Android/ +├── app/ # Main app, navigation, DI setup +├── network/ # Apollo GraphQL client +│ └── src/main/graphql/ # GraphQL queries & fragments +├── data/ # Repository implementations +│ └── src/main/java/.../data/ +│ ├── repository/ # Repository implementations +│ ├── service/ # API services +│ └── mapper/ # Data to domain mappers +├── domain/ # Business logic +│ ├── src/main/java/.../domain/ +│ │ ├── usecase/ # Use cases +│ │ └── model/ # Domain models +│ └── repository/ # Repository interfaces +├── designsystem/ # Theme and UI components +│ └── src/main/java/.../designsystem/ +│ ├── theme/ # Colors, typography, spacing +│ └── component/ # Reusable components +├── feature/ # Feature modules +│ ├── home/ +│ ├── bag/ +│ ├── pdp/ # Product Details Page +│ ├── plp/ # Product Listing Page +│ ├── wishlist/ +│ └── ... +├── core/ # Core utilities +│ ├── analytics/ # Analytics tracking +│ ├── navigation/ # Navigation interfaces +│ ├── commons/ # Common utilities +│ └── ... +└── buildconvention/ # Gradle convention plugins +``` + +### Common Commands + +```bash +# Build +./gradlew assembleDebug +./gradlew assembleRelease + +# Test +./gradlew test +./gradlew :feature:home:test + +# Lint +./gradlew detekt +./gradlew detektAutoFix + +# Coverage +./gradlew :app:koverHtmlReportRelease + +# Clean +./gradlew clean + +# Dependencies +./gradlew dependencies + +# Install on device +./gradlew installDebug +``` + +### Key Dependencies + +- **Jetpack Compose**: Modern declarative UI (BOM 2025.01.01) +- **Apollo Kotlin**: GraphQL client (v4.0.0-beta.4) +- **Hilt**: Dependency injection (v2.51) +- **Compose Destinations**: Type-safe navigation (v1.10.0) +- **Kotlin Coroutines**: Async programming (v1.7.3) +- **Glide Compose**: Image loading (v1.0.0-beta01) +- **Firebase**: Analytics, Crashlytics, Remote Config (BOM 32.7.3) +- **DataStore**: Preferences storage (v1.1.2) +- **Room**: Local database (if needed) +- **Timber**: Logging (latest) +- **MockK**: Testing mocks (v1.13.8) +- **JUnit 5**: Test framework (v5.10.0) +- **Detekt**: Static analysis (v1.23.7) +- **Kover**: Code coverage (v0.7.6) + +--- + +## Code Review Guidelines + +### PR Review Checklist + +- [ ] **Architecture**: Clean Architecture, MVVM, proper layer separation +- [ ] **Dependency Injection**: Hilt modules configured correctly +- [ ] **Localization**: All strings use string resources +- [ ] **State Management**: StateFlow used correctly, proper state handling +- [ ] **Navigation**: Compose Destinations configured properly +- [ ] **Tests**: Unit tests for ViewModels, Use Cases, Repositories +- [ ] **Lint**: Detekt passes with no violations +- [ ] **GraphQL**: Fragments used, queries optimized +- [ ] **UI**: Design system components used, Compose best practices followed +- [ ] **Error Handling**: Proper error states and user feedback +- [ ] **Performance**: No blocking operations on main thread + +### 🔴 Critical (Block Merge) + +- ViewModels accessing repositories directly (must use Use Cases) +- Hardcoded user-facing strings +- Android dependencies in Domain layer +- Business logic in Composables or ViewModels +- Blocking operations on main thread +- Missing error handling +- Detekt violations + +### 🟠 High Priority + +- Missing tests for ViewModels or Use Cases +- GraphQL queries without fragments +- Missing string resources +- Not using design system components +- Improper state management +- Memory leaks (incorrect lifecycle handling) + +### Security Review Points + +- No API keys, tokens, passwords in code (use BuildConfig or secure storage) +- Sensitive data uses encrypted storage +- No PII in logs +- Input validation on all user inputs +- Proper certificate pinning for network requests +- ProGuard/R8 rules for release builds + +--- + +## Additional Context + +- **Min SDK**: 26 (Android 8.0 Oreo) +- **Target SDK**: Latest stable +- **Kotlin Version**: 1.9.22 +- **Compose Compiler**: 1.5.10 +- **JVM Target**: 17 +- **Build System**: Gradle with Kotlin DSL +- **Version Naming**: Semantic versioning (M.m.p format) +- **Mock Server**: Available for development/testing +- **CI/CD**: GitHub Actions workflows in `.github/workflows/` + +--- + +This document should be updated when major architectural decisions change or new patterns are introduced.