mad-pro-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/index.js +17 -0
  2. package/lib/commands/init.js +42 -0
  3. package/package.json +26 -0
  4. package/templates/mad-skills/SKILL.md +77 -0
  5. package/templates/mad-skills/references/accessibility.md +63 -0
  6. package/templates/mad-skills/references/adaptive_layouts.md +51 -0
  7. package/templates/mad-skills/references/advanced_performance.md +52 -0
  8. package/templates/mad-skills/references/advanced_ui.md +64 -0
  9. package/templates/mad-skills/references/ai_ml.md +38 -0
  10. package/templates/mad-skills/references/architecture_di.md +63 -0
  11. package/templates/mad-skills/references/automation_cicd.md +53 -0
  12. package/templates/mad-skills/references/clean_architecture.md +80 -0
  13. package/templates/mad-skills/references/cmp_migration.md +56 -0
  14. package/templates/mad-skills/references/concurrency.md +50 -0
  15. package/templates/mad-skills/references/deeplinks.md +42 -0
  16. package/templates/mad-skills/references/design_principles.md +47 -0
  17. package/templates/mad-skills/references/design_systems.md +47 -0
  18. package/templates/mad-skills/references/domain_layer.md +47 -0
  19. package/templates/mad-skills/references/google_play_skills.md +40 -0
  20. package/templates/mad-skills/references/kmp_migration.md +62 -0
  21. package/templates/mad-skills/references/layouts.md +57 -0
  22. package/templates/mad-skills/references/local_data.md +53 -0
  23. package/templates/mad-skills/references/migration_xml_to_compose.md +80 -0
  24. package/templates/mad-skills/references/modifiers.md +46 -0
  25. package/templates/mad-skills/references/modularization.md +70 -0
  26. package/templates/mad-skills/references/multiplatform.md +55 -0
  27. package/templates/mad-skills/references/navigation.md +48 -0
  28. package/templates/mad-skills/references/network_data.md +87 -0
  29. package/templates/mad-skills/references/observability.md +46 -0
  30. package/templates/mad-skills/references/performance.md +51 -0
  31. package/templates/mad-skills/references/security.md +67 -0
  32. package/templates/mad-skills/references/solid_principles.md +61 -0
  33. package/templates/mad-skills/references/state_management.md +46 -0
  34. package/templates/mad-skills/references/testing.md +48 -0
  35. package/templates/mad-skills/references/theming.md +52 -0
  36. package/templates/mad-skills/references/ui_patterns.md +80 -0
  37. package/templates/mad-skills/references/workmanager.md +55 -0
