android-sdd 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +143 -0
- package/package.json +27 -0
- package/skills/Android Ecosystem/Baseline Profile Generator/SKILL.md +277 -0
- package/skills/Android Ecosystem/Glance/SKILL.md +315 -0
- package/skills/Android Platform/Configuration/SKILL.md +201 -0
- package/skills/Android Platform/Filesystem/SKILL.md +216 -0
- package/skills/Android Platform/Lifecycle/SKILL.md +233 -0
- package/skills/Android Platform/Manifest/SKILL.md +226 -0
- package/skills/Android Platform/Process Death Recovery/SKILL.md +214 -0
- package/skills/Android Platform/Resources/SKILL.md +234 -0
- package/skills/Android Platform/SavedStateHandle/SKILL.md +217 -0
- package/skills/Android Platform/State Restoration/SKILL.md +210 -0
- package/skills/Architecture/Bounded Context/SKILL.md +207 -0
- package/skills/Architecture/Clean Architecture/SKILL.md +229 -0
- package/skills/Architecture/Domain Modeling/SKILL.md +236 -0
- package/skills/Architecture/Entity Design/SKILL.md +243 -0
- package/skills/Architecture/Feature Isolation/SKILL.md +216 -0
- package/skills/Architecture/MVI/SKILL.md +224 -0
- package/skills/Architecture/MVVM/SKILL.md +198 -0
- package/skills/Architecture/Modularization/SKILL.md +194 -0
- package/skills/Architecture/Offline First/SKILL.md +249 -0
- package/skills/Architecture/Repository Pattern/SKILL.md +216 -0
- package/skills/Architecture/Side Effect Management/SKILL.md +278 -0
- package/skills/Architecture/State Management/SKILL.md +229 -0
- package/skills/Architecture/Unidirectional Data Flow/SKILL.md +196 -0
- package/skills/Architecture/Use Case Design/SKILL.md +244 -0
- package/skills/Architecture/Value Object/SKILL.md +226 -0
- package/skills/Build Infrastructure/Build Orchestration/SKILL.md +257 -0
- package/skills/Build Infrastructure/Dependency Compatibility Resolver/SKILL.md +259 -0
- package/skills/Build Infrastructure/Environment Validator/SKILL.md +311 -0
- package/skills/Build System/Build Cache/SKILL.md +233 -0
- package/skills/Build System/Build Flavor Strategy/SKILL.md +171 -0
- package/skills/Build System/Build Variant/SKILL.md +215 -0
- package/skills/Build System/Convention Plugin/SKILL.md +288 -0
- package/skills/Build System/Dependency Management/SKILL.md +261 -0
- package/skills/Build System/Gradle/SKILL.md +284 -0
- package/skills/Build System/Incremental Build/SKILL.md +199 -0
- package/skills/Build System/KAPT/SKILL.md +198 -0
- package/skills/Build System/KSP/SKILL.md +263 -0
- package/skills/Build System/Module Dependency Graph Validation/SKILL.md +223 -0
- package/skills/Build System/Specialized/C++/SKILL.md +308 -0
- package/skills/Build System/Specialized/JNI/SKILL.md +306 -0
- package/skills/Build System/Specialized/NDK/SKILL.md +264 -0
- package/skills/Build System/Version Catalog/SKILL.md +304 -0
- package/skills/Concurrency/Background Processing/SKILL.md +185 -0
- package/skills/Concurrency/Channel/SKILL.md +207 -0
- package/skills/Concurrency/Coroutine/SKILL.md +200 -0
- package/skills/Concurrency/Flow/SKILL.md +179 -0
- package/skills/Concurrency/Mutex Strategy/SKILL.md +185 -0
- package/skills/Concurrency/SharedFlow/SKILL.md +171 -0
- package/skills/Concurrency/StateFlow/SKILL.md +175 -0
- package/skills/Concurrency/Structured Concurrency/SKILL.md +197 -0
- package/skills/Concurrency/Synchronization Policy/SKILL.md +192 -0
- package/skills/Core Language/Annotation Processing/SKILL.md +224 -0
- package/skills/Core Language/DSL/SKILL.md +186 -0
- package/skills/Core Language/Extension Functions Design/SKILL.md +191 -0
- package/skills/Core Language/Immutability/SKILL.md +156 -0
- package/skills/Core Language/KMP/SKILL.md +182 -0
- package/skills/Core Language/Kotlin/SKILL.md +187 -0
- package/skills/Core Language/Reactive State Management/SKILL.md +228 -0
- package/skills/Core Language/Reactive Streams/SKILL.md +235 -0
- package/skills/Core Language/Serialization/SKILL.md +191 -0
- package/skills/Data Layer/Cache Strategy/SKILL.md +261 -0
- package/skills/Data Layer/Conflict Resolution/SKILL.md +248 -0
- package/skills/Data Layer/DAO/SKILL.md +225 -0
- package/skills/Data Layer/DTO Mapping/SKILL.md +269 -0
- package/skills/Data Layer/DataStore/SKILL.md +264 -0
- package/skills/Data Layer/Database Versioning Strategy/SKILL.md +215 -0
- package/skills/Data Layer/Encrypted Database/SKILL.md +212 -0
- package/skills/Data Layer/File Storage/SKILL.md +247 -0
- package/skills/Data Layer/Indexing/SKILL.md +184 -0
- package/skills/Data Layer/Key-Value Store Strategy/SKILL.md +185 -0
- package/skills/Data Layer/Merge Strategy/SKILL.md +240 -0
- package/skills/Data Layer/Migration/SKILL.md +243 -0
- package/skills/Data Layer/Paging/SKILL.md +264 -0
- package/skills/Data Layer/Proto DataStore/SKILL.md +250 -0
- package/skills/Data Layer/Room/SKILL.md +244 -0
- package/skills/Data Layer/SQLite/SKILL.md +255 -0
- package/skills/Data Layer/Sync Engine/SKILL.md +268 -0
- package/skills/Dependency Injection/Dagger/SKILL.md +283 -0
- package/skills/Dependency Injection/Hilt/SKILL.md +345 -0
- package/skills/Dependency Injection/Koin/SKILL.md +282 -0
- package/skills/Developer Experience/Detekt/SKILL.md +272 -0
- package/skills/Developer Experience/Lint Rule/SKILL.md +281 -0
- package/skills/Google Ecosystem/Analytics/SKILL.md +281 -0
- package/skills/Google Ecosystem/Crashlytics/SKILL.md +234 -0
- package/skills/Google Ecosystem/Firebase/SKILL.md +200 -0
- package/skills/Google Ecosystem/Firebase Messaging/SKILL.md +266 -0
- package/skills/Media/Audio/SKILL.md +257 -0
- package/skills/Media/Camera/SKILL.md +229 -0
- package/skills/Media/CameraX/SKILL.md +295 -0
- package/skills/Media/ExoPlayer/SKILL.md +258 -0
- package/skills/Media/Video/SKILL.md +228 -0
- package/skills/Meta Skills/Domain Error Model/SKILL.md +238 -0
- package/skills/Meta Skills/Error Handling/SKILL.md +255 -0
- package/skills/Meta Skills/Error Mapping/SKILL.md +232 -0
- package/skills/Meta Skills/Failure Strategy/SKILL.md +294 -0
- package/skills/Meta Skills/Migration Strategy/SKILL.md +305 -0
- package/skills/Meta Skills/User Friendly Errors/SKILL.md +334 -0
- package/skills/Navigation/Deep Navigation/SKILL.md +209 -0
- package/skills/Navigation/Navigation/SKILL.md +215 -0
- package/skills/Navigation/Nested Navigation/SKILL.md +214 -0
- package/skills/Networking/API Contract/SKILL.md +220 -0
- package/skills/Networking/Authentication/SKILL.md +210 -0
- package/skills/Networking/Certificate Pinning/SKILL.md +167 -0
- package/skills/Networking/Fallback Strategy/SKILL.md +182 -0
- package/skills/Networking/Ktor/SKILL.md +219 -0
- package/skills/Networking/Multipart Upload/SKILL.md +213 -0
- package/skills/Networking/OkHttp/SKILL.md +193 -0
- package/skills/Networking/REST/SKILL.md +178 -0
- package/skills/Networking/Rate Limiting/SKILL.md +170 -0
- package/skills/Networking/Retrofit/SKILL.md +241 -0
- package/skills/Networking/Retry-Backoff/SKILL.md +181 -0
- package/skills/Networking/Server-Sent Events (SSE)/SKILL.md +196 -0
- package/skills/Networking/WebSocket/SKILL.md +224 -0
- package/skills/Observability/Crash Reporting/SKILL.md +219 -0
- package/skills/Observability/Logging/SKILL.md +168 -0
- package/skills/Observability/Metrics/SKILL.md +227 -0
- package/skills/Observability/Structured Logging/SKILL.md +234 -0
- package/skills/Performance/ANR Prevention/SKILL.md +192 -0
- package/skills/Performance/Allocation Optimization/SKILL.md +179 -0
- package/skills/Performance/App Startup/SKILL.md +183 -0
- package/skills/Performance/Baseline Profile/SKILL.md +205 -0
- package/skills/Performance/Battery Optimization/SKILL.md +192 -0
- package/skills/Performance/Benchmark/SKILL.md +182 -0
- package/skills/Performance/Bitmap Optimization/SKILL.md +178 -0
- package/skills/Performance/Compose Optimization/SKILL.md +187 -0
- package/skills/Performance/Heap Management/SKILL.md +184 -0
- package/skills/Performance/Macrobenchmark/SKILL.md +214 -0
- package/skills/Performance/Memory Leak Prevention/SKILL.md +218 -0
- package/skills/Performance/Rendering Performance/SKILL.md +205 -0
- package/skills/Performance/Startup Optimization/SKILL.md +219 -0
- package/skills/Security/Biometric/SKILL.md +224 -0
- package/skills/Security/Certificate Transparency/SKILL.md +158 -0
- package/skills/Security/Cryptography/SKILL.md +244 -0
- package/skills/Security/Encrypted Storage/SKILL.md +273 -0
- package/skills/Security/Frida Detection/SKILL.md +230 -0
- package/skills/Security/Hook Detection/SKILL.md +197 -0
- package/skills/Security/Keystore/SKILL.md +272 -0
- package/skills/Security/Network Security Config/SKILL.md +186 -0
- package/skills/Security/Obfuscation/SKILL.md +226 -0
- package/skills/Security/Proguard/SKILL.md +202 -0
- package/skills/Security/R8/SKILL.md +234 -0
- package/skills/Security/Reverse Engineering Resistance/SKILL.md +267 -0
- package/skills/Security/Root Detection/SKILL.md +220 -0
- package/skills/Security/Secure Networking/SKILL.md +220 -0
- package/skills/System Integration/AlarmManager/SKILL.md +182 -0
- package/skills/System Integration/App Widget/SKILL.md +182 -0
- package/skills/System Integration/Deep Link/SKILL.md +187 -0
- package/skills/System Integration/Foreground Service/SKILL.md +212 -0
- package/skills/System Integration/Notification/SKILL.md +237 -0
- package/skills/System Integration/WorkManager/SKILL.md +256 -0
- package/skills/System Integration/clipboard/SKILL.md +155 -0
- package/skills/System Integration/share-intent/SKILL.md +182 -0
- package/skills/Testing/Compose Testing/SKILL.md +296 -0
- package/skills/Testing/Espresso/SKILL.md +292 -0
- package/skills/Testing/Fake Data/SKILL.md +245 -0
- package/skills/Testing/Integration Testing/SKILL.md +288 -0
- package/skills/Testing/Mocking/SKILL.md +229 -0
- package/skills/Testing/Snapshot Testing/SKILL.md +259 -0
- package/skills/Testing/UI Testing/SKILL.md +293 -0
- package/skills/Testing/Unit Testing/SKILL.md +309 -0
- package/skills/UI System/Bottom Sheet Patterns/SKILL.md +279 -0
- package/skills/UI System/Compose/SKILL.md +296 -0
- package/skills/UI System/Compose Animation/SKILL.md +281 -0
- package/skills/UI System/Compose Multiplatform/SKILL.md +261 -0
- package/skills/UI System/Compose Navigation/SKILL.md +255 -0
- package/skills/UI System/Compose Performance/SKILL.md +274 -0
- package/skills/UI System/Design System/SKILL.md +217 -0
- package/skills/UI System/Empty State Strategy/SKILL.md +208 -0
- package/skills/UI System/Keyboard Navigation/SKILL.md +214 -0
- package/skills/UI System/Loading Strategy/SKILL.md +254 -0
- package/skills/UI System/Material 3/SKILL.md +279 -0
- package/skills/UI System/RTL/SKILL.md +179 -0
- package/src/index.ts +182 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: glance
|
|
3
|
+
description: >
|
|
4
|
+
Jetpack Glance for building App Widgets with Compose-like API.
|
|
5
|
+
Load this skill when creating home screen or lock screen widgets,
|
|
6
|
+
updating widget content, or handling widget interactions.
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Glance
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
Jetpack Glance provides a Compose-like API for building Android App Widgets. Instead of XML RemoteViews, Glance lets you write widget UI in a declarative style. Glance is built on top of RemoteViews internally — it translates Composable-like functions to RemoteViews at runtime.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Core Principles
|
|
17
|
+
|
|
18
|
+
- Glance is **not Compose** — it only supports a subset of Compose-like APIs
|
|
19
|
+
- Widget UI runs in a **different process** — no direct state sharing with the app
|
|
20
|
+
- Use **GlanceStateDefinition** for widget state — not ViewModel or StateFlow
|
|
21
|
+
- Widget updates must be triggered explicitly — they don't auto-update on app state change
|
|
22
|
+
- Glance components are a **limited subset** — not all Compose components are available
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Setup
|
|
27
|
+
|
|
28
|
+
```toml
|
|
29
|
+
[versions]
|
|
30
|
+
glance = "1.1.0"
|
|
31
|
+
|
|
32
|
+
[libraries]
|
|
33
|
+
glance-appwidget = { module = "androidx.glance:glance-appwidget", version.ref = "glance" }
|
|
34
|
+
glance-material3 = { module = "androidx.glance:glance-material3", version.ref = "glance" }
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```kotlin
|
|
38
|
+
dependencies {
|
|
39
|
+
implementation(libs.glance.appwidget)
|
|
40
|
+
implementation(libs.glance.material3)
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Widget Definition
|
|
47
|
+
|
|
48
|
+
```kotlin
|
|
49
|
+
// ✅ Define the widget
|
|
50
|
+
class MyAppWidget : GlanceAppWidget() {
|
|
51
|
+
|
|
52
|
+
override val stateDefinition = MyWidgetStateDefinition
|
|
53
|
+
|
|
54
|
+
@Composable
|
|
55
|
+
override fun Content() {
|
|
56
|
+
val state = currentState<MyWidgetState>()
|
|
57
|
+
val context = LocalContext.current
|
|
58
|
+
|
|
59
|
+
GlanceTheme {
|
|
60
|
+
MyWidgetContent(
|
|
61
|
+
state = state,
|
|
62
|
+
onRefreshClick = actionRunCallback<RefreshAction>()
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ✅ Composable widget UI
|
|
69
|
+
@Composable
|
|
70
|
+
fun MyWidgetContent(
|
|
71
|
+
state: MyWidgetState,
|
|
72
|
+
onRefreshClick: Action
|
|
73
|
+
) {
|
|
74
|
+
Column(
|
|
75
|
+
modifier = GlanceModifier
|
|
76
|
+
.fillMaxSize()
|
|
77
|
+
.background(GlanceTheme.colors.surface)
|
|
78
|
+
.padding(16.dp)
|
|
79
|
+
.cornerRadius(16.dp),
|
|
80
|
+
verticalAlignment = Alignment.CenterVertically
|
|
81
|
+
) {
|
|
82
|
+
Text(
|
|
83
|
+
text = state.title,
|
|
84
|
+
style = TextStyle(
|
|
85
|
+
color = GlanceTheme.colors.onSurface,
|
|
86
|
+
fontSize = 16.sp,
|
|
87
|
+
fontWeight = FontWeight.Bold
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
Spacer(modifier = GlanceModifier.height(8.dp))
|
|
92
|
+
|
|
93
|
+
Text(
|
|
94
|
+
text = state.subtitle,
|
|
95
|
+
style = TextStyle(
|
|
96
|
+
color = GlanceTheme.colors.onSurface,
|
|
97
|
+
fontSize = 12.sp
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
Spacer(modifier = GlanceModifier.defaultWeight())
|
|
102
|
+
|
|
103
|
+
Button(
|
|
104
|
+
text = "Refresh",
|
|
105
|
+
onClick = onRefreshClick
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Widget State
|
|
114
|
+
|
|
115
|
+
```kotlin
|
|
116
|
+
// ✅ Define state model
|
|
117
|
+
data class MyWidgetState(
|
|
118
|
+
val title: String = "",
|
|
119
|
+
val subtitle: String = "",
|
|
120
|
+
val isLoading: Boolean = false
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
// ✅ State definition using DataStore
|
|
124
|
+
val MyWidgetStateDefinition = object : GlanceStateDefinition<MyWidgetState> {
|
|
125
|
+
|
|
126
|
+
override suspend fun getDataStore(
|
|
127
|
+
context: Context,
|
|
128
|
+
fileKey: String
|
|
129
|
+
): DataStore<MyWidgetState> = DataStoreFactory.create(
|
|
130
|
+
serializer = MyWidgetStateSerializer,
|
|
131
|
+
produceFile = { context.dataStoreFile(fileKey) }
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
override fun getLocation(context: Context, fileKey: String): File =
|
|
135
|
+
context.dataStoreFile(fileKey)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ✅ Serializer for widget state
|
|
139
|
+
object MyWidgetStateSerializer : Serializer<MyWidgetState> {
|
|
140
|
+
override val defaultValue = MyWidgetState()
|
|
141
|
+
|
|
142
|
+
override suspend fun readFrom(input: InputStream): MyWidgetState = try {
|
|
143
|
+
Json.decodeFromString(input.readBytes().decodeToString())
|
|
144
|
+
} catch (e: Exception) {
|
|
145
|
+
defaultValue
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
override suspend fun writeTo(t: MyWidgetState, output: OutputStream) {
|
|
149
|
+
output.write(Json.encodeToString(t).toByteArray())
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Widget Receiver
|
|
157
|
+
|
|
158
|
+
```kotlin
|
|
159
|
+
// ✅ GlanceAppWidgetReceiver — entry point for the widget
|
|
160
|
+
class MyAppWidgetReceiver : GlanceAppWidgetReceiver() {
|
|
161
|
+
override val glanceAppWidget = MyAppWidget()
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
```xml
|
|
166
|
+
<!-- AndroidManifest.xml -->
|
|
167
|
+
<receiver
|
|
168
|
+
android:name=".widget.MyAppWidgetReceiver"
|
|
169
|
+
android:exported="true">
|
|
170
|
+
<intent-filter>
|
|
171
|
+
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
|
172
|
+
</intent-filter>
|
|
173
|
+
<meta-data
|
|
174
|
+
android:name="android.appwidget.provider"
|
|
175
|
+
android:resource="@xml/my_widget_info" />
|
|
176
|
+
</receiver>
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
```xml
|
|
180
|
+
<!-- res/xml/my_widget_info.xml -->
|
|
181
|
+
<appwidget-provider
|
|
182
|
+
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
183
|
+
android:minWidth="250dp"
|
|
184
|
+
android:minHeight="110dp"
|
|
185
|
+
android:targetCellWidth="4"
|
|
186
|
+
android:targetCellHeight="2"
|
|
187
|
+
android:resizeMode="horizontal|vertical"
|
|
188
|
+
android:updatePeriodMillis="1800000"
|
|
189
|
+
android:widgetCategory="home_screen"
|
|
190
|
+
android:initialLayout="@layout/glance_default_loading_layout" />
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Updating Widget State
|
|
196
|
+
|
|
197
|
+
```kotlin
|
|
198
|
+
// ✅ Update widget from app — e.g., from ViewModel or Worker
|
|
199
|
+
suspend fun updateWidget(context: Context, newState: MyWidgetState) {
|
|
200
|
+
// Update state in all widget instances
|
|
201
|
+
GlanceAppWidgetManager(context)
|
|
202
|
+
.getGlanceIds(MyAppWidget::class.java)
|
|
203
|
+
.forEach { glanceId ->
|
|
204
|
+
updateAppWidgetState(context, MyWidgetStateDefinition, glanceId) {
|
|
205
|
+
newState
|
|
206
|
+
}
|
|
207
|
+
MyAppWidget().update(context, glanceId)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ✅ Update from WorkManager for periodic refresh
|
|
212
|
+
class WidgetUpdateWorker(
|
|
213
|
+
context: Context,
|
|
214
|
+
params: WorkerParameters
|
|
215
|
+
) : CoroutineWorker(context, params) {
|
|
216
|
+
|
|
217
|
+
override suspend fun doWork(): Result {
|
|
218
|
+
val data = repository.fetchLatestData()
|
|
219
|
+
updateWidget(
|
|
220
|
+
applicationContext,
|
|
221
|
+
MyWidgetState(title = data.title, subtitle = data.subtitle)
|
|
222
|
+
)
|
|
223
|
+
return Result.success()
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Widget Actions
|
|
231
|
+
|
|
232
|
+
```kotlin
|
|
233
|
+
// ✅ Handle button clicks via ActionCallback
|
|
234
|
+
class RefreshAction : ActionCallback {
|
|
235
|
+
override suspend fun onAction(
|
|
236
|
+
context: Context,
|
|
237
|
+
glanceId: GlanceId,
|
|
238
|
+
parameters: ActionParameters
|
|
239
|
+
) {
|
|
240
|
+
// Update widget state on click
|
|
241
|
+
updateAppWidgetState(context, MyWidgetStateDefinition, glanceId) { state ->
|
|
242
|
+
state.copy(isLoading = true)
|
|
243
|
+
}
|
|
244
|
+
MyAppWidget().update(context, glanceId)
|
|
245
|
+
|
|
246
|
+
// Fetch new data
|
|
247
|
+
val newData = repository.fetchData()
|
|
248
|
+
updateAppWidgetState(context, MyWidgetStateDefinition, glanceId) {
|
|
249
|
+
MyWidgetState(title = newData.title, subtitle = newData.subtitle)
|
|
250
|
+
}
|
|
251
|
+
MyAppWidget().update(context, glanceId)
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ✅ Open app on widget click
|
|
256
|
+
val openAppAction = actionStartActivity<MainActivity>()
|
|
257
|
+
|
|
258
|
+
// ✅ Open specific screen via deep link
|
|
259
|
+
val openDetailAction = actionStartActivity(
|
|
260
|
+
Intent(context, MainActivity::class.java).apply {
|
|
261
|
+
putExtra("destination", "detail")
|
|
262
|
+
}
|
|
263
|
+
)
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Glance Components Reference
|
|
269
|
+
|
|
270
|
+
```kotlin
|
|
271
|
+
// ✅ Available layout components
|
|
272
|
+
Row { }
|
|
273
|
+
Column { }
|
|
274
|
+
Box { }
|
|
275
|
+
LazyColumn { } // for lists
|
|
276
|
+
|
|
277
|
+
// ✅ Available UI components
|
|
278
|
+
Text(text = "Hello")
|
|
279
|
+
Button(text = "Click", onClick = action)
|
|
280
|
+
Image(provider = ImageProvider(R.drawable.icon), contentDescription = null)
|
|
281
|
+
CircularProgressIndicator()
|
|
282
|
+
LinearProgressIndicator(progress = 0.5f)
|
|
283
|
+
Spacer(modifier = GlanceModifier.height(8.dp))
|
|
284
|
+
Switch(checked = true, onCheckedChange = action)
|
|
285
|
+
CheckBox(checked = false, onCheckedChange = action)
|
|
286
|
+
|
|
287
|
+
// ✅ GlanceModifier (not Compose Modifier)
|
|
288
|
+
GlanceModifier
|
|
289
|
+
.fillMaxSize()
|
|
290
|
+
.fillMaxWidth()
|
|
291
|
+
.wrapContentSize()
|
|
292
|
+
.padding(16.dp)
|
|
293
|
+
.background(Color.White)
|
|
294
|
+
.clickable(onClick = action)
|
|
295
|
+
.cornerRadius(12.dp)
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
---
|
|
299
|
+
|
|
300
|
+
## Anti-Patterns
|
|
301
|
+
|
|
302
|
+
- Using Compose components not available in Glance — runtime crash
|
|
303
|
+
- Sharing ViewModel state directly with widget — different processes
|
|
304
|
+
- Not calling `widget.update()` after state change — UI stays stale
|
|
305
|
+
- Complex animations in widgets — not supported, RemoteViews limitation
|
|
306
|
+
- Making widgets too complex — widgets should show summary, not replace the app
|
|
307
|
+
- Not handling widget resize — use `SizeMode.Responsive` for multiple sizes
|
|
308
|
+
|
|
309
|
+
---
|
|
310
|
+
|
|
311
|
+
## Related Skills
|
|
312
|
+
- `compose` — Glance has similar but different API
|
|
313
|
+
- `datastore` — widget state persistence
|
|
314
|
+
- `workmanager` — periodic widget updates
|
|
315
|
+
- `app-widget` — traditional RemoteViews-based widgets
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: configuration
|
|
3
|
+
description: >
|
|
4
|
+
Android Configuration changes handling — screen rotation, locale change,
|
|
5
|
+
dark mode, font scale, and other runtime configuration events.
|
|
6
|
+
Load this skill when handling configuration changes, deciding what to retain
|
|
7
|
+
across rotation, or supporting dynamic locale/theme switching.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Configuration
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
|
|
14
|
+
Android Configuration represents the current state of the device — screen orientation, locale, font scale, night mode, and more. When configuration changes, Android destroys and recreates the Activity by default. Proper handling ensures seamless UX without data loss or memory leaks.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Core Principles
|
|
19
|
+
|
|
20
|
+
- Never store UI state in Activity — use ViewModel to survive configuration changes
|
|
21
|
+
- Never use `android:configChanges` to avoid recreating Activity unless absolutely necessary
|
|
22
|
+
- Handle configuration-dependent resources via the resource qualifier system — not in code
|
|
23
|
+
- Use `SavedStateHandle` for state that must survive both configuration change and process death
|
|
24
|
+
- Test rotation and locale change explicitly — they expose the most lifecycle bugs
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## What Happens on Configuration Change
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
User rotates screen
|
|
32
|
+
→ Activity.onPause()
|
|
33
|
+
→ Activity.onStop()
|
|
34
|
+
→ Activity.onDestroy() ← Activity is destroyed
|
|
35
|
+
→ ViewModel stays alive ← ViewModel is NOT destroyed
|
|
36
|
+
→ new Activity.onCreate()
|
|
37
|
+
→ new Activity.onStart()
|
|
38
|
+
→ new Activity.onResume()
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## ViewModel — Surviving Configuration Change
|
|
44
|
+
|
|
45
|
+
```kotlin
|
|
46
|
+
// ✅ ViewModel survives rotation — store all UI state here
|
|
47
|
+
class UserViewModel : ViewModel() {
|
|
48
|
+
private val _state = MutableStateFlow(UserUiState())
|
|
49
|
+
val state: StateFlow<UserUiState> = _state.asStateFlow()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ✅ Activity/Fragment — just observe, never store state locally
|
|
53
|
+
class UserFragment : Fragment() {
|
|
54
|
+
private val viewModel: UserViewModel by viewModels()
|
|
55
|
+
|
|
56
|
+
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
|
57
|
+
viewLifecycleOwner.lifecycleScope.launch {
|
|
58
|
+
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
|
59
|
+
viewModel.state.collect { render(it) }
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Detecting Configuration in Code
|
|
69
|
+
|
|
70
|
+
```kotlin
|
|
71
|
+
// ✅ Read current configuration
|
|
72
|
+
val config = resources.configuration
|
|
73
|
+
|
|
74
|
+
// Night mode
|
|
75
|
+
val isNightMode = config.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
|
|
76
|
+
Configuration.UI_MODE_NIGHT_YES
|
|
77
|
+
|
|
78
|
+
// Orientation
|
|
79
|
+
val isLandscape = config.orientation == Configuration.ORIENTATION_LANDSCAPE
|
|
80
|
+
|
|
81
|
+
// Locale
|
|
82
|
+
val locale = config.locales[0]
|
|
83
|
+
|
|
84
|
+
// Screen size class
|
|
85
|
+
val isTablet = config.smallestScreenWidthDp >= 600
|
|
86
|
+
|
|
87
|
+
// ✅ In Compose
|
|
88
|
+
val isSystemInDarkTheme = isSystemInDarkTheme()
|
|
89
|
+
val windowSizeClass = calculateWindowSizeClass(activity)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Dynamic Locale Change (API 33+)
|
|
95
|
+
|
|
96
|
+
```kotlin
|
|
97
|
+
// ✅ API 33+ — use LocaleManager
|
|
98
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
99
|
+
getSystemService(LocaleManager::class.java)
|
|
100
|
+
.applicationLocales = LocaleList(Locale.forLanguageTag("fa"))
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ✅ API < 33 — use AppCompatDelegate
|
|
104
|
+
AppCompatDelegate.setApplicationLocales(
|
|
105
|
+
LocaleListCompat.forLanguageTags("fa")
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Dynamic Dark Mode
|
|
112
|
+
|
|
113
|
+
```kotlin
|
|
114
|
+
// ✅ Change theme at runtime
|
|
115
|
+
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
|
|
116
|
+
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
|
|
117
|
+
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
|
118
|
+
|
|
119
|
+
// ✅ Persist preference and apply on app start
|
|
120
|
+
class ThemeManager(private val prefs: DataStore<Preferences>) {
|
|
121
|
+
suspend fun applyTheme() {
|
|
122
|
+
val isDark = prefs.data.first()[DARK_MODE_KEY] ?: false
|
|
123
|
+
AppCompatDelegate.setDefaultNightMode(
|
|
124
|
+
if (isDark) AppCompatDelegate.MODE_NIGHT_YES
|
|
125
|
+
else AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## configChanges — When to Use
|
|
134
|
+
|
|
135
|
+
```xml
|
|
136
|
+
<!-- ✅ Only for cases where recreation is genuinely harmful -->
|
|
137
|
+
<!-- Example: video player, camera, map — recreation causes flicker/reset -->
|
|
138
|
+
<activity
|
|
139
|
+
android:name=".PlayerActivity"
|
|
140
|
+
android:configChanges="orientation|screenSize|keyboardHidden" />
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
```kotlin
|
|
144
|
+
// ✅ Handle manually when configChanges is declared
|
|
145
|
+
override fun onConfigurationChanged(newConfig: Configuration) {
|
|
146
|
+
super.onConfigurationChanged(newConfig)
|
|
147
|
+
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
|
148
|
+
adjustLayoutForLandscape()
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ❌ Don't use configChanges just to avoid writing proper ViewModel code
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Window Size Classes (Adaptive UI)
|
|
158
|
+
|
|
159
|
+
```kotlin
|
|
160
|
+
// ✅ Use WindowSizeClass for adaptive layouts
|
|
161
|
+
@Composable
|
|
162
|
+
fun AdaptiveScreen(windowSizeClass: WindowSizeClass) {
|
|
163
|
+
when (windowSizeClass.widthSizeClass) {
|
|
164
|
+
WindowWidthSizeClass.Compact -> PhoneLayout()
|
|
165
|
+
WindowWidthSizeClass.Medium -> TabletLayout()
|
|
166
|
+
WindowWidthSizeClass.Expanded -> DesktopLayout()
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Setup in Activity
|
|
171
|
+
class MainActivity : ComponentActivity() {
|
|
172
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
173
|
+
super.onCreate(savedInstanceState)
|
|
174
|
+
val windowSizeClass = calculateWindowSizeClass(this)
|
|
175
|
+
setContent {
|
|
176
|
+
AdaptiveScreen(windowSizeClass)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## Anti-Patterns
|
|
185
|
+
|
|
186
|
+
- Storing UI state in Activity fields — lost on rotation
|
|
187
|
+
- Using `android:configChanges` to avoid ViewModel — just delays the problem
|
|
188
|
+
- Reading configuration in ViewModel — ViewModel should be config-independent
|
|
189
|
+
- Hardcoding layout logic for orientation in code instead of resource qualifiers
|
|
190
|
+
- Not testing rotation — the most common source of state loss bugs
|
|
191
|
+
- Using `onRetainNonConfigurationInstance()` — replaced by ViewModel
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Related Skills
|
|
196
|
+
|
|
197
|
+
- `savedstatehandle` — persisting state across process death
|
|
198
|
+
- `lifecycle` — Activity/Fragment lifecycle during config change
|
|
199
|
+
- `viewmodel` — surviving configuration changes
|
|
200
|
+
- `resources` — configuration qualifiers in res/
|
|
201
|
+
- `adaptive-ui` — WindowSizeClass and responsive layouts
|