Describe the bug
Hello, We have some legacy screens in our codebase in which the activity handles uiMode in manifest manually:
<activity
android:name="sample.compose.MainActivity"
android:exported="true"
android:configChanges="uiMode"> // Can't touch this
The issue is, when AsyncImage is used, it does not load the drawable-night variant or rather, AsyncImage thinks it's the same exact painter resource:
| Video |
Light Mode |
Dark Mode |
 |
 |
 |
In here, the icon used has 2 versions, one in drawable and one in drawable-night.
To Reproduce
Option A: checkout branch
- Checkout the branch https://github.com/nishatoma/coil/tree/async-error-painter-no-recompose-reproducer
- Run the
samples.compose app
Option B: manual code snippet
Manifest:
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:enableOnBackInvokedCallback="true"
tools:ignore="GoogleAppIndexingWarning,UnusedAttribute">
<profileable
android:shell="true"
tools:targetApi="q"/>
<activity
android:name="sample.compose.MainActivity"
android:exported="true"
android:configChanges="uiMode">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
Code:
setContent {
val painter = painterResource(R.drawable.sample_icon)
val isDark = isSystemInDarkTheme()
val lightColors = lightColorScheme()
val darkColors = darkColorScheme()
MaterialTheme(
colorScheme = if (isDark) darkColors else lightColors,
) {
Surface(
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.fillMaxSize()
) {
Column(
modifier = Modifier.padding(vertical = 8.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = if (isDark) {
"Dark Mode"
} else {
"Light Mode"
},
style = MaterialTheme.typography.headlineLarge
)
Spacer(modifier = Modifier.height(24.dp))
Text("Regular Image")
Spacer(modifier = Modifier.height(8.dp))
Image(
painter = painter,
contentDescription = null
)
Spacer(modifier = Modifier.height(20.dp))
HorizontalDivider(thickness = 2.dp)
Spacer(modifier = Modifier.height(20.dp))
Text("Async Image")
Spacer(modifier = Modifier.height(8.dp))
AsyncImage(
model = "https://imagedoesnotexist.com/image.png",
contentDescription = "Image",
error = painter
)
}
}
}
}
Sample Icon (drawable):
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFF"
android:pathData="M0,0h24v24H0z" />
<path
android:pathData="M0.5,0.5h23v23h-23z"
android:fillColor="@android:color/transparent"
android:strokeColor="#000000"
android:strokeWidth="1"/>
<path
android:fillColor="#000000"
android:pathData="M12,7a5,5 0 1,1 -0.001,0zM12,1v3M12,20v3M4.22,4.22l2.12,2.12M17.66,17.66l2.12,2.12M1,12h3M20,12h3M4.22,19.78l2.12,-2.12M17.66,6.34l2.12,-2.12" />
</vector>
Sample Icon (drawable-night):
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="48dp"
android:height="48dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#000000"
android:pathData="M0,0h24v24H0z" />
<path
android:pathData="M0.5,0.5h23v23h-23z"
android:fillColor="@android:color/transparent"
android:strokeColor="#FFFFFF"
android:strokeWidth="1"/>
<path
android:fillColor="#FFFF00"
android:pathData="M12,7a5,5 0 1,1 -0.001,0zM12,1v3M12,20v3M4.22,4.22l2.12,2.12M17.66,17.66l2.12,2.12M1,12h3M20,12h3M4.22,19.78l2.12,-2.12M17.66,6.34l2.12,-2.12" />
</vector>
Solutions we can't implement due to legacy code:
- Changing manifest property
- Using Subcompose image
- Changing the actual drawable resources breaks current regressions / snapshot tests and is shared between other teams
Possible temporary work-around:
- Have an if-statement that checks if there is an error, and if there is, use a regular image instead of async..
- Force entire AsyncImage to recompose by creating a new size constraint / modifying the model, but this makes network request again
If there is an easier way to handle this I would appreciate some guidance, thank you!
Version
From master branch of coil3
Describe the bug
Hello, We have some legacy screens in our codebase in which the activity handles
uiModein manifest manually:The issue is, when
AsyncImageis used, it does not load thedrawable-nightvariant or rather,AsyncImagethinks it's the same exact painter resource:In here, the icon used has 2 versions, one in
drawableand one indrawable-night.To Reproduce
Option A: checkout branch
samples.composeappOption B: manual code snippet
Manifest:
Code:
Sample Icon (drawable):
Sample Icon (drawable-night):
Solutions we can't implement due to legacy code:
Possible temporary work-around:
If there is an easier way to handle this I would appreciate some guidance, thank you!
Version
From master branch of coil3