20 Sep 2018
Belle
Receiving images from other apps on Android
Today I was working on making it possible to share an image (or several) from another app to Pico on Android. I needed to take the image I received and upload it to the Micro.blog API. It took me a while to piece together the different parts of this process, so I wanted to share how it works.
First up, I needed to register to receive images in my app's Manifest. Here's the example I used from the Android developer guides:
<activity android:name=".ui.MyActivity" >
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
This guide follows on with some example code for handling the data that's been shared with your app. The example includes two separate methods for handling images—one for a single image, and one for multiple (in Kotlin):
private fun handleSendImage(intent: Intent) {
(intent.getParcelableExtra<Parcelable>(Intent.EXTRA_STREAM) as? Uri)?.let {
// Update UI to reflect image being shared
}
}
private fun handleSendMultipleImages(intent: Intent) {
intent.getParcelableArrayListExtra<Parcelable>(Intent.EXTRA_STREAM)?.let {
// Update UI to reflect multiple images being shared
}
}
You can see that in the case where only a single image was shared, the example code casts the Parcelable
from the intent extra to a Uri
, and only runs the code inside the let
lambda if this cast succeeds. This is great, because I'd already implemented an image picker which returned me a Uri
, so I knew how to turn the Uri
into a ByteArray
that I could send to an API. Here's how I did that:
private fun imageFrom(uri: Uri): ByteArray {
val stream = contentResolver.openInputStream(uri)
val bitmap = BitmapFactory.decodeStream(stream)
stream.close()
val baos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)
val image = baos.toByteArray()
return image
}
But if you look at the example code above for handling multiple images received, you'll see I'm dealing with a Parcelable
ArrayList
. This is where I got stuck, because I needed a Uri
for each image, but I couldn't figure out how to cast this ArrayList
or even cast each item within it to what I needed.
It seems that this is because Parcelable
is an Interface
, so this ArrayList
is full of items that conform to Parcelable
, but they could be of any old type. The Kotlin compiler can't be sure that they're all Uri
instances when I try to cast them that way. When I tried to cast the entire ArrayList
to an ArrayList<Uri>
I got a compiler warning that this was an "unchecked cast", which led me to this Stack Overflow question.
As the chosen answer to that question explains you can suppress these warnings, but since I'm not 100% sure that these will all be Uri
s, I went for a safer, but convenient option: Kotlin's filterIsInstance
, which returns a List
of the class you ask for, including only the items from the original ArrayList
that are actually instances of that class. And it's just one line!
val uris: List<Uri> = it.filterIsInstance<Uri>()
So now I have a List
of Uri
s that I can loop over, calling my imageFrom(uri)
method on each one. Here's the final handleSendMultipleImages
function:
private fun handleSendMultipleImages(intent: Intent) {
intent.getParcelableArrayListExtra<Parcelable>(Intent.EXTRA_STREAM)?.let {
val uris: List<Uri> = it.filterIsInstance<Uri>()
for (uri in uris) {
val image = imageFrom(uri)
postImage(image)
}
}
}