0
# Resource Environment
1
2
The Resource Environment system manages the context for resource selection, including locale (language and region), theme (light/dark), and screen density. This enables automatic selection of the most appropriate resource variant for the current device and user preferences.
3
4
## Core Types
5
6
```kotlin { .api }
7
class ResourceEnvironment(
8
internal val language: LanguageQualifier,
9
internal val region: RegionQualifier,
10
internal val theme: ThemeQualifier,
11
internal val density: DensityQualifier
12
)
13
```
14
15
The ResourceEnvironment encapsulates all the factors that influence resource selection. It provides a complete context for determining which specific resource variant to load from the available options.
16
17
## Environment Creation
18
19
### Composable Environment
20
21
```kotlin { .api }
22
@Composable
23
fun rememberResourceEnvironment(): ResourceEnvironment
24
```
25
26
Creates and remembers a ResourceEnvironment based on the current Compose context. This automatically tracks changes in system locale, theme, and density, updating resources as needed.
27
28
**Usage:**
29
```kotlin
30
@Composable
31
fun LocalizedContent() {
32
val environment = rememberResourceEnvironment()
33
34
// Environment automatically reflects:
35
// - Current system locale (language + region)
36
// - System theme (light/dark mode)
37
// - Screen density (MDPI, HDPI, XHDPI, etc.)
38
39
LaunchedEffect(environment) {
40
// React to environment changes
41
updateContentForEnvironment(environment)
42
}
43
}
44
```
45
46
### System Environment
47
48
```kotlin { .api }
49
fun getSystemResourceEnvironment(): ResourceEnvironment
50
```
51
52
Provides the resource environment for non-Composable contexts. This is an expensive operation and should be used sparingly, preferably with caching.
53
54
**Usage:**
55
```kotlin
56
suspend fun loadSystemResources() {
57
val environment = getSystemResourceEnvironment()
58
val welcomeMessage = getString(environment, Res.string.welcome)
59
60
// Use environment for multiple resource loads
61
val appName = getString(environment, Res.string.app_name)
62
val version = getString(environment, Res.string.version_info)
63
}
64
65
class BackgroundService {
66
private var cachedEnvironment: ResourceEnvironment? = null
67
68
suspend fun processWithResources() {
69
val environment = cachedEnvironment ?: getSystemResourceEnvironment().also {
70
cachedEnvironment = it
71
}
72
73
val statusMessage = getString(environment, Res.string.processing_status)
74
updateNotification(statusMessage)
75
}
76
}
77
```
78
79
## Qualifiers
80
81
### Language Qualifier
82
83
```kotlin { .api }
84
class LanguageQualifier(val language: String) : Qualifier
85
```
86
87
Represents the language component of the locale (ISO 639-1 language codes).
88
89
**Common Language Codes:**
90
- `"en"` - English
91
- `"es"` - Spanish
92
- `"fr"` - French
93
- `"de"` - German
94
- `"ja"` - Japanese
95
- `"zh"` - Chinese
96
- `"ar"` - Arabic
97
98
### Region Qualifier
99
100
```kotlin { .api }
101
class RegionQualifier(val region: String) : Qualifier
102
```
103
104
Represents the region/country component of the locale (ISO 3166-1 alpha-2 country codes).
105
106
**Common Region Codes:**
107
- `"US"` - United States
108
- `"GB"` - United Kingdom
109
- `"CA"` - Canada
110
- `"FR"` - France
111
- `"DE"` - Germany
112
- `"JP"` - Japan
113
- `"CN"` - China
114
115
### Theme Qualifier
116
117
```kotlin { .api }
118
enum class ThemeQualifier : Qualifier {
119
LIGHT,
120
DARK;
121
122
companion object {
123
fun selectByValue(isDark: Boolean): ThemeQualifier
124
}
125
}
126
```
127
128
Represents the system theme preference for light or dark mode.
129
130
**Usage:**
131
```kotlin
132
val currentTheme = ThemeQualifier.selectByValue(isSystemInDarkTheme())
133
134
// Manual theme selection
135
val lightTheme = ThemeQualifier.LIGHT
136
val darkTheme = ThemeQualifier.DARK
137
```
138
139
### Density Qualifier
140
141
```kotlin { .api }
142
enum class DensityQualifier(val dpi: Int) : Qualifier {
143
LDPI(120), // Low density
144
MDPI(160), // Medium density (baseline)
145
HDPI(240), // High density
146
XHDPI(320), // Extra high density
147
XXHDPI(480), // Extra extra high density
148
XXXHDPI(640); // Extra extra extra high density
149
150
companion object {
151
fun selectByValue(dpi: Int): DensityQualifier
152
fun selectByDensity(density: Float): DensityQualifier
153
}
154
}
155
```
156
157
Represents the screen density for selecting appropriate image and layout resources.
158
159
**Density Selection:**
160
```kotlin
161
// From DPI value
162
val density1 = DensityQualifier.selectByValue(320) // Returns XHDPI
163
164
// From density multiplier
165
val density2 = DensityQualifier.selectByDensity(2.0f) // Returns XHDPI
166
```
167
168
## Resource Selection Algorithm
169
170
The library uses a sophisticated algorithm to select the best matching resource:
171
172
### Priority Order
173
1. **Locale matching** (language + region)
174
- Exact match (language + region)
175
- Language match (ignoring region)
176
- Default (no locale qualifiers)
177
178
2. **Theme matching**
179
- Exact theme match
180
- Default (no theme qualifier)
181
182
3. **Density matching**
183
- Exact or higher density match (preferred)
184
- Lower density with upscaling
185
- Default (MDPI assumed)
186
- LDPI as last resort
187
188
### Selection Examples
189
190
**Available Resources:**
191
```
192
drawable/icon.png # Default (MDPI)
193
drawable-hdpi/icon.png # High density
194
drawable-xhdpi/icon.png # Extra high density
195
drawable-night/icon.png # Dark theme default
196
drawable-night-hdpi/icon.png # Dark theme high density
197
```
198
199
**Selection for XHDPI + Dark Theme:**
200
1. `drawable-night-xhdpi/icon.png` (exact match) - **Not available**
201
2. `drawable-night-hdpi/icon.png` (theme + lower density) - **Selected**
202
203
### Custom Environment Creation
204
205
```kotlin
206
// Create specific environment for testing
207
val testEnvironment = ResourceEnvironment(
208
language = LanguageQualifier("es"),
209
region = RegionQualifier("MX"),
210
theme = ThemeQualifier.DARK,
211
density = DensityQualifier.XXHDPI
212
)
213
214
// Use for resource loading
215
suspend fun loadSpanishResources(): String {
216
return getString(testEnvironment, Res.string.welcome_message)
217
}
218
```
219
220
## Environment Monitoring
221
222
### Compose Integration
223
224
```kotlin
225
@Composable
226
fun EnvironmentAwareContent() {
227
val environment = rememberResourceEnvironment()
228
229
// React to specific environment changes
230
LaunchedEffect(environment.theme) {
231
// Theme changed
232
updateThemeSpecificContent(environment.theme)
233
}
234
235
LaunchedEffect(environment.language, environment.region) {
236
// Locale changed
237
updateLocalizedContent(environment.language, environment.region)
238
}
239
}
240
```
241
242
### Environment Comparison
243
244
```kotlin
245
fun environmentChanged(old: ResourceEnvironment, new: ResourceEnvironment): Boolean {
246
return old.language != new.language ||
247
old.region != new.region ||
248
old.theme != new.theme ||
249
old.density != new.density
250
}
251
252
// Track specific changes
253
fun localeChanged(old: ResourceEnvironment, new: ResourceEnvironment): Boolean {
254
return old.language != new.language || old.region != new.region
255
}
256
```
257
258
## Platform-Specific Behavior
259
260
### Android
261
- Automatic locale detection from system settings
262
- Theme detection from `Configuration.uiMode`
263
- Density detection from display metrics
264
- Supports all Android density buckets
265
266
### Desktop (JVM)
267
- Locale from `Locale.getDefault()`
268
- Theme detection from system preferences (when available)
269
- Density simulation for high-DPI displays
270
- Cross-platform consistency
271
272
### iOS
273
- Locale from `NSLocale.currentLocale`
274
- Theme detection from `UITraitCollection`
275
- Density mapping to Retina display scales
276
- Native iOS locale and theme integration
277
278
### Web (JS/Wasm)
279
- Browser locale detection from `navigator.language`
280
- Media query-based theme detection
281
- Device pixel ratio for density
282
- Progressive enhancement for older browsers
283
284
## Advanced Usage
285
286
### Environment Caching
287
288
```kotlin
289
class EnvironmentCache {
290
private var cachedEnvironment: ResourceEnvironment? = null
291
private var lastCheck: Long = 0
292
private val cacheTimeout = 5000L // 5 seconds
293
294
fun getCurrentEnvironment(): ResourceEnvironment {
295
val now = System.currentTimeMillis()
296
if (cachedEnvironment == null || now - lastCheck > cacheTimeout) {
297
cachedEnvironment = getSystemResourceEnvironment()
298
lastCheck = now
299
}
300
return cachedEnvironment!!
301
}
302
}
303
```
304
305
### Multi-Environment Resource Loading
306
307
```kotlin
308
suspend fun loadMultiLanguageContent(): Map<String, String> {
309
val languages = listOf("en", "es", "fr", "de")
310
val content = mutableMapOf<String, String>()
311
312
languages.forEach { lang ->
313
val environment = ResourceEnvironment(
314
LanguageQualifier(lang),
315
RegionQualifier(""),
316
ThemeQualifier.LIGHT,
317
DensityQualifier.MDPI
318
)
319
content[lang] = getString(environment, Res.string.app_description)
320
}
321
322
return content
323
}
324
```
325
326
### Environment-Specific Resource Preloading
327
328
```kotlin
329
class ResourcePreloader {
330
suspend fun preloadForEnvironment(environment: ResourceEnvironment) {
331
// Preload commonly used resources for this environment
332
val commonResources = listOf(
333
Res.string.app_name,
334
Res.string.loading,
335
Res.string.error_message,
336
Res.drawable.app_icon
337
)
338
339
commonResources.forEach { resource ->
340
when (resource) {
341
is StringResource -> getString(environment, resource)
342
is DrawableResource -> getDrawableResourceBytes(environment, resource)
343
}
344
}
345
}
346
}
347
```
348
349
## Testing and Development
350
351
### Environment Mocking
352
353
```kotlin
354
// Create test environments for different scenarios
355
val testEnvironments = mapOf(
356
"US English" to ResourceEnvironment(
357
LanguageQualifier("en"), RegionQualifier("US"),
358
ThemeQualifier.LIGHT, DensityQualifier.XHDPI
359
),
360
"Spanish Mexico" to ResourceEnvironment(
361
LanguageQualifier("es"), RegionQualifier("MX"),
362
ThemeQualifier.DARK, DensityQualifier.XXHDPI
363
),
364
"French Canada" to ResourceEnvironment(
365
LanguageQualifier("fr"), RegionQualifier("CA"),
366
ThemeQualifier.LIGHT, DensityQualifier.HDPI
367
)
368
)
369
370
// Test resource selection
371
testEnvironments.forEach { (name, environment) ->
372
val text = getString(environment, Res.string.welcome_message)
373
println("$name: $text")
374
}
375
```
376
377
### Environment Validation
378
379
```kotlin
380
fun validateResourceCoverage(
381
environments: List<ResourceEnvironment>,
382
resources: List<StringResource>
383
) {
384
environments.forEach { environment ->
385
resources.forEach { resource ->
386
try {
387
val text = getString(environment, resource)
388
println("✓ ${resource.key} available for $environment")
389
} catch (e: Exception) {
390
println("✗ ${resource.key} missing for $environment: ${e.message}")
391
}
392
}
393
}
394
}
395
```
396
397
## Best Practices
398
399
1. **Cache environment objects** when used frequently:
400
```kotlin
401
@Composable
402
fun OptimizedContent() {
403
val environment = rememberResourceEnvironment()
404
// Environment is automatically cached and updated by Compose
405
}
406
```
407
408
2. **Use appropriate environment access**:
409
```kotlin
410
// In Composables - use rememberResourceEnvironment()
411
@Composable
412
fun MyComposable() {
413
val env = rememberResourceEnvironment()
414
}
415
416
// Outside Composables - use getSystemResourceEnvironment() sparingly
417
suspend fun backgroundTask() {
418
val env = getSystemResourceEnvironment()
419
}
420
```
421
422
3. **Test with multiple environments**:
423
- Different locales (language + region combinations)
424
- Both light and dark themes
425
- Various screen densities
426
- Edge cases (missing resources, fallbacks)
427
428
4. **Handle environment changes gracefully**:
429
```kotlin
430
@Composable
431
fun AdaptiveContent() {
432
val environment = rememberResourceEnvironment()
433
434
// Content adapts automatically to environment changes
435
val title = stringResource(Res.string.title)
436
val icon = painterResource(Res.drawable.icon)
437
438
// Resources are automatically reloaded when environment changes
439
}
440
```
441
442
5. **Provide appropriate fallbacks**:
443
- Always include default resources (no qualifiers)
444
- Test resource selection with missing variants
445
- Handle graceful degradation for unsupported configurations