Skip to content

AsyncImage error painter does not update when switching from light/dark mode #3245

@nishatoma

Description

@nishatoma

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
 Image Image

In here, the icon used has 2 versions, one in drawable and one in drawable-night.

To Reproduce

Option A: checkout branch

  1. Checkout the branch https://github.com/nishatoma/coil/tree/async-error-painter-no-recompose-reproducer
  2. 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:

  1. Changing manifest property
  2. Using Subcompose image
  3. Changing the actual drawable resources breaks current regressions / snapshot tests and is shared between other teams

Possible temporary work-around:

  1. Have an if-statement that checks if there is an error, and if there is, use a regular image instead of async..
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions