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.
- package/index.js +17 -0
- package/lib/commands/init.js +42 -0
- package/package.json +26 -0
- package/templates/mad-skills/SKILL.md +77 -0
- package/templates/mad-skills/references/accessibility.md +63 -0
- package/templates/mad-skills/references/adaptive_layouts.md +51 -0
- package/templates/mad-skills/references/advanced_performance.md +52 -0
- package/templates/mad-skills/references/advanced_ui.md +64 -0
- package/templates/mad-skills/references/ai_ml.md +38 -0
- package/templates/mad-skills/references/architecture_di.md +63 -0
- package/templates/mad-skills/references/automation_cicd.md +53 -0
- package/templates/mad-skills/references/clean_architecture.md +80 -0
- package/templates/mad-skills/references/cmp_migration.md +56 -0
- package/templates/mad-skills/references/concurrency.md +50 -0
- package/templates/mad-skills/references/deeplinks.md +42 -0
- package/templates/mad-skills/references/design_principles.md +47 -0
- package/templates/mad-skills/references/design_systems.md +47 -0
- package/templates/mad-skills/references/domain_layer.md +47 -0
- package/templates/mad-skills/references/google_play_skills.md +40 -0
- package/templates/mad-skills/references/kmp_migration.md +62 -0
- package/templates/mad-skills/references/layouts.md +57 -0
- package/templates/mad-skills/references/local_data.md +53 -0
- package/templates/mad-skills/references/migration_xml_to_compose.md +80 -0
- package/templates/mad-skills/references/modifiers.md +46 -0
- package/templates/mad-skills/references/modularization.md +70 -0
- package/templates/mad-skills/references/multiplatform.md +55 -0
- package/templates/mad-skills/references/navigation.md +48 -0
- package/templates/mad-skills/references/network_data.md +87 -0
- package/templates/mad-skills/references/observability.md +46 -0
- package/templates/mad-skills/references/performance.md +51 -0
- package/templates/mad-skills/references/security.md +67 -0
- package/templates/mad-skills/references/solid_principles.md +61 -0
- package/templates/mad-skills/references/state_management.md +46 -0
- package/templates/mad-skills/references/testing.md +48 -0
- package/templates/mad-skills/references/theming.md +52 -0
- package/templates/mad-skills/references/ui_patterns.md +80 -0
- package/templates/mad-skills/references/workmanager.md +55 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Migrating from Jetpack Compose to Compose Multiplatform (CMP)
|
|
2
|
+
|
|
3
|
+
Compose Multiplatform (CMP) allows you to use your Jetpack Compose UI skills to build applications for iOS, Web, and Desktop.
|
|
4
|
+
|
|
5
|
+
## 1. The Migration Workflow
|
|
6
|
+
|
|
7
|
+
Once your project has a KMP module (see [KMP Migration](file:///Users/denirohimat/Works/JetpackComposeSkills/references/kmp_migration.md)), you can start sharing your UI.
|
|
8
|
+
|
|
9
|
+
### Phase 1: Dependency Update
|
|
10
|
+
|
|
11
|
+
- Change your Gradle dependencies from `androidx.compose.*` to the multiplatform equivalent `org.jetbrains.compose.*`.
|
|
12
|
+
- Use the **Compose Multiplatform Gradle Plugin** to manage versions across platforms.
|
|
13
|
+
|
|
14
|
+
### Phase 2: Move UI to `commonMain`
|
|
15
|
+
|
|
16
|
+
- Move your `@Composable` functions from the `:app` module (Android) to the `commonMain` source set of your shared module.
|
|
17
|
+
- **Goal**: Keep as much UI logic in `commonMain` as possible.
|
|
18
|
+
|
|
19
|
+
### Phase 3: Replacement of Android-Specific APIs
|
|
20
|
+
|
|
21
|
+
Replace Android-only APIs with their KMP/CMP equivalents:
|
|
22
|
+
|
|
23
|
+
- **Resources**: Replace `R.string.*` or `R.drawable.*` with **library-based resource management** (e.g., `Moko-Resources` or the built-in CMP resources API).
|
|
24
|
+
- **ViewModels**: Use **KMP ViewModels** (e.g., from `androidx.lifecycle` KMP support or `Decompose`).
|
|
25
|
+
- **Navigation**: Switch to multiplatform navigation libraries like **Compose-Navigation (Multiplatform)**, **Voyager**, or **Decompose**.
|
|
26
|
+
|
|
27
|
+
## 2. Platform-Specific UI: `expect/actual`
|
|
28
|
+
|
|
29
|
+
Sometimes you need a native UI component (e.g., a Google Map or a specialized iOS picker).
|
|
30
|
+
|
|
31
|
+
- **Expect**: Define a `Composable` interface in `commonMain`.
|
|
32
|
+
- **Actual (Android)**: Implement using `AndroidView` to wrap existing native views.
|
|
33
|
+
- **Actual (iOS)**: Implement using `UIKitView` to wrap UIKit/SwiftUI components.
|
|
34
|
+
|
|
35
|
+
## 3. Handling Graphics and Interop
|
|
36
|
+
|
|
37
|
+
- **Canvas**: CMP supports a common Canvas API across all platforms.
|
|
38
|
+
- **Images**: Ensure images are in formats supported by all platforms (WebP, PNG, SVG).
|
|
39
|
+
- **Fonts**: Bundle custom fonts in the shared module to ensure consistent typography.
|
|
40
|
+
|
|
41
|
+
## 4. Migration Checklist
|
|
42
|
+
|
|
43
|
+
- [ ] Compose Multiplatform plugin applied?
|
|
44
|
+
- [ ] Dependencies switched to `org.jetbrains.compose`?
|
|
45
|
+
- [ ] UI components moved to `commonMain`?
|
|
46
|
+
- [ ] Resources (Strings/Images) migrated to multiplatform-ready API?
|
|
47
|
+
- [ ] Navigation replaced with multiplatform solution?
|
|
48
|
+
- [ ] ViewModels updated to support KMP?
|
|
49
|
+
|
|
50
|
+
## 5. Next Steps
|
|
51
|
+
|
|
52
|
+
After migrating the UI, test on different targets:
|
|
53
|
+
|
|
54
|
+
- **iOS**: Run using the `iosApp` target in Android Studio or Xcode.
|
|
55
|
+
- **Desktop**: Run using `./gradlew :shared:run`.
|
|
56
|
+
- **Web**: Run using `./gradlew :shared:jsBrowserDevelopmentRun`.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Concurrency and Flow in Jetpack Compose
|
|
2
|
+
|
|
3
|
+
Kotlin Coroutines and Flow are the lifeblood of asynchronous programming in modern Android development.
|
|
4
|
+
|
|
5
|
+
## 1. Collecting Flow Safely
|
|
6
|
+
|
|
7
|
+
**NEVER** use `collectAsState()` in production. Always use `collectAsStateWithLifecycle()` to ensure collection stops when the app is in the background.
|
|
8
|
+
|
|
9
|
+
```kotlin
|
|
10
|
+
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
*Requires `androidx.lifecycle:lifecycle-runtime-compose` dependency.*
|
|
14
|
+
|
|
15
|
+
## 2. Side Effect APIs
|
|
16
|
+
|
|
17
|
+
- **`LaunchedEffect`**: Runs a coroutine when a "key" changes. If the key is `Unit`, it runs once when the composable enters the composition.
|
|
18
|
+
- **`rememberCoroutineScope`**: Returns a scope tied to the composition lifecycle. Use this for firing coroutines in response to user events (like button clicks).
|
|
19
|
+
- **`DisposableEffect`**: Used for setup/teardown logic (e.g., registering listeners).
|
|
20
|
+
|
|
21
|
+
```kotlin
|
|
22
|
+
val scope = rememberCoroutineScope()
|
|
23
|
+
Button(onClick = {
|
|
24
|
+
scope.launch { /* trigger long running task */ }
|
|
25
|
+
}) { ... }
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 3. Best Practices for Coroutines
|
|
29
|
+
|
|
30
|
+
- **Main-Safe ViewModels**: All ViewModel functions should be "main-safe". The ViewModel is responsible for choosing the correct Dispatcher (usually `Dispatchers.IO` for background tasks).
|
|
31
|
+
- **Avoid GlobalScope**: Always use `viewModelScope` or `rememberCoroutineScope` to ensure tasks are canceled when no longer needed.
|
|
32
|
+
- **Flow Operators**: Use `onEach`, `map`, and `filter` in the ViewModel to transform data before it reaches the UI.
|
|
33
|
+
|
|
34
|
+
## 4. Derived State and Flow
|
|
35
|
+
|
|
36
|
+
If you have a Flow that depends on another Flow, use `combine` or `flatMapLatest` in the ViewModel.
|
|
37
|
+
|
|
38
|
+
```kotlin
|
|
39
|
+
val filteredData = combine(searchQuery, rawData) { query, data ->
|
|
40
|
+
data.filter { it.contains(query) }
|
|
41
|
+
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 5. Threading Rule
|
|
45
|
+
|
|
46
|
+
- **Composition**: Must be on the Main Thread.
|
|
47
|
+
- **Business Logic/Data**: Should be on background threads (managed by the Repository/ViewModel).
|
|
48
|
+
- **State Updates**: Updates to `MutableStateFlow` or Compose `State` are thread-safe, but usually happen on the Main Thread.
|
|
49
|
+
|
|
50
|
+
## 6. Threading Rule
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Deep Links and App Links
|
|
2
|
+
|
|
3
|
+
Deep links allow users to navigate directly to a specific part of your app from an external source (Email, Website, SMS).
|
|
4
|
+
|
|
5
|
+
## 1. Traditional Deep Links
|
|
6
|
+
|
|
7
|
+
Easy to implement but not exclusive to your app. Multiple apps might try to handle the same URI.
|
|
8
|
+
|
|
9
|
+
- **Scheme**: e.g., `myapp://product/123`.
|
|
10
|
+
- **Implementation**: Define an `<intent-filter>` in `AndroidManifest.xml`.
|
|
11
|
+
|
|
12
|
+
## 2. Android App Links (Verified Deep Links)
|
|
13
|
+
|
|
14
|
+
The most secure way to handle links. These are verified against a web domain you own.
|
|
15
|
+
|
|
16
|
+
- **Security**: Ensures that only your app can handle your website's URLs.
|
|
17
|
+
- **UX**: Opens the app directly without showing the "Disambiguation Dialog".
|
|
18
|
+
- **Verification**: Requires a `assetlinks.json` file hosted on your website at `.well-known/assetlinks.json`.
|
|
19
|
+
|
|
20
|
+
## 3. Navigation Component Integration
|
|
21
|
+
|
|
22
|
+
Integration with the Jetpack Navigation library makes Deep Linking seamless.
|
|
23
|
+
|
|
24
|
+
```kotlin
|
|
25
|
+
composable(
|
|
26
|
+
route = "product/{id}",
|
|
27
|
+
deepLinks = listOf(navDeepLink { uriPattern = "https://www.myapp.com/product/{id}" })
|
|
28
|
+
) { backStackEntry ->
|
|
29
|
+
val id = backStackEntry.arguments?.getString("id")
|
|
30
|
+
ProductScreen(productId = id)
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 4. Handling Parameters
|
|
35
|
+
|
|
36
|
+
- **Type Safety**: Use the default navigation arguments to parse IDs or tokens.
|
|
37
|
+
- **Fallback**: Always provide a fallback (e.g., Home Screen) if the deep link parameters are malformed or the resource no longer exists.
|
|
38
|
+
|
|
39
|
+
## 4. Security Considerations
|
|
40
|
+
|
|
41
|
+
- **Input Validation**: Treat all deep link data as "untrusted". Validate strings, IDs, and tokens before using them.
|
|
42
|
+
- **Authentication**: Ensure that sensitive screens (like "Change Password") still require a valid session even if opened via a deep link.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Jetpack Compose Design Principles
|
|
2
|
+
|
|
3
|
+
Compose isn't just a new UI toolkit; it's a shift in how we think about building UIs.
|
|
4
|
+
|
|
5
|
+
## 1. Declarative over Imperative
|
|
6
|
+
|
|
7
|
+
- **Imperative**: You manually mutate the UI state (e.g., `textView.text = "Hello"`). You manage the transition between states.
|
|
8
|
+
- **Declarative**: You describe what the UI should look like for a given state. Compose handles the transitions (recomposition) automatically.
|
|
9
|
+
|
|
10
|
+
## 2. Composition over Inheritance
|
|
11
|
+
|
|
12
|
+
Compose favors shallow hierarchies of reusable components rather than deep class inheritance.
|
|
13
|
+
|
|
14
|
+
- **Slot API**: Instead of extending a class, pass a `@Composable` lambda to a "slot" to customize content.
|
|
15
|
+
- **Small Components**: Build complex UIs by combining many small, single-purpose composables.
|
|
16
|
+
|
|
17
|
+
## 3. Unidirectional Data Flow (UDF)
|
|
18
|
+
|
|
19
|
+
- **State flows down**: The parent provides data to the child via parameters.
|
|
20
|
+
- **Events flow up**: The child notifies the parent of user interactions via lambdas.
|
|
21
|
+
|
|
22
|
+
This makes the UI predictable, easier to test, and reduces bugs related to state synchronization.
|
|
23
|
+
|
|
24
|
+
## 4. Pure & Stateless Composables
|
|
25
|
+
|
|
26
|
+
A good composable should ideally be a "pure function" of its arguments.
|
|
27
|
+
|
|
28
|
+
- **Stateless**: The composable doesn't manage its own state; it receives it (State Hoisting).
|
|
29
|
+
- **Side-Effect Free**: Recomposition can happen many times. Never perform business logic, network calls, or database writes directly inside a composable function. Use `LaunchedEffect` or `produceState`.
|
|
30
|
+
|
|
31
|
+
## 5. Single Source of Truth
|
|
32
|
+
|
|
33
|
+
State should live in one place (usually a `ViewModel`). Any piece of data should have exactly one "owner" who is responsible for updating it.
|
|
34
|
+
|
|
35
|
+
## 6. Stability and Idempotence
|
|
36
|
+
|
|
37
|
+
- **Stable**: If the inputs haven't changed, the output (UI) shouldn't change.
|
|
38
|
+
- **Idempotent**: Running the same composable with the same parameters should always produce the same result and have no side effects.
|
|
39
|
+
|
|
40
|
+
## Why it Matters
|
|
41
|
+
|
|
42
|
+
Following these principles leads to:
|
|
43
|
+
|
|
44
|
+
1. **Less Code**: Declarative UI is often much more concise than XML.
|
|
45
|
+
2. **Fewer Bugs**: UDF eliminates entire classes of state-synchronization bugs.
|
|
46
|
+
3. **Easier Testing**: Pure functions are straightforward to unit test.
|
|
47
|
+
4. **Better Performance**: Compose can skip recomposition for stable, unchanged components.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Design Systems at Scale
|
|
2
|
+
|
|
3
|
+
A professional Design System goes beyond basic theming. It provides a common language between designers and developers.
|
|
4
|
+
|
|
5
|
+
## 1. Design Tokens
|
|
6
|
+
|
|
7
|
+
Tokens are the smallest pieces of the design system: colors, spacing, typography, and elevation stored as data.
|
|
8
|
+
|
|
9
|
+
### Standard Token Structure
|
|
10
|
+
|
|
11
|
+
- **Primitive Tokens**: `blue-500`, `spacing-16`.
|
|
12
|
+
- **Semantic Tokens**: `color-primary`, `spacing-medium`.
|
|
13
|
+
- **Component Tokens**: `button-background-color`.
|
|
14
|
+
|
|
15
|
+
## 2. Shared Component Library
|
|
16
|
+
|
|
17
|
+
Encapsulate Material 3 components into your own brand-specific wrappers to ensure consistency.
|
|
18
|
+
|
|
19
|
+
```kotlin
|
|
20
|
+
@Composable
|
|
21
|
+
fun MadButton(
|
|
22
|
+
text: String,
|
|
23
|
+
onClick: () -> Unit,
|
|
24
|
+
modifier: Modifier = Modifier,
|
|
25
|
+
enabled: Boolean = true
|
|
26
|
+
) {
|
|
27
|
+
Button(
|
|
28
|
+
onClick = onClick,
|
|
29
|
+
modifier = modifier,
|
|
30
|
+
enabled = enabled,
|
|
31
|
+
shape = MadTheme.shapes.button, // Custom shape from tokens
|
|
32
|
+
colors = ButtonDefaults.buttonColors(
|
|
33
|
+
containerColor = MadTheme.colors.primary
|
|
34
|
+
)
|
|
35
|
+
) {
|
|
36
|
+
Text(text = text, style = MadTheme.typography.label)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 3. Documentation (Showcase)
|
|
42
|
+
|
|
43
|
+
Create a "Catalog" or "Showcase" app within your project to view and test components in isolation.
|
|
44
|
+
|
|
45
|
+
- Use **Compose Previews** extensively with different configurations (Dark mode, Large font).
|
|
46
|
+
- Consider tools like **Showkase** to automatically generate a component browser.
|
|
47
|
+
- Use **Screenshot Testing** (Paparazzi or Roborazzi) to prevent visual regressions.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Domain Layer in Modern Android Development
|
|
2
|
+
|
|
3
|
+
The Domain Layer is an optional layer that sits between the UI and Data layers. It is responsible for encapsulating complex business logic or logic that is reused by multiple ViewModels.
|
|
4
|
+
|
|
5
|
+
## 1. The Role of the Domain Layer
|
|
6
|
+
|
|
7
|
+
- **Simplification**: Simplifies ViewModels by removing business logic.
|
|
8
|
+
- **Reusability**: Allows sharing logic across different screens (e.g., calculating a complex price).
|
|
9
|
+
- **Testability**: Makes business logic easy to unit test without dependencies on Android classes or UI.
|
|
10
|
+
|
|
11
|
+
## 2. Use Cases
|
|
12
|
+
|
|
13
|
+
The primary component of the Domain Layer is the `UseCase` (often called an Interactor).
|
|
14
|
+
|
|
15
|
+
### Naming Convention
|
|
16
|
+
|
|
17
|
+
A Use Case should be named after the action it performs: `Verb` + `Noun` + `UseCase`.
|
|
18
|
+
Example: `GetUserDataUseCase`, `ValidateEmailUseCase`.
|
|
19
|
+
|
|
20
|
+
### Implementation
|
|
21
|
+
|
|
22
|
+
Each Use Case should typically perform only **one** task and expose a single public function (usually using the `operator fun invoke`).
|
|
23
|
+
|
|
24
|
+
```kotlin
|
|
25
|
+
class GetUserUseCase @Inject constructor(
|
|
26
|
+
private val userRepository: UserRepository
|
|
27
|
+
) {
|
|
28
|
+
operator fun invoke(userId: String): Flow<User> {
|
|
29
|
+
return userRepository.getUser(userId)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## 3. When to Use a Domain Layer
|
|
35
|
+
|
|
36
|
+
You don't always need a Domain Layer. Google recommends adding it only when:
|
|
37
|
+
|
|
38
|
+
1. **Logic is complex**: E.g., data from multiple repositories needs to be combined.
|
|
39
|
+
2. **Logic is reused**: E.g., multiple ViewModels need to perform the same validation.
|
|
40
|
+
3. **Purity is desired**: You want to keep your domain logic purely in Kotlin, away from the Data layer's implementation details.
|
|
41
|
+
|
|
42
|
+
## 4. Best Practices
|
|
43
|
+
|
|
44
|
+
- **Stateless**: Use Cases should be stateless. Any state should be managed by the UI or Data layers.
|
|
45
|
+
- **Dependency Direction**: The Domain layer depends on the Data layer's **interfaces** (abstractions), not its implementations.
|
|
46
|
+
- **Single Source of Truth**: Use Cases should not maintain their own cache; they should rely on the Data layer for that.
|
|
47
|
+
- **Error Handling**: Use Cases can catch data-layer-specific exceptions and map them to domain-specific errors.
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Google Play Skills and Compliance
|
|
2
|
+
|
|
3
|
+
Publishing an app successfully requires adhering to Google Play policies and ensuring technical quality.
|
|
4
|
+
|
|
5
|
+
## 1. App Quality & Performance
|
|
6
|
+
|
|
7
|
+
Google Play prioritizes apps that are stable and performant.
|
|
8
|
+
|
|
9
|
+
- **Vitals**: Monitor Android Vitals in the Play Console to track crash rates and ANRs (Application Not Responding).
|
|
10
|
+
- **Frame Rate**: Ensure your Compose UI hits 60fps (or 120fps on capable screens) to avoid "jank" that lowers your store ranking.
|
|
11
|
+
|
|
12
|
+
## 2. Policy Compliance
|
|
13
|
+
|
|
14
|
+
- **Data Safety**: Be transparent about data collection. Every app must have a Privacy Policy URL.
|
|
15
|
+
- **Permissions**: Only request "dangerous" permissions that are essential for the app's core functionality. Explain clearly to the user why they are needed (Requirement: Data Safety Section).
|
|
16
|
+
- **Sensitive Content**: Adhere to content ratings and avoid restricted content (e.g., hate speech, illegal acts).
|
|
17
|
+
|
|
18
|
+
## 3. Technical Requirements
|
|
19
|
+
|
|
20
|
+
- **Android App Bundle (AAB)**: Google Play now requires the `.aab` format instead of `.apk` for new apps. This allows for dynamic delivery and smaller download sizes.
|
|
21
|
+
- **Target API Level**: Keep your app updated. Google Play requires apps to target a recent API level (usually within 1 year of the latest release).
|
|
22
|
+
- **Play Integrity API**: Use this to ensure your app is running on a genuine device and hasn't been tampered with (crucial for games and financial apps).
|
|
23
|
+
|
|
24
|
+
## 4. App Store Optimization (ASO)
|
|
25
|
+
|
|
26
|
+
- **Keywords**: Use relevant terms in your title and short description.
|
|
27
|
+
- **Visuals**: High-quality screenshots and a compelling feature graphic significantly increase conversion rates.
|
|
28
|
+
- **Localization**: Localize your store listing and in-app text for your main target markets.
|
|
29
|
+
|
|
30
|
+
## 5. Release Management
|
|
31
|
+
|
|
32
|
+
- **Internal Testing**: Use Internal Testing tracks for quick feedback from your team.
|
|
33
|
+
- **Staged Rollouts**: Deploy updates to a small percentage of users (e.g., 1%, 10%) first to catch unforeseen bugs early.
|
|
34
|
+
- **Review Management**: Respond to user reviews professionally; this improves user trust and can lead to better ratings.
|
|
35
|
+
|
|
36
|
+
## 6. Best Practices for Developers
|
|
37
|
+
|
|
38
|
+
- **API Deprecation**: Monitor the Android Developers blog for upcoming API changes or deprecations.
|
|
39
|
+
- **ProGuard/R8**: Always enable R8 in your release builds to shrink and obfuscate your code, reducing app size and protecting your IP.
|
|
40
|
+
- **Firebase App Distribution**: Use this for pre-release testing before moving to Play Console tracks.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Migrating from Native Android to Kotlin Multiplatform (KMP)
|
|
2
|
+
|
|
3
|
+
Migrating an existing Android project to KMP allows you to share business logic with iOS, Web, and Desktop while keeping your existing Android UI.
|
|
4
|
+
|
|
5
|
+
## 1. The Migration Strategy: "Data-First"
|
|
6
|
+
|
|
7
|
+
The most successful migrations follow a bottom-up approach, starting with the data layer.
|
|
8
|
+
|
|
9
|
+
### Phase 1: Preparation
|
|
10
|
+
|
|
11
|
+
- **Modularize**: Ensure your app has a clear separation between UI and Data/Domain.
|
|
12
|
+
- **Dependency Audit**: Identify libraries that don't support KMP (e.g., standard Room, Retrofit, Glide). Find KMP alternatives (Ktor, SQLDelight/Room-KMP).
|
|
13
|
+
|
|
14
|
+
### Phase 2: Create the Shared Module
|
|
15
|
+
|
|
16
|
+
- Add a new `KMP Shared Module` to your project (usually named `:shared` or `:common`).
|
|
17
|
+
- Configure `commonMain`, `androidMain`, and `iosMain` source sets in `build.gradle.kts`.
|
|
18
|
+
|
|
19
|
+
### Phase 3: Migrate Data Models
|
|
20
|
+
|
|
21
|
+
- Move your POJOs/Data Classes to `commonMain`.
|
|
22
|
+
- Ensure they only use Kotlin standard libraries (no `android.*` imports).
|
|
23
|
+
|
|
24
|
+
### Phase 4: Migrate Networking & Persistence
|
|
25
|
+
|
|
26
|
+
- Replace Retrofit with **Ktor**.
|
|
27
|
+
- Replace Room with **SQLDelight** or use the new **Room-KMP** version.
|
|
28
|
+
- Move Repositories to `commonMain`.
|
|
29
|
+
|
|
30
|
+
### Phase 5: Domain Layer Migration
|
|
31
|
+
|
|
32
|
+
- Move Use Cases and business logic to `commonMain`.
|
|
33
|
+
- Use `expect/actual` for platform-specific logic (e.g., getting a device ID or logging).
|
|
34
|
+
|
|
35
|
+
## 2. Handling Android-Specific Dependencies
|
|
36
|
+
|
|
37
|
+
If a class depends on `Context`, you have two choices:
|
|
38
|
+
|
|
39
|
+
1. **Remove the dependency**: Refactor the class to not need `Context`.
|
|
40
|
+
2. **Inject from Android**: Define an interface in `commonMain` and provide the implementation in `androidMain` using the Android `Context`.
|
|
41
|
+
|
|
42
|
+
## 3. Integration with iOS
|
|
43
|
+
|
|
44
|
+
Once the shared module is ready:
|
|
45
|
+
|
|
46
|
+
- Connect the shared module to an Xcode project using the `KMP CocoaPods plugin` or `Swift Package Manager (SPM)`.
|
|
47
|
+
- Call the shared Repositories/Use Cases from SwiftUI.
|
|
48
|
+
|
|
49
|
+
## 4. Common Pitfalls
|
|
50
|
+
|
|
51
|
+
- **Avoid `android.*` in Shared**: Using any Android-specific library in `commonMain` will break the iOS build.
|
|
52
|
+
- **Serialization**: Use `kotlinx-serialization` instead of GSON or Moshi if you want to share models.
|
|
53
|
+
- **Concurrency**: Use `Flow` as the bridge between Kotlin and Swift (with SKIE for better compatibility).
|
|
54
|
+
|
|
55
|
+
## 5. Migration Checklist
|
|
56
|
+
|
|
57
|
+
- [ ] Project modularized by layer?
|
|
58
|
+
- [ ] KMP shared module created?
|
|
59
|
+
- [ ] Networking moved to Ktor?
|
|
60
|
+
- [ ] Database moved to SQLDelight/Room-KMP?
|
|
61
|
+
- [ ] Business logic (Use Cases) in `commonMain`?
|
|
62
|
+
- [ ] iOS project consuming shared code?
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Layouts and Structure
|
|
2
|
+
|
|
3
|
+
Jetpack Compose provides powerful tools for building flexible and responsive layouts.
|
|
4
|
+
|
|
5
|
+
## Fundamental Components
|
|
6
|
+
|
|
7
|
+
1. **`Scaffold`**: Always use `Scaffold` as the root of a screen to handle Top Bars, Bottom Bars, and Snackbars.
|
|
8
|
+
2. **`Column` / `Row`**: The primary building blocks for vertical and horizontal alignment.
|
|
9
|
+
3. **`Box`**: Use for overlapping content or centering elements.
|
|
10
|
+
4. **`LazyColumn` / `LazyRow`**: Essential for lists with many items to ensure performance.
|
|
11
|
+
|
|
12
|
+
## Best Practices
|
|
13
|
+
|
|
14
|
+
- **Weight**: Use `Modifier.weight()` within Columns and Rows to distribute space proportionally.
|
|
15
|
+
- **Content Padding**: Use `PaddingValues` in Lazy lists instead of adding padding to individual items to avoid clipping issues.
|
|
16
|
+
- **Intrinsics**: Avoid fixed heights where possible; use `Modifier.height(IntrinsicSize.Min/Max)` if you need components to match heights.
|
|
17
|
+
|
|
18
|
+
## Responsive Design
|
|
19
|
+
|
|
20
|
+
- Use `Adaptive` layouts for larger screens.
|
|
21
|
+
- Check `WindowWidthSizeClass` to switch between Navigation Rail (Tablet/Desktop) and Bottom Navigation (Mobile).
|
|
22
|
+
|
|
23
|
+
## Example: Standard Screen Structure
|
|
24
|
+
|
|
25
|
+
```kotlin
|
|
26
|
+
@Composable
|
|
27
|
+
fun MyScreen(
|
|
28
|
+
uiState: MyUiState,
|
|
29
|
+
onBackClick: () -> Unit
|
|
30
|
+
) {
|
|
31
|
+
Scaffold(
|
|
32
|
+
topBar = {
|
|
33
|
+
TopAppBar(
|
|
34
|
+
title = { Text("Title") },
|
|
35
|
+
navigationIcon = {
|
|
36
|
+
IconButton(onClick = onBackClick) {
|
|
37
|
+
Icon(Icons.Default.ArrowBack, contentDescription = null)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
) { innerPadding ->
|
|
43
|
+
Column(
|
|
44
|
+
modifier = Modifier
|
|
45
|
+
.padding(innerPadding)
|
|
46
|
+
.fillMaxSize()
|
|
47
|
+
) {
|
|
48
|
+
// Content here
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 5. Optimization
|
|
55
|
+
|
|
56
|
+
- Use `LazyColumn` keys (`items(items, key = { it.id })`) to help Compose maintain state during list updates.
|
|
57
|
+
- Use `contentType` in Lazy lists to help with view recycling efficiency.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Local Data Persistence in Jetpack Compose
|
|
2
|
+
|
|
3
|
+
Handling persistent data correctly ensures a seamless offline-first experience.
|
|
4
|
+
|
|
5
|
+
## 1. Room Database
|
|
6
|
+
|
|
7
|
+
Room is the recommended way to handle structured local data.
|
|
8
|
+
|
|
9
|
+
**Pattern**: Expose data as a `Flow` in the DAO.
|
|
10
|
+
|
|
11
|
+
```kotlin
|
|
12
|
+
@Dao
|
|
13
|
+
interface UserDao {
|
|
14
|
+
@Query("SELECT * FROM users")
|
|
15
|
+
fun getAllUsers(): Flow<List<User>>
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Consumption in Compose**:
|
|
20
|
+
|
|
21
|
+
```kotlin
|
|
22
|
+
val users by viewModel.usersFlow.collectAsStateWithLifecycle(initialValue = emptyList())
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## 2. DataStore (Preferences)
|
|
26
|
+
|
|
27
|
+
Use DataStore instead of SharedPreferences for simple key-value pairs.
|
|
28
|
+
|
|
29
|
+
- **Async/Safe**: It uses Coroutines and handles data updates atomically.
|
|
30
|
+
- **State Integration**: Since it exposes a `Flow`, it integrates perfectly with Compose.
|
|
31
|
+
|
|
32
|
+
```kotlin
|
|
33
|
+
val settingsFlow: Flow<UserSettings> = context.dataStore.data
|
|
34
|
+
.map { preferences ->
|
|
35
|
+
UserSettings(theme = preferences[THEME_KEY] ?: "light")
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## 3. Offline-First Architecture
|
|
40
|
+
|
|
41
|
+
A standard offline-first app follows this flow:
|
|
42
|
+
|
|
43
|
+
1. UI requests data from the ViewModel.
|
|
44
|
+
2. ViewModel observes a Flow from the Repository (which observes the Room DB).
|
|
45
|
+
3. Repository triggers a network sync if needed and saves the result to Room.
|
|
46
|
+
4. Room automatically emits the new data to the Flow, updating the UI.
|
|
47
|
+
|
|
48
|
+
## 4. Best Practices
|
|
49
|
+
|
|
50
|
+
- **Never block the Main Thread**: Room and DataStore operations should always run on `Dispatchers.IO`.
|
|
51
|
+
- **Encrypted DataStore**: Use encryption for sensitive settings.
|
|
52
|
+
- **Migration**: Always provide a `Migration` strategy when changing your database schema to avoid app crashes.
|
|
53
|
+
- **Single Instance**: Ensure you use a single instance of your Room Database (usually via Hilt/Koin).
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Migration from XML to Jetpack Compose
|
|
2
|
+
|
|
3
|
+
Migrating a legacy application to Jetpack Compose should be a gradual process, not a "big bang" rewrite.
|
|
4
|
+
|
|
5
|
+
## Gradual Migration Strategies
|
|
6
|
+
|
|
7
|
+
1. **New Features in Compose**: Build all new screens and features using Compose.
|
|
8
|
+
2. **Bottom-Up**: Start by migrating small, leaf-level components (buttons, text fields).
|
|
9
|
+
3. **Top-Down**: Migrate entire screens at once by replacing the Fragment/Activity layout with a `ComposeView`.
|
|
10
|
+
|
|
11
|
+
## Interoperability APIs
|
|
12
|
+
|
|
13
|
+
### Using Compose in XML (`ComposeView`)
|
|
14
|
+
|
|
15
|
+
To add Compose to an existing XML layout, use `ComposeView`.
|
|
16
|
+
|
|
17
|
+
**XML Layout:**
|
|
18
|
+
|
|
19
|
+
```xml
|
|
20
|
+
<LinearLayout ...>
|
|
21
|
+
<androidx.compose.ui.platform.ComposeView
|
|
22
|
+
android:id="@+id/compose_view"
|
|
23
|
+
android:layout_width="match_parent"
|
|
24
|
+
android:layout_height="wrap_content" />
|
|
25
|
+
</LinearLayout>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Activity/Fragment:**
|
|
29
|
+
|
|
30
|
+
```kotlin
|
|
31
|
+
binding.composeView.apply {
|
|
32
|
+
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
|
33
|
+
setContent {
|
|
34
|
+
M3Theme {
|
|
35
|
+
MyComposeComponent()
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Using Views in Compose (`AndroidView`)
|
|
42
|
+
|
|
43
|
+
If you need a component that doesn't exist in Compose yet (e.g., `MapView`, `WebView`), use `AndroidView`.
|
|
44
|
+
|
|
45
|
+
```kotlin
|
|
46
|
+
@Composable
|
|
47
|
+
fun LegacyWebView(url: String) {
|
|
48
|
+
AndroidView(
|
|
49
|
+
factory = { context ->
|
|
50
|
+
WebView(context).apply {
|
|
51
|
+
webViewClient = WebViewClient()
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
update = { webView ->
|
|
55
|
+
webView.loadUrl(url)
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Theme Interop
|
|
62
|
+
|
|
63
|
+
Use the **Material Theme Adapter** library to bridge your existing XML XML (MDC/AppCompat) theme with `MaterialTheme` in Compose.
|
|
64
|
+
|
|
65
|
+
```kotlin
|
|
66
|
+
MdcTheme { // Automatically inherits colors from your XML theme
|
|
67
|
+
MyScreen()
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## 4. Navigation Interop
|
|
72
|
+
|
|
73
|
+
- **Fragment to Compose**: Use `findNavController().navigate(R.id.compose_fragment)`.
|
|
74
|
+
- **Compose to Fragment**: If using `NavHost` in Compose, you can still navigate to deep links that are handled by your existing Fragment-based navigation.
|
|
75
|
+
|
|
76
|
+
## 5. Best Practices
|
|
77
|
+
|
|
78
|
+
- **Avoid Hybrid State**: Do not share `MutableState` directly between Views and Composables. Use `ViewModel` as the single source of truth.
|
|
79
|
+
- **Dispose Correctly**: Always set a `ViewCompositionStrategy` to avoid memory leaks in Fragments.
|
|
80
|
+
- **Standardize UI**: Migrating is a great time to audit your design system and ensure consistency.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Modifiers in Jetpack Compose
|
|
2
|
+
|
|
3
|
+
Modifiers allow you to decorate or augment composables. They are the primary way to style and position elements.
|
|
4
|
+
|
|
5
|
+
## Rule of Order
|
|
6
|
+
|
|
7
|
+
**ORDER MATTERS.** Modifiers are applied from left to right (or top to bottom in code).
|
|
8
|
+
|
|
9
|
+
- `Modifier.padding(10.dp).background(Color.Red)` -> Background is applied *after* padding.
|
|
10
|
+
- `Modifier.background(Color.Red).padding(10.dp)` -> Padding is applied *inside* the background.
|
|
11
|
+
|
|
12
|
+
## Standard Order Recommendation
|
|
13
|
+
|
|
14
|
+
1. Sizing (`fillMaxSize`, `size`, `height`, `width`)
|
|
15
|
+
2. Padding
|
|
16
|
+
3. Background / Border / Shape
|
|
17
|
+
4. Constraints or Offsets
|
|
18
|
+
5. Interaction handlers (`clickable`, `toggleable`)
|
|
19
|
+
|
|
20
|
+
## Best Practices
|
|
21
|
+
|
|
22
|
+
- **Pass a Modifier**: Every reusable composable should accept a `modifier: Modifier = Modifier` parameter as its first optional argument.
|
|
23
|
+
- **Consumer Ownership**: Let the caller decide the size and outer padding of your component.
|
|
24
|
+
- **`clickable` vs `IconButton`**: Use `IconButton` for simple icon buttons. Use `Modifier.clickable` for custom layouts, ensuring you use `indication` and `interactionSource` for proper visual feedback (Ripples).
|
|
25
|
+
|
|
26
|
+
## Reusability Pattern
|
|
27
|
+
|
|
28
|
+
```kotlin
|
|
29
|
+
@Composable
|
|
30
|
+
fun MyComponent(
|
|
31
|
+
modifier: Modifier = Modifier
|
|
32
|
+
) {
|
|
33
|
+
Box(
|
|
34
|
+
modifier = modifier
|
|
35
|
+
.background(Color.Gray)
|
|
36
|
+
.padding(16.dp)
|
|
37
|
+
) {
|
|
38
|
+
// ...
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 4. Advanced Usage
|
|
44
|
+
|
|
45
|
+
- **`graphicsLayer`**: Use for efficient rotations, scaling, and opacity changes without triggering recomposition as often.
|
|
46
|
+
- **Custom Modifiers**: Avoid overusing them; usually, a simple extension function on `Modifier` is enough.
|