In jetpack compose, you can display drawables easily with the Image composable like this:
Image(painter = painterResource(id = R.drawable.my_drawable))
I'm building an app that requires listing all the apps on the device and I need to display their icons. I managed to get the icons using the PackageManager class:
val packageManager = context.packageManager
val installedPackages = packageManager.getInstalledPackages(0)
val packageInfo = installedPackages[0]
val iconId: Int = packageInfo.applicationInfo.icon
Then:
Image(painter = painterResource(id = iconId))
But the app crashes everytime. I don't know where the problem is? Any Idea? Thanks
android.content.res.Resources$NotFoundException: Resource ID #0x7f030001
For some reason icon may contain identifier that points to non-existent drawable.
To get application icon use method loadIcon:
val icon: Drawable = applicationInfo.loadIcon(packageManager)
Or method getApplicationIcon from PackageManager:
val icon: Drawable = packageManager.getApplicationIcon(applicationInfo)
Then, use AndroidDrawablePainter to convert Drawable to Painter:
Image(painter = AndroidDrawablePainter(icon))
Or DrawablePainter is available in accompanist library:
Image(painter = DrawablePainter(icon))
If you're using the Coil library, rememberImagePainter will accept an Any? as its data argument, allowing you to use the Drawable returned from the PackageManager, like so:
Image(
painter = rememberImagePainter(
data = LocalContext.current.packageManager.getApplicationIcon("com.package.example")
)
)
Related
I am trying to make the tutorials from a Jetpack Compose book but the app crashes when I start it:
This is the error code
2022-09-15 22:03:05.983 14360-14360/com.raywenderlich.android.jetreddit E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.raywenderlich.android.jetreddit, PID: 14360
java.lang.IllegalArgumentException: Only VectorDrawables and rasterized asset types are supported ex. PNG, JPG
at androidx.compose.ui.res.PainterResources_androidKt.loadImageBitmapResource(PainterResources.android.kt:99)
at androidx.compose.ui.res.PainterResources_androidKt.painterResource(PainterResources.android.kt:71)
at com.raywenderlich.android.jetreddit.components.PostKt.ImageContent(Post.kt:196)
at com.raywenderlich.android.jetreddit.components.PostKt$ImagePost$1.invoke(Post.kt:74)
at com.raywenderlich.android.jetreddit.components.PostKt$ImagePost$1.invoke(Post.kt:73)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
That's the position from the error code:
#Composable
fun ImageContent(image: Int) {
val imageAsset = ImageBitmap.imageResource(id = image)
Image(
bitmap = imageAsset,
contentDescription = stringResource(id = R.string.post_header_description),
modifier = Modifier
.fillMaxWidth()
.aspectRatio(imageAsset.width.toFloat() / imageAsset.height),
contentScale = ContentScale.Crop
)
}
#Composable
fun ImagePost(post: PostModel) {
Post(post) {
ImageContent(post.image ?: R.drawable.compose_course)
}
}
From the exception, It seems you are trying to load an image that is not supported by ImageBitmap.imageResource.
java.lang.IllegalArgumentException: Only VectorDrawables and rasterized asset types are supported ex. PNG, JPG.
Check that if the type of post.image or R.drawable.compose_course is supported by the API you are using.
I take uri from db and would like to show an image:
val uri by viewModel.uri.collectAsState()
Image(
painter = rememberAsyncImagePainter(
ImageRequest
.Builder(LocalContext.current)
.data(data = uri)
.build()
),
contentDescription = ""
)
but it is not loading - it is blank.
When debugging I can see that on recomposition
Image->Uri->uriString is set to correct value - "content://com.android.providers.media.documents/document/image%3A44"
but itis still not visible.
It is only visible if I pick it again using:
val pickMedia =
rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
if (uri != null) {
viewModel.onImageSelected(uri)
} else {
Log.d("PhotoPicker", "No media selected")
}
}
I am reusing the same image in many places and from now on it is visible everywhere (but still no other image is visible until I repick it again).
After closing and reopening app images are not visible again.
Maybe it is some caching? or permissions?
I have a project with several flavors. Each of these flavors has its own configuration which is available as a json file in the assets folder in the respective project structure.
In the theme definition I read the JSON using Gson and cast it into a corresponding model.
My problem is now the following:
At runtime of the app this all works wonderfully but in the composable preview in Android Studio it unfortunately only works for a single flavor. As soon as I switch to another flavor in the build variant, the json-asset of the old variant continues to load. Since the configuration also contains assets that are only available in the respective flavors, this leads to a crash of the preview.
I debugged the preview handling by throwing some exceptions during the casting and it seems, like if there'S something cached and not reset after build-variant change. A restart of Android Studio didn't also help so I don't quite know what to do about it.
Has anyone noticed a similar behavior and/or found a solution for it?
Here is some code to explain::
My theme definition:
object AppTheme {
val colors: AppColors
#Composable
#ReadOnlyComposable
get() = LocalAppColors.current
val typography: AppTypography
#Composable
#ReadOnlyComposable
get() = LocalAppTypography.current
val configuration: ConfigurationC
#Composable
#ReadOnlyComposable
get() = LocalAppConfiguration.current
}
private val LocalAppColors = staticCompositionLocalOf {
lightAppColors
}
private val LocalAppTypography = staticCompositionLocalOf {
appTypography
}
private val LocalAppConfiguration = staticCompositionLocalOf {
ConfigurationC()
}
#Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: #Composable () -> Unit,
) {
val colors = if (darkTheme) darkAppColors else lightAppColors
CompositionLocalProvider(
LocalAppConfiguration provides ConfigurationC.init(LocalContext.current),
LocalAppColors provides colors,
LocalAppTypography provides typography,
) {
MaterialTheme(
colors = colors.materialColors,
typography = typography.materialTypography,
content = content,
)
}
}
A simple Preview:
#Composable
#Preview(name = "light", showBackground = true)
#Preview(name = "dark", showBackground = true, uiMode = UI_MODE_NIGHT_YES)
fun EnabledPreview() {
AppTheme {
Button.MyCustomButton(
modifier = Modifier,
title = "Custom Button",
font = AppTheme.configuration.font.h1
color = AppTheme.configuration.colors.text1
enabled = enabled,
onClick = {}
)
}
}
I'm currently getting an index out of bound which I can't figure out why...
Following situation:
Sometimes I update my compose bottom navigation to add an item or to change tint color etc. When the app is running and the update is happening it works. But when i close the app and it starts to initialize the navigation with the new state I get an index out of bound exception on the painterResource...
Would be nice if anyone has a hint for me. Another thing to add is i wire the compose currently to xml but i can't see a problem with that.
Thanks in advance
BottomNavigation(
backgroundColor = backgroundColor,
) {
navigation.navigationItems.forEach { navigationItem ->
val isSelected = selectedItem == navigationItem.destination
val iconResId =
if (isSelected) navigationItem.selectedIconRes else navigationItem.iconRes
val hasBadge = badges.contains(navigationItem.destination)
BottomNavigationItem(
icon = {
if (hasBadge) {
BadgedBox(
modifier = Modifier.background(badgeBackgroundColor),
badge = { Badge(backgroundColor = badgeColor) }
) {
Icon(
painterResource(iconResId),
contentDescription = null
)
}
} else {
Icon(
painterResource(iconResId),
contentDescription = null
)
}
},
selected = isSelected,
onClick = { onItemSelected(navigationItem.destination) },
alwaysShowLabel = true,
selectedContentColor = selectedTintColor,
unselectedContentColor = tintColor
)
}
}
Update:
I found now out why it happens but it’s still strange. The drawable itself produced somehow that error. After putting a different one there it worked all the time. The question is why did it not work after restarting the app but when running on the dynamic change… I have no idea…
Update 2:
After the dynamic change there are artifacts happening within the icons… For example 3rd icon has artifacts of 4th etc. What could cause that? Any idea?
I have a caller app that allows the user to pick up photos for the contacts and audio files for the ringtones from their mobile.
When I pick up the photo, it can be displayed correctly using the URI on the interface. on both the main page and contact page.
and i save it inside the room database...
if i close the app and reopen it, it gets the same URI from db. but no image is displayed. neither on the main page or contact page.
i tried to insert the URI code manually it didn't display anything as well... also change the image loader to GlideImage from rememberImagePainter (another implementation) but same issue...
the URI i have from db and image picker looks like this
val uri2:Uri = "content://com.android.providers.media.documents/document/image%3A34".toUri()
the code the pick up the image Uri is this
class GetContentActivityResult(
private val launcher: ManagedActivityResultLauncher<String, Uri>,
val uri: Uri?
) {
fun launch(mimeType: String) {
launcher.launch(mimeType)
}
}
#Composable
fun rememberGetContentActivityResult(): GetContentActivityResult {
var uri by rememberSaveable { mutableStateOf<Uri?>(null) }
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent(), onResult = {
uri = it
})
return remember(launcher, uri) { GetContentActivityResult(launcher, uri) }
}
i display the image in the contact page like this
val getContent = rememberGetContentActivityResult() <-- to access the class of pickup files
if (roomViewModel.photoUri.value.isBlank()){ <-- if no image from db display the picked image using image picker
getContent.uri?.let {
Image(
modifier = Modifier
.align(Alignment.TopCenter)
.fillMaxSize(1f),
contentDescription = "UserImage",
contentScale = ContentScale.Crop,
painter = rememberImagePainter(data = it))
}
}else{
Image(
modifier = Modifier
.align(Alignment.TopCenter)
.fillMaxSize(1f),
contentDescription = "UserImage",
contentScale = ContentScale.Crop,
painter = rememberImagePainter(data = roomViewModel.photoUri.value))
}
}
TextButton(
onClick = { getContent.launch("image/*") }, <-- launching the class to get the image URI by defining the Mime as image/*
modifier = Modifier.layoutId("changePhoto")
) {
Text(
text = "Change Photo",
color = Color.Black)}
inside the main page. there is no image picker, i just display the data that i have in the Database, and i display the image this way
#OptIn(ExperimentalCoilApi::class)
#Composable
fun ProfilePicture(uri : Uri, imageSize: Dp) {
Card(
shape = CircleShape,
border = BorderStroke(
width = 2.dp,
color = Color.Red ),
modifier = Modifier.padding(16.dp),
elevation = 4.dp) {
val uri2:Uri = "content://com.android.providers.media.documents/document/image%3A34".toUri() //<-- i tried to load the image this way also it didn't work.
val painter = rememberImagePainter(
data = uri,
builder = {
placeholder(R.drawable.yara) <-- the placeholder image blinks quickly when app is loaded and then disappear...
}
)
Image(
painter = painter,
contentDescription = "User Image",
modifier = Modifier.size(imageSize) )
}
}
Is it an issue with the Uri picker or image display?
other content from room db are displayed correctly (strings)
i tried to use the code on emulator and real device and it is the same for both cases...
one thing i just noticed... if i used the image picker and pick a previously picked image (it's uri is in the db) the image is displayed on the other locations in the app automatically without doing anything else... just picking the image and displaying it without saving it even...
Well, I didn't know how to do the takePersistableUriPermission(), I'm still learning. But I created a converter class (the longest way solution) to use the Uri to convert it to bit map... but i have to make it drawable first. I didn't know how to do it bitmap from Uri directly.
I updated the data class in the first place
#Entity
data class Contact(
#PrimaryKey()
val id: UUID = UUID.randomUUID(),
val name: String,
val land_line : String,
val mobile: String,
val contact_photo: Bitmap
)
Added a TypeConverter for the bitmap in database
#TypeConverter
fun fromBitmap(bitmap: Bitmap): ByteArray {
val outputStream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
return outputStream.toByteArray()
}
#TypeConverter
fun toBitmap(byteArray: ByteArray): Bitmap {
return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
}
Changed the values types all the way to UI, and created a converter class in the Util package to convert the Uri into drawable then Bitmap (I know it looks stupid - any better solution is accepted).
suspend fun convertToBitmap(uri: Uri, context: Context, widthPixels: Int, heightPixels: Int): Bitmap? {
val mutableBitmap = Bitmap.createBitmap(widthPixels, heightPixels, Bitmap.Config.ARGB_8888)
val canvas = Canvas(mutableBitmap)
val drawable = getDrawable(uri, context)
drawable?.setBounds(0, 0, widthPixels, heightPixels)
drawable?.draw(canvas)
return mutableBitmap
}
#OptIn(ExperimentalCoilApi::class)
suspend fun getDrawable(uri: Uri, context: Context): Drawable? {
val imageLoader = ImageLoader.Builder(context)
.availableMemoryPercentage(0.25)
.allowHardware(false)
.crossfade(true)
.build()
val request= ImageRequest.Builder(context)
.data(uri)
.build()
return imageLoader.execute(request).drawable
}
It is working now and loading perfectly. I know that I have the width and height by 50 50 px, but this is a test anyway.