How to show an Image via URI for an android glance widget

1.2k Views Asked by At

I have an android homescreen widget created with the new Glance api which contains a lazy column. Each row in the column displays an image with ImageProvider(contentUri).

The image has been retrieved from a URL with Glide and saved to internal storage file with FileOutputStream(filename). see MainActivity below.

When attempting to retrieve and show the image with getUriForFile() my widget just shows a message "Loading..." and never displays the image. No crash occurs. It just never loads the image.

How can I pull the image from internal storage and display it in LazyColumnRow()?

Note that I am only showing one bitmap in this example as proof of concept, but intend to eventually show 10+ bitmaps. I am following the recommendation in below post, which suggests using URI for multiple images.

Crash in glance app widget image when trying to display bitmap

Note that when I pull the same URI in MainActivity() it works and displays the image in ImageView.

Composable defined in GlanceAppWidget Content()

@Composable
fun LazyColumnRow(
    /*LazyColumnRow is called from a LazyColumn, with each filename passed in here*/
) {
    val context = LocalContext.current
    val filepath = File(context.getFilesDir(), "my_images")
    val filename = File(filepath, "default_image.png") /*each LazyColumn item will have different filename in PROD*/
    val contentUri: Uri = FileProvider.getUriForFile(context,"${context.packageName}.fileprovider", filename)
    
    Row(modifier = GlanceModifier) {
        Image(
            modifier = GlanceModifier.size(28.dp),
            provider = ImageProvider(contentUri), /*this is not working*/
            contentDescription = "Image"
        )
    }
}

MainActivity class which downloads and stores Bitmap. It can also succesfully retrieve URI and display in imageView.

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

    @OptIn(ExperimentalAnimationApi::class)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = inflate(layoutInflater)
        setContentView(binding.root)

        //Download image from url
        var bitmap: Bitmap? = null
        CoroutineScope(Dispatchers.IO).launch {
            bitmap = Glide.with(baseContext)
                .asBitmap()
                .load("https://picsum.photos/id/237/200")
                .submit()
                .get()
        }

        //store image in internal storage file
        val filepath = File(baseContext.getFilesDir(), "my_images")
        if (!filepath.exists()) {
            filepath.mkdirs()
        }
        val filename = File(filepath, "default_image.png")
        try {
            FileOutputStream(filename).use { out ->
                bitmap?.compress(Bitmap.CompressFormat.PNG, 100, out)
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }

        //retrieve image from internal storage file
        val contentUri: Uri = getUriForFile(
            baseContext,
            "$packageName.fileprovider",
            filename)

        //display in imageView. This code works.
        val imageView = findViewById<ImageView>(R.id.myImage)
        imageView.setImageURI(contentUri)
    }
}

Content Provider declared in Manifest for FileProvider URI

<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths" />
</provider>

xml/file_paths

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <files-path name="my_images" path="/" />
    <files-path name="my_docs" path="docs/" />
</paths>

1

There are 1 best solutions below

0
On

You need to explicitly grant read permission to the Home activities, for example with:

/**
 * When sharing a content URI to an app widget, we need to grant read access to the Home activity.
 *
 * See https://stackoverflow.com/a/59108192/1474476
 */
fun Context.grantUriPermissionToHomeActivity(uri: Uri, flags: Int = Intent.FLAG_GRANT_READ_URI_PERMISSION) {
    grantUriPermission(packageManager.homeActivityPackages(), uri, flags)
}

fun PackageManager.homeActivityPackages(): List<String> {
    val intent = Intent(Intent.ACTION_MAIN)
    intent.addCategory(Intent.CATEGORY_HOME)
    return queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
        .map { it.activityInfo.packageName }
}

fun Context.grantUriPermission(packages: List<String>, uri: Uri, flags: Int) {
    for (name in packages) {
        grantUriPermission(name, uri, flags)
    }
}