@@ -0,0 +1,70 @@
1
+ # Modularization Strategy in Modern Android
2
+
3
+ Modularization involves breaking your application into smaller, independent parts.
4
+
5
+ ```mermaid
6
+ graph BT
7
+ App[":app (Shell)"] --> FeatureA[":feature:home"]
8
+ App --> FeatureB[":feature:profile"]
9
+ FeatureA --> CoreUI[":core:ui"]
10
+ FeatureB --> CoreUI
11
+ FeatureA --> Domain[":core:domain"]
12
+ FeatureB --> Domain
13
+ Domain --> Data[":core:data"]
14
+ Data --> Network[":core:network"]
15
+ Data --> Database[":core:database"]
16
+ ```
17
+
18
+ ## 1. Why Modularize?
19
+
20
+ - **Faster Build Times**: Gradle can compile independent modules in parallel.
21
+ - **Improved Reusability**: Share code between different apps or target platforms.
22
+ - **Strict Separation of Concerns**: Enforce boundaries between different layers and features.
23
+ - **Team Scalability**: Large teams can work on different modules without merge conflicts.
24
+
25
+ ## 2. Recommended Structures
26
+
27
+ ### Layer-based Modularization
28
+
29
+ Divide the app by architectural layers:
30
+
31
+ - `:core`: Common utilities, themes, and base classes.
32
+ - `:data`: Repositories and data sources (Room, Retrofit).
33
+ - `:domain`: Use Cases and business logic.
34
+ - `:ui`: Standardized UI components (Design System).
35
+
36
+ ### Feature-based Modularization
37
+
38
+ Divide the app by user-facing features:
39
+
40
+ - `:feature:auth`
41
+ - `:feature:profile`
42
+ - `:feature:home`
43
+
44
+ **The Goal**: A balanced approach where features depend on core/data/domain modules but NOT on each other.
45
+
46
+ ## 3. Dependency Management: Version Catalogs
47
+
48
+ Use `libs.versions.toml` to manage all dependencies in one place.
49
+
50
+ ```toml
51
+ [versions]
52
+ compose = "1.5.0"
53
+ hilt = "2.48"
54
+
55
+ [libraries]
56
+ compose-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "compose" }
57
+ hilt-android = { group = "google.dagger", name = "hilt-android", version.ref = "hilt" }
58
+
59
+ [plugins]
60
+ android-application = { id = "com.android.application", version = "8.1.0" }
61
+ ```
62
+
63
+ ## 4. Navigation in Multi-module Apps
64
+
65
+ - **Feature-to-Feature**: Use high-level feature interfaces or deep links to navigate between modules without creating circular dependencies.
66
+ - **Coordinator Pattern**: Use a dedicated navigation module or the `:app` module to coordinate flows between features.
67
+
68
+ ## 5. Internal Visibility
69
+
70
+ Use the `internal` modifier to hide implementation details within a module. Only expose what is absolutely necessary (usually interfaces or data classes).
@@ -0,0 +1,55 @@
1
+ # KMP and Compose Multiplatform
2
+
3
+ Kotlin Multiplatform (KMP) allows you to share code between Android, iOS, Web, and Desktop while keeping the native performance and look of each platform.
4
+
5
+ ## 1. Code Sharing Strategy
6
+
7
+ The most common approach in MAD is sharing the **Data** and **Domain** layers:
8
+
9
+ - **Shared (`:commonMain`)**: Business logic, Use Cases, Repositories, and API definitions.
10
+ - **Android (`:androidMain`)**: Android-specific implementations (e.g., Room, Biometrics).
11
+ - **iOS (`:iosMain`)**: iOS-specific implementations (e.g., Keychain, CoreData).
12
+
13
+ ## 2. Compose Multiplatform (CMP)
14
+
15
+ Compose Multiplatform extends Jetpack Compose to other platforms. You can share not just the logic, but the **UI** as well.
16
+
17
+ ### Standard Module Structure
18
+
19
+ ```text
20
+ shared/
21
+ commonMain/ <- Shared UI and Logic
22
+ androidMain/ <- Android specific UI/Logic
23
+ iosMain/ <- iOS specific UI/Logic
24
+ desktopMain/ <- Desktop specific UI/Logic
25
+ ```
26
+
27
+ ## 3. Libraries for KMP
28
+
29
+ Use multiplatform-ready libraries to maximize code sharing:
30
+
31
+ - **Networking**: Ktor
32
+ - **Database**: SQLDelight or Room (KMP support)
33
+ - **Dependency Injection**: Koin
34
+ - **Serialization**: Kotlinx.serialization
35
+ - **Concurrency**: Kotlin Coroutines
36
+
37
+ ## 4. Platform-Specific Implementation: `expect/actual`
38
+
39
+ When you need to access platform APIs (like the Camera or UUIDs), use the `expect/actual` pattern:
40
+
41
+ ```kotlin
42
+ // In commonMain
43
+ expect fun getDeviceName(): String
44
+
45
+ // In androidMain
46
+ actual fun getDeviceName(): String = "Android ${Build.MODEL}"
47
+
48
+ // In iosMain
49
+ actual fun getDeviceName(): String = UIDevice.currentDevice.name
50
+ ```
51
+
52
+ ## 5. Integration with iOS
53
+
54
+ - **SwiftUI Integration**: You can host a Compose Multiplatform view inside a SwiftUI `UIViewControllerRepresentable`.
55
+ - **SKIE**: Use the SKIE library to improve the Swift/Kotlin compatibility layer (e.g., making Flows and Sealed Classes easier to use in Swift).
@@ -0,0 +1,48 @@
1
+ # Navigation in Jetpack Compose
2
+
3
+ Modern Compose navigation is type-safe and integrated with the ViewModel lifecycle.
4
+
5
+ ## Type-Safe Navigation (Compose 2.8.0+)
6
+
7
+ 1. **Define Routes as Classes/Objects**: Annotated with `@Serializable`.
8
+ 2. **Setup NavHost**: Use the class/object instances directly.
9
+
10
+ ### Example Configuration
11
+
12
+ ```kotlin
13
+ @Serializable
14
+ object Home
15
+
16
+ @Serializable
17
+ data class Profile(val userId: String)
18
+
19
+ @Composable
20
+ fun AppNavigation(navController: NavHostController) {
21
+ NavHost(navController = navController, startDestination = Home) {
22
+ composable<Home> {
23
+ HomeScreen(onNavigateToProfile = { id ->
24
+ navController.navigate(Profile(id))
25
+ })
26
+ }
27
+ composable<Profile> { backStackEntry ->
28
+ val profile: Profile = backStackEntry.toRoute()
29
+ ProfileScreen(userId = profile.userId)
30
+ }
31
+ }
32
+ }
33
+ ```
34
+
35
+ ## 2. Best Practices
36
+
37
+ - **Screen Composable**: The direct child of a `composable<Route>` should be a "Screen" level composable that takes the ViewModel.
38
+ - **Navigating from VM**: Use a `NavigationManager` or a `SideEffect` (Channel/SharedFlow) to trigger navigation from the ViewModel, rather than passing the `NavController` into the ViewModel.
39
+ - **Single Top Bar**: Usually, a single top bar in the parent `Scaffold` is easier to manage, using `navController.currentBackStackEntryAsState()` to update the title.
40
+
41
+ ## 3. Deep Linking
42
+
43
+ - Routes defined as classes handle deep links naturally with the same parameters.
44
+ - Define `deepLinks` within the `composable` builder if specific URI patterns are needed.
45
+
46
+ ## 4. Pro Tip
47
+
48
+ Avoid hardcoded strings for routes. Always use the new Kotlin Serialization-based navigation for compile-time safety.
@@ -0,0 +1,87 @@
1
+ # Network Data Best Practices in Jetpack Compose
2
+
3
+ Handling network requests efficiently and safely is crucial for a responsive user experience.
4
+
5
+ ## 1. Networking Engines
6
+
7
+ ### Retrofit (Standard)
8
+
9
+ The industry standard for type-safe REST clients on Android.
10
+
11
+ **Setup with Serialization:**
12
+
13
+ ```kotlin
14
+ val retrofit = Retrofit.Builder()
15
+ .baseUrl("https://api.example.com/")
16
+ .addConverterFactory(Json.asConverterFactory("application/json".toMediaType()))
17
+ .build()
18
+ ```
19
+
20
+ ### Ktor Client (Kotlin-First)
21
+
22
+ A multiplatform-ready asynchronous client for HTTP requests.
23
+
24
+ ```kotlin
25
+ val client = HttpClient(Android) {
26
+ install(ContentNegotiation) {
27
+ json()
28
+ }
29
+ }
30
+ ```
31
+
32
+ ## 2. JSON Serialization
33
+
34
+ Use **KotlinX Serialization** instead of GSON or Moshi for better Kotlin integration and performance.
35
+
36
+ ```kotlin
37
+ @Serializable
38
+ data class User(val id: Int, val name: String)
39
+ ```
40
+
41
+ ## 3. Handling API Responses
42
+
43
+ ### The Result Wrapper Pattern
44
+
45
+ Avoid throwing exceptions in the data layer. Use a `Result` or a custom `Resource` wrapper.
46
+
47
+ ```kotlin
48
+ sealed class NetworkResult<out T> {
49
+ data class Success<out T>(val data: T) : NetworkResult<T>()
50
+ data class Error(val exception: Exception) : NetworkResult<Nothing>()
51
+ object Loading : NetworkResult<Nothing>()
52
+ }
53
+ ```
54
+
55
+ ### Flow Integration
56
+
57
+ Expose network calls as a `Flow` to the ViewModel.
58
+
59
+ ```kotlin
60
+ fun getUser(userId: String): Flow<NetworkResult<User>> = flow {
61
+ emit(NetworkResult.Loading)
62
+ try {
63
+ val user = apiService.getUser(userId)
64
+ emit(NetworkResult.Success(user))
65
+ } catch (e: Exception) {
66
+ emit(NetworkResult.Error(e))
67
+ }
68
+ }
69
+ ```
70
+
71
+ ## 4. Caching and Offline Support
72
+
73
+ - **Repository as Source of Truth**: The UI should only ever observe the local database (Room). The Repository is responsible for syncing the network data into the database.
74
+ - **OkHttp Cache**: Use `Cache-Control` headers for simple GET request caching.
75
+
76
+ ## 5. Error Handling & Connectivity
77
+
78
+ - **Retry Logic**: Implement exponential backoff for transient errors.
79
+ - **Connectivity Monitoring**: Use `ConnectivityManager` to observe network changes and notify the user when they are offline.
80
+ - **User-Friendly Messages**: Never show raw stack traces or "SocketTimeoutException" to the user. Map them to human-readable strings.
81
+
82
+ ## 6. Best Practices
83
+
84
+ - **Timeout Configuration**: Set reasonable connection and read timeouts.
85
+ - **Logging**: Use `HttpLoggingInterceptor` (OkHttp) or `Logging` feature (Ktor) only in debug builds.
86
+ - **Security**: Use HTTPS for all traffic. Sanitize sensitive data in logs.
87
+ - **Efficiency**: Use `Gzip` compression if supported by the server.
@@ -0,0 +1,46 @@
1
+ # Observability and App Health
2
+
3
+ An app isn't finished when it's released. You need to observe how it behaves in the real world to fix bugs and improve the user experience.
4
+
5
+ ## 1. Crash Reporting: Firebase Crashlytics
6
+
7
+ The industry standard for catching and analyzing crashes.
8
+
9
+ - **Custom Keys**: Add state information to help reproduce crashes.
10
+ - **Custom Logs**: Log non-fatal exceptions to understand unexpected flows.
11
+ - **User IDs**: Associate crashes with a user ID (anonymized) for better support.
12
+
13
+ ```kotlin
14
+ Firebase.crashlytics.setCustomKey("payment_method", "credit_card")
15
+ Firebase.crashlytics.log("Started checkout flow")
16
+ ```
17
+
18
+ ## 2. Remote Configuration
19
+
20
+ Change the behavior or appearance of your app without a new release.
21
+
22
+ - **Feature Toggles**: Safely roll out new features to a percentage of users.
23
+ - **Emergency Fixes**: Disable a broken feature instantly.
24
+ - **A/B Testing**: Compare two versions of a screen to see which performs better.
25
+
26
+ ## 3. Performance Monitoring
27
+
28
+ Tracks the performance of specific parts of your app.
29
+
30
+ - **Trace Duration**: Measure how long a specific task takes (e.g., Image processing).
31
+ - **Network Performance**: Automatically monitor the latency and success rate of your API calls.
32
+ - **Frame Drop Tracking**: Identify screens where the UI is lagging.
33
+
34
+ ## 4. User Analytics
35
+
36
+ Understand how users navigate your app.
37
+
38
+ - **Screen Tracking**: Monitor which screens are most popular.
39
+ - **Conversion Funnels**: Track where users drop off in a multi-step process (e.g., Sign-up).
40
+ - **Custom Events**: Track specific actions like "Added to cart" or "Shared content".
41
+
42
+ ## 5. Privacy and Ethics
43
+
44
+ - **Anonymization**: Never send Personally Identifiable Information (PII).
45
+ - **Opt-out**: Provide users with the ability to disable tracking.
46
+ - **Data Minimization**: Only collect the data you actually need to improve the app.
@@ -0,0 +1,51 @@
1
+ # Performance Optimization in Jetpack Compose
2
+
3
+ Compose is fast, but improper patterns can lead to unnecessary recompositions and UI lag.
4
+
5
+ ## The Three Phases of Compose
6
+
7
+ 1. **Composition**: What to show (running composable functions).
8
+ 2. **Layout**: Where to show it (measuring and placing).
9
+ 3. **Drawing**: How to draw it (rendering pixels).
10
+
11
+ **Rule**: Always defer state reads to the latest possible phase.
12
+
13
+ ## Optimization Techniques
14
+
15
+ ### 1. Stable Types
16
+
17
+ State classes should ideally be stable. Use the `@Stable` or `@Immutable` annotations if the compiler cannot infer stability (e.g., for classes in modules without Compose enabled).
18
+
19
+ - Avoid `List` (unstable); use `ImmutableList` (from Kotlin collections immutable) or a wrapper class.
20
+
21
+ ### 2. `derivedStateOf`
22
+
23
+ Use `derivedStateOf` when you have state that depends on other state, and that input state changes more frequently than you need to update the UI.
24
+ *Example: Scrolling position to show a "Back to Top" button.*
25
+
26
+ ```kotlin
27
+ val showButton by remember {
28
+ derivedStateOf { listState.firstVisibleItemIndex > 5 }
29
+ }
30
+ ```
31
+
32
+ ### 3. Deferring State Reads
33
+
34
+ Use lambda-based modifiers when reading state that changes frequently (like scroll or animation values).
35
+
36
+ ```kotlin
37
+ // GOOD: Read happens only during Draw phase
38
+ Box(Modifier.graphicsLayer { alpha = scrollState.value / 100f })
39
+
40
+ // BAD: Causes recomposition every scroll change
41
+ Box(Modifier.alpha(scrollState.value / 100f))
42
+ ```
43
+
44
+ ### 4. `remember` computations
45
+
46
+ Expensive operations (like sorting a list or parsing a date) should be wrapped in `remember(key)`.
47
+
48
+ ## 4. Tools
49
+
50
+ - **Layout Inspector**: Use the "Recomposition Counts" feature to identify hot spots.
51
+ - **Strong Skipping Mode**: Enabled by default in newer Compose versions, but still requires stable data types to work perfectly.
@@ -0,0 +1,67 @@
1
+ # Security Best Practices in Jetpack Compose
2
+
3
+ Ensuring your Android application is secure involves protecting data at rest, in transit, and during interaction.
4
+
5
+ ## Secure Storage
6
+
7
+ Avoid storing sensitive information (tokens, PII) in plain `SharedPreferences` or `DataStore`.
8
+
9
+ 1. **EncryptedSharedPreferences**: Use the Security library to encrypt key-value pairs.
10
+ 2. **Encrypted DataStore**: Use `okio` based encryption or a custom wrapper for `DataStore<Preferences>`.
11
+
12
+ ```kotlin
13
+ val masterKey = MasterKey.Builder(context)
14
+ .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
15
+ .build()
16
+
17
+ val sharedPreferences = EncryptedSharedPreferences.create(
18
+ context,
19
+ "secret_shared_prefs",
20
+ masterKey,
21
+ EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
22
+ EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
23
+ )
24
+ ```
25
+
26
+ ## Biometric Authentication
27
+
28
+ Use `BiometricPrompt` for sensitive actions (e.g., payments or revealing secrets). Integration with Compose requires handling the fragment-based callback.
29
+
30
+ - **Always verify**: Ensure the device supports biometrics before showing the prompt.
31
+ - **CryptoObject**: Link the biometric scan to a `Cipher` or `Signature` for maximum security.
32
+
33
+ ## Permissions
34
+
35
+ Modern Android (API 33+) requires granular permissions.
36
+
37
+ - **Accompanist Permissions**: Use libraries like `com.google.accompanist:accompanist-permissions` for a better Compose experience.
38
+ - **Explain why**: Always provide a clear rationale to the user *before* requesting a dangerous permission.
39
+
40
+ ```kotlin
41
+ val permissionState = rememberPermissionState(android.Manifest.permission.CAMERA)
42
+ if (permissionState.status.isGranted) {
43
+ Text("Camera permission granted")
44
+ } else {
45
+ Button(onClick = { permissionState.launchPermissionRequest() }) {
46
+ Text("Request permission")
47
+ }
48
+ }
49
+ ```
50
+
51
+ ## Root Detection & Play Integrity
52
+
53
+ Protect your app from running on compromised devices.
54
+
55
+ - **Play Integrity API**: Use Google's official API to verify that you are talking to a genuine app binary running on a genuine Android device.
56
+ - **Root Checks**: Use libraries like `RootBeer` for basic local checks, but rely on server-side validation for critical flows.
57
+
58
+ ## Network Security
59
+
60
+ - **SSL Pinning**: Use OkHttp's `CertificatePinner` to prevent MiTM attacks.
61
+ - **Network Security Config**: Define a `network_security_config.xml` to enforce HTTPS and disable cleartext traffic.
62
+
63
+ ## Best Practices
64
+
65
+ - **Hide sensitive content in task switcher**: Use `FLAG_SECURE` in the relevant activity or window.
66
+ - **Input Sanitization**: Always sanitize user input before sending it to a backend or a database.
67
+ - **Hardcoded Secrets**: Never store API keys or secrets in your source code. Use `local.properties` or environment variables with the Secrets Gradle Plugin.
@@ -0,0 +1,61 @@
1
+ # SOLID Principles in Modern Android
2
+
3
+ SOLID is an acronym for five design principles intended to make software designs more understandable, flexible, and maintainable.
4
+
5
+ ## 1. Single Responsibility Principle (SRP)
6
+
7
+ > **"A class should have one, and only one, reason to change."**
8
+
9
+ - **Android Example**: Don't put data fetching logic inside a Composable or a ViewModel.
10
+ - **Improved**: Use a Repository for data fetching and a Use Case for business logic. The Composable should only be responsible for rendering the UI.
11
+
12
+ ## 2. Open/Closed Principle (OCP)
13
+
14
+ > **"Software entities should be open for extension, but closed for modification."**
15
+
16
+ - **Android Example**: Use **Slot APIs** in Jetpack Compose.
17
+ - **Improved**: Instead of a giant `MyButton` with 20 parameters, accept a `content: @Composable () -> Unit` lambda. This allows the button to be extended with any content without modifying the `MyButton` code.
18
+
19
+ ## 3. Liskov Substitution Principle (LSP)
20
+
21
+ > **"Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program."**
22
+
23
+ - **Android Example**: If you have a `BaseRepository`, any implementation (e.g., `ProdRepo`, `MockRepo`) should behave predictably according to the interface contract.
24
+ - **Avoid**: Throwing `NotImplementedError` in a subclass for a method that is part of the parent's contract.
25
+
26
+ ## 4. Interface Segregation Principle (ISP)
27
+
28
+ > **"Many client-specific interfaces are better than one general-purpose interface."**
29
+
30
+ - **Android Example**: Instead of one massive `Actions` interface for a screen, split it into smaller ones like `AuthActions`, `ProfileActions`.
31
+ - **Improved**: Prevents a class from being forced to implement methods it doesn't need.
32
+
33
+ ## 5. Dependency Inversion Principle (DIP)
34
+
35
+ > **"Depend upon abstractions, not concretions."**
36
+
37
+ - **Android Example**: A ViewModel should depend on a `UserRepository` interface, not the `UserRepositoryImpl` class.
38
+ - **Implementation**:
39
+ - Higher-level modules should not depend on lower-level modules. Both should depend on abstractions.
40
+ - Abstractions should not depend on details. Details should depend on abstractions.
41
+
42
+ ## Implementation Pattern (DIP)
43
+
44
+ ```kotlin
45
+ // Abstraction (Domain Layer)
46
+ interface WeatherRepository {
47
+ suspend fun getCurrentWeather(): Weather
48
+ }
49
+
50
+ // Low-level detail (Data Layer)
51
+ class OpenWeatherRepo : WeatherRepository {
52
+ override suspend fun getCurrentWeather() = // ...
53
+ }
54
+
55
+ // High-level module (UI Layer)
56
+ class WeatherViewModel @Inject constructor(
57
+ private val repository: WeatherRepository // Depends on abstraction
58
+ ) : ViewModel() {
59
+ // ...
60
+ }
61
+ ```
@@ -0,0 +1,46 @@
1
+ # State Management in Jetpack Compose
2
+
3
+ Effective state management is crucial for creating responsive and predictable UIs.
4
+
5
+ ## Core Rules
6
+
7
+ 1. **Prefer ViewModels**: Business logic and state should reside in a `ViewModel` to survive configuration changes.
8
+ 2. **Use `StateFlow`**: Represent UI state as a single `StateFlow<UiState>` in the ViewModel.
9
+ 3. **Immutability**: UI state objects must be immutable. Use `data class` with `copy()`.
10
+ 4. **State Hoisting**: Composables should accept state and emit events (lambdas).
11
+
12
+ ## 2. Patterns
13
+
14
+ ### The UiState Pattern
15
+
16
+ ```kotlin
17
+ data class UserProfileUiState(
18
+ val username: String = "",
19
+ val isLoading: Boolean = false,
20
+ val errorMessage: String? = null
21
+ )
22
+ ```
23
+
24
+ ### ViewModel Implementation
25
+
26
+ ```kotlin
27
+ class UserProfileViewModel : ViewModel() {
28
+ private val _uiState = MutableStateFlow(UserProfileUiState())
29
+ val uiState: StateFlow<UserProfileUiState> = _uiState.asStateFlow()
30
+
31
+ fun onUsernameChanged(newName: String) {
32
+ _uiState.update { it.copy(username = newName) }
33
+ }
34
+ }
35
+ ```
36
+
37
+ ## 3. Side Effects
38
+
39
+ - **`LaunchedEffect`**: For actions triggered by state changes (e.g., showing a Snackbar).
40
+ - **`SideEffect`**: For actions that must run after every successful recomposition.
41
+ - **`rememberUpdatedState`**: To capture the latest value in a long-running effect without restarting it.
42
+
43
+ ## 4. Avoid
44
+
45
+ - Avoid `MutableState` inside ViewModels; use `MutableStateFlow` instead for better integration with multi-platform or other layers.
46
+ - Never pass a `ViewModel` deeper than the "Screen" level composable.
@@ -0,0 +1,48 @@
1
+ # Testing in Jetpack Compose
2
+
3
+ Compose testing is semantic-based, meaning it focuses on what the user sees and interacts with, rather than internal implementation details.
4
+
5
+ ## Rule of Selection
6
+
7
+ 1. **`hasText`** / **`hasContentDescription`**: Best for accessibility.
8
+ 2. **`hasTestTag`**: Use only as a last resort for unique identification of complex nodes.
9
+
10
+ ## 2. Setup
11
+
12
+ ```kotlin
13
+ class MyComposeTest {
14
+ @get:Rule
15
+ val composeTestRule = createComposeRule()
16
+
17
+ @Test
18
+ fun loginFlow_showsErrorOnEmptyPassword() {
19
+ composeTestRule.setContent {
20
+ AppTheme {
21
+ LoginScreen(...)
22
+ }
23
+ }
24
+
25
+ // Action
26
+ composeTestRule.onNodeWithText("Login").performClick()
27
+
28
+ // Assertion
29
+ composeTestRule.onNodeWithText("Password cannot be empty").assertIsDisplayed()
30
+ }
31
+ }
32
+
33
+ ## 3. Best Practices
34
+
35
+ - **Avoid `Thread.sleep`**: The `ComposeTestRule` automatically waits for the UI to be idle (IdlingResources).
36
+ - **Semantics**: Use `Modifier.semantics { contentDescription = "..." }` to make your UI testable and accessible at the same time.
37
+ - **Isolate Screenshots**: Use `captureToImage()` for visual regression testing.
38
+ - **Hilt Testing**: Use `@HiltAndroidTest` for integration tests that require a real ViewModel/Repository.
39
+
40
+ ## 4. Verification Checklist
41
+
42
+ - Assert visibility with `assertIsDisplayed()`.
43
+ - Assert existence with `assertExists()`.
44
+ - Assert interactive state with `assertIsEnabled()` or `assertIsSelected()`.
45
+
46
+ ## 5. Pro Tip
47
+
48
+ Use the `DeviceConfigurationOverride` APIs to test your UI against different screen sizes, font scales, and locales within a single test file.
@@ -0,0 +1,52 @@
1
+ # Theming in Jetpack Compose
2
+
3
+ Jetpack Compose uses Material 3 (M3) as its default design system.
4
+
5
+ ## Material Theme Structure
6
+
7
+ A standard M3 theme consists of:
8
+
9
+ 1. **`ColorScheme`**: Dynamic vs Static colors.
10
+ 2. **`Typography`**: Standardized text styles (Headline, Body, Label).
11
+ 3. **`Shapes`**: Corner radiuses for components.
12
+
13
+ ## Implementation Pattern
14
+
15
+ ```kotlin
16
+ @Composable
17
+ fun AppTheme(
18
+ darkTheme: Boolean = isSystemInDarkTheme(),
19
+ dynamicColor: Boolean = true,
20
+ content: @Composable () -> Unit
21
+ ) {
22
+ val colorScheme = when {
23
+ dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
24
+ val context = LocalContext.current
25
+ if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
26
+ }
27
+ darkTheme -> DarkColorScheme
28
+ else -> LightColorScheme
29
+ }
30
+
31
+ MaterialTheme(
32
+ colorScheme = colorScheme,
33
+ typography = Typography,
34
+ content = content
35
+ )
36
+ }
37
+ ```
38
+
39
+ ## 3. Best Practices
40
+
41
+ - **Use Theme Tokens**: Never hardcode hex values like `Color(0xFF00FF00)`. Always use `MaterialTheme.colorScheme.primary`.
42
+ - **Text Styles**: Use `MaterialTheme.typography.bodyLarge` instead of modifying `fontSize` and `fontWeight` manually.
43
+ - **Local Providers**: Create your own `CompositionLocal` if you need to pass custom theme attributes (e.g., custom spacing or extra semantic colors) that aren't in M3.
44
+
45
+ ## 4. Visual Hierarchy
46
+
47
+ - Use `Surface` to automatically handle background colors and content color nesting (providing correct contrast).
48
+ - Use `Elevated` vs `Filled` vs `Outlined` components to signify importance.
49
+
50
+ ## 5. Dark Mode Support
51
+
52
+ - Always verify your theme in both light and dark modes. Use `@Preview` with `uiMode = UI_MODE_NIGHT_YES`.