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,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`.
|