feat(skills): 添加 android-native-dev Skill
This commit is contained in:
@@ -0,0 +1,883 @@
|
|||||||
|
---
|
||||||
|
name: android-native-dev
|
||||||
|
description: Android native application development and UI design guide. Covers Material Design 3, Kotlin/Compose development, project configuration, accessibility, and build troubleshooting. Read this before Android native application development.
|
||||||
|
license: MIT
|
||||||
|
metadata:
|
||||||
|
version: "1.0.0"
|
||||||
|
category: mobile
|
||||||
|
sources:
|
||||||
|
- Material Design 3 Guidelines (material.io)
|
||||||
|
- Android Developer Documentation (developer.android.com)
|
||||||
|
- Google Play Quality Guidelines
|
||||||
|
- WCAG Accessibility Guidelines
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Project Scenario Assessment
|
||||||
|
|
||||||
|
Before starting development, assess the current project state:
|
||||||
|
|
||||||
|
| Scenario | Characteristics | Approach |
|
||||||
|
|----------|-----------------|----------|
|
||||||
|
| **Empty Directory** | No files present | Full initialization required, including Gradle Wrapper |
|
||||||
|
| **Has Gradle Wrapper** | `gradlew` and `gradle/wrapper/` exist | Use `./gradlew` directly for builds |
|
||||||
|
| **Android Studio Project** | Complete project structure, may lack wrapper | Check wrapper, run `gradle wrapper` if needed |
|
||||||
|
| **Incomplete Project** | Partial files present | Check missing files, complete configuration |
|
||||||
|
|
||||||
|
**Key Principles**:
|
||||||
|
- Before writing business logic, ensure `./gradlew assembleDebug` succeeds
|
||||||
|
- If `gradle.properties` is missing, create it first and configure AndroidX
|
||||||
|
|
||||||
|
### 1.1 Required Files Checklist
|
||||||
|
|
||||||
|
```
|
||||||
|
MyApp/
|
||||||
|
├── gradle.properties # Configure AndroidX and other settings
|
||||||
|
├── settings.gradle.kts
|
||||||
|
├── build.gradle.kts # Root level
|
||||||
|
├── gradle/wrapper/
|
||||||
|
│ └── gradle-wrapper.properties
|
||||||
|
├── app/
|
||||||
|
│ ├── build.gradle.kts # Module level
|
||||||
|
│ └── src/main/
|
||||||
|
│ ├── AndroidManifest.xml
|
||||||
|
│ ├── java/com/example/myapp/
|
||||||
|
│ │ └── MainActivity.kt
|
||||||
|
│ └── res/
|
||||||
|
│ ├── values/
|
||||||
|
│ │ ├── strings.xml
|
||||||
|
│ │ ├── colors.xml
|
||||||
|
│ │ └── themes.xml
|
||||||
|
│ └── mipmap-*/ # App icons
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Project Configuration
|
||||||
|
|
||||||
|
### 2.1 gradle.properties
|
||||||
|
|
||||||
|
```properties
|
||||||
|
# Required configuration
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
||||||
|
|
||||||
|
# Build optimization
|
||||||
|
org.gradle.parallel=true
|
||||||
|
kotlin.code.style=official
|
||||||
|
|
||||||
|
# JVM memory settings (adjust based on project size)
|
||||||
|
# Small projects: 2048m, Medium: 4096m, Large: 8192m+
|
||||||
|
# org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**: If you encounter `OutOfMemoryError` during build, increase `-Xmx` value. Large projects with many dependencies may require 8GB or more.
|
||||||
|
|
||||||
|
### 2.2 Dependency Declaration Standards
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
dependencies {
|
||||||
|
// Use BOM to manage Compose versions
|
||||||
|
implementation(platform("androidx.compose:compose-bom:2024.02.00"))
|
||||||
|
implementation("androidx.compose.ui:ui")
|
||||||
|
implementation("androidx.compose.material3:material3")
|
||||||
|
|
||||||
|
// Activity & ViewModel
|
||||||
|
implementation("androidx.activity:activity-compose:1.8.2")
|
||||||
|
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.3 Build Variants & Product Flavors
|
||||||
|
|
||||||
|
Product Flavors allow you to create different versions of your app (e.g., free/paid, dev/staging/prod).
|
||||||
|
|
||||||
|
**Configuration in app/build.gradle.kts**:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
android {
|
||||||
|
// Define flavor dimensions
|
||||||
|
flavorDimensions += "environment"
|
||||||
|
|
||||||
|
productFlavors {
|
||||||
|
create("dev") {
|
||||||
|
dimension = "environment"
|
||||||
|
applicationIdSuffix = ".dev"
|
||||||
|
versionNameSuffix = "-dev"
|
||||||
|
|
||||||
|
// Different config values per flavor
|
||||||
|
buildConfigField("String", "API_BASE_URL", "\"https://dev-api.example.com\"")
|
||||||
|
buildConfigField("Boolean", "ENABLE_LOGGING", "true")
|
||||||
|
|
||||||
|
// Different resources
|
||||||
|
resValue("string", "app_name", "MyApp Dev")
|
||||||
|
}
|
||||||
|
|
||||||
|
create("staging") {
|
||||||
|
dimension = "environment"
|
||||||
|
applicationIdSuffix = ".staging"
|
||||||
|
versionNameSuffix = "-staging"
|
||||||
|
|
||||||
|
buildConfigField("String", "API_BASE_URL", "\"https://staging-api.example.com\"")
|
||||||
|
buildConfigField("Boolean", "ENABLE_LOGGING", "true")
|
||||||
|
resValue("string", "app_name", "MyApp Staging")
|
||||||
|
}
|
||||||
|
|
||||||
|
create("prod") {
|
||||||
|
dimension = "environment"
|
||||||
|
// No suffix for production
|
||||||
|
|
||||||
|
buildConfigField("String", "API_BASE_URL", "\"https://api.example.com\"")
|
||||||
|
buildConfigField("Boolean", "ENABLE_LOGGING", "false")
|
||||||
|
resValue("string", "app_name", "MyApp")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
debug {
|
||||||
|
isDebuggable = true
|
||||||
|
isMinifyEnabled = false
|
||||||
|
}
|
||||||
|
release {
|
||||||
|
isDebuggable = false
|
||||||
|
isMinifyEnabled = true
|
||||||
|
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Build Variant Naming**: `{flavor}{BuildType}` → e.g., `devDebug`, `prodRelease`
|
||||||
|
|
||||||
|
**Gradle Build Commands**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List all available build variants
|
||||||
|
./gradlew tasks --group="build"
|
||||||
|
|
||||||
|
# Build specific variant (flavor + buildType)
|
||||||
|
./gradlew assembleDevDebug # Dev flavor, Debug build
|
||||||
|
./gradlew assembleStagingDebug # Staging flavor, Debug build
|
||||||
|
./gradlew assembleProdRelease # Prod flavor, Release build
|
||||||
|
|
||||||
|
# Build all variants of a specific flavor
|
||||||
|
./gradlew assembleDev # All Dev variants (debug + release)
|
||||||
|
./gradlew assembleProd # All Prod variants
|
||||||
|
|
||||||
|
# Build all variants of a specific build type
|
||||||
|
./gradlew assembleDebug # All flavors, Debug build
|
||||||
|
./gradlew assembleRelease # All flavors, Release build
|
||||||
|
|
||||||
|
# Install specific variant to device
|
||||||
|
./gradlew installDevDebug
|
||||||
|
./gradlew installProdRelease
|
||||||
|
|
||||||
|
# Build and install in one command
|
||||||
|
./gradlew installDevDebug && adb shell am start -n com.example.myapp.dev/.MainActivity
|
||||||
|
```
|
||||||
|
|
||||||
|
**Access BuildConfig in Code**:
|
||||||
|
|
||||||
|
> **Note**: Starting from AGP 8.0, `BuildConfig` is no longer generated by default. You must explicitly enable it in your `build.gradle.kts`:
|
||||||
|
> ```kotlin
|
||||||
|
> android {
|
||||||
|
> buildFeatures {
|
||||||
|
> buildConfig = true
|
||||||
|
> }
|
||||||
|
> }
|
||||||
|
> ```
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// Use build config values in your code
|
||||||
|
val apiUrl = BuildConfig.API_BASE_URL
|
||||||
|
val isLoggingEnabled = BuildConfig.ENABLE_LOGGING
|
||||||
|
|
||||||
|
if (BuildConfig.DEBUG) {
|
||||||
|
// Debug-only code
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Flavor-Specific Source Sets**:
|
||||||
|
|
||||||
|
```
|
||||||
|
app/src/
|
||||||
|
├── main/ # Shared code for all flavors
|
||||||
|
├── dev/ # Dev-only code and resources
|
||||||
|
│ ├── java/
|
||||||
|
│ └── res/
|
||||||
|
├── staging/ # Staging-only code and resources
|
||||||
|
├── prod/ # Prod-only code and resources
|
||||||
|
├── debug/ # Debug build type code
|
||||||
|
└── release/ # Release build type code
|
||||||
|
```
|
||||||
|
|
||||||
|
**Multiple Flavor Dimensions** (e.g., environment + tier):
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
android {
|
||||||
|
flavorDimensions += listOf("environment", "tier")
|
||||||
|
|
||||||
|
productFlavors {
|
||||||
|
create("dev") { dimension = "environment" }
|
||||||
|
create("prod") { dimension = "environment" }
|
||||||
|
|
||||||
|
create("free") { dimension = "tier" }
|
||||||
|
create("paid") { dimension = "tier" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Results in: devFreeDebug, devPaidDebug, prodFreeRelease, etc.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Kotlin Development Standards
|
||||||
|
|
||||||
|
### 3.1 Naming Conventions
|
||||||
|
|
||||||
|
| Type | Convention | Example |
|
||||||
|
|------|------------|---------|
|
||||||
|
| Class/Interface | PascalCase | `UserRepository`, `MainActivity` |
|
||||||
|
| Function/Variable | camelCase | `getUserName()`, `isLoading` |
|
||||||
|
| Constant | SCREAMING_SNAKE | `MAX_RETRY_COUNT` |
|
||||||
|
| Package | lowercase | `com.example.myapp` |
|
||||||
|
| Composable | PascalCase | `@Composable fun UserCard()` |
|
||||||
|
|
||||||
|
### 3.2 Code Standards (Important)
|
||||||
|
|
||||||
|
**Null Safety**:
|
||||||
|
```kotlin
|
||||||
|
// ❌ Avoid: Non-null assertion !! (may crash)
|
||||||
|
val name = user!!.name
|
||||||
|
|
||||||
|
// ✅ Recommended: Safe call + default value
|
||||||
|
val name = user?.name ?: "Unknown"
|
||||||
|
|
||||||
|
// ✅ Recommended: let handling
|
||||||
|
user?.let { processUser(it) }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Exception Handling**:
|
||||||
|
```kotlin
|
||||||
|
// ❌ Avoid: Random try-catch in business layer swallowing exceptions
|
||||||
|
fun loadData() {
|
||||||
|
try {
|
||||||
|
val data = api.fetch()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Swallowing exception, hard to debug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Recommended: Let exceptions propagate, handle at appropriate layer
|
||||||
|
suspend fun loadData(): Result<Data> {
|
||||||
|
return try {
|
||||||
|
Result.success(api.fetch())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Result.failure(e) // Wrap and return, let caller decide handling
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Recommended: Unified handling in ViewModel
|
||||||
|
viewModelScope.launch {
|
||||||
|
runCatching { repository.loadData() }
|
||||||
|
.onSuccess { _uiState.value = UiState.Success(it) }
|
||||||
|
.onFailure { _uiState.value = UiState.Error(it.message) }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Threading & Coroutines (Critical)
|
||||||
|
|
||||||
|
**Thread Selection Principles**:
|
||||||
|
|
||||||
|
| Operation Type | Thread | Description |
|
||||||
|
|----------------|--------|-------------|
|
||||||
|
| UI Updates | `Dispatchers.Main` | Update View, State, LiveData |
|
||||||
|
| Network Requests | `Dispatchers.IO` | HTTP calls, API requests |
|
||||||
|
| File I/O | `Dispatchers.IO` | Local storage, database operations |
|
||||||
|
| Compute Intensive | `Dispatchers.Default` | JSON parsing, sorting, encryption |
|
||||||
|
|
||||||
|
**Correct Usage**:
|
||||||
|
```kotlin
|
||||||
|
// In ViewModel
|
||||||
|
viewModelScope.launch {
|
||||||
|
// Default Main thread, can update UI State
|
||||||
|
_uiState.value = UiState.Loading
|
||||||
|
|
||||||
|
// Switch to IO thread for network request
|
||||||
|
val result = withContext(Dispatchers.IO) {
|
||||||
|
repository.fetchData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Automatically returns to Main thread, update UI
|
||||||
|
_uiState.value = UiState.Success(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// In Repository (suspend functions should be main-safe)
|
||||||
|
suspend fun fetchData(): Data = withContext(Dispatchers.IO) {
|
||||||
|
api.getData()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Common Mistakes**:
|
||||||
|
```kotlin
|
||||||
|
// ❌ Wrong: Updating UI on IO thread
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
val data = api.fetch()
|
||||||
|
_uiState.value = data // Crash or warning!
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ Wrong: Executing time-consuming operation on Main thread
|
||||||
|
viewModelScope.launch {
|
||||||
|
val data = api.fetch() // Blocking main thread! ANR
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Correct: Fetch on IO, update on Main
|
||||||
|
viewModelScope.launch {
|
||||||
|
val data = withContext(Dispatchers.IO) { api.fetch() }
|
||||||
|
_uiState.value = data
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.4 Visibility Rules
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// Default is public, declare explicitly when needed
|
||||||
|
class UserRepository { // public
|
||||||
|
private val cache = mutableMapOf<String, User>() // Visible only within class
|
||||||
|
internal fun clearCache() {} // Visible only within module
|
||||||
|
}
|
||||||
|
|
||||||
|
// data class properties are public by default, be careful when used across modules
|
||||||
|
data class User(
|
||||||
|
val id: String, // public
|
||||||
|
val name: String
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.5 Common Syntax Pitfalls
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// ❌ Wrong: Accessing uninitialized lateinit
|
||||||
|
class MyViewModel : ViewModel() {
|
||||||
|
lateinit var data: String
|
||||||
|
fun process() = data.length // May crash
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Correct: Use nullable or default value
|
||||||
|
class MyViewModel : ViewModel() {
|
||||||
|
var data: String? = null
|
||||||
|
fun process() = data?.length ?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ Wrong: Using return in lambda
|
||||||
|
list.forEach { item ->
|
||||||
|
if (item.isEmpty()) return // Returns from outer function!
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Correct: Use return@forEach
|
||||||
|
list.forEach { item ->
|
||||||
|
if (item.isEmpty()) return@forEach
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.6 Server Response Data Class Fields Must Be Nullable
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// ❌ Wrong: Fields declared as non-null (server may not return them)
|
||||||
|
data class UserResponse(
|
||||||
|
val id: String = "",
|
||||||
|
val name: String = "",
|
||||||
|
val avatar: String = ""
|
||||||
|
)
|
||||||
|
|
||||||
|
// ✅ Correct: All fields declared as nullable
|
||||||
|
data class UserResponse(
|
||||||
|
@SerializedName("id")
|
||||||
|
val id: String? = null,
|
||||||
|
@SerializedName("name")
|
||||||
|
val name: String? = null,
|
||||||
|
@SerializedName("avatar")
|
||||||
|
val avatar: String? = null
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.7 Lifecycle Resource Management
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// ❌ Wrong: Only adding Observer, not removing
|
||||||
|
class MyView : View {
|
||||||
|
override fun onAttachedToWindow() {
|
||||||
|
super.onAttachedToWindow()
|
||||||
|
activity?.lifecycle?.addObserver(this)
|
||||||
|
}
|
||||||
|
// Memory leak!
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Correct: Paired add and remove
|
||||||
|
class MyView : View {
|
||||||
|
override fun onAttachedToWindow() {
|
||||||
|
super.onAttachedToWindow()
|
||||||
|
activity?.lifecycle?.addObserver(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDetachedFromWindow() {
|
||||||
|
activity?.lifecycle?.removeObserver(this)
|
||||||
|
super.onDetachedFromWindow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.8 Logging Level Usage
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
// Info: Key checkpoints in normal flow
|
||||||
|
Log.i(TAG, "loadData: started, userId = $userId")
|
||||||
|
|
||||||
|
// Warning: Abnormal but recoverable situations
|
||||||
|
Log.w(TAG, "loadData: cache miss, fallback to network")
|
||||||
|
|
||||||
|
// Error: Failure/error situations
|
||||||
|
Log.e(TAG, "loadData failed: ${error.message}")
|
||||||
|
```
|
||||||
|
|
||||||
|
| Level | Use Case |
|
||||||
|
|-------|----------|
|
||||||
|
| `i` (Info) | Normal flow, method entry, key parameters |
|
||||||
|
| `w` (Warning) | Recoverable exceptions, fallback handling, null returns |
|
||||||
|
| `e` (Error) | Request failures, caught exceptions, unrecoverable errors |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Jetpack Compose Standards
|
||||||
|
|
||||||
|
### 4.1 @Composable Context Rules
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// ❌ Wrong: Calling Composable from non-Composable function
|
||||||
|
fun showError(message: String) {
|
||||||
|
Text(message) // Compile error!
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Correct: Mark as @Composable
|
||||||
|
@Composable
|
||||||
|
fun ErrorMessage(message: String) {
|
||||||
|
Text(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ❌ Wrong: Using suspend outside LaunchedEffect
|
||||||
|
@Composable
|
||||||
|
fun MyScreen() {
|
||||||
|
val data = fetchData() // Error!
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Correct: Use LaunchedEffect
|
||||||
|
@Composable
|
||||||
|
fun MyScreen() {
|
||||||
|
var data by remember { mutableStateOf<Data?>(null) }
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
data = fetchData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 State Management
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// Basic State
|
||||||
|
var count by remember { mutableStateOf(0) }
|
||||||
|
|
||||||
|
// Derived State (avoid redundant computation)
|
||||||
|
val isEven by remember { derivedStateOf { count % 2 == 0 } }
|
||||||
|
|
||||||
|
// Persist across recomposition (e.g., scroll position)
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
|
|
||||||
|
// State in ViewModel
|
||||||
|
class MyViewModel : ViewModel() {
|
||||||
|
private val _uiState = MutableStateFlow(UiState())
|
||||||
|
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Common Compose Mistakes
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// ❌ Wrong: Creating objects in Composable (created on every recomposition)
|
||||||
|
@Composable
|
||||||
|
fun MyScreen() {
|
||||||
|
val viewModel = MyViewModel() // Wrong!
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Correct: Use viewModel() or remember
|
||||||
|
@Composable
|
||||||
|
fun MyScreen(viewModel: MyViewModel = viewModel()) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Resources & Icons
|
||||||
|
|
||||||
|
### 5.1 App Icon Requirements
|
||||||
|
|
||||||
|
Must provide multi-resolution icons:
|
||||||
|
|
||||||
|
| Directory | Size | Purpose |
|
||||||
|
|-----------|------|---------|
|
||||||
|
| mipmap-mdpi | 48x48 | Baseline |
|
||||||
|
| mipmap-hdpi | 72x72 | 1.5x |
|
||||||
|
| mipmap-xhdpi | 96x96 | 2x |
|
||||||
|
| mipmap-xxhdpi | 144x144 | 3x |
|
||||||
|
| mipmap-xxxhdpi | 192x192 | 4x |
|
||||||
|
|
||||||
|
Recommended: Use Adaptive Icon (Android 8+):
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- res/mipmap-anydpi-v26/ic_launcher.xml -->
|
||||||
|
<adaptive-icon>
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
</adaptive-icon>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.2 Resource Naming Conventions
|
||||||
|
|
||||||
|
| Type | Prefix | Example |
|
||||||
|
|------|--------|---------|
|
||||||
|
| Layout | layout_ | `layout_main.xml` |
|
||||||
|
| Image | ic_, img_, bg_ | `ic_user.png` |
|
||||||
|
| Color | color_ | `color_primary` |
|
||||||
|
| String | - | `app_name`, `btn_submit` |
|
||||||
|
|
||||||
|
### 5.3 Avoid Android Reserved Names (Important)
|
||||||
|
|
||||||
|
Variable names, resource IDs, colors, icons, and XML elements **must not** use Android reserved words or system resource names. Using reserved names causes build errors or resource conflicts.
|
||||||
|
|
||||||
|
**Common Reserved Names to Avoid**:
|
||||||
|
|
||||||
|
| Category | Reserved Names (Do NOT Use) |
|
||||||
|
|----------|----------------------------|
|
||||||
|
| Colors | `background`, `foreground`, `transparent`, `white`, `black` |
|
||||||
|
| Icons/Drawables | `icon`, `logo`, `image`, `drawable` |
|
||||||
|
| Views | `view`, `text`, `button`, `layout`, `container` |
|
||||||
|
| Attributes | `id`, `name`, `type`, `style`, `theme`, `color` |
|
||||||
|
| System | `app`, `android`, `content`, `data`, `action` |
|
||||||
|
|
||||||
|
**Examples**:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- ❌ Wrong: Using reserved names -->
|
||||||
|
<color name="background">#FFFFFF</color>
|
||||||
|
<color name="icon">#000000</color>
|
||||||
|
|
||||||
|
<!-- ✅ Correct: Add prefix or specific naming -->
|
||||||
|
<color name="app_background">#FFFFFF</color>
|
||||||
|
<color name="icon_primary">#000000</color>
|
||||||
|
```
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// ❌ Wrong: Variable names conflict with system
|
||||||
|
val icon = R.drawable.my_icon
|
||||||
|
val background = Color.White
|
||||||
|
|
||||||
|
// ✅ Correct: Use descriptive names
|
||||||
|
val appIcon = R.drawable.my_icon
|
||||||
|
val screenBackground = Color.White
|
||||||
|
```
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<!-- ❌ Wrong: Drawable name conflicts -->
|
||||||
|
<ImageView android:src="@drawable/icon" />
|
||||||
|
|
||||||
|
<!-- ✅ Correct: Add prefix -->
|
||||||
|
<ImageView android:src="@drawable/ic_home" />
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Build Error Diagnosis & Fixes
|
||||||
|
|
||||||
|
### 6.1 Common Error Quick Reference
|
||||||
|
|
||||||
|
| Error Keyword | Cause | Fix |
|
||||||
|
|---------------|-------|-----|
|
||||||
|
| `Unresolved reference` | Missing import or undefined | Check imports, verify dependencies |
|
||||||
|
| `Type mismatch` | Type incompatibility | Check parameter types, add conversion |
|
||||||
|
| `Cannot access` | Visibility issue | Check public/private/internal |
|
||||||
|
| `@Composable invocations` | Composable context error | Ensure caller is also @Composable |
|
||||||
|
| `Duplicate class` | Dependency conflict | Use `./gradlew dependencies` to investigate |
|
||||||
|
| `AAPT: error` | Resource file error | Check XML syntax and resource references |
|
||||||
|
|
||||||
|
### 6.2 Fix Best Practices
|
||||||
|
|
||||||
|
1. **Read the complete error message first**: Locate file and line number
|
||||||
|
2. **Check recent changes**: Problems usually in latest modifications
|
||||||
|
3. **Clean Build**: `./gradlew clean assembleDebug`
|
||||||
|
4. **Check dependency versions**: Version conflicts are common causes
|
||||||
|
5. **Refresh dependencies if needed**: Clear cache and rebuild
|
||||||
|
|
||||||
|
### 6.3 Debugging Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clean and build
|
||||||
|
./gradlew clean assembleDebug
|
||||||
|
|
||||||
|
# View dependency tree (investigate conflicts)
|
||||||
|
./gradlew :app:dependencies
|
||||||
|
|
||||||
|
# View detailed errors
|
||||||
|
./gradlew assembleDebug --stacktrace
|
||||||
|
|
||||||
|
# Refresh dependencies
|
||||||
|
./gradlew --refresh-dependencies
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Material Design 3 Guidelines
|
||||||
|
|
||||||
|
Review Android UI files for compliance with Material Design 3 Guidelines and Android best practices.
|
||||||
|
|
||||||
|
### Design Philosophy
|
||||||
|
|
||||||
|
#### M3 Core Principles
|
||||||
|
|
||||||
|
| Principle | Description |
|
||||||
|
|-----------|-------------|
|
||||||
|
| **Personal** | Dynamic color based on user preferences and wallpaper |
|
||||||
|
| **Adaptive** | Responsive across all screen sizes and form factors |
|
||||||
|
| **Expressive** | Bold colors and typography with personality |
|
||||||
|
| **Accessible** | Inclusive design for all users |
|
||||||
|
|
||||||
|
#### M3 Expressive (Latest)
|
||||||
|
|
||||||
|
The latest evolution adds emotion-driven UX through:
|
||||||
|
- Vibrant, dynamic colors
|
||||||
|
- Intuitive motion physics
|
||||||
|
- Adaptive components
|
||||||
|
- Flexible typography
|
||||||
|
- Contrasting shapes (35 new shape options)
|
||||||
|
|
||||||
|
### App Style Selection
|
||||||
|
|
||||||
|
**Critical Decision**: Match visual style to app category and target audience.
|
||||||
|
|
||||||
|
| App Category | Visual Style | Key Characteristics |
|
||||||
|
|--------------|--------------|---------------------|
|
||||||
|
| Utility/Tool | Minimalist | Clean, efficient, neutral colors |
|
||||||
|
| Finance/Banking | Professional Trust | Conservative colors, security-focused |
|
||||||
|
| Health/Wellness | Calm & Natural | Soft colors, organic shapes |
|
||||||
|
| Kids (3-5) | Playful Simple | Bright colors, large targets (56dp+) |
|
||||||
|
| Kids (6-12) | Fun & Engaging | Vibrant, gamified feedback |
|
||||||
|
| Social/Entertainment | Expressive | Brand-driven, gesture-rich |
|
||||||
|
| Productivity | Clean & Focused | Minimal, high contrast |
|
||||||
|
| E-commerce | Conversion-focused | Clear CTAs, scannable |
|
||||||
|
|
||||||
|
See [Design Style Guide](references/design-style-guide.md) for detailed style profiles.
|
||||||
|
|
||||||
|
### Quick Reference: Key Specifications
|
||||||
|
|
||||||
|
#### Color Contrast Requirements
|
||||||
|
|
||||||
|
| Element | Minimum Ratio |
|
||||||
|
|---------|---------------|
|
||||||
|
| Body text | **4.5:1** |
|
||||||
|
| Large text (18sp+) | **3:1** |
|
||||||
|
| UI components | **3:1** |
|
||||||
|
|
||||||
|
#### Touch Targets
|
||||||
|
|
||||||
|
| Type | Size |
|
||||||
|
|------|------|
|
||||||
|
| Minimum | 48 × 48dp |
|
||||||
|
| Recommended (primary actions) | 56 × 56dp |
|
||||||
|
| Kids apps | 56dp+ |
|
||||||
|
| Spacing between targets | 8dp minimum |
|
||||||
|
|
||||||
|
#### 8dp Grid System
|
||||||
|
|
||||||
|
| Token | Value | Usage |
|
||||||
|
|-------|-------|-------|
|
||||||
|
| xs | 4dp | Icon padding |
|
||||||
|
| sm | 8dp | Tight spacing |
|
||||||
|
| md | 16dp | Default padding |
|
||||||
|
| lg | 24dp | Section spacing |
|
||||||
|
| xl | 32dp | Large gaps |
|
||||||
|
| xxl | 48dp | Screen margins |
|
||||||
|
|
||||||
|
#### Typography Scale (Summary)
|
||||||
|
|
||||||
|
| Category | Sizes |
|
||||||
|
|----------|-------|
|
||||||
|
| Display | 57sp, 45sp, 36sp |
|
||||||
|
| Headline | 32sp, 28sp, 24sp |
|
||||||
|
| Title | 22sp, 16sp, 14sp |
|
||||||
|
| Body | 16sp, 14sp, 12sp |
|
||||||
|
| Label | 14sp, 12sp, 11sp |
|
||||||
|
|
||||||
|
#### Animation Duration
|
||||||
|
|
||||||
|
| Type | Duration |
|
||||||
|
|------|----------|
|
||||||
|
| Micro (ripples) | 50-100ms |
|
||||||
|
| Short (simple) | 100-200ms |
|
||||||
|
| Medium (expand/collapse) | 200-300ms |
|
||||||
|
| Long (complex) | 300-500ms |
|
||||||
|
|
||||||
|
#### Component Dimensions
|
||||||
|
|
||||||
|
| Component | Height | Min Width |
|
||||||
|
|-----------|--------|-----------|
|
||||||
|
| Button | 40dp | 64dp |
|
||||||
|
| FAB | 56dp | 56dp |
|
||||||
|
| Text Field | 56dp | 280dp |
|
||||||
|
| App Bar | 64dp | - |
|
||||||
|
| Bottom Nav | 80dp | - |
|
||||||
|
|
||||||
|
### Anti-Patterns (Must Avoid)
|
||||||
|
|
||||||
|
#### UI Anti-Patterns
|
||||||
|
- More than 5 bottom navigation items
|
||||||
|
- Multiple FABs on same screen
|
||||||
|
- Touch targets smaller than 48dp
|
||||||
|
- Inconsistent spacing (non-8dp multiples)
|
||||||
|
- Missing dark theme support
|
||||||
|
- Text on colored backgrounds without contrast check
|
||||||
|
|
||||||
|
#### Performance Anti-Patterns
|
||||||
|
- Startup time > 2 seconds without progress indicator
|
||||||
|
- Frame rate < 60 FPS (> 16ms per frame)
|
||||||
|
- Crash rate > 1.09% (Google Play threshold)
|
||||||
|
- ANR rate > 0.47% (Google Play threshold)
|
||||||
|
|
||||||
|
#### Accessibility Anti-Patterns
|
||||||
|
- Missing contentDescription on interactive elements
|
||||||
|
- Element type in labels (e.g., "Save button" instead of "Save")
|
||||||
|
- Complex gestures in kids apps
|
||||||
|
- Text-only buttons for non-readers
|
||||||
|
|
||||||
|
### Review Checklist
|
||||||
|
|
||||||
|
- [ ] 8dp spacing grid compliance
|
||||||
|
- [ ] 48dp minimum touch targets
|
||||||
|
- [ ] Proper typography scale usage
|
||||||
|
- [ ] Color contrast compliance (4.5:1+ for text)
|
||||||
|
- [ ] Dark theme support
|
||||||
|
- [ ] contentDescription on all interactive elements
|
||||||
|
- [ ] Startup < 2 seconds or shows progress
|
||||||
|
- [ ] Visual style matches app category
|
||||||
|
|
||||||
|
### Design References
|
||||||
|
|
||||||
|
| Topic | Reference |
|
||||||
|
|-------|-----------|
|
||||||
|
| Colors, Typography, Spacing, Shapes | [Visual Design](references/visual-design.md) |
|
||||||
|
| Animation & Transitions | [Motion System](references/motion-system.md) |
|
||||||
|
| Accessibility Guidelines | [Accessibility](references/accessibility.md) |
|
||||||
|
| Large Screens & Foldables | [Adaptive Screens](references/adaptive-screens.md) |
|
||||||
|
| Android Vitals & Performance | [Performance & Stability](references/performance-stability.md) |
|
||||||
|
| Privacy & Security | [Privacy & Security](references/privacy-security.md) |
|
||||||
|
| Audio, Video, Notifications | [Functional Requirements](references/functional-requirements.md) |
|
||||||
|
| App Style by Category | [Design Style Guide](references/design-style-guide.md) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Testing
|
||||||
|
|
||||||
|
> **Note**: Only add test dependencies when the user explicitly asks for testing.
|
||||||
|
|
||||||
|
A well-tested Android app uses layered testing: fast local unit tests for logic, instrumentation tests for UI and integration, and Gradle Managed Devices to run emulators reproducibly on any machine — including CI.
|
||||||
|
|
||||||
|
### 8.1 Test Dependencies
|
||||||
|
|
||||||
|
Before adding test dependencies, inspect the project's existing versions to avoid conflicts:
|
||||||
|
|
||||||
|
1. Check `gradle/libs.versions.toml` — if present, add test deps using the project's version catalog style
|
||||||
|
2. Check existing `build.gradle.kts` for already-pinned dependency versions
|
||||||
|
3. Match version families using the table below
|
||||||
|
|
||||||
|
**Version Alignment Rules**:
|
||||||
|
|
||||||
|
| Test Dependency | Must Align With | How to Check |
|
||||||
|
|----------------------------------------------|--------------------------------------------------|-----------------------------------------------------------------------|
|
||||||
|
| `kotlinx-coroutines-test` | Project's `kotlinx-coroutines-core` version | Search for `kotlinx-coroutines` in build files or version catalog |
|
||||||
|
| `compose-ui-test-junit4` | Project's Compose BOM or `compose-compiler` | Search for `compose-bom` or `compose.compiler` in build files |
|
||||||
|
| `espresso-*` | All Espresso artifacts must use the same version | Search for `espresso` in build files |
|
||||||
|
| `androidx.test:runner`, `rules`, `ext:junit` | Should use compatible AndroidX Test versions | Search for `androidx.test` in build files |
|
||||||
|
| `mockk` | Must support the project's Kotlin version | Check `kotlin` version in root `build.gradle.kts` or version catalog |
|
||||||
|
|
||||||
|
**Dependencies Reference** — add only the groups you need:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
dependencies {
|
||||||
|
// --- Local unit tests (src/test/) ---
|
||||||
|
testImplementation("junit:junit:<version>") // 4.13.2+
|
||||||
|
testImplementation("org.robolectric:robolectric:<version>") // 4.16.1+
|
||||||
|
testImplementation("io.mockk:mockk:<version>") // match Kotlin version
|
||||||
|
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:<version>") // match coroutines-core
|
||||||
|
testImplementation("androidx.arch.core:core-testing:<version>") // InstantTaskExecutorRule for LiveData
|
||||||
|
testImplementation("app.cash.turbine:turbine:<version>") // Flow/StateFlow testing
|
||||||
|
|
||||||
|
// --- Instrumentation tests (src/androidTest/) ---
|
||||||
|
androidTestImplementation("androidx.test.ext:junit:<version>")
|
||||||
|
androidTestImplementation("androidx.test:runner:<version>")
|
||||||
|
androidTestImplementation("androidx.test:rules:<version>")
|
||||||
|
androidTestImplementation("androidx.test.espresso:espresso-core:<version>")
|
||||||
|
androidTestImplementation("androidx.test.espresso:espresso-contrib:<version>") // RecyclerView, Drawer
|
||||||
|
androidTestImplementation("androidx.test.espresso:espresso-intents:<version>") // Intent verification
|
||||||
|
androidTestImplementation("androidx.test.espresso:espresso-idling-resource:<version>")
|
||||||
|
androidTestImplementation("androidx.test.uiautomator:uiautomator:<version>")
|
||||||
|
|
||||||
|
// --- Compose UI tests (only if project uses Compose) ---
|
||||||
|
androidTestImplementation("androidx.compose.ui:ui-test-junit4") // version from Compose BOM
|
||||||
|
debugImplementation("androidx.compose.ui:ui-test-manifest") // required for createComposeRule
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**: If the project uses a Compose BOM, `ui-test-junit4` and `ui-test-manifest` don't need explicit versions — the BOM manages them.
|
||||||
|
|
||||||
|
Enable Robolectric resource support in the `android` block:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
android {
|
||||||
|
testOptions {
|
||||||
|
unitTests.isIncludeAndroidResources = true // required for Robolectric
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8.2 Testing by Layer
|
||||||
|
|
||||||
|
| Layer | Location | Runs On | Speed | Use For |
|
||||||
|
|--------------------|--------------------|-------------------------|----------------------|--------------------------------------------------|
|
||||||
|
| Unit (JUnit) | `src/test/` | JVM | ~ms | ViewModels, repos, mappers, validators |
|
||||||
|
| Unit + Robolectric | `src/test/` | JVM + simulated Android | ~100ms | Code needing Context, resources, SharedPrefs |
|
||||||
|
| Compose UI (local) | `src/test/` | JVM + Robolectric | ~100ms | Composable rendering & interaction |
|
||||||
|
| Espresso | `src/androidTest/` | Device/Emulator | ~seconds | View-based UI flows, Intents, DB integration |
|
||||||
|
| Compose UI (device)| `src/androidTest/` | Device/Emulator | ~seconds | Full Compose UI flows with real rendering |
|
||||||
|
| UI Automator | `src/androidTest/` | Device/Emulator | ~seconds | System dialogs, notifications, multi-app |
|
||||||
|
| Managed Device | `src/androidTest/` | Gradle-managed AVD | ~minutes (first run) | CI, matrix testing across API levels |
|
||||||
|
|
||||||
|
See [Testing](references/testing.md) for detailed examples, code patterns, and Gradle Managed Device configuration.
|
||||||
|
|
||||||
|
### 8.3 Testing Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Local unit tests (fast, no emulator)
|
||||||
|
./gradlew test # all modules
|
||||||
|
./gradlew :app:testDebugUnitTest # app module, debug variant
|
||||||
|
|
||||||
|
# Single test class
|
||||||
|
./gradlew :app:testDebugUnitTest --tests "com.example.myapp.CounterViewModelTest"
|
||||||
|
|
||||||
|
# Instrumentation tests (requires device or managed device)
|
||||||
|
./gradlew connectedDebugAndroidTest # on connected device
|
||||||
|
./gradlew pixel6Api34DebugAndroidTest # on managed device
|
||||||
|
|
||||||
|
# Both together
|
||||||
|
./gradlew test connectedDebugAndroidTest
|
||||||
|
|
||||||
|
# Test with coverage report (JaCoCo)
|
||||||
|
./gradlew testDebugUnitTest jacocoTestReport
|
||||||
|
```
|
||||||
@@ -0,0 +1,209 @@
|
|||||||
|
# Accessibility Guidelines
|
||||||
|
|
||||||
|
Comprehensive accessibility requirements for Android applications.
|
||||||
|
|
||||||
|
## Core Requirements
|
||||||
|
|
||||||
|
### Minimum Standards
|
||||||
|
|
||||||
|
| Requirement | Specification |
|
||||||
|
|-------------|---------------|
|
||||||
|
| Color contrast (text) | 4.5:1 minimum |
|
||||||
|
| Color contrast (large text) | 3:1 minimum |
|
||||||
|
| Color contrast (UI components) | 3:1 minimum |
|
||||||
|
| Touch targets | 48 × 48dp minimum |
|
||||||
|
| Content descriptions | All interactive elements |
|
||||||
|
| Focus indicators | Clearly visible |
|
||||||
|
| Screen reader support | Proper semantics |
|
||||||
|
|
||||||
|
## Content Labels
|
||||||
|
|
||||||
|
### contentDescription
|
||||||
|
|
||||||
|
Use for non-text interactive elements.
|
||||||
|
|
||||||
|
**When to use:**
|
||||||
|
- ImageView, ImageButton
|
||||||
|
- CheckBox, Switch (state description)
|
||||||
|
- Custom drawable views
|
||||||
|
- Icons that convey meaning
|
||||||
|
|
||||||
|
**When NOT to use:**
|
||||||
|
- TextView (uses text content automatically)
|
||||||
|
- Decorative images (set to null)
|
||||||
|
- Elements with labelFor relationship
|
||||||
|
|
||||||
|
### android:hint
|
||||||
|
|
||||||
|
Use for editable text fields to show placeholder text.
|
||||||
|
|
||||||
|
**Important**: Don't use contentDescription on EditText—it interferes with accessibility services.
|
||||||
|
|
||||||
|
### android:labelFor
|
||||||
|
|
||||||
|
Link labels to input fields by setting labelFor on the TextView to reference the EditText ID.
|
||||||
|
|
||||||
|
## Label Best Practices
|
||||||
|
|
||||||
|
### Do's
|
||||||
|
|
||||||
|
| Practice | Example |
|
||||||
|
|----------|---------|
|
||||||
|
| Be concise | "Save" not "Click here to save" |
|
||||||
|
| Describe action/purpose | "Delete message" |
|
||||||
|
| Be unique in context | "Delete item 3" not just "Delete" |
|
||||||
|
| Update dynamically | "Pause" ↔ "Play" based on state |
|
||||||
|
|
||||||
|
### Don'ts
|
||||||
|
|
||||||
|
| Avoid | Reason |
|
||||||
|
|-------|--------|
|
||||||
|
| Include element type | TalkBack announces "button" automatically |
|
||||||
|
| Say "button", "image", etc. | Redundant with accessibility info |
|
||||||
|
| Use "click" or "tap" | Input method varies |
|
||||||
|
| Leave empty/generic | "Button" or "Image" is unhelpful |
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
| Bad | Good |
|
||||||
|
|-----|------|
|
||||||
|
| "Save button" | "Save" |
|
||||||
|
| "Click here to submit" | "Submit" |
|
||||||
|
| "Image" | "Profile photo of John" |
|
||||||
|
| "Button 1" | "Add to cart" |
|
||||||
|
|
||||||
|
## Focus and Navigation
|
||||||
|
|
||||||
|
### Focus Groups
|
||||||
|
|
||||||
|
Group related elements using `screenReaderFocusable="true"` on the container and `focusable="false"` on child elements. TalkBack will announce all children's content in a single utterance.
|
||||||
|
|
||||||
|
### Headings
|
||||||
|
|
||||||
|
Mark section headers with `accessibilityHeading="true"`. Users can navigate between headings for quick scanning.
|
||||||
|
|
||||||
|
### Pane Titles
|
||||||
|
|
||||||
|
Identify screen regions with `accessibilityPaneTitle`. Accessibility services announce pane changes.
|
||||||
|
|
||||||
|
### Focus Order
|
||||||
|
|
||||||
|
- Natural reading order (top-to-bottom, start-to-end)
|
||||||
|
- Use `accessibilityTraversalBefore/After` for custom order
|
||||||
|
- Ensure all interactive elements are focusable
|
||||||
|
- Skip decorative elements
|
||||||
|
|
||||||
|
## Decorative Elements
|
||||||
|
|
||||||
|
Skip elements that don't convey information:
|
||||||
|
- Set `contentDescription="@null"`
|
||||||
|
- Or set `importantForAccessibility="no"`
|
||||||
|
|
||||||
|
## Custom Accessibility Actions
|
||||||
|
|
||||||
|
### Adding Actions
|
||||||
|
|
||||||
|
Provide alternatives for gesture-based interactions using `ViewCompat.addAccessibilityAction()`. This exposes swipe actions to accessibility services.
|
||||||
|
|
||||||
|
### Replacing Action Labels
|
||||||
|
|
||||||
|
Make default actions more descriptive using `ViewCompat.replaceAccessibilityAction()`. Example: "Double tap and hold to add to favorites" instead of generic "long press".
|
||||||
|
|
||||||
|
## Color and Visual Cues
|
||||||
|
|
||||||
|
### Don't Rely on Color Alone
|
||||||
|
|
||||||
|
Combine color with other indicators:
|
||||||
|
|
||||||
|
| Information | Color + Alternative |
|
||||||
|
|-------------|---------------------|
|
||||||
|
| Error state | Red + error icon + text |
|
||||||
|
| Success | Green + checkmark + text |
|
||||||
|
| Required field | Red asterisk + "Required" label |
|
||||||
|
| Selected item | Highlight + checkmark + bold |
|
||||||
|
| Link text | Blue + underline |
|
||||||
|
|
||||||
|
### Contrast Testing
|
||||||
|
|
||||||
|
Use tools to verify contrast:
|
||||||
|
- Android Accessibility Scanner
|
||||||
|
- Contrast Checker plugins
|
||||||
|
- Manual calculation: (L1 + 0.05) / (L2 + 0.05)
|
||||||
|
|
||||||
|
## Touch Targets
|
||||||
|
|
||||||
|
### Minimum Sizes
|
||||||
|
|
||||||
|
| Element | Minimum | Recommended |
|
||||||
|
|---------|---------|-------------|
|
||||||
|
| Standard | 48 × 48dp | 48 × 48dp |
|
||||||
|
| Primary actions | 48 × 48dp | 56 × 56dp |
|
||||||
|
| Kids apps | 56 × 56dp | 64 × 64dp |
|
||||||
|
|
||||||
|
### Spacing
|
||||||
|
|
||||||
|
- Minimum 8dp between adjacent touch targets
|
||||||
|
- Visual element can be smaller if touch area is adequate (use padding)
|
||||||
|
|
||||||
|
## Screen Reader Announcements
|
||||||
|
|
||||||
|
### Live Regions
|
||||||
|
|
||||||
|
Announce dynamic content changes using `accessibilityLiveRegion`:
|
||||||
|
|
||||||
|
| Mode | Usage |
|
||||||
|
|------|-------|
|
||||||
|
| polite | Announces when user is idle |
|
||||||
|
| assertive | Interrupts current speech |
|
||||||
|
| none | No automatic announcements |
|
||||||
|
|
||||||
|
### Custom Announcements
|
||||||
|
|
||||||
|
Use `announceForAccessibility()` sparingly—prefer live regions.
|
||||||
|
|
||||||
|
## Keyboard and Hardware Navigation
|
||||||
|
|
||||||
|
### Focus Indicators
|
||||||
|
|
||||||
|
- Visible focus state for all interactive elements
|
||||||
|
- Don't remove default focus indicators
|
||||||
|
- Custom focus: 2dp+ border or background change
|
||||||
|
|
||||||
|
### Keyboard Shortcuts
|
||||||
|
|
||||||
|
- Support Tab navigation
|
||||||
|
- Enter/Space for activation
|
||||||
|
- Arrow keys for lists/grids
|
||||||
|
- Escape for dismissal
|
||||||
|
|
||||||
|
## Testing Accessibility
|
||||||
|
|
||||||
|
### Manual Testing
|
||||||
|
|
||||||
|
1. **TalkBack**: Navigate entire app with screen reader
|
||||||
|
2. **Switch Access**: Test with switch navigation
|
||||||
|
3. **Keyboard**: Navigate with external keyboard only
|
||||||
|
4. **Magnification**: Test with zoom enabled
|
||||||
|
5. **Large text**: Test with 200% font scale
|
||||||
|
6. **High contrast**: Test with high contrast mode
|
||||||
|
|
||||||
|
### Automated Testing
|
||||||
|
|
||||||
|
| Tool | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| Accessibility Scanner | On-device scanning |
|
||||||
|
| Espresso Accessibility Checks | Automated UI tests |
|
||||||
|
| Lint checks | Static analysis |
|
||||||
|
|
||||||
|
### Checklist
|
||||||
|
|
||||||
|
- [ ] All interactive elements have descriptions
|
||||||
|
- [ ] Touch targets are 48dp minimum
|
||||||
|
- [ ] Color contrast meets requirements
|
||||||
|
- [ ] Focus order is logical
|
||||||
|
- [ ] Headings are properly marked
|
||||||
|
- [ ] Custom actions have descriptive labels
|
||||||
|
- [ ] Live regions announce important changes
|
||||||
|
- [ ] Keyboard navigation works
|
||||||
|
- [ ] Works with TalkBack enabled
|
||||||
|
- [ ] Works with large text (200%)
|
||||||
@@ -0,0 +1,231 @@
|
|||||||
|
# Adaptive Screens Guidelines
|
||||||
|
|
||||||
|
Requirements for large screens, tablets, foldables, and multi-window support.
|
||||||
|
|
||||||
|
## Adaptive Quality Tiers
|
||||||
|
|
||||||
|
Google defines three progressive quality tiers for adaptive apps:
|
||||||
|
|
||||||
|
### Tier 3: Adaptive Ready (Basic)
|
||||||
|
|
||||||
|
Minimum requirements for all apps:
|
||||||
|
|
||||||
|
| Requirement | Description |
|
||||||
|
|-------------|-------------|
|
||||||
|
| Full screen | App fills display, no letterboxing |
|
||||||
|
| Configuration changes | Handles rotation, folding, resizing |
|
||||||
|
| Multi-window | Supports split-screen mode |
|
||||||
|
| Basic input | Keyboard, mouse, trackpad support |
|
||||||
|
|
||||||
|
### Tier 2: Adaptive Optimized (Better)
|
||||||
|
|
||||||
|
Enhanced experience:
|
||||||
|
|
||||||
|
| Requirement | Description |
|
||||||
|
|-------------|-------------|
|
||||||
|
| Layout optimization | Responsive layouts for all sizes |
|
||||||
|
| Enhanced input | Full keyboard shortcuts, mouse hover states |
|
||||||
|
| Continuity | Seamless state preservation |
|
||||||
|
|
||||||
|
### Tier 1: Adaptive Differentiated (Best)
|
||||||
|
|
||||||
|
Device-specific excellence:
|
||||||
|
|
||||||
|
| Requirement | Description |
|
||||||
|
|-------------|-------------|
|
||||||
|
| Multitasking | Drag and drop, activity embedding |
|
||||||
|
| Foldable postures | Table-top mode, book mode support |
|
||||||
|
| Stylus | Full stylus input support |
|
||||||
|
| Desktop | Windowed mode optimization |
|
||||||
|
|
||||||
|
## Screen Size Classes
|
||||||
|
|
||||||
|
### Width-Based Classes
|
||||||
|
|
||||||
|
| Class | Width | Typical Devices |
|
||||||
|
|-------|-------|-----------------|
|
||||||
|
| Compact | < 600dp | Phone portrait |
|
||||||
|
| Medium | 600-840dp | Tablet portrait, phone landscape |
|
||||||
|
| Expanded | > 840dp | Tablet landscape, desktop |
|
||||||
|
|
||||||
|
### Layout Strategies
|
||||||
|
|
||||||
|
| Screen Class | Navigation | Content Layout |
|
||||||
|
|--------------|------------|----------------|
|
||||||
|
| Compact | Bottom nav | Single pane |
|
||||||
|
| Medium | Nav rail | List-detail (optional) |
|
||||||
|
| Expanded | Nav drawer/rail | List-detail, multi-pane |
|
||||||
|
|
||||||
|
## Configuration Changes
|
||||||
|
|
||||||
|
### Must Handle
|
||||||
|
|
||||||
|
| Change | Trigger |
|
||||||
|
|--------|---------|
|
||||||
|
| Rotation | Device rotated |
|
||||||
|
| Fold/Unfold | Foldable state change |
|
||||||
|
| Window resize | Multi-window adjustment |
|
||||||
|
| Split screen | Enter/exit split mode |
|
||||||
|
| Keyboard | External keyboard attach/detach |
|
||||||
|
|
||||||
|
### Configuration Handling
|
||||||
|
|
||||||
|
| Approach | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| Let system handle | Default, activity recreated |
|
||||||
|
| Handle manually | Declare configChanges, implement onConfigurationChanged |
|
||||||
|
|
||||||
|
### State Preservation
|
||||||
|
|
||||||
|
- Use ViewModel for UI state
|
||||||
|
- Use SavedStateHandle for process death
|
||||||
|
- Test with "Don't keep activities" enabled
|
||||||
|
|
||||||
|
## Multi-Window Support
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
| Feature | Status |
|
||||||
|
|---------|--------|
|
||||||
|
| resizeableActivity | true (default API 24+) |
|
||||||
|
| Minimum size | Support 220dp width |
|
||||||
|
| State handling | Preserve across resize |
|
||||||
|
|
||||||
|
### Best Practices
|
||||||
|
|
||||||
|
- Don't assume full-screen ownership
|
||||||
|
- Handle onConfigurationChanged gracefully
|
||||||
|
- Test at minimum supported size
|
||||||
|
- Support free-form windows (desktop mode)
|
||||||
|
|
||||||
|
## Foldable Devices
|
||||||
|
|
||||||
|
### Postures
|
||||||
|
|
||||||
|
| Posture | Description | Use Case |
|
||||||
|
|---------|-------------|----------|
|
||||||
|
| Flat | Fully open | Normal tablet use |
|
||||||
|
| Half-opened (tabletop) | Hinged at ~90° horizontal | Video calls, media |
|
||||||
|
| Half-opened (book) | Hinged at ~90° vertical | Reading, productivity |
|
||||||
|
| Folded | Closed | Compact phone mode |
|
||||||
|
|
||||||
|
### Design Considerations
|
||||||
|
|
||||||
|
- Avoid placing interactive elements on the fold
|
||||||
|
- Consider separate content for each screen segment
|
||||||
|
- Support continuity when fold state changes
|
||||||
|
- Use WindowInfoTracker to detect fold state
|
||||||
|
|
||||||
|
## External Input Devices
|
||||||
|
|
||||||
|
### Keyboard Support
|
||||||
|
|
||||||
|
| Requirement | Implementation |
|
||||||
|
|-------------|----------------|
|
||||||
|
| Tab navigation | Focusable elements in order |
|
||||||
|
| Enter/Space | Activates focused element |
|
||||||
|
| Arrow keys | Navigate lists, grids |
|
||||||
|
| Shortcuts | Common actions (Ctrl+S, etc.) |
|
||||||
|
| Focus indicators | Visible focus states |
|
||||||
|
|
||||||
|
### Mouse/Trackpad Support
|
||||||
|
|
||||||
|
| Requirement | Implementation |
|
||||||
|
|-------------|----------------|
|
||||||
|
| Hover states | Visual feedback on hover |
|
||||||
|
| Right-click | Context menu support |
|
||||||
|
| Scroll | Smooth scrolling |
|
||||||
|
| Pointer cursor | Appropriate cursor types |
|
||||||
|
|
||||||
|
### Stylus Support
|
||||||
|
|
||||||
|
| Feature | Implementation |
|
||||||
|
|---------|----------------|
|
||||||
|
| Pressure sensitivity | Variable stroke width |
|
||||||
|
| Palm rejection | Ignore palm touches |
|
||||||
|
| Tilt detection | Shading effects |
|
||||||
|
| Hover preview | Show cursor before touch |
|
||||||
|
|
||||||
|
## Navigation Patterns
|
||||||
|
|
||||||
|
### By Screen Width
|
||||||
|
|
||||||
|
| Width | Primary Nav | Secondary Nav |
|
||||||
|
|-------|-------------|---------------|
|
||||||
|
| < 600dp | Bottom nav (3-5 items) | Hamburger menu |
|
||||||
|
| 600-840dp | Navigation rail | Drawer on demand |
|
||||||
|
| > 840dp | Permanent drawer or rail | Drawer or none |
|
||||||
|
|
||||||
|
### Navigation Rail Specs
|
||||||
|
|
||||||
|
| Property | Value |
|
||||||
|
|----------|-------|
|
||||||
|
| Width | 80dp |
|
||||||
|
| Icon size | 24dp |
|
||||||
|
| Touch target | 56dp |
|
||||||
|
| Items | 3-7 destinations |
|
||||||
|
| FAB | Optional, at top |
|
||||||
|
|
||||||
|
### Permanent Navigation Drawer
|
||||||
|
|
||||||
|
| Property | Value |
|
||||||
|
|----------|-------|
|
||||||
|
| Width | 256-360dp |
|
||||||
|
| Position | Left edge (LTR) |
|
||||||
|
| Behavior | Always visible |
|
||||||
|
| Content | Full labels, icons |
|
||||||
|
|
||||||
|
## Responsive Layouts
|
||||||
|
|
||||||
|
### Breakpoints
|
||||||
|
|
||||||
|
| Class | Width Range |
|
||||||
|
|-------|-------------|
|
||||||
|
| COMPACT | < 600dp |
|
||||||
|
| MEDIUM | 600-840dp |
|
||||||
|
| EXPANDED | > 840dp |
|
||||||
|
|
||||||
|
Use WindowSizeClass to determine current breakpoint and adapt layout accordingly.
|
||||||
|
|
||||||
|
## Content Considerations
|
||||||
|
|
||||||
|
### Text Readability
|
||||||
|
|
||||||
|
- Line length: 45-75 characters max
|
||||||
|
- Use multiple columns on wide screens
|
||||||
|
- Maintain hierarchy with consistent spacing
|
||||||
|
|
||||||
|
### Media
|
||||||
|
|
||||||
|
- Support multiple aspect ratios
|
||||||
|
- Provide high-resolution assets
|
||||||
|
- Consider picture-in-picture for video
|
||||||
|
|
||||||
|
### Touch vs. Precise Input
|
||||||
|
|
||||||
|
- Large screens often use mouse/keyboard
|
||||||
|
- Don't assume touch-only interaction
|
||||||
|
- Provide hover states and tooltips
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
### Device Matrix
|
||||||
|
|
||||||
|
| Device Type | Test Priority |
|
||||||
|
|-------------|---------------|
|
||||||
|
| Phone (portrait) | Required |
|
||||||
|
| Phone (landscape) | Required |
|
||||||
|
| Tablet (both orientations) | Required |
|
||||||
|
| Foldable (all postures) | High |
|
||||||
|
| Desktop/Chromebook | Medium |
|
||||||
|
|
||||||
|
### Test Cases
|
||||||
|
|
||||||
|
- [ ] App fills screen in all configurations
|
||||||
|
- [ ] No letterboxing or black bars
|
||||||
|
- [ ] State preserved across configuration changes
|
||||||
|
- [ ] Multi-window works at minimum size
|
||||||
|
- [ ] Keyboard navigation functional
|
||||||
|
- [ ] Mouse hover states present
|
||||||
|
- [ ] Foldable postures handled (if applicable)
|
||||||
|
- [ ] Navigation adapts to screen width
|
||||||
@@ -0,0 +1,365 @@
|
|||||||
|
# Design Style Guide
|
||||||
|
|
||||||
|
Match visual design to app category and target audience for cohesive user experience.
|
||||||
|
|
||||||
|
## Style Selection Principle
|
||||||
|
|
||||||
|
> **The visual style must match the app's purpose and audience.**
|
||||||
|
> A finance app should feel trustworthy, not playful.
|
||||||
|
> A children's app should feel fun, not corporate.
|
||||||
|
|
||||||
|
## Style Selection Matrix
|
||||||
|
|
||||||
|
| App Category | Visual Style | Color Palette | Typography | Interaction |
|
||||||
|
|--------------|--------------|---------------|------------|-------------|
|
||||||
|
| Utility/Tool | Minimalist | Neutral + 1 accent | Clean sans-serif | Direct, efficient |
|
||||||
|
| Finance/Banking | Professional Trust | Blue/Green/Navy | Conservative | Secure, deliberate |
|
||||||
|
| Health/Wellness | Calm & Natural | Soft greens, earth tones | Rounded, friendly | Gentle, encouraging |
|
||||||
|
| Kids (3-5) | Playful Simple | Bright primary colors | Large, rounded | Big targets, forgiving |
|
||||||
|
| Kids (6-12) | Fun & Engaging | Vibrant, varied | Bold, readable | Gamified feedback |
|
||||||
|
| Social/Entertainment | Expressive | Brand-driven | Dynamic | Gesture-rich |
|
||||||
|
| Productivity | Clean & Focused | Minimal, high contrast | Professional | Keyboard-friendly |
|
||||||
|
| E-commerce | Conversion-focused | Brand + CTA colors | Scannable | Quick actions |
|
||||||
|
| Gaming | Immersive | Theme-driven | Stylized | Custom gestures |
|
||||||
|
|
||||||
|
## Detailed Style Profiles
|
||||||
|
|
||||||
|
### Minimalist / iOS-like (Utility Apps)
|
||||||
|
|
||||||
|
**When to use**: Tools, utilities, calculators, file managers, settings apps
|
||||||
|
|
||||||
|
**Visual Characteristics**:
|
||||||
|
|
||||||
|
| Element | Specification |
|
||||||
|
|---------|---------------|
|
||||||
|
| Colors | 2-3 colors max, neutral base |
|
||||||
|
| Whitespace | Generous, 24-48dp margins |
|
||||||
|
| Typography | Single font family, clear hierarchy |
|
||||||
|
| Icons | Line-based, consistent stroke |
|
||||||
|
| Shadows | Subtle or none |
|
||||||
|
| Borders | Thin (1dp) or none |
|
||||||
|
| Shapes | Subtle corners (8-12dp) |
|
||||||
|
|
||||||
|
**Interaction Style**:
|
||||||
|
- Direct manipulation
|
||||||
|
- Immediate feedback
|
||||||
|
- No unnecessary animations
|
||||||
|
- Efficient task completion
|
||||||
|
|
||||||
|
**Color Palette**:
|
||||||
|
|
||||||
|
| Role | Light Mode | Dark Mode |
|
||||||
|
|------|------------|-----------|
|
||||||
|
| Background | #FAFAFA | #1C1C1E |
|
||||||
|
| Surface | #FFFFFF | #2C2C2E |
|
||||||
|
| Primary | #007AFF | #0A84FF |
|
||||||
|
| Text | #000000 | #FFFFFF |
|
||||||
|
| Secondary | #8E8E93 | #8E8E93 |
|
||||||
|
|
||||||
|
**Reference Apps**: iOS Settings, Apple Notes, Google Calculator
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Professional Trust (Finance/Business)
|
||||||
|
|
||||||
|
**When to use**: Banking, investment, enterprise, B2B applications
|
||||||
|
|
||||||
|
**Visual Characteristics**:
|
||||||
|
|
||||||
|
| Element | Specification |
|
||||||
|
|---------|---------------|
|
||||||
|
| Colors | Blues, greens, navy (trust colors) |
|
||||||
|
| Whitespace | Structured, grid-based |
|
||||||
|
| Typography | Formal, conservative weights |
|
||||||
|
| Icons | Filled or outlined, consistent |
|
||||||
|
| Data visualization | Clear, accurate charts |
|
||||||
|
| Security indicators | Prominent locks, badges |
|
||||||
|
|
||||||
|
**Interaction Style**:
|
||||||
|
- Confirmatory (double-check important actions)
|
||||||
|
- Deliberate (not rushed)
|
||||||
|
- Secure-feeling
|
||||||
|
- Clear feedback on transactions
|
||||||
|
|
||||||
|
**Color Palette**:
|
||||||
|
|
||||||
|
| Role | Color | Name |
|
||||||
|
|------|-------|------|
|
||||||
|
| Primary | #00695C or #1565C0 | Teal 800 / Blue 800 |
|
||||||
|
| Secondary | #37474F | Blue Grey 800 |
|
||||||
|
| Accent | #FFC107 | Amber |
|
||||||
|
| Background | #ECEFF1 | Blue Grey 50 |
|
||||||
|
| Success | #2E7D32 | Green 800 |
|
||||||
|
| Error | #C62828 | Red 800 |
|
||||||
|
|
||||||
|
**Key Patterns**:
|
||||||
|
- Balance summaries prominent
|
||||||
|
- Transaction history easily scannable
|
||||||
|
- Secure entry for sensitive data
|
||||||
|
- Biometric authentication prompts
|
||||||
|
|
||||||
|
**Reference Apps**: Banking apps, Trading platforms, Enterprise tools
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Calm & Wellness (Health Apps)
|
||||||
|
|
||||||
|
**When to use**: Meditation, fitness tracking, health monitoring, therapy
|
||||||
|
|
||||||
|
**Visual Characteristics**:
|
||||||
|
|
||||||
|
| Element | Specification |
|
||||||
|
|---------|---------------|
|
||||||
|
| Colors | Soft, muted, natural |
|
||||||
|
| Whitespace | Abundant (breathing room) |
|
||||||
|
| Typography | Rounded, friendly fonts |
|
||||||
|
| Shapes | Organic, soft corners (16dp+) |
|
||||||
|
| Animation | Gentle, slow transitions |
|
||||||
|
| Imagery | Nature, soft gradients |
|
||||||
|
|
||||||
|
**Interaction Style**:
|
||||||
|
- Encouraging, not demanding
|
||||||
|
- Progress-oriented
|
||||||
|
- Gentle reminders
|
||||||
|
- Celebration of achievements
|
||||||
|
|
||||||
|
**Color Palette**:
|
||||||
|
|
||||||
|
| Role | Color | Name |
|
||||||
|
|------|-------|------|
|
||||||
|
| Primary | #4CAF50 | Green 500 |
|
||||||
|
| Secondary | #81C784 | Green 300 |
|
||||||
|
| Tertiary | #B2DFDB | Teal 100 |
|
||||||
|
| Background | #F1F8E9 | Light Green 50 |
|
||||||
|
| Text | #33691E | Light Green 900 |
|
||||||
|
| Accent | #FFB74D | Orange 300 |
|
||||||
|
|
||||||
|
**Key Patterns**:
|
||||||
|
- Progress rings and charts
|
||||||
|
- Streak tracking
|
||||||
|
- Motivational messages
|
||||||
|
- Quiet notification style
|
||||||
|
|
||||||
|
**Reference Apps**: Headspace, Calm, Apple Fitness
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Playful & Kid-Friendly (Children's Apps)
|
||||||
|
|
||||||
|
**When to use**: Educational games, children's content, family apps
|
||||||
|
|
||||||
|
#### Ages 3-5
|
||||||
|
|
||||||
|
**Visual Characteristics**:
|
||||||
|
|
||||||
|
| Element | Specification |
|
||||||
|
|---------|---------------|
|
||||||
|
| Colors | Bright, saturated primary colors |
|
||||||
|
| Touch targets | 56dp minimum, 64dp recommended |
|
||||||
|
| Shapes | Very rounded (full radius) |
|
||||||
|
| Typography | Large (18sp+ minimum), simple fonts |
|
||||||
|
| Icons | Large, colorful, recognizable |
|
||||||
|
| Animation | Frequent, rewarding |
|
||||||
|
|
||||||
|
**Interaction Style**:
|
||||||
|
- Simple gestures only (tap, drag)
|
||||||
|
- No multi-finger gestures
|
||||||
|
- Forgiving error handling
|
||||||
|
- Immediate, multi-sensory feedback (sound + visual + haptic)
|
||||||
|
- No text-only buttons
|
||||||
|
|
||||||
|
**Color Palette**:
|
||||||
|
|
||||||
|
| Role | Color | Name |
|
||||||
|
|------|-------|------|
|
||||||
|
| Primary | #F44336 | Red 500 |
|
||||||
|
| Secondary | #FFEB3B | Yellow 500 |
|
||||||
|
| Tertiary | #2196F3 | Blue 500 |
|
||||||
|
| Background | #FFFFFF | White or soft pastels |
|
||||||
|
| Accent | #4CAF50 | Green 500 |
|
||||||
|
|
||||||
|
#### Ages 6-12
|
||||||
|
|
||||||
|
**Visual Characteristics**:
|
||||||
|
|
||||||
|
| Element | Specification |
|
||||||
|
|---------|---------------|
|
||||||
|
| Colors | Vibrant, varied palette |
|
||||||
|
| Touch targets | 48dp minimum |
|
||||||
|
| Shapes | Rounded but can be varied |
|
||||||
|
| Typography | Bold, readable, can include text |
|
||||||
|
| Icons | Stylized, character-driven |
|
||||||
|
| Animation | Gamified, achievement-based |
|
||||||
|
|
||||||
|
**Interaction Style**:
|
||||||
|
- Can introduce some complexity
|
||||||
|
- Gamification elements
|
||||||
|
- Progress and rewards
|
||||||
|
- Some text is acceptable
|
||||||
|
|
||||||
|
**Key Patterns for All Kids Apps**:
|
||||||
|
- Icon-based navigation (no text-only)
|
||||||
|
- Home button always visible
|
||||||
|
- Back navigation clear
|
||||||
|
- Parent gate for settings (math problem, hold button)
|
||||||
|
- Multi-sensory feedback
|
||||||
|
- Encouraging error states (no punishment)
|
||||||
|
- Joint engagement opportunities with parents
|
||||||
|
|
||||||
|
**Reference Apps**: PBS Kids, Khan Academy Kids, Duolingo ABC
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Expressive & Social (Entertainment Apps)
|
||||||
|
|
||||||
|
**When to use**: Social media, content creation, entertainment
|
||||||
|
|
||||||
|
**Visual Characteristics**:
|
||||||
|
|
||||||
|
| Element | Specification |
|
||||||
|
|---------|---------------|
|
||||||
|
| Colors | Bold brand colors |
|
||||||
|
| Typography | Dynamic, personality-driven |
|
||||||
|
| Media | Rich, prominent |
|
||||||
|
| Animation | Expressive, delightful |
|
||||||
|
| Shapes | Brand-specific |
|
||||||
|
|
||||||
|
**Interaction Style**:
|
||||||
|
- Gesture-rich
|
||||||
|
- Quick actions
|
||||||
|
- Social interactions prominent
|
||||||
|
- Content-first design
|
||||||
|
|
||||||
|
**Key Patterns**:
|
||||||
|
- Feed-based layouts
|
||||||
|
- Quick action buttons (like, share, comment)
|
||||||
|
- Stories/ephemeral content
|
||||||
|
- Creation tools accessible
|
||||||
|
- Notification badges
|
||||||
|
|
||||||
|
**Reference Apps**: Instagram, TikTok, Snapchat
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Clean & Focused (Productivity Apps)
|
||||||
|
|
||||||
|
**When to use**: Note-taking, task management, email, documents
|
||||||
|
|
||||||
|
**Visual Characteristics**:
|
||||||
|
|
||||||
|
| Element | Specification |
|
||||||
|
|---------|---------------|
|
||||||
|
| Colors | High contrast, minimal |
|
||||||
|
| Whitespace | Strategic, content-focused |
|
||||||
|
| Typography | Highly readable, clear hierarchy |
|
||||||
|
| Icons | Functional, consistent |
|
||||||
|
| Density | Adjustable (compact to comfortable) |
|
||||||
|
|
||||||
|
**Interaction Style**:
|
||||||
|
- Keyboard-friendly
|
||||||
|
- Batch operations
|
||||||
|
- Drag and drop
|
||||||
|
- Quick capture
|
||||||
|
- Search-centric
|
||||||
|
|
||||||
|
**Color Palette**:
|
||||||
|
|
||||||
|
| Role | Light Mode | Dark Mode |
|
||||||
|
|------|------------|-----------|
|
||||||
|
| Primary | #1976D2 | #64B5F6 |
|
||||||
|
| Background | #FFFFFF | #121212 |
|
||||||
|
| Surface | #F5F5F5 | #1E1E1E |
|
||||||
|
| Text | #212121 | #E0E0E0 |
|
||||||
|
| Accent/Priority | #FF5722 | #FF7043 |
|
||||||
|
|
||||||
|
**Key Patterns**:
|
||||||
|
- List views with swipe actions
|
||||||
|
- Quick add buttons
|
||||||
|
- Checkbox interactions
|
||||||
|
- Due dates and reminders
|
||||||
|
- Tags and categories
|
||||||
|
|
||||||
|
**Reference Apps**: Notion, Todoist, Google Tasks
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Conversion-Focused (E-commerce)
|
||||||
|
|
||||||
|
**When to use**: Shopping, marketplace, booking apps
|
||||||
|
|
||||||
|
**Visual Characteristics**:
|
||||||
|
|
||||||
|
| Element | Specification |
|
||||||
|
|---------|---------------|
|
||||||
|
| Colors | Brand + clear CTA colors |
|
||||||
|
| Images | High quality, zoomable |
|
||||||
|
| Typography | Scannable, price prominent |
|
||||||
|
| Cards | Product-focused |
|
||||||
|
| Badges | Sale, new, limited |
|
||||||
|
|
||||||
|
**Interaction Style**:
|
||||||
|
- Quick add to cart
|
||||||
|
- Easy checkout flow
|
||||||
|
- Comparison features
|
||||||
|
- Reviews accessible
|
||||||
|
- Wishlist/save for later
|
||||||
|
|
||||||
|
**Key Patterns**:
|
||||||
|
- Grid and list view toggle
|
||||||
|
- Filter and sort
|
||||||
|
- Product detail with gallery
|
||||||
|
- Cart always accessible
|
||||||
|
- One-tap purchase options
|
||||||
|
|
||||||
|
**Reference Apps**: Amazon, Shopify apps, Booking.com
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Consistency Principles
|
||||||
|
|
||||||
|
### Match Style to Subject Matter
|
||||||
|
|
||||||
|
| App Purpose | Style Should Feel |
|
||||||
|
|-------------|-------------------|
|
||||||
|
| Utility | Efficient, invisible |
|
||||||
|
| Finance | Trustworthy, secure |
|
||||||
|
| Health | Supportive, calm |
|
||||||
|
| Kids | Safe, fun |
|
||||||
|
| Social | Expressive, personal |
|
||||||
|
| Productivity | Focused, powerful |
|
||||||
|
| Shopping | Exciting, trustworthy |
|
||||||
|
|
||||||
|
### Internal Consistency Rules
|
||||||
|
|
||||||
|
| Rule | Implementation |
|
||||||
|
|------|----------------|
|
||||||
|
| Same icon style | All outlined OR all filled |
|
||||||
|
| Consistent color meaning | Red = destructive, Green = success |
|
||||||
|
| Uniform spacing | Use 8dp grid |
|
||||||
|
| Predictable interaction | Same gesture = same result |
|
||||||
|
| Typography system | Use M3 type scale |
|
||||||
|
|
||||||
|
## Anti-Patterns: Style Mismatch
|
||||||
|
|
||||||
|
| Mismatch | Problem |
|
||||||
|
|----------|---------|
|
||||||
|
| Playful colors in banking app | Undermines trust |
|
||||||
|
| Complex gestures in kids app | Frustrates young users |
|
||||||
|
| Cluttered UI in wellness app | Defeats calming purpose |
|
||||||
|
| Boring visuals in entertainment | Fails to engage |
|
||||||
|
| Aggressive CTAs in health app | Feels manipulative |
|
||||||
|
| Childish design in professional tool | Lacks credibility |
|
||||||
|
| Dense information in casual app | Overwhelms users |
|
||||||
|
|
||||||
|
## Implementation Checklist
|
||||||
|
|
||||||
|
- [ ] Identified app category and target audience
|
||||||
|
- [ ] Selected appropriate style profile
|
||||||
|
- [ ] Color palette matches style
|
||||||
|
- [ ] Typography matches style
|
||||||
|
- [ ] Interaction patterns match style
|
||||||
|
- [ ] Touch targets appropriate for audience
|
||||||
|
- [ ] Animation style consistent
|
||||||
|
- [ ] Internal consistency maintained
|
||||||
|
- [ ] No style mismatches
|
||||||
|
- [ ] Tested with target users
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
# Functional Requirements
|
||||||
|
|
||||||
|
Audio, video, notifications, and other functional behavior requirements.
|
||||||
|
|
||||||
|
## Audio
|
||||||
|
|
||||||
|
### Playback Initialization
|
||||||
|
|
||||||
|
| Requirement | Specification |
|
||||||
|
|-------------|---------------|
|
||||||
|
| Response time | < 1 second |
|
||||||
|
| If delayed | Show visual progress indicator |
|
||||||
|
| User feedback | Immediate acknowledgment of action |
|
||||||
|
|
||||||
|
### Audio Focus Rules
|
||||||
|
|
||||||
|
| Event | Required Action |
|
||||||
|
|-------|-----------------|
|
||||||
|
| Another app requests focus | Pause or reduce volume |
|
||||||
|
| Focus regained | Resume or restore volume |
|
||||||
|
| Playback stops | Abandon focus |
|
||||||
|
|
||||||
|
### Audio Focus Handling
|
||||||
|
|
||||||
|
| Focus Change | Action |
|
||||||
|
|--------------|--------|
|
||||||
|
| AUDIOFOCUS_LOSS | Stop playback |
|
||||||
|
| AUDIOFOCUS_LOSS_TRANSIENT | Pause playback |
|
||||||
|
| AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK | Reduce volume |
|
||||||
|
| AUDIOFOCUS_GAIN | Resume playback |
|
||||||
|
|
||||||
|
### Background Playback
|
||||||
|
|
||||||
|
| Requirement | Implementation |
|
||||||
|
|-------------|----------------|
|
||||||
|
| Continue when backgrounded | Use Foreground Service |
|
||||||
|
| Notification | MediaStyle notification required |
|
||||||
|
| Media controls | System media controls integration |
|
||||||
|
| Session | MediaSession for system integration |
|
||||||
|
|
||||||
|
## Video
|
||||||
|
|
||||||
|
### Picture-in-Picture (PiP)
|
||||||
|
|
||||||
|
| Requirement | Specification |
|
||||||
|
|-------------|---------------|
|
||||||
|
| Video apps | Should support PiP |
|
||||||
|
| Aspect ratio | 16:9 to 2.39:1 |
|
||||||
|
| Auto-enter | When user navigates away during playback |
|
||||||
|
|
||||||
|
### Video Encoding
|
||||||
|
|
||||||
|
| Standard | Requirement |
|
||||||
|
|----------|-------------|
|
||||||
|
| Compression | HEVC (H.265) recommended |
|
||||||
|
| Fallback | H.264 for compatibility |
|
||||||
|
| Quality | Adaptive based on network |
|
||||||
|
|
||||||
|
### Video Player Requirements
|
||||||
|
|
||||||
|
| Feature | Implementation |
|
||||||
|
|---------|----------------|
|
||||||
|
| Fullscreen | Support landscape |
|
||||||
|
| Controls | Play, pause, seek, volume |
|
||||||
|
| Captions | Support closed captions |
|
||||||
|
| Resume | Remember playback position |
|
||||||
|
|
||||||
|
## Notifications
|
||||||
|
|
||||||
|
### Channel Best Practices
|
||||||
|
|
||||||
|
| Practice | Reason |
|
||||||
|
|----------|--------|
|
||||||
|
| Multiple channels | User can control each type |
|
||||||
|
| Descriptive names | User understands purpose |
|
||||||
|
| Appropriate importance | Match user expectation |
|
||||||
|
| Don't share channels | Different content = different channel |
|
||||||
|
|
||||||
|
### Notification Priority
|
||||||
|
|
||||||
|
| Importance | Usage |
|
||||||
|
|------------|-------|
|
||||||
|
| HIGH | Time-sensitive (messages, calls) |
|
||||||
|
| DEFAULT | Normal notifications |
|
||||||
|
| LOW | Background info |
|
||||||
|
| MIN | Minimal interruption |
|
||||||
|
|
||||||
|
### Notification Content Rules
|
||||||
|
|
||||||
|
| Do | Don't |
|
||||||
|
|-----|-------|
|
||||||
|
| Relevant information | Cross-promotion |
|
||||||
|
| Clear, concise text | Advertising other products |
|
||||||
|
| Actionable content | Unnecessary interruptions |
|
||||||
|
| Set timeouts | Persistent non-ongoing notifications |
|
||||||
|
|
||||||
|
### Messaging Apps Requirements
|
||||||
|
|
||||||
|
| Feature | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| MessagingStyle | Use for conversation notifications |
|
||||||
|
| Direct reply | Support inline reply action |
|
||||||
|
| Conversation shortcuts | Enable direct share |
|
||||||
|
| Bubbles | Support floating conversations |
|
||||||
|
|
||||||
|
### Notification Grouping
|
||||||
|
|
||||||
|
Group related notifications together with a summary notification. Set appropriate group keys and summary flags.
|
||||||
|
|
||||||
|
## Sharing
|
||||||
|
|
||||||
|
### Android Sharesheet
|
||||||
|
|
||||||
|
Use the system sharesheet for sharing content. Create an ACTION_SEND intent with appropriate type and extras, then use createChooser().
|
||||||
|
|
||||||
|
### Direct Share
|
||||||
|
|
||||||
|
Provide conversation shortcuts for Direct Share ranking:
|
||||||
|
- Create ShortcutInfo for each conversation
|
||||||
|
- Set appropriate categories
|
||||||
|
- Push dynamic shortcuts
|
||||||
|
|
||||||
|
## Background Services
|
||||||
|
|
||||||
|
### Service Restrictions
|
||||||
|
|
||||||
|
| Rule | Implementation |
|
||||||
|
|------|----------------|
|
||||||
|
| Avoid long-running services | Use WorkManager |
|
||||||
|
| No background starts (API 26+) | Use foreground service or JobScheduler |
|
||||||
|
| Battery-efficient | Batch work, respect Doze |
|
||||||
|
|
||||||
|
### Poor Background Service Uses
|
||||||
|
|
||||||
|
| Don't Use For | Alternative |
|
||||||
|
|---------------|-------------|
|
||||||
|
| Maintaining network connection | FCM (push notifications) |
|
||||||
|
| Persistent Bluetooth | Companion device manager |
|
||||||
|
| Keeping GPS on | Geofencing, fused location |
|
||||||
|
| Polling server | FCM or WorkManager |
|
||||||
|
|
||||||
|
## State Management
|
||||||
|
|
||||||
|
### State Preservation Requirements
|
||||||
|
|
||||||
|
| Scenario | Required Behavior |
|
||||||
|
|----------|-------------------|
|
||||||
|
| App switcher return | Exact previous state |
|
||||||
|
| Device wake | Exact previous state |
|
||||||
|
| Process death | Restore critical state |
|
||||||
|
| Configuration change | Seamless transition |
|
||||||
|
|
||||||
|
### State Categories
|
||||||
|
|
||||||
|
| State Type | Storage |
|
||||||
|
|------------|---------|
|
||||||
|
| UI state (scroll, selection) | ViewModel + SavedState |
|
||||||
|
| User input (forms) | SavedState |
|
||||||
|
| Navigation | NavController state |
|
||||||
|
| Persistent data | Room database |
|
||||||
|
|
||||||
|
## Navigation
|
||||||
|
|
||||||
|
### Back Button/Gesture
|
||||||
|
|
||||||
|
| Requirement | Implementation |
|
||||||
|
|-------------|----------------|
|
||||||
|
| System back | Navigate to previous screen |
|
||||||
|
| Gesture navigation | Support back gesture |
|
||||||
|
| No custom back buttons | Use system navigation |
|
||||||
|
| Predictable | User knows what back does |
|
||||||
|
|
||||||
|
## Gestures
|
||||||
|
|
||||||
|
### Gesture Navigation Support
|
||||||
|
|
||||||
|
| Gesture | Default Action |
|
||||||
|
|---------|----------------|
|
||||||
|
| Swipe from left edge | Back |
|
||||||
|
| Swipe up from bottom | Home |
|
||||||
|
| Swipe up and hold | Recent apps |
|
||||||
|
|
||||||
|
### Custom Gestures
|
||||||
|
|
||||||
|
| Practice | Reason |
|
||||||
|
|----------|--------|
|
||||||
|
| Avoid edge swipes | Conflicts with navigation |
|
||||||
|
| Provide alternatives | Not all users gesture-capable |
|
||||||
|
| Test with gesture nav | Ensure no conflicts |
|
||||||
|
|
||||||
|
Handle system gesture insets to avoid conflicts with edge gestures.
|
||||||
|
|
||||||
|
## Functional Checklist
|
||||||
|
|
||||||
|
### Audio
|
||||||
|
- [ ] Playback starts within 1 second
|
||||||
|
- [ ] Audio focus requested and released
|
||||||
|
- [ ] Responds to focus changes (duck/pause)
|
||||||
|
- [ ] Background playback with notification
|
||||||
|
- [ ] MediaSession integration
|
||||||
|
|
||||||
|
### Video
|
||||||
|
- [ ] Picture-in-picture supported
|
||||||
|
- [ ] HEVC encoding used
|
||||||
|
- [ ] Playback position remembered
|
||||||
|
- [ ] Captions supported
|
||||||
|
|
||||||
|
### Notifications
|
||||||
|
- [ ] Appropriate channels defined
|
||||||
|
- [ ] Correct importance levels
|
||||||
|
- [ ] No promotional content
|
||||||
|
- [ ] Grouped when appropriate
|
||||||
|
- [ ] Timeouts set where applicable
|
||||||
|
|
||||||
|
### Messaging (if applicable)
|
||||||
|
- [ ] MessagingStyle used
|
||||||
|
- [ ] Direct reply supported
|
||||||
|
- [ ] Conversation shortcuts
|
||||||
|
- [ ] Bubbles supported
|
||||||
|
|
||||||
|
### Background
|
||||||
|
- [ ] WorkManager for background work
|
||||||
|
- [ ] No long-running services
|
||||||
|
- [ ] Battery-efficient design
|
||||||
|
|
||||||
|
### Navigation
|
||||||
|
- [ ] Standard back behavior
|
||||||
|
- [ ] Gesture navigation supported
|
||||||
|
- [ ] State preserved across lifecycle
|
||||||
@@ -0,0 +1,203 @@
|
|||||||
|
# Motion System Guidelines
|
||||||
|
|
||||||
|
Animation and transition specifications for Material Design 3.
|
||||||
|
|
||||||
|
## Motion Principles
|
||||||
|
|
||||||
|
### Four Core Characteristics
|
||||||
|
|
||||||
|
| Principle | Description |
|
||||||
|
|-----------|-------------|
|
||||||
|
| **Responsive** | Quickly responds to user input at the point of interaction |
|
||||||
|
| **Natural** | Follows real-world physics (gravity, friction, momentum) |
|
||||||
|
| **Aware** | Elements are aware of surroundings and other elements |
|
||||||
|
| **Intentional** | Guides focus to the right place at the right time |
|
||||||
|
|
||||||
|
## Duration Guidelines
|
||||||
|
|
||||||
|
### By Interaction Type
|
||||||
|
|
||||||
|
| Type | Duration | Usage |
|
||||||
|
|------|----------|-------|
|
||||||
|
| Micro | 50-100ms | Ripples, state changes, hover |
|
||||||
|
| Short | 100-200ms | Simple transitions, toggles |
|
||||||
|
| Medium | 200-300ms | Expanding, collapsing, revealing |
|
||||||
|
| Long | 300-500ms | Complex choreography, page transitions |
|
||||||
|
|
||||||
|
### By Device Type
|
||||||
|
|
||||||
|
| Device | Typical Duration | Adjustment |
|
||||||
|
|--------|------------------|------------|
|
||||||
|
| Mobile | 300ms | Baseline |
|
||||||
|
| Tablet | 390ms | +30% slower |
|
||||||
|
| Desktop | 150-200ms | Faster, more responsive |
|
||||||
|
| Wearable | 210ms | -30% faster |
|
||||||
|
|
||||||
|
### Duration Rules
|
||||||
|
|
||||||
|
- **Maximum**: Keep under 400ms for most transitions
|
||||||
|
- **User-initiated**: Faster (closer to instant feedback)
|
||||||
|
- **System-initiated**: Can be slightly longer
|
||||||
|
- **Loading states**: Use indeterminate indicators for unknown duration
|
||||||
|
|
||||||
|
## Easing Curves
|
||||||
|
|
||||||
|
### Standard Curves
|
||||||
|
|
||||||
|
| Curve | Usage | Characteristics |
|
||||||
|
|-------|-------|-----------------|
|
||||||
|
| **Standard** | Most common transitions | Quick acceleration, slow deceleration |
|
||||||
|
| **Emphasized** | Important/significant transitions | More dramatic curve |
|
||||||
|
| **Decelerate** | Elements entering screen | Starts fast, ends slow |
|
||||||
|
| **Accelerate** | Elements leaving screen permanently | Starts slow, ends fast |
|
||||||
|
| **Sharp** | Elements temporarily leaving | Quick, snappy motion |
|
||||||
|
|
||||||
|
### Curve Values (Cubic Bezier)
|
||||||
|
|
||||||
|
| Curve | Value |
|
||||||
|
|-------|-------|
|
||||||
|
| Standard | cubic-bezier(0.2, 0.0, 0.0, 1.0) |
|
||||||
|
| Emphasized | cubic-bezier(0.2, 0.0, 0.0, 1.0) |
|
||||||
|
| Decelerate | cubic-bezier(0.0, 0.0, 0.0, 1.0) |
|
||||||
|
| Accelerate | cubic-bezier(0.3, 0.0, 1.0, 1.0) |
|
||||||
|
|
||||||
|
## Movement Patterns
|
||||||
|
|
||||||
|
### Arc Motion
|
||||||
|
|
||||||
|
- Use natural, concave arcs for diagonal movement
|
||||||
|
- Single-axis movement (horizontal/vertical only) stays straight
|
||||||
|
- Elements entering/exiting screen move on single axis
|
||||||
|
|
||||||
|
### Choreography
|
||||||
|
|
||||||
|
- **Stagger**: Offset timing for related elements (20-40ms between)
|
||||||
|
- **Cascade**: Sequential reveal from a focal point
|
||||||
|
- **Shared motion**: Elements that move together maintain relationship
|
||||||
|
|
||||||
|
## Transition Patterns
|
||||||
|
|
||||||
|
### Container Transform
|
||||||
|
|
||||||
|
Best for: Navigation from card/list item to detail screen
|
||||||
|
|
||||||
|
- Origin container morphs into destination
|
||||||
|
- Maintains visual continuity
|
||||||
|
- Content fades during transformation
|
||||||
|
|
||||||
|
### Shared Axis
|
||||||
|
|
||||||
|
Best for: Same-level navigation (tabs, stepper)
|
||||||
|
|
||||||
|
| Axis | Direction | Usage |
|
||||||
|
|------|-----------|-------|
|
||||||
|
| X-axis | Horizontal | Tabs, horizontal paging |
|
||||||
|
| Y-axis | Vertical | Vertical lists, feeds |
|
||||||
|
| Z-axis | Depth | Parent-child relationships |
|
||||||
|
|
||||||
|
### Fade Through
|
||||||
|
|
||||||
|
Best for: Unrelated screen transitions
|
||||||
|
|
||||||
|
- Outgoing content fades out
|
||||||
|
- Incoming content fades in
|
||||||
|
- Brief overlap period
|
||||||
|
- No shared elements
|
||||||
|
|
||||||
|
### Fade
|
||||||
|
|
||||||
|
Best for: Show/hide single elements
|
||||||
|
|
||||||
|
- Simple opacity change
|
||||||
|
- Optionally combine with scale
|
||||||
|
- Quick duration (100-200ms)
|
||||||
|
|
||||||
|
## Component-Specific Motion
|
||||||
|
|
||||||
|
### FAB
|
||||||
|
|
||||||
|
| State | Animation |
|
||||||
|
|-------|-----------|
|
||||||
|
| Appear | Scale up + fade in |
|
||||||
|
| Disappear | Scale down + fade out |
|
||||||
|
| Transform | Morph to extended FAB |
|
||||||
|
| Press | Elevation change (3dp → 8dp) |
|
||||||
|
|
||||||
|
### Bottom Sheet
|
||||||
|
|
||||||
|
| State | Animation |
|
||||||
|
|-------|-----------|
|
||||||
|
| Expand | Slide up with decelerate curve |
|
||||||
|
| Collapse | Slide down with accelerate curve |
|
||||||
|
| Dismiss | Swipe down with velocity-based duration |
|
||||||
|
|
||||||
|
### Navigation
|
||||||
|
|
||||||
|
| Pattern | Animation |
|
||||||
|
|---------|-----------|
|
||||||
|
| Push | Incoming slides from right, outgoing shifts left |
|
||||||
|
| Pop | Incoming slides from left, outgoing shifts right |
|
||||||
|
| Modal | Slide up from bottom |
|
||||||
|
|
||||||
|
### Cards
|
||||||
|
|
||||||
|
| State | Animation |
|
||||||
|
|-------|-----------|
|
||||||
|
| Expand | Container transform to detail |
|
||||||
|
| Press | Subtle elevation increase |
|
||||||
|
| Reorder | Follow finger with physics |
|
||||||
|
|
||||||
|
## Loading & Progress
|
||||||
|
|
||||||
|
### Indeterminate Indicators
|
||||||
|
|
||||||
|
- Use for unknown duration
|
||||||
|
- Continuous, looping animation
|
||||||
|
- M3 Expressive: Customizable waveform and thickness
|
||||||
|
|
||||||
|
### Determinate Indicators
|
||||||
|
|
||||||
|
- Use when progress is measurable
|
||||||
|
- Smooth, linear progression
|
||||||
|
- Update frequently for responsiveness
|
||||||
|
|
||||||
|
### Skeleton Screens
|
||||||
|
|
||||||
|
- Show layout structure immediately
|
||||||
|
- Subtle shimmer animation
|
||||||
|
- Replace with content as it loads
|
||||||
|
|
||||||
|
## Accessibility Considerations
|
||||||
|
|
||||||
|
### Reduced Motion
|
||||||
|
|
||||||
|
- Respect prefers-reduced-motion setting
|
||||||
|
- Provide alternatives:
|
||||||
|
- Instant transitions (no animation)
|
||||||
|
- Simple fade instead of complex motion
|
||||||
|
- Static loading indicators
|
||||||
|
|
||||||
|
### Motion Duration
|
||||||
|
|
||||||
|
- Keep essential feedback < 100ms
|
||||||
|
- Avoid motion that could trigger vestibular issues
|
||||||
|
- Test with motion sensitivity settings enabled
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
### Android Animation APIs
|
||||||
|
|
||||||
|
| API | Usage |
|
||||||
|
|-----|-------|
|
||||||
|
| MotionLayout | Complex, coordinated animations |
|
||||||
|
| Transition | Activity/Fragment transitions |
|
||||||
|
| Animator | Property animations |
|
||||||
|
| AnimatedContent | Compose content transitions |
|
||||||
|
| animateContentSize | Compose size changes |
|
||||||
|
|
||||||
|
### Performance Tips
|
||||||
|
|
||||||
|
- Use hardware layers for complex animations
|
||||||
|
- Avoid animating layout properties (use transform)
|
||||||
|
- Profile with GPU rendering tools
|
||||||
|
- Target 60 FPS (16ms per frame)
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
# Performance & Stability Guidelines
|
||||||
|
|
||||||
|
Android Vitals thresholds, performance requirements, and stability best practices.
|
||||||
|
|
||||||
|
## Android Vitals Thresholds
|
||||||
|
|
||||||
|
### Core Metrics (Google Play)
|
||||||
|
|
||||||
|
Exceeding these thresholds affects app visibility on Google Play:
|
||||||
|
|
||||||
|
| Metric | Overall Threshold | Per Phone Model | Per Watch Model |
|
||||||
|
|--------|-------------------|-----------------|-----------------|
|
||||||
|
| User-perceived crash rate | **1.09%** | 8% | 4% |
|
||||||
|
| User-perceived ANR rate | **0.47%** | 8% | 5% |
|
||||||
|
| Excessive battery usage | 1% | - | 1% |
|
||||||
|
| Excessive wake locks | 5% | - | - |
|
||||||
|
|
||||||
|
### Consequences of Exceeding Thresholds
|
||||||
|
|
||||||
|
- Reduced app visibility in Google Play
|
||||||
|
- Warning label on store listing
|
||||||
|
- Lower ranking in search results
|
||||||
|
- Negative impact on user trust
|
||||||
|
|
||||||
|
## Startup Performance
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
| Metric | Target | Maximum |
|
||||||
|
|--------|--------|---------|
|
||||||
|
| Cold start | < 1 second | 2 seconds |
|
||||||
|
| Warm start | < 500ms | 1 second |
|
||||||
|
| Hot start | < 100ms | 500ms |
|
||||||
|
|
||||||
|
### If Startup Exceeds 2 Seconds
|
||||||
|
|
||||||
|
Must provide visual feedback:
|
||||||
|
- Progress indicator
|
||||||
|
- Splash screen with animation
|
||||||
|
- Loading skeleton
|
||||||
|
|
||||||
|
### Optimization Techniques
|
||||||
|
|
||||||
|
| Technique | Impact |
|
||||||
|
|-----------|--------|
|
||||||
|
| Lazy initialization | Defer non-critical work |
|
||||||
|
| Async loading | Move I/O off main thread |
|
||||||
|
| View hierarchy optimization | Reduce layout depth |
|
||||||
|
| App Startup library | Initialize components efficiently |
|
||||||
|
| Baseline Profiles | Pre-compile hot paths |
|
||||||
|
|
||||||
|
## Rendering Performance
|
||||||
|
|
||||||
|
### Frame Rate Requirements
|
||||||
|
|
||||||
|
| Target | Frame Time | Notes |
|
||||||
|
|--------|------------|-------|
|
||||||
|
| 60 FPS | ≤ 16.67ms | Standard requirement |
|
||||||
|
| 90 FPS | ≤ 11.11ms | High refresh rate displays |
|
||||||
|
| 120 FPS | ≤ 8.33ms | Premium devices |
|
||||||
|
|
||||||
|
### Jank Detection
|
||||||
|
|
||||||
|
| Metric | Threshold | Severity |
|
||||||
|
|--------|-----------|----------|
|
||||||
|
| Slow frames | > 16ms | Warning |
|
||||||
|
| Frozen frames | > 700ms | Critical |
|
||||||
|
| Jank rate | > 1% of frames | Poor experience |
|
||||||
|
|
||||||
|
### Common Rendering Issues
|
||||||
|
|
||||||
|
| Issue | Cause | Solution |
|
||||||
|
|-------|-------|----------|
|
||||||
|
| Overdraw | Multiple layers drawn | Reduce background stacking |
|
||||||
|
| Deep hierarchy | Complex view nesting | Use ConstraintLayout, Compose |
|
||||||
|
| Main thread work | Blocking operations | Move to background thread |
|
||||||
|
| Large bitmaps | Unoptimized images | Downsample, use vector |
|
||||||
|
|
||||||
|
## ANR Prevention
|
||||||
|
|
||||||
|
### ANR Triggers
|
||||||
|
|
||||||
|
| Scenario | Timeout |
|
||||||
|
|----------|---------|
|
||||||
|
| Input dispatch | 5 seconds |
|
||||||
|
| Broadcast receiver | 10 seconds |
|
||||||
|
| Service start | 20 seconds |
|
||||||
|
|
||||||
|
### Prevention Strategies
|
||||||
|
|
||||||
|
- Never perform network calls on main thread
|
||||||
|
- Never perform database operations on main thread
|
||||||
|
- Never perform file I/O on main thread
|
||||||
|
- Use coroutines, RxJava, or other async mechanisms
|
||||||
|
- Reduce synchronized block contention
|
||||||
|
|
||||||
|
### Common ANR Causes
|
||||||
|
|
||||||
|
| Cause | Solution |
|
||||||
|
|-------|----------|
|
||||||
|
| Network on main thread | Use coroutines/RxJava |
|
||||||
|
| Database on main thread | Use Room with suspend |
|
||||||
|
| File I/O on main thread | Use Dispatchers.IO |
|
||||||
|
| Lock contention | Reduce synchronized blocks |
|
||||||
|
| Dead locks | Careful threading design |
|
||||||
|
|
||||||
|
## Battery Optimization
|
||||||
|
|
||||||
|
### Wake Lock Guidelines
|
||||||
|
|
||||||
|
| Rule | Implementation |
|
||||||
|
|------|----------------|
|
||||||
|
| Minimize duration | Release as soon as possible |
|
||||||
|
| Use appropriate type | PARTIAL_WAKE_LOCK only when needed |
|
||||||
|
| Always release | Use try-finally or lifecycle |
|
||||||
|
| Prefer WorkManager | System-managed scheduling |
|
||||||
|
|
||||||
|
### Background Restrictions
|
||||||
|
|
||||||
|
| Feature | Best Practice |
|
||||||
|
|---------|---------------|
|
||||||
|
| Background services | Use WorkManager instead |
|
||||||
|
| Location | Request only when necessary |
|
||||||
|
| Network | Batch requests, respect connectivity |
|
||||||
|
| Alarms | Use inexact alarms when possible |
|
||||||
|
|
||||||
|
### Doze and App Standby
|
||||||
|
|
||||||
|
| Mode | Behavior | Adaptation |
|
||||||
|
|------|----------|------------|
|
||||||
|
| Doze | Limited network, alarms delayed | Use FCM for high-priority |
|
||||||
|
| App Standby | Background work restricted | Use expedited WorkManager |
|
||||||
|
| Buckets | Frequency limits by usage | Design for infrequent execution |
|
||||||
|
|
||||||
|
## Memory Management
|
||||||
|
|
||||||
|
### Memory Best Practices
|
||||||
|
|
||||||
|
| Practice | Benefit |
|
||||||
|
|----------|---------|
|
||||||
|
| Avoid memory leaks | Prevent OutOfMemoryError |
|
||||||
|
| Use weak references | Allow garbage collection |
|
||||||
|
| Recycle bitmaps | Reduce memory pressure |
|
||||||
|
| Monitor heap | Profile regularly |
|
||||||
|
|
||||||
|
### Common Memory Issues
|
||||||
|
|
||||||
|
| Issue | Detection | Solution |
|
||||||
|
|-------|-----------|----------|
|
||||||
|
| Activity leak | LeakCanary | Fix lifecycle references |
|
||||||
|
| Bitmap leak | Memory profiler | Recycle, use Glide/Coil |
|
||||||
|
| Context leak | Static analysis | Use application context |
|
||||||
|
| Handler leak | Lint warning | Use WeakReference |
|
||||||
|
|
||||||
|
## StrictMode
|
||||||
|
|
||||||
|
### What StrictMode Detects
|
||||||
|
|
||||||
|
| Category | Issues |
|
||||||
|
|----------|--------|
|
||||||
|
| Thread | Disk reads/writes, network, slow calls |
|
||||||
|
| VM | Leaked objects, unsafe intents, content URI exposure |
|
||||||
|
|
||||||
|
Enable StrictMode in debug builds to detect violations during development.
|
||||||
|
|
||||||
|
## SDK Requirements
|
||||||
|
|
||||||
|
### Version Requirements
|
||||||
|
|
||||||
|
| Property | Requirement |
|
||||||
|
|----------|-------------|
|
||||||
|
| targetSdk | Latest Android SDK (Google Play requirement) |
|
||||||
|
| compileSdk | Latest Android SDK |
|
||||||
|
| minSdk | Based on target audience |
|
||||||
|
|
||||||
|
### Third-Party SDK Management
|
||||||
|
|
||||||
|
| Practice | Reason |
|
||||||
|
|----------|--------|
|
||||||
|
| Keep updated | Security fixes, compatibility |
|
||||||
|
| Audit regularly | Remove unused dependencies |
|
||||||
|
| Monitor crashes | SDKs can cause issues |
|
||||||
|
| Check permissions | SDKs may request excessive permissions |
|
||||||
|
|
||||||
|
### Non-SDK Interface Restrictions
|
||||||
|
|
||||||
|
- Don't use reflection for hidden APIs
|
||||||
|
- Use Android Studio lint to detect
|
||||||
|
- APIs may break in future versions
|
||||||
|
|
||||||
|
## Monitoring and Profiling
|
||||||
|
|
||||||
|
### Tools
|
||||||
|
|
||||||
|
| Tool | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| Android Studio Profiler | CPU, memory, network, energy |
|
||||||
|
| Android Vitals (Play Console) | Production crash/ANR data |
|
||||||
|
| Firebase Performance | Real-time performance monitoring |
|
||||||
|
| Perfetto | Advanced system tracing |
|
||||||
|
| Benchmark library | Reproducible measurements |
|
||||||
|
|
||||||
|
### Key Metrics to Track
|
||||||
|
|
||||||
|
| Metric | Tool |
|
||||||
|
|--------|------|
|
||||||
|
| Startup time | Macrobenchmark |
|
||||||
|
| Frame timing | JankStats |
|
||||||
|
| Memory usage | Memory Profiler |
|
||||||
|
| Network latency | Network Profiler |
|
||||||
|
| Battery drain | Energy Profiler |
|
||||||
|
|
||||||
|
## Performance Checklist
|
||||||
|
|
||||||
|
- [ ] Cold startup < 2 seconds
|
||||||
|
- [ ] Rendering at 60 FPS
|
||||||
|
- [ ] No StrictMode violations
|
||||||
|
- [ ] Crash rate < 1.09%
|
||||||
|
- [ ] ANR rate < 0.47%
|
||||||
|
- [ ] No memory leaks
|
||||||
|
- [ ] Background work uses WorkManager
|
||||||
|
- [ ] Wake locks properly released
|
||||||
|
- [ ] SDKs up to date
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
# Privacy & Security Guidelines
|
||||||
|
|
||||||
|
Security best practices and privacy requirements for Android applications.
|
||||||
|
|
||||||
|
## Permissions
|
||||||
|
|
||||||
|
### Principle of Least Privilege
|
||||||
|
|
||||||
|
| Rule | Implementation |
|
||||||
|
|------|----------------|
|
||||||
|
| Request minimum | Only permissions essential for core features |
|
||||||
|
| Request when needed | At point of use, not app startup |
|
||||||
|
| Explain why | Show rationale before system dialog |
|
||||||
|
| Degrade gracefully | App works (limited) if denied |
|
||||||
|
|
||||||
|
### Permission Request Flow
|
||||||
|
|
||||||
|
1. Check if already granted
|
||||||
|
2. If not, show educational UI (rationale)
|
||||||
|
3. Request permission
|
||||||
|
4. Handle result (grant or denial)
|
||||||
|
5. If denied, offer alternative or reduced functionality
|
||||||
|
|
||||||
|
### Sensitive Permissions
|
||||||
|
|
||||||
|
| Permission | Consideration |
|
||||||
|
|------------|---------------|
|
||||||
|
| Location | Use coarse if fine not needed |
|
||||||
|
| Camera | Request only when capturing |
|
||||||
|
| Microphone | Request only when recording |
|
||||||
|
| Contacts | Consider contact picker intent |
|
||||||
|
| Storage | Use scoped storage |
|
||||||
|
| SMS/Call Log | Restricted, needs approval |
|
||||||
|
|
||||||
|
### Alternative Approaches
|
||||||
|
|
||||||
|
| Instead of... | Consider... |
|
||||||
|
|---------------|-------------|
|
||||||
|
| READ_CONTACTS | Contact picker intent |
|
||||||
|
| ACCESS_FINE_LOCATION | Coarse location |
|
||||||
|
| READ_EXTERNAL_STORAGE | Storage Access Framework |
|
||||||
|
| CAMERA | Camera intent |
|
||||||
|
|
||||||
|
## Data Storage
|
||||||
|
|
||||||
|
### Storage Types
|
||||||
|
|
||||||
|
| Type | Security | Usage |
|
||||||
|
|------|----------|-------|
|
||||||
|
| Internal storage | Private to app | Sensitive data |
|
||||||
|
| External storage | World-readable | Shared files only |
|
||||||
|
| SharedPreferences | Private, unencrypted | Non-sensitive settings |
|
||||||
|
| EncryptedSharedPreferences | Private, encrypted | Sensitive settings |
|
||||||
|
| Room database | Private, optional encryption | Structured data |
|
||||||
|
|
||||||
|
### Sensitive Data Rules
|
||||||
|
|
||||||
|
| Rule | Implementation |
|
||||||
|
|------|----------------|
|
||||||
|
| Store internally | Use internal storage, not external |
|
||||||
|
| Encrypt at rest | Use EncryptedSharedPreferences, SQLCipher |
|
||||||
|
| Don't log | Never log PII or credentials |
|
||||||
|
| Clear on logout | Wipe user data completely |
|
||||||
|
|
||||||
|
### Data Logging
|
||||||
|
|
||||||
|
Never log sensitive data such as passwords, emails, tokens, or personal information. Only log non-sensitive operational information.
|
||||||
|
|
||||||
|
## Network Security
|
||||||
|
|
||||||
|
### HTTPS Requirements
|
||||||
|
|
||||||
|
- All network traffic must use SSL/TLS
|
||||||
|
- Configure Network Security Config
|
||||||
|
- Don't allow cleartext traffic
|
||||||
|
|
||||||
|
### Network Security Config
|
||||||
|
|
||||||
|
Define a network security configuration that:
|
||||||
|
- Disables cleartext traffic
|
||||||
|
- Specifies trusted certificate authorities
|
||||||
|
- Optionally implements certificate pinning for high-security apps
|
||||||
|
|
||||||
|
### Certificate Pinning (Optional)
|
||||||
|
|
||||||
|
For high-security apps, pin certificates to prevent MITM attacks. Include backup pins and plan for certificate rotation.
|
||||||
|
|
||||||
|
## User Identity
|
||||||
|
|
||||||
|
### Credential Manager
|
||||||
|
|
||||||
|
Integrate Credential Manager for unified sign-in supporting:
|
||||||
|
- Passkeys
|
||||||
|
- Federated identity
|
||||||
|
- Traditional passwords
|
||||||
|
|
||||||
|
### Biometric Authentication
|
||||||
|
|
||||||
|
Use biometric authentication for sensitive operations like:
|
||||||
|
- Financial transactions
|
||||||
|
- Accessing sensitive documents
|
||||||
|
- Confirming identity
|
||||||
|
|
||||||
|
### Autofill Support
|
||||||
|
|
||||||
|
Provide autofill hints on input fields:
|
||||||
|
- emailAddress, username for identity fields
|
||||||
|
- password for credential fields
|
||||||
|
- creditCardNumber, postalCode for payment fields
|
||||||
|
|
||||||
|
## App Components Security
|
||||||
|
|
||||||
|
### Exported Components
|
||||||
|
|
||||||
|
| Component | Exported Rule |
|
||||||
|
|-----------|---------------|
|
||||||
|
| Launcher Activity | exported="true" with intent-filter |
|
||||||
|
| Internal Activity | exported="false" |
|
||||||
|
| Internal Service | exported="false" |
|
||||||
|
| Content Provider (shared) | exported="true" with permissions |
|
||||||
|
|
||||||
|
Always explicitly set the exported attribute on all components.
|
||||||
|
|
||||||
|
### Custom Permissions
|
||||||
|
|
||||||
|
Use signature-level protection for custom permissions that control access between your own apps.
|
||||||
|
|
||||||
|
### Intent Validation
|
||||||
|
|
||||||
|
- Validate all intent data before use
|
||||||
|
- Check URI scheme and host
|
||||||
|
- Use explicit intents when possible
|
||||||
|
- Don't trust extras from unknown sources
|
||||||
|
|
||||||
|
### PendingIntent Security
|
||||||
|
|
||||||
|
Use FLAG_IMMUTABLE for PendingIntents unless mutability is required. This prevents other apps from modifying the intent.
|
||||||
|
|
||||||
|
## WebView Security
|
||||||
|
|
||||||
|
### Safe WebView Configuration
|
||||||
|
|
||||||
|
| Setting | Recommendation |
|
||||||
|
|---------|----------------|
|
||||||
|
| JavaScript | Disabled unless required |
|
||||||
|
| File access | Disabled |
|
||||||
|
| Content access | Disabled |
|
||||||
|
| Universal file access | Never enable |
|
||||||
|
|
||||||
|
### Avoid Dangerous Practices
|
||||||
|
|
||||||
|
| Don't | Why |
|
||||||
|
|-------|-----|
|
||||||
|
| setAllowUniversalAccessFromFileURLs(true) | Security vulnerability |
|
||||||
|
| addJavascriptInterface() with untrusted content | Code injection risk |
|
||||||
|
| Load untrusted URLs | XSS, phishing |
|
||||||
|
|
||||||
|
## Cryptography
|
||||||
|
|
||||||
|
### Use Platform APIs
|
||||||
|
|
||||||
|
- Use Android Keystore for key storage
|
||||||
|
- Use standard algorithms (AES-GCM, RSA)
|
||||||
|
- Never implement custom cryptography
|
||||||
|
- Use SecureRandom for random generation
|
||||||
|
|
||||||
|
### Avoid
|
||||||
|
|
||||||
|
- Custom encryption implementations
|
||||||
|
- Weak algorithms (MD5, SHA1 for security)
|
||||||
|
- Hardcoded keys or secrets
|
||||||
|
- Non-cryptographic random generators
|
||||||
|
|
||||||
|
## Code Security
|
||||||
|
|
||||||
|
### No Dynamic Code Loading
|
||||||
|
|
||||||
|
| Don't | Do Instead |
|
||||||
|
|-------|------------|
|
||||||
|
| Load code at runtime | Android App Bundles |
|
||||||
|
| Download DEX files | Play Feature Delivery |
|
||||||
|
| Execute scripts | Predefined functionality |
|
||||||
|
|
||||||
|
### Debug Code Removal
|
||||||
|
|
||||||
|
- Set debuggable=false in release builds
|
||||||
|
- Enable minification (R8/ProGuard)
|
||||||
|
- Remove debug libraries from production
|
||||||
|
|
||||||
|
## Device Identifiers
|
||||||
|
|
||||||
|
### Don't Use Hardware IDs
|
||||||
|
|
||||||
|
| Identifier | Status |
|
||||||
|
|------------|--------|
|
||||||
|
| IMEI | Don't use |
|
||||||
|
| MAC address | Don't use |
|
||||||
|
| Serial number | Don't use |
|
||||||
|
| Android ID | Limited use only |
|
||||||
|
|
||||||
|
### Recommended Alternatives
|
||||||
|
|
||||||
|
| Use Case | Solution |
|
||||||
|
|----------|----------|
|
||||||
|
| Analytics | Firebase Analytics ID |
|
||||||
|
| Advertising | Advertising ID (resettable) |
|
||||||
|
| App instance | Generate UUID on install |
|
||||||
|
| User identity | Account-based ID |
|
||||||
|
|
||||||
|
## Google Play Policies
|
||||||
|
|
||||||
|
### Data Safety
|
||||||
|
|
||||||
|
- Declare all data collected
|
||||||
|
- Explain data usage
|
||||||
|
- Provide privacy policy
|
||||||
|
- Allow data deletion requests
|
||||||
|
|
||||||
|
### User Data Policy
|
||||||
|
|
||||||
|
| Rule | Requirement |
|
||||||
|
|------|-------------|
|
||||||
|
| Transparency | Clear disclosure of data use |
|
||||||
|
| Security | Protect user data appropriately |
|
||||||
|
| Minimization | Collect only what's needed |
|
||||||
|
| Control | Allow users to manage data |
|
||||||
|
|
||||||
|
## Security Checklist
|
||||||
|
|
||||||
|
- [ ] Permissions requested only when needed
|
||||||
|
- [ ] Permissions explained to user
|
||||||
|
- [ ] Sensitive data stored internally
|
||||||
|
- [ ] No sensitive data in logs
|
||||||
|
- [ ] All network traffic over HTTPS
|
||||||
|
- [ ] Network security config defined
|
||||||
|
- [ ] Components export status explicit
|
||||||
|
- [ ] Custom permissions use signature protection
|
||||||
|
- [ ] Intents validated before use
|
||||||
|
- [ ] PendingIntents use FLAG_IMMUTABLE
|
||||||
|
- [ ] WebView configured securely
|
||||||
|
- [ ] Platform crypto APIs used
|
||||||
|
- [ ] No debug code in production
|
||||||
|
- [ ] No hardware IDs used
|
||||||
|
- [ ] Privacy policy available
|
||||||
@@ -0,0 +1,554 @@
|
|||||||
|
# Testing
|
||||||
|
|
||||||
|
Detailed examples and patterns for each Android test layer. Read the section relevant to the layer you're working with.
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
1. [Local Unit Tests (JUnit + Robolectric)](#1-local-unit-tests-junit--robolectric)
|
||||||
|
2. [Instrumentation Tests (Espresso)](#2-instrumentation-tests-espresso)
|
||||||
|
3. [UI Automator (Cross-App & System UI)](#3-ui-automator-cross-app--system-ui)
|
||||||
|
4. [Compose UI Testing](#4-compose-ui-testing)
|
||||||
|
5. [Gradle Managed Devices](#5-gradle-managed-devices)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Local Unit Tests (JUnit + Robolectric)
|
||||||
|
|
||||||
|
Local tests live in `src/test/` and run on the JVM — no emulator needed, so they're fast (milliseconds each). Use them for ViewModels, Repositories, mappers, validators, and any pure logic.
|
||||||
|
|
||||||
|
### Basic ViewModel Test
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
class CounterViewModelTest {
|
||||||
|
@get:Rule
|
||||||
|
val mainDispatcherRule = MainDispatcherRule() // see below
|
||||||
|
|
||||||
|
private lateinit var viewModel: CounterViewModel
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
viewModel = CounterViewModel()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `increment updates count`() = runTest {
|
||||||
|
viewModel.increment()
|
||||||
|
assertEquals(1, viewModel.uiState.value.count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing Coroutines (Critical)
|
||||||
|
|
||||||
|
The Main dispatcher doesn't exist on the JVM. Replace it with `TestDispatcher` or tests crash with `IllegalStateException`.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// Reusable rule — put in a shared test-util module
|
||||||
|
class MainDispatcherRule(
|
||||||
|
private val dispatcher: TestDispatcher = UnconfinedTestDispatcher()
|
||||||
|
) : TestWatcher() {
|
||||||
|
override fun starting(description: Description) {
|
||||||
|
Dispatchers.setMain(dispatcher)
|
||||||
|
}
|
||||||
|
override fun finished(description: Description) {
|
||||||
|
Dispatchers.resetMain()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// ❌ Wrong: No Main dispatcher replacement → crash
|
||||||
|
@Test
|
||||||
|
fun `load data`() = runTest {
|
||||||
|
val vm = MyViewModel(repo)
|
||||||
|
vm.load() // launches on Dispatchers.Main → IllegalStateException
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Correct: Use MainDispatcherRule
|
||||||
|
@get:Rule
|
||||||
|
val mainDispatcherRule = MainDispatcherRule()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `load data`() = runTest {
|
||||||
|
val vm = MyViewModel(repo)
|
||||||
|
vm.load()
|
||||||
|
assertEquals(UiState.Success, vm.uiState.value)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing StateFlow with Turbine
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@Test
|
||||||
|
fun `loading then success states`() = runTest {
|
||||||
|
val vm = MyViewModel(fakeRepo)
|
||||||
|
|
||||||
|
vm.uiState.test { // Turbine extension
|
||||||
|
assertEquals(UiState.Idle, awaitItem())
|
||||||
|
vm.load()
|
||||||
|
assertEquals(UiState.Loading, awaitItem())
|
||||||
|
assertEquals(UiState.Success(data), awaitItem())
|
||||||
|
cancelAndIgnoreRemainingEvents()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mocking with MockK
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@Test
|
||||||
|
fun `repository calls api and caches`() = runTest {
|
||||||
|
val api = mockk<UserApi>()
|
||||||
|
coEvery { api.getUser("42") } returns User("42", "Alice")
|
||||||
|
|
||||||
|
val repo = UserRepository(api)
|
||||||
|
val user = repo.getUser("42")
|
||||||
|
|
||||||
|
assertEquals("Alice", user.name)
|
||||||
|
coVerify(exactly = 1) { api.getUser("42") }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| MockK Function | Purpose |
|
||||||
|
|----------------|------------------------|
|
||||||
|
| `mockk<T>()` | Create mock instance |
|
||||||
|
| `every { }` | Stub synchronous calls |
|
||||||
|
| `coEvery { }` | Stub suspend functions |
|
||||||
|
| `verify { }` | Verify call happened |
|
||||||
|
| `coVerify { }` | Verify suspend call |
|
||||||
|
| `slot<T>()` | Capture argument value |
|
||||||
|
|
||||||
|
### Robolectric — When You Need Android Classes
|
||||||
|
|
||||||
|
Robolectric simulates the Android framework on the JVM, so tests stay fast while accessing `Context`, `SharedPreferences`, resources, etc.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@RunWith(RobolectricTestRunner::class)
|
||||||
|
@Config(sdk = [34])
|
||||||
|
class PreferencesManagerTest {
|
||||||
|
|
||||||
|
private lateinit var context: Context
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
context = ApplicationProvider.getApplicationContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `saves and reads theme preference`() {
|
||||||
|
val prefs = PreferencesManager(context)
|
||||||
|
prefs.setDarkMode(true)
|
||||||
|
assertTrue(prefs.isDarkMode())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Local Test Mistakes
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// ❌ Wrong: Testing implementation details (fragile)
|
||||||
|
@Test
|
||||||
|
fun `check internal cache map size`() {
|
||||||
|
repo.load()
|
||||||
|
assertEquals(1, repo.cacheMap.size) // breaks if cache strategy changes
|
||||||
|
}
|
||||||
|
|
||||||
|
// ✅ Correct: Test observable behavior
|
||||||
|
@Test
|
||||||
|
fun `second call returns cached result without network`() = runTest {
|
||||||
|
coEvery { api.fetch() } returns data
|
||||||
|
|
||||||
|
repo.load()
|
||||||
|
repo.load()
|
||||||
|
|
||||||
|
coVerify(exactly = 1) { api.fetch() } // only one network call
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Instrumentation Tests (Espresso)
|
||||||
|
|
||||||
|
Instrumentation tests live in `src/androidTest/` and run on a real device or emulator. Slower than local tests, but they exercise the actual Android stack — use them for UI flows, database integration, and cross-component interaction.
|
||||||
|
|
||||||
|
### Test Runner Setup
|
||||||
|
|
||||||
|
In `app/build.gradle.kts`:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
android {
|
||||||
|
defaultConfig {
|
||||||
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Espresso Basics
|
||||||
|
|
||||||
|
Espresso's API follows a consistent pattern: **find → act → assert**.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class LoginScreenTest {
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val activityRule = ActivityScenarioRule(LoginActivity::class.java)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun validLogin_navigatesToHome() {
|
||||||
|
// Find and act
|
||||||
|
onView(withId(R.id.email_input))
|
||||||
|
.perform(typeText("user@example.com"), closeSoftKeyboard())
|
||||||
|
onView(withId(R.id.password_input))
|
||||||
|
.perform(typeText("secret123"), closeSoftKeyboard())
|
||||||
|
onView(withId(R.id.login_button))
|
||||||
|
.perform(click())
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
onView(withId(R.id.home_container))
|
||||||
|
.check(matches(isDisplayed()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Category | Common Matchers / Actions |
|
||||||
|
|------------|------------------------------------------------------------------------------------|
|
||||||
|
| **Find** | `withId(R.id.x)`, `withText("x")`, `withContentDescription("x")`, `withHint("x")` |
|
||||||
|
| **Act** | `click()`, `typeText("x")`, `clearText()`, `scrollTo()`, `swipeUp()` |
|
||||||
|
| **Assert** | `isDisplayed()`, `withText("x")`, `isEnabled()`, `isChecked()`, `doesNotExist()` |
|
||||||
|
|
||||||
|
### Testing Intents
|
||||||
|
|
||||||
|
Espresso-Intents lets you verify outgoing Intents and stub responses (e.g., camera, file picker).
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@get:Rule
|
||||||
|
val intentsRule = IntentsRule()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun shareButton_launchesShareIntent() {
|
||||||
|
onView(withId(R.id.share_button)).perform(click())
|
||||||
|
|
||||||
|
intended(allOf(
|
||||||
|
hasAction(Intent.ACTION_SEND),
|
||||||
|
hasType("text/plain")
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun cameraButton_handlesResult() {
|
||||||
|
val resultData = Intent().apply { putExtra("photo_uri", "content://mock") }
|
||||||
|
intending(hasAction(MediaStore.ACTION_IMAGE_CAPTURE))
|
||||||
|
.respondWith(Instrumentation.ActivityResult(RESULT_OK, resultData))
|
||||||
|
|
||||||
|
onView(withId(R.id.camera_button)).perform(click())
|
||||||
|
onView(withId(R.id.photo_preview)).check(matches(isDisplayed()))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### IdlingResource for Async Operations
|
||||||
|
|
||||||
|
Espresso waits for the UI thread and AsyncTask by default, but not for custom async work (Retrofit, coroutines, etc.). `IdlingResource` tells Espresso when your app is busy.
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// In production code (thin wrapper)
|
||||||
|
object NetworkIdlingResource {
|
||||||
|
private val counter = CountingIdlingResource("Network")
|
||||||
|
fun increment() = counter.increment()
|
||||||
|
fun decrement() = counter.decrement()
|
||||||
|
fun get(): IdlingResource = counter
|
||||||
|
}
|
||||||
|
|
||||||
|
// In test setup
|
||||||
|
@Before
|
||||||
|
fun registerIdling() {
|
||||||
|
IdlingRegistry.getInstance().register(NetworkIdlingResource.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun unregisterIdling() {
|
||||||
|
IdlingRegistry.getInstance().unregister(NetworkIdlingResource.get())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. UI Automator (Cross-App & System UI)
|
||||||
|
|
||||||
|
UI Automator can interact with any visible UI — system dialogs, notifications, other apps. Use it when Espresso can't reach outside your app's process.
|
||||||
|
|
||||||
|
| Use Case | Why UI Automator |
|
||||||
|
|------------------------------|----------------------------------------|
|
||||||
|
| Runtime permission dialogs | System UI, outside app process |
|
||||||
|
| Notification actions | System notification shade |
|
||||||
|
| Device settings interaction | Settings app |
|
||||||
|
| Multi-app workflows | e.g., share to another app and return |
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class PermissionFlowTest {
|
||||||
|
|
||||||
|
private lateinit var device: UiDevice
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun grantsCameraPermission_andOpensCamera() {
|
||||||
|
// Trigger permission request from within your app
|
||||||
|
onView(withId(R.id.camera_button)).perform(click())
|
||||||
|
|
||||||
|
// Handle the system permission dialog via UI Automator
|
||||||
|
val allowButton = device.findObject(
|
||||||
|
By.res("com.android.permissioncontroller:id/permission_allow_foreground_only_button")
|
||||||
|
)
|
||||||
|
allowButton?.click()
|
||||||
|
|
||||||
|
// Back in Espresso territory — verify the camera view appeared
|
||||||
|
onView(withId(R.id.camera_preview)).check(matches(isDisplayed()))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun notificationTap_opensDetail() {
|
||||||
|
// Open notification shade
|
||||||
|
device.openNotification()
|
||||||
|
device.wait(Until.hasObject(By.textStartsWith("New message")), 5000)
|
||||||
|
|
||||||
|
// Tap the notification
|
||||||
|
val notification = device.findObject(By.textStartsWith("New message"))
|
||||||
|
notification.click()
|
||||||
|
|
||||||
|
// Verify deep-link target
|
||||||
|
onView(withId(R.id.message_detail)).check(matches(isDisplayed()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Compose UI Testing
|
||||||
|
|
||||||
|
Compose has its own testing framework that works with the semantic tree rather than the view hierarchy. Tests can run as local tests (with Robolectric) or instrumentation tests — the API is the same.
|
||||||
|
|
||||||
|
### Basic Setup
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class GreetingScreenTest {
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val composeTestRule = createComposeRule()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun displaysGreeting_andRespondsToClick() {
|
||||||
|
composeTestRule.setContent {
|
||||||
|
MyAppTheme {
|
||||||
|
GreetingScreen(name = "World")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithText("Hello, World!")
|
||||||
|
.assertIsDisplayed()
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithText("Say Hi")
|
||||||
|
.performClick()
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithText("Hi back!")
|
||||||
|
.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Finders, Assertions & Actions
|
||||||
|
|
||||||
|
| Category | API | Example |
|
||||||
|
|------------|----------------------------------------------|---------------------------------|
|
||||||
|
| **Find** | `onNodeWithText("x")` | Matches visible text |
|
||||||
|
| | `onNodeWithTag("x")` | Matches `Modifier.testTag("x")` |
|
||||||
|
| | `onNodeWithContentDescription("x")` | Matches semantics label |
|
||||||
|
| | `onAllNodesWithTag("x")` | Returns list of matches |
|
||||||
|
| **Assert** | `assertIsDisplayed()` | Node is visible |
|
||||||
|
| | `assertTextEquals("x")` | Exact text match |
|
||||||
|
| | `assertIsEnabled()` / `assertIsNotEnabled()` | Enabled state |
|
||||||
|
| | `assertDoesNotExist()` | Node not in tree |
|
||||||
|
| | `assertCountEquals(n)` | For `onAllNodes` |
|
||||||
|
| **Act** | `performClick()` | Tap |
|
||||||
|
| | `performTextInput("x")` | Type into text field |
|
||||||
|
| | `performScrollTo()` | Scroll node into view |
|
||||||
|
| | `performTouchInput { swipeUp() }` | Gestures |
|
||||||
|
|
||||||
|
### Using testTag for Reliable Selectors
|
||||||
|
|
||||||
|
Text-based finders break with localization or copy changes. Use `testTag` for stable selectors:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// ❌ Fragile: breaks if text changes or app is localized
|
||||||
|
composeTestRule.onNodeWithText("Submit Order").performClick()
|
||||||
|
|
||||||
|
// ✅ Stable: testTag doesn't change with locale
|
||||||
|
composeTestRule.onNodeWithTag("submit_order_button").performClick()
|
||||||
|
```
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// In production Composable
|
||||||
|
Button(
|
||||||
|
onClick = { /* ... */ },
|
||||||
|
modifier = Modifier.testTag("submit_order_button")
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.submit_order))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing with Activity Context
|
||||||
|
|
||||||
|
When your Composable needs a `ComponentActivity` (e.g., for `viewModel()` or navigation), use `createAndroidComposeRule`:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@get:Rule
|
||||||
|
val composeTestRule = createAndroidComposeRule<MainActivity>()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun fullScreen_endToEnd() {
|
||||||
|
// Activity is already launched — interact with the real content
|
||||||
|
composeTestRule.onNodeWithTag("login_email")
|
||||||
|
.performTextInput("user@test.com")
|
||||||
|
composeTestRule.onNodeWithTag("login_password")
|
||||||
|
.performTextInput("pass123")
|
||||||
|
composeTestRule.onNodeWithTag("login_submit")
|
||||||
|
.performClick()
|
||||||
|
|
||||||
|
composeTestRule.waitUntil(timeoutMillis = 5000) {
|
||||||
|
composeTestRule.onAllNodesWithTag("home_screen")
|
||||||
|
.fetchSemanticsNodes().isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
composeTestRule.onNodeWithTag("home_screen")
|
||||||
|
.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing Navigation
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
@Test
|
||||||
|
fun navigatesToDetail_onItemClick() {
|
||||||
|
val navController = TestNavHostController(ApplicationProvider.getApplicationContext())
|
||||||
|
|
||||||
|
composeTestRule.setContent {
|
||||||
|
navController.navigatorProvider.addNavigator(ComposeNavigator())
|
||||||
|
MyAppNavHost(navController = navController)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Click item on list screen
|
||||||
|
composeTestRule.onNodeWithTag("item_0").performClick()
|
||||||
|
|
||||||
|
// Verify navigation destination
|
||||||
|
assertEquals("detail/0", navController.currentBackStackEntry?.destination?.route)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Compose Test Mistakes
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
// ❌ Wrong: Asserting immediately after async operation
|
||||||
|
composeTestRule.onNodeWithTag("submit").performClick()
|
||||||
|
composeTestRule.onNodeWithText("Success").assertIsDisplayed() // may fail — UI hasn't updated yet
|
||||||
|
|
||||||
|
// ✅ Correct: Wait for the UI to settle
|
||||||
|
composeTestRule.onNodeWithTag("submit").performClick()
|
||||||
|
composeTestRule.waitForIdle()
|
||||||
|
composeTestRule.onNodeWithText("Success").assertIsDisplayed()
|
||||||
|
|
||||||
|
// ✅ Also correct: waitUntil for longer async work
|
||||||
|
composeTestRule.onNodeWithTag("submit").performClick()
|
||||||
|
composeTestRule.waitUntil(timeoutMillis = 3000) {
|
||||||
|
composeTestRule.onAllNodesWithText("Success")
|
||||||
|
.fetchSemanticsNodes().isNotEmpty()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Gradle Managed Devices
|
||||||
|
|
||||||
|
Define emulator profiles in `build.gradle.kts` so anyone (including CI) can run instrumentation tests without manually creating AVDs. Gradle downloads the system image, creates the emulator, runs tests, and tears it down automatically.
|
||||||
|
|
||||||
|
### Device Configuration
|
||||||
|
|
||||||
|
In `app/build.gradle.kts`:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
android {
|
||||||
|
testOptions {
|
||||||
|
managedDevices {
|
||||||
|
localDevices {
|
||||||
|
create("pixel6Api34") {
|
||||||
|
device = "Pixel 6"
|
||||||
|
apiLevel = 34
|
||||||
|
systemImageSource = "aosp-atd" // ATD = faster, headless
|
||||||
|
}
|
||||||
|
create("pixel4Api30") {
|
||||||
|
device = "Pixel 4"
|
||||||
|
apiLevel = 30
|
||||||
|
systemImageSource = "aosp-atd"
|
||||||
|
}
|
||||||
|
create("smallTabletApi34") {
|
||||||
|
device = "Nexus 7"
|
||||||
|
apiLevel = 34
|
||||||
|
systemImageSource = "google" // full Google APIs image
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group devices for matrix testing
|
||||||
|
groups {
|
||||||
|
create("phoneTests") {
|
||||||
|
targetDevices.add(devices["pixel6Api34"])
|
||||||
|
targetDevices.add(devices["pixel4Api30"])
|
||||||
|
}
|
||||||
|
create("allDevices") {
|
||||||
|
targetDevices.add(devices["pixel6Api34"])
|
||||||
|
targetDevices.add(devices["pixel4Api30"])
|
||||||
|
targetDevices.add(devices["smallTabletApi34"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### System Image Sources
|
||||||
|
|
||||||
|
| Source | Description | Best For |
|
||||||
|
|----------------|---------------------------------------------------|------------------------------|
|
||||||
|
| `"aosp-atd"` | Automated Test Device — minimal, no Play Services | Fast CI, pure logic tests |
|
||||||
|
| `"google-atd"` | ATD with Google APIs | Tests needing Maps, Firebase |
|
||||||
|
| `"aosp"` | Full AOSP image | Standard emulator testing |
|
||||||
|
| `"google"` | Full image with Google Play Services | Play Services integration |
|
||||||
|
|
||||||
|
ATD images boot faster and consume less memory because they strip out UI chrome and preinstalled apps irrelevant to testing. Prefer `aosp-atd` or `google-atd` for CI pipelines.
|
||||||
|
|
||||||
|
### Running Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run on a single managed device
|
||||||
|
./gradlew pixel6Api34DebugAndroidTest
|
||||||
|
|
||||||
|
# Run on a device group (all devices in parallel if hardware allows)
|
||||||
|
./gradlew phoneTestsGroupDebugAndroidTest
|
||||||
|
./gradlew allDevicesGroupDebugAndroidTest
|
||||||
|
|
||||||
|
# With specific flavor
|
||||||
|
./gradlew pixel6Api34DevDebugAndroidTest
|
||||||
|
|
||||||
|
# Enable test sharding across devices (speeds up large suites)
|
||||||
|
./gradlew allDevicesGroupDebugAndroidTest \
|
||||||
|
-Pandroid.experimental.androidTest.numManagedDeviceShards=2
|
||||||
|
|
||||||
|
# Generate HTML test report
|
||||||
|
./gradlew pixel6Api34DebugAndroidTest \
|
||||||
|
--continue # don't stop on first failure
|
||||||
|
```
|
||||||
|
|
||||||
|
Test results are written to `app/build/reports/androidTests/managedDevice/`.
|
||||||
@@ -0,0 +1,246 @@
|
|||||||
|
# Visual Design Guidelines
|
||||||
|
|
||||||
|
Detailed specifications for colors, typography, spacing, elevation, and shapes in Material Design 3.
|
||||||
|
|
||||||
|
## Color System
|
||||||
|
|
||||||
|
### Color Roles (Tokens)
|
||||||
|
|
||||||
|
Material Design 3 uses a token-based color system with three accent groups:
|
||||||
|
|
||||||
|
| Role | Usage |
|
||||||
|
|------|-------|
|
||||||
|
| **Primary** | Key components, FAB, prominent buttons |
|
||||||
|
| **Secondary** | Less prominent components, filters, chips |
|
||||||
|
| **Tertiary** | Accent, complementary elements |
|
||||||
|
| **Error** | Error states, destructive actions |
|
||||||
|
| **Surface** | Backgrounds, cards, dialogs |
|
||||||
|
|
||||||
|
Each role includes variants: base color, onColor, container, onContainer.
|
||||||
|
|
||||||
|
### Color Contrast Requirements
|
||||||
|
|
||||||
|
| Element | Minimum Contrast Ratio | Notes |
|
||||||
|
|---------|----------------------|-------|
|
||||||
|
| Body text | **4.5:1** | WCAG AA compliance |
|
||||||
|
| Large text (18sp+) | **3:1** | 14sp bold also qualifies |
|
||||||
|
| UI components | **3:1** | Icons, borders, controls |
|
||||||
|
| Focus indicators | **3:1** | Must be clearly visible |
|
||||||
|
|
||||||
|
### Recommended Color Palettes
|
||||||
|
|
||||||
|
#### Modern Professional (Business Apps)
|
||||||
|
|
||||||
|
| Role | Color | Name |
|
||||||
|
|------|-------|------|
|
||||||
|
| Primary | #1976D2 | Blue 700 |
|
||||||
|
| Secondary | #455A64 | Blue Grey 700 |
|
||||||
|
| Tertiary | #00897B | Teal 600 |
|
||||||
|
| Background | #FAFAFA | Grey 50 |
|
||||||
|
|
||||||
|
#### Vibrant & Playful (Consumer Apps)
|
||||||
|
|
||||||
|
| Role | Color | Name |
|
||||||
|
|------|-------|------|
|
||||||
|
| Primary | #6200EE | Deep Purple |
|
||||||
|
| Secondary | #03DAC6 | Teal Accent |
|
||||||
|
| Tertiary | #FF5722 | Deep Orange |
|
||||||
|
| Background | #FFFFFF | White |
|
||||||
|
|
||||||
|
#### Dark & Elegant (Premium Apps)
|
||||||
|
|
||||||
|
| Role | Color | Name |
|
||||||
|
|------|-------|------|
|
||||||
|
| Primary | #BB86FC | Purple 200 |
|
||||||
|
| Secondary | #03DAC6 | Teal 200 |
|
||||||
|
| Tertiary | #CF6679 | Red 200 |
|
||||||
|
| Background | #121212 | Dark surface |
|
||||||
|
|
||||||
|
#### Nature & Wellness (Health Apps)
|
||||||
|
|
||||||
|
| Role | Color | Name |
|
||||||
|
|------|-------|------|
|
||||||
|
| Primary | #4CAF50 | Green 500 |
|
||||||
|
| Secondary | #8BC34A | Light Green 500 |
|
||||||
|
| Tertiary | #FFEB3B | Yellow 500 |
|
||||||
|
| Background | #F1F8E9 | Light Green 50 |
|
||||||
|
|
||||||
|
#### Finance & Trust (Banking Apps)
|
||||||
|
|
||||||
|
| Role | Color | Name |
|
||||||
|
|------|-------|------|
|
||||||
|
| Primary | #00695C | Teal 800 |
|
||||||
|
| Secondary | #37474F | Blue Grey 800 |
|
||||||
|
| Tertiary | #FFC107 | Amber 500 |
|
||||||
|
| Background | #ECEFF1 | Blue Grey 50 |
|
||||||
|
|
||||||
|
### Dark Theme Requirements
|
||||||
|
|
||||||
|
- Background: #121212 or darker
|
||||||
|
- Surface colors use elevation-based tonal overlay
|
||||||
|
- Primary colors should be lighter variants (200-300 range)
|
||||||
|
- Maintain contrast ratios in dark mode
|
||||||
|
- Test all states (hover, focus, pressed) in dark mode
|
||||||
|
|
||||||
|
## Typography System
|
||||||
|
|
||||||
|
### Type Scale
|
||||||
|
|
||||||
|
| Style | Size | Weight | Line Height | Usage |
|
||||||
|
|-------|------|--------|-------------|-------|
|
||||||
|
| Display Large | 57sp | 400 | 64sp | Hero text |
|
||||||
|
| Display Medium | 45sp | 400 | 52sp | Large headers |
|
||||||
|
| Display Small | 36sp | 400 | 44sp | Section headers |
|
||||||
|
| Headline Large | 32sp | 400 | 40sp | Screen titles |
|
||||||
|
| Headline Medium | 28sp | 400 | 36sp | Subsection titles |
|
||||||
|
| Headline Small | 24sp | 400 | 32sp | Card titles |
|
||||||
|
| Title Large | 22sp | 400 | 28sp | App bar titles |
|
||||||
|
| Title Medium | 16sp | 500 | 24sp | List item titles |
|
||||||
|
| Title Small | 14sp | 500 | 20sp | Tabs |
|
||||||
|
| Body Large | 16sp | 400 | 24sp | Primary body text |
|
||||||
|
| Body Medium | 14sp | 400 | 20sp | Secondary body text |
|
||||||
|
| Body Small | 12sp | 400 | 16sp | Captions |
|
||||||
|
| Label Large | 14sp | 500 | 20sp | Button text |
|
||||||
|
| Label Medium | 12sp | 500 | 16sp | Navigation labels |
|
||||||
|
| Label Small | 11sp | 500 | 16sp | Badges |
|
||||||
|
|
||||||
|
### Recommended Fonts
|
||||||
|
|
||||||
|
| Category | Fonts |
|
||||||
|
|----------|-------|
|
||||||
|
| Primary | Roboto (system default) |
|
||||||
|
| Display | Roboto Serif, Google Sans |
|
||||||
|
| Monospace | Roboto Mono, JetBrains Mono |
|
||||||
|
|
||||||
|
### Text Readability
|
||||||
|
|
||||||
|
- **Line length**: 45-75 characters per line (including spaces)
|
||||||
|
- **Paragraph spacing**: 1.5x line height between paragraphs
|
||||||
|
- **Letter spacing**: Use default unless brand requires adjustment
|
||||||
|
- **Text alignment**: Left-aligned for body text (LTR languages)
|
||||||
|
|
||||||
|
## Spacing & Layout
|
||||||
|
|
||||||
|
### 8dp Grid System
|
||||||
|
|
||||||
|
All spacing values should be multiples of 8dp (with 4dp for fine adjustments).
|
||||||
|
|
||||||
|
| Token | Value | Usage |
|
||||||
|
|-------|-------|-------|
|
||||||
|
| xs | 4dp | Icon padding, fine adjustments |
|
||||||
|
| sm | 8dp | Tight spacing, inline elements |
|
||||||
|
| md | 16dp | Default padding, card content |
|
||||||
|
| lg | 24dp | Section spacing |
|
||||||
|
| xl | 32dp | Large gaps, group separation |
|
||||||
|
| xxl | 48dp | Screen margins, major sections |
|
||||||
|
|
||||||
|
### Component Dimensions
|
||||||
|
|
||||||
|
| Component | Height | Min Width | Notes |
|
||||||
|
|-----------|--------|-----------|-------|
|
||||||
|
| Button | 40dp | 64dp | Touch target 48dp |
|
||||||
|
| FAB | 56dp | 56dp | Standard size |
|
||||||
|
| Mini FAB | 40dp | 40dp | Secondary actions |
|
||||||
|
| Extended FAB | 56dp | 80dp | With text label |
|
||||||
|
| Text Field | 56dp | 280dp | Including label |
|
||||||
|
| App Bar | 64dp | - | Top app bar |
|
||||||
|
| Bottom Nav | 80dp | - | With labels |
|
||||||
|
| Nav Rail | - | 80dp | Tablet/desktop |
|
||||||
|
| List Item | 56-88dp | - | Depends on content |
|
||||||
|
| Chip | 32dp | - | Filter/action chips |
|
||||||
|
|
||||||
|
### Touch Targets
|
||||||
|
|
||||||
|
| Type | Size | Notes |
|
||||||
|
|------|------|-------|
|
||||||
|
| Minimum | 48 × 48dp | WCAG requirement |
|
||||||
|
| Recommended | 56 × 56dp | Primary actions |
|
||||||
|
| Kids apps | 56dp+ | Larger for motor skills |
|
||||||
|
| Spacing | 8dp minimum | Between adjacent targets |
|
||||||
|
|
||||||
|
## Elevation & Shadows
|
||||||
|
|
||||||
|
### Elevation Levels
|
||||||
|
|
||||||
|
| Level | Elevation | Usage |
|
||||||
|
|-------|-----------|-------|
|
||||||
|
| Level 0 | 0dp | Flat surfaces |
|
||||||
|
| Level 1 | 1dp | Cards, elevated buttons |
|
||||||
|
| Level 2 | 3dp | FAB (resting), raised elements |
|
||||||
|
| Level 3 | 6dp | Navigation drawer, bottom sheet |
|
||||||
|
| Level 4 | 8dp | FAB (pressed), menus |
|
||||||
|
| Level 5 | 12dp | Dialogs, modal surfaces |
|
||||||
|
|
||||||
|
### Shadow Guidelines
|
||||||
|
|
||||||
|
- Use elevation consistently for same component types
|
||||||
|
- Higher elevation = more important/prominent
|
||||||
|
- In dark theme, use surface tint instead of shadows
|
||||||
|
- Avoid excessive elevation (keeps UI grounded)
|
||||||
|
|
||||||
|
## Shape System
|
||||||
|
|
||||||
|
### Corner Radius
|
||||||
|
|
||||||
|
| Size | Radius | Usage |
|
||||||
|
|------|--------|-------|
|
||||||
|
| None | 0dp | Sharp edges, dividers |
|
||||||
|
| Extra Small | 4dp | Badges, small chips |
|
||||||
|
| Small | 8dp | Buttons, chips, small cards |
|
||||||
|
| Medium | 12dp | Cards, dialogs, text fields |
|
||||||
|
| Large | 16dp | FAB, bottom sheets |
|
||||||
|
| Extra Large | 28dp | Large sheets, expanded cards |
|
||||||
|
| Full | 50% | Pills, avatars, circular buttons |
|
||||||
|
|
||||||
|
### M3 Expressive Shapes
|
||||||
|
|
||||||
|
Material 3 Expressive introduces 35 new decorative shapes:
|
||||||
|
- Organic curves
|
||||||
|
- Asymmetric corners
|
||||||
|
- Cut corners
|
||||||
|
- Scalloped edges
|
||||||
|
|
||||||
|
Use sparingly for brand differentiation and visual interest.
|
||||||
|
|
||||||
|
### Shape Consistency Rules
|
||||||
|
|
||||||
|
- Same component type = same shape
|
||||||
|
- Related components should share shape family
|
||||||
|
- Don't mix too many shape styles on one screen
|
||||||
|
- Consider shape in dark/light theme transitions
|
||||||
|
|
||||||
|
## Icons
|
||||||
|
|
||||||
|
### Size Specifications
|
||||||
|
|
||||||
|
| Size | Dimensions | Usage |
|
||||||
|
|------|------------|-------|
|
||||||
|
| Small | 20 × 20dp | Compact UI, inline |
|
||||||
|
| Standard | 24 × 24dp | Default for most uses |
|
||||||
|
| Large | 40 × 40dp | Emphasis, empty states |
|
||||||
|
|
||||||
|
### Icon Guidelines
|
||||||
|
|
||||||
|
- **Touch target**: Always wrap in 48dp minimum clickable area
|
||||||
|
- **Style**: Outlined (default), Filled (selected/active states)
|
||||||
|
- **Stroke width**: 2dp for outlined icons
|
||||||
|
- **Optical alignment**: May need visual adjustments
|
||||||
|
- **Color**: Use semantic colors (primary, error, etc.)
|
||||||
|
|
||||||
|
### Recommended Icon Sets
|
||||||
|
|
||||||
|
| Set | Usage |
|
||||||
|
|-----|-------|
|
||||||
|
| Material Symbols | Recommended, variable font support |
|
||||||
|
| Material Icons | Legacy, still widely used |
|
||||||
|
|
||||||
|
### Adaptive Icons (App Icon)
|
||||||
|
|
||||||
|
| Property | Value |
|
||||||
|
|----------|-------|
|
||||||
|
| Canvas size | 108 × 108dp |
|
||||||
|
| Safe zone | 66 × 66dp (centered circle) |
|
||||||
|
| Logo size | 48-66dp |
|
||||||
|
| Max display | 72 × 72dp |
|
||||||
|
| Layers | Foreground + Background (both 108dp) |
|
||||||
|
| Android 13+ | Include monochrome layer for theming |
|
||||||
Reference in New Issue
Block a user