I am looking for help on how to share image binary content from my PhotoApp to external Social Media apps.
I load the image from an API using Coil and display the image in a composable.
#Composable
fun PhotoDetailsScreen( photo: AstroPhoto ... ) {
val context = LocalContext.current
val imgUri = photo.url.toUri()
.buildUpon()
.scheme("https")
.build()
Column ( ...
//Image
Image(
painter = rememberImagePainter(
data = imgUri,
builder = {
crossfade(true)
placeholder(R.drawable.loading_animation)
}
) ...
//Assist Chip
AssistChip(onClick = { shareAstroPhoto("", context) }
Clicking on the AssistChip calls the below shareAstroPhoto() fxn that takes the uri pointing to the image file to fire an ACTION_SEND Intent
fun shareAstroPhoto(uri:String, context: Context){
val intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_STREAM, uri)
type = "image/jpg"
}
context.startActivity(Intent.createChooser(intent, null))
}
I intend to get a Bitmap out of my composable, save the Bitmap in my own ContentProvider or MediaStore (I know how these 2 work) and then pass the uri of the saved BitMap to Intent.EXTRA_STREAM.
Have browsed through similar cases and videos but all I find is working with XML code.
Therefore my query is how to convert Jetpack Compose Image from a composable into a Bitmap to enable sharing the file with other external apps through Android Sharesheet.
I'm not sure if I understand your question. If you just want to share the image, then:
Util function:
fun Context.shareImage(title: String, image: Drawable, filename: String) {
val file = try {
val outputFile = File(cacheDir, "$filename.png")
val outPutStream = FileOutputStream(outputFile)
image.toBitmap().compress(CompressFormat.PNG, 100, outPutStream)
outPutStream.flush()
outPutStream.close()
outputFile
} catch (e: Throwable) {
return toast(e)
}
val uri = file.toUriCompat(this)
val shareIntent = Intent().apply {
action = Intent.ACTION_SEND
type = "image/png"
putExtra(Intent.EXTRA_STREAM, uri)
}
startActivity(Intent.createChooser(shareIntent, title))
}
Share image with (Coil 2.0):
val painter = rememberAsyncImagePainter(
ImageRequest.Builder(LocalContext.current)
.data(url)
.build()
)
Button(
onClick={
val state = painter.state as? AsyncImagePainter.State.Success
val drawable = state?.result.drawable
context.shareImage(
"Share image via",
drawable,
"filename"
)
}
)
Updated
Those are small helper functions, but if you want them:
fun File.toUriCompat(context: Context): Uri {
return FileProvider.getUriForFile(context, context.packageName + ".provider", this)
}
fun Context.toast(throwable: Throwable) =
throwable.message?.let { toast(it) }
?: toast(R.string.unknown_error)
fun Context.toast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
You can get drawable using painter in jetpack compose, then use that Drawble to share..
#Composable
fun photoItem(
photoUrl: String,
modifier: Modifier = Modifier,
contentScale: ContentScale = ContentScale.Fit
): Drawable? {
val painter = rememberAsyncImagePainter(
Builder(LocalContext.current)
.placeholder(drawable.placeholder)
.data(photoUrl)
.build()
)
Image(
contentScale = contentScale,
painter = painter,
contentDescription = null,
modifier = modifier,
)
val state = painter.state as? AsyncImagePainter.State.Success
return state?.result?.drawable
}
then cast the Drawable to BitmapDrawable
Related
I want to show picture with Coil from Uri.
Following code shows picture but when I choose one.
I want to store Uri and when Screen starts i want to show it immediately.
val uri = "content://com.android.providers.media.documents/document/image%3A18".toUri()
var imageUri by remember { mutableStateOf<Uri?>(null) }
val context = LocalContext.current
val launcher =
rememberLauncherForActivityResult(contract = ActivityResultContracts.GetContent()) {
uri: Uri? ->
imageUri = uri
}
Column {
Button(onClick = {
//here we are going to add logic for picking image
launcher.launch(
"image/*"
)
}, content = {
Text(text = "Select Image From Gallery")
})
AsyncImage(
model = ImageRequest.Builder(context = context)
.data(imageUri)
.crossfade(true)
.build(),
contentDescription = "",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(144.dp)
)
Image(
painter = rememberAsyncImagePainter(imageUri),
contentDescription = "Picture",
)
val uri is uri I chooshed before and I copied it and put to string to show image.
But this way first I need to choose picture and it will show. If I put uri instead of imageUri in .data() or rememberAsyncImagePainter nothing happens.
Hey I'm new to kotlin and I'm having problem with this. The images are in the drawable folder. And I need to share the image in jetpack composer. When I click now I can only see the text but not the image to share.
Image(
painter = painterResource(id = image.imgId),
contentDescription = "Test",
modifier = Modifier.fillMaxWidth()
)
var pickedImageUri by remember { mutableStateOf<Uri?>(null) }
val context = LocalContext.current
val intent = Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_STREAM, pickedImageUri)
putExtra(Intent.EXTRA_TEXT, "Description of Image")
type = "image/*"
}
val shareIntent = Intent.createChooser(intent, "Share")
Button( onClick = {
//val description = "Image sent"
context.startActivity(shareIntent)
}) {
Text("Share")
}
I have a screen which shows LazyVerticalGrid with Pictures and TopBar with OnClick that creates new activity for result to choose a Directory for pictures to show
#ExperimentalFoundationApi
#Composable
fun DisplayPictures(
pictures: List<Uri>,
navController: NavController,
appBarName: String = stringResource(id = R.string.choose_folder),
onNewDirectoryUri: (uri: Uri?) -> Unit = { }
) {
var showDirectorySelect by remember { mutableStateOf(false) }
if (showDirectorySelect) {
val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.OpenDocumentTree(),
onResult = {
onNewDirectoryUri(it)
showDirectorySelect = false
})
LaunchedEffect(key1 = Unit) {
launcher.launch(null)
}
}
Scaffold(topBar = {
TopAppBar(title = {
Text(
text = appBarName.dropLastWhile { predicate -> predicate == '%' },
Modifier.padding(8.dp)
)
}, Modifier.clickable { showDirectorySelect = true })
})
{
if (!showDirectorySelect) {
LazyVerticalGrid(
maxColumnWidth = 150.dp,
paddingDp = 4.dp,
pictures = pictures
) { uri ->
val route = Screen.Detail.createRoute(
URLEncoder.encode(
uri.toString(),
"UTF-8"
)
)
//Prevent multi-clicking and multi-touch
if (!navController.currentDestination?.route?.contains("detail")!!) {
navController.navigate(route)
}
}
}
}
}
But for some reason my app crashes with TransactionTooLargeException after user chose directory or just closed the app to background(clicked home button for example). I guess the problem is in LazyVerticalGrid(Its another composable function which is just lazy column with rows) which contains Pictures. So Picture is Composable function which loads Image from URI
//A wrapper around Image that shows placeholder and loading image via URI
#ExperimentalFoundationApi
#Composable
fun Picture(
uri: Uri,
modifier: Modifier,
size: Size,
onClick: () -> Unit = {},
) {
val bitmap: MutableState<Bitmap?> = rememberSaveable { mutableStateOf(null) }
//Coil and other libraries that can get image from uri get context this way
val context = LocalContext.current
bitmap.value ?: run {
LaunchedEffect(Unit) {
launch(Dispatchers.IO) {
try {
bitmap.value = context.contentResolver.loadThumbnail(uri, size, null)
} catch (e: Exception) {
}
}
}
}
Box(
modifier = modifier
.aspectRatio(1f)
.placeholder(visible = bitmap.value == null)
) {
bitmap.value?.let {
Log.i("longgg",uri.toString())
Image(
bitmap = it.asImageBitmap(),
contentDescription = null,
contentScale = ContentScale.Crop,
modifier = Modifier
.clip(RectangleShape)
.fillMaxSize()
.clickable { onClick() }
)
}
}
As I researched the whole Compose function DisplayPictures does recompose and LazyGrid download all pictures again after ActivityResultContract. This results to load a lot of thumbnails and I get a TransactionTooLargeException. The onNewDirectoryUri creates new mvi Event which starts DisplayPictures but with new params(new pictures and etc). How can I fix this behavior?
I found out that loadThumbnail method creates bundle under the hood
final Bundle opts = new Bundle();
opts.putParcelable(EXTRA_SIZE, new Point(size.getWidth(), size.getHeight()));
TransactionTooLargeException occurs when the data being passed among two activities is too large I think the bundle has limitations on how much data it can carry. Check what result you are sending through bundles.
Try to avoid sending bitmaps instead just send the URL of the image and reload the same in your activity.
I have this composable that used to work fine, but now after some libraries update it doesn't.
I'm using a ViewModel to save an image returned from ActivityResultContracts.TakePicture() and show it in an Image within a Box.
The PhotoButton composable is working fine and return the correct image url, but the image string in my main composable is always null.
SamplePage.kt
#Composable
fun SamplePage(navController: NavController) {
val inputViewModel = InputViewModel()
val context = LocalContext.current
Column{
InputFields(inputViewModel, navController, setPerm)
}
}
#Composable
fun InputFields(inputViewModel: InputViewModel, navController: NavController) {
val image: String by inputViewModel.image.observeAsState("")
Column() {
Row(verticalAlignment = Alignment.CenterVertically) {
Box(contentAlignment = Alignment.Center) {
val painter = rememberImagePainter(data = image)
Image(
painter = painter,
contentScale = ContentScale.FillWidth,
contentDescription = null
)
if (painter.state !is ImagePainter.State.Success) {
Icon(
painter = painterResource(id = R.drawable.icon),
contentDescription = null
)
}
}
PhotoButton() {
inputViewModel.onImageChange(it)
}
}
}
}
class InputViewModel : ViewModel() {
private val _image: MutableLiveData<String> = MutableLiveData("")
val image: LiveData<String> = _image
fun onImageChange(newImage: String) {
_image.value = newImage
}
}
PhotoButton.kt
#Composable
fun PhotoButton(onValChange: ((String) -> Unit)?){
val context = LocalContext.current
val storageDir: File? = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
val file = File(storageDir, "picFromCamera")
val uri = FileProvider.getUriForFile(
context,
context.packageName.toString() + ".provider",
file
)
val launcher = rememberLauncherForActivityResult(ActivityResultContracts.TakePicture()) {
if (onValChange != null) {
onValChange(uri.toString())
}
}
FAB() {
launcher.launch(uri)
}
}
You're creating a new view model on each recomposition:
val inputViewModel = InputViewModel()
Instead you should use viewModel(): it'll create a new view model on the first call, and store it for the future calls:
val inputViewModel = viewModel<InputViewModel>()
Check out more about view models usage in compose state documentation.
I am using accompanist-coil:0.12.0. I want to load image from a url and then pass the drawable to a method. I am using this:
val painter = rememberCoilPainter(
request = ImageRequest.Builder(LocalContext.current)
.data(imageUrl)
.target {
viewModel.calcDominantColor(it) { color ->
dominantColor = color
}
}
.build(),
fadeIn = true
)
and then passing the painter to Image like this:
Image(
painter = painter,
contentDescription = "Some Image",
)
The image loads without any problem but the method calcDominantColor is never called.
Am I doing it the wrong way?
UPDATE:
I was able to call the method using Transformation in requestBuilder but I am not sure, if this is how it is supposed to be done because I am not actually transforming the Bitmap itself:
val painter = rememberCoilPainter(
request = entry.imageUrl,
requestBuilder = {
transformations(
object: Transformation{
override fun key(): String {
return entry.imageUrl
}
override suspend fun transform(
pool: BitmapPool,
input: Bitmap,
size: Size
): Bitmap {
viewModel.calcDominantColor(input) { color ->
dominantColor = color
}
return input
}
}
)
}
)
This works fine for first time but when the composable recomposes, transformation is returned from cache and my method doesn't run.
I think you want to use LaunchedEffect along with an ImageLoader to access the bitmap from the loader result.
val context = LocalContext.current
val imageLoader = ImageLoader(context)
val request = ImageRequest.Builder(context)
.transformations(RoundedCornersTransformation(12.dp.value))
.data(imageUrl)
.build()
val imagePainter = rememberCoilPainter(
request = request,
imageLoader = imageLoader
)
LaunchedEffect(key1 = imagePainter) {
launch {
val result = (imageLoader.execute(request) as SuccessResult).drawable
val bitmap = (result as BitmapDrawable).bitmap
val vibrant = Palette.from(bitmap)
.generate()
.getVibrantColor(defaultColor)
// do something with vibrant color
}
}
I would suggest using the new coil-compose library. Just copy the following and add it to the app build.gradle file:
implementation "io.coil-kt:coil-compose:1.4.0"
I was also following the tutorial and got stuck at this point. I would suggest copying and pasting the following code:
Column {
val painter = rememberImagePainter(
data = entry.imageUrl
)
val painterState = painter.state
Image(
painter = painter,
contentDescription = entry.pokemonName,
modifier = Modifier
.size(120.dp)
.align(CenterHorizontally),
)
if (painterState is ImagePainter.State.Loading) {
CircularProgressIndicator(
color = MaterialTheme.colors.primary,
modifier = Modifier
.scale(0.5f)
.align(CenterHorizontally)
)
}
else if (painterState is ImagePainter.State.Success) {
LaunchedEffect(key1 = painter) {
launch {
val image = painter.imageLoader.execute(painter.request).drawable
viewModel.calcDominantColor(image!!) {
dominantColor = it
}
}
}
}
Text(
text = entry.pokemonName,
fontFamily = RobotoCondensed,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth()
)
}
Replace your "AsyncImage" with "AsyncImageWithDrawable" :
#Composable
fun AsyncImageWithDrawable(
model: Any?,
contentDescription: String?,
modifier: Modifier = Modifier,
placeholderResId: Int? = null,
errorResId: Int? = null,
fallbackResId: Int? = errorResId,
contentScale: ContentScale,
onDrawableLoad: (Drawable?) -> Unit) {
val painter = rememberAsyncImagePainter(
ImageRequest.Builder(LocalContext.current).data(data = model)
.apply(block = fun ImageRequest.Builder.() {
crossfade(true)
placeholderResId?.let { placeholder(it) }
errorResId?.let { error(it) }
fallbackResId?.let { fallback(it) }
allowHardware(false)
}).build()
)
val state = painter.state
Image(
painter = painter,
contentDescription = contentDescription,
modifier = modifier,
contentScale = contentScale
)
when (state) {
is AsyncImagePainter.State.Success -> {
LaunchedEffect(key1 = painter) {
launch {
val drawable: Drawable? =
painter.imageLoader.execute(painter.request).drawable
onDrawableLoad(drawable)
}
}
}
else -> {}
}
}
I've created this Composable inspired by #Aknk answer and Coil source code.
Hint: You can use this Composable to Load and Render your Image from Url and get your Image Palette by the returned Drawable.
You can use the onSuccess callback to get the drawable:
AsyncImage(
model = url,
contentDescription = null,
onSuccess = { success ->
val drawable = success.result.drawable
}
)
You can use the same approach also with rememberAsyncImagePainter.