How to Save an Image in Android Q Using Mediastore

How to save an image in Android Q using MediaStore?

Try the next method. Android Q (and above) already takes care of creating the folders if they don’t exist. The example is hard-coded to output into the DCIM folder. If you need a sub-folder then append the sub-folder name as next:

final String relativeLocation = Environment.DIRECTORY_DCIM + File.separator + “YourSubforderName”;

Consider that the compress format should be related to the mime-type parameter. For example, with a JPEG compress format the mime-type would be "image/jpeg", and so on. Probably you may also want to pass the compress quality as a parameter, in this example is hardcoded to 95.

Java:

@NonNull
public Uri saveBitmap(@NonNull final Context context, @NonNull final Bitmap bitmap,
@NonNull final Bitmap.CompressFormat format,
@NonNull final String mimeType,
@NonNull final String displayName) throws IOException {

final ContentValues values = new ContentValues();
values.put(MediaStore.MediaColumns.DISPLAY_NAME, displayName);
values.put(MediaStore.MediaColumns.MIME_TYPE, mimeType);
values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM);

final ContentResolver resolver = context.getContentResolver();
Uri uri = null;

try {
final Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
uri = resolver.insert(contentUri, values);

if (uri == null)
throw new IOException("Failed to create new MediaStore record.");

try (final OutputStream stream = resolver.openOutputStream(uri)) {
if (stream == null)
throw new IOException("Failed to open output stream.");

if (!bitmap.compress(format, 95, stream))
throw new IOException("Failed to save bitmap.");
}

return uri;
}
catch (IOException e) {

if (uri != null) {
// Don't leave an orphan entry in the MediaStore
resolver.delete(uri, null, null);
}

throw e;
}
}

Kotlin:

@Throws(IOException::class)
fun saveBitmap(
context: Context, bitmap: Bitmap, format: Bitmap.CompressFormat,
mimeType: String, displayName: String
): Uri {

val values = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)
}

val resolver = context.contentResolver
var uri: Uri? = null

try {
uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
?: throw IOException("Failed to create new MediaStore record.")

resolver.openOutputStream(uri)?.use {
if (!bitmap.compress(format, 95, it))
throw IOException("Failed to save bitmap.")
} ?: throw IOException("Failed to open output stream.")

return uri

} catch (e: IOException) {

uri?.let { orphanUri ->
// Don't leave an orphan entry in the MediaStore
resolver.delete(orphanUri, null, null)
}

throw e
}
}

Kotlin variant, with a more functional style:

@Throws(IOException::class)
fun saveBitmap(
context: Context, bitmap: Bitmap, format: Bitmap.CompressFormat,
mimeType: String, displayName: String
): Uri {

val values = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM)
}

var uri: Uri? = null

return runCatching {
with(context.contentResolver) {
insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)?.also {
uri = it // Keep uri reference so it can be removed on failure

openOutputStream(it)?.use { stream ->
if (!bitmap.compress(format, 95, stream))
throw IOException("Failed to save bitmap.")
} ?: throw IOException("Failed to open output stream.")

} ?: throw IOException("Failed to create new MediaStore record.")
}
}.getOrElse {
uri?.let { orphanUri ->
// Don't leave an orphan entry in the MediaStore
context.contentResolver.delete(orphanUri, null, null)
}

throw it
}
}

How to save image to camera folder in Android Q?

The most generalized version I was able to write is:

private Uri saveImage(Context context, Bitmap bitmap, @NonNull String folderName, @NonNull String fileName) throws IOException {
OutputStream fos = null;
File imageFile = null;
Uri imageUri = null;

try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ContentResolver resolver = context.getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/png");
contentValues.put(
MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + File.separator + folderName);
imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);

if (imageUri == null)
throw new IOException("Failed to create new MediaStore record.");

fos = resolver.openOutputStream(imageUri);
} else {
File imagesDir = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES).toString() + File.separator + folderName);

if (!imagesDir.exists())
imagesDir.mkdir();

imageFile = new File(imagesDir, fileName + ".png");
fos = new FileOutputStream(imageFile);
}

if (!bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos))
throw new IOException("Failed to save bitmap.");
fos.flush();
} finally {
if (fos != null)
fos.close();
}

if (imageFile != null) {//pre Q
MediaScannerConnection.scanFile(context, new String[]{imageFile.toString()}, null, null);
imageUri = Uri.fromFile(imageFile);
}
return imageUri;
}

If you've found a better way, post here, I'll mark it as answer.

Save image using MediaStore, saved with wrong name and extension

I delete this code fos = resolver.openOutputStream(imageUri); was duplicate. the image work fine but steal wrong name.

About the Wrong name and extension, i think that MediaStore doesn't work fine with Android 9 and lower, because i tested the same code on Android 11 and it's working fine

How to save an image in a subdirectory on android Q whilst remaining backwards compatible

is it okay to use deprecated code in this situation or will these methods soon be deleted from the sdk?

The DATA option will not work on Android Q, as that data is not included in query() results, even if you ask for it you cannot use the paths returned by it, even if they get returned.

The Environment.getExternalStoragePublicDirectory(...) option will not work by default on Android Q, though you can add a manifest entry to re-enable it. However, that manifest entry may be removed in Android R, so unless you are short on time, I would not go this route.

AFAIK, MediaStore.Images.Media.insertImage(...) still works, even though it is deprecated.

is it possible to implement it in such a way, so it's backwards compatible, but doesn't require deprecated code?

My guess is that you will need to use two different storage strategies, one for API Level 29+ and one for older devices. I took that approach in this sample app, though there I am working with video content, not images, so insertImage() was not an option.



Related Topics



Leave a reply



Submit