Reputation: 321
unfortunately the solutions I've found didn't work on android 5.1.1.
I have a bitmap called source. I need to save it directly to my phone's gallery. My manifest contains <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Can you give me a working method to do this?
Upvotes: 21
Views: 41952
Reputation: 1584
Since it 2024, I'll try update the answer with Kotlin. Right now I also use this code in my Jetpack Compose app.
Since all the answer have not 100% similarity with mine, and my answer is no need to use any file providers or generate any File object, I'll show it here.
Environment I used on my app is Pictures (Environment.DIRECTORY_PICTURES
).
First of all, because my app min SDK is 25, I need to add Manifest uses-permission for read and write.
<manifest ...
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<application
...
</manifest>
Second, let's say you already have your own bitmap, so I'll just skip to the MediaStore
, ContentValues
, and ContentResolver
configuration. I'm using Kotlin Coroutines with Dispatchers.IO
as the dispatcher. This second stage is occur in my ViewModel
file.
To create ContentValues
with MediaStore
efficiently, I put a function to create it.
// This code, is in my ViewModel file
private fun createContentValues(title: String, subfolder: String): ContentValues =
ContentValues().apply {
put(MediaStore.Images.Media.TITLE, "$title.png")
put(MediaStore.Images.Media.DISPLAY_NAME, "$title.png")
put(MediaStore.Images.Media.MIME_TYPE, "image/png")
put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis())
put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis())
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
put(
MediaStore.MediaColumns.RELATIVE_PATH,
"${Environment.DIRECTORY_PICTURES}/$subfolder"
)
}
}
Then we back on configure the ContentResolver
// This code, is in my ViewModel file
suspend fun saveShareBitmap(applicationContext: Context, bitmap: Bitmap, subfolder: String, filename: String): List<Any> =
withContext(Dispatchers.IO) {
val contentValues = createContentValues(filename, subfolder)
var uri: Uri? = null
try {
uri = applicationContext.contentResolver.insert(
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues
) ?: throw IOException("Failed to create new MediaStore record")
val stream = applicationContext.contentResolver.openOutputStream(uri)
?: throw IOException("Failed to open output stream")
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
stream.flush()
stream.close()
} catch (e: IOException) {
if (uri != null) {
applicationContext.contentResolver.delete(uri, null, null)
}
}
return@withContext listOf(uri ?: Uri.parse(""), filename)
}
Third, we back to our layout code file. We need to check if build version code is <= SDK 28, we need to grant permission for read-write file. Inside the CoroutineScope
that I already initialized before, it is show how to share the image using current created URI
from my previous ViewModel
function.
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
if (!(writePermissionCheckResult && readPermissionCheckResult)) {
launcherPermission.launch(
arrayOf(
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
)
)
} else {
isWriteReadPermission = true
}
} else { // SDK after 28 no need check
isWriteReadPermission = true
}
if (!isWriteReadPermission && !isRationaleWriteReadPermission) {
context.showToast(context.getString(R.string.accept_on_settings))
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
data = Uri.fromParts("package", applicationContext.packageName, null)
context.startActivity(this)
}
} else if (isRationaleWriteReadPermission) {
context.showToast(
"${context.getString(R.string.accept_permissions)}\n" +
context.getString(R.string.please_press_again)
)
} else {
// code if permission accepted
// Coroutine Scope already initialized as scope
// ViewModel already initialized as
scope.launch{
withContext(Dispatchers.Main){
// in this scope I make share bitmap by URI
val (uri, filename) = viewModel.saveShareBitmap(applicationContext, bitmap, subfolder, filename)
if (uri is Uri && filename is String) {
val chooser = Intent.createChooser(
Intent(Intent.ACTION_SEND).apply {
clipData =
ClipData.newRawUri(filename, uri) // to fix Security Exception
putExtra(Intent.EXTRA_STREAM, uri)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
type = "image/png"
}, applicationContext.getString(R.string.share_where)
)
context.startActivity(chooser)
}
}
}
}
That's all from me, thank you and feel free to correct my answer by comment.
Upvotes: 1
Reputation: 413
I'd like to add Java code based on @Bao Lei 's answer that I used in my app.
private void saveImage(Bitmap bitmap, Context context, String folderName) throws FileNotFoundException {
if (android.os.Build.VERSION.SDK_INT >= 29) {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/" + folderName);
values.put(MediaStore.Images.Media.IS_PENDING, true);
// RELATIVE_PATH and IS_PENDING are introduced in API 29.
Uri uri = context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
if (uri != null) {
saveImageToStream(bitmap, context.getContentResolver().openOutputStream(uri));
values.put(MediaStore.Images.Media.IS_PENDING, false);
context.getContentResolver().update(uri, values, null, null);
}
} else {
dir = new File(getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES),"");
// getExternalStorageDirectory is deprecated in API 29
if (!dir.exists()) {
dir.mkdirs();
}
java.util.Date date = new java.util.Date();
imageFile = new File(dir.getAbsolutePath()
+ File.separator
+ new Timestamp(date.getTime()).toString()
+ "Image.jpg");
imageFile = new File(dir.getAbsolutePath()
+ File.separator
+ new Timestamp(date.getTime()).toString()
+ "Image.jpg");
saveImageToStream(bitmap, new FileOutputStream(imageFile));
if (imageFile.getAbsolutePath() != null) {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DATA, imageFile.getAbsolutePath());
// .DATA is deprecated in API 29
context.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
}
}
}
private ContentValues contentValues() {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.MIME_TYPE, "image/png");
values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
return values;
}
private void saveImageToStream(Bitmap bitmap, OutputStream outputStream) {
if (outputStream != null) {
try {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
This one worked fine in my app.
Upvotes: 2
Reputation: 1558
From Android Q there are changes in saving image to gallery.Thanks to @BaoLei, here is my answer in java if anybody needs it.
private void saveImage(Bitmap bitmap) {
if (android.os.Build.VERSION.SDK_INT >= 29) {
ContentValues values = contentValues();
values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/" + getString(R.string.app_name));
values.put(MediaStore.Images.Media.IS_PENDING, true);
Uri uri = this.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
if (uri != null) {
try {
saveImageToStream(bitmap, this.getContentResolver().openOutputStream(uri));
values.put(MediaStore.Images.Media.IS_PENDING, false);
this.getContentResolver().update(uri, values, null, null);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
} else {
File directory = new File(Environment.getExternalStorageDirectory().toString() + '/' + getString(R.string.app_name));
if (!directory.exists()) {
directory.mkdirs();
}
String fileName = System.currentTimeMillis() + ".png";
File file = new File(directory, fileName);
try {
saveImageToStream(bitmap, new FileOutputStream(file));
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.DATA, file.getAbsolutePath());
this.getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
private ContentValues contentValues() {
ContentValues values = new ContentValues();
values.put(MediaStore.Images.Media.MIME_TYPE, "image/png");
values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
}
return values;
}
private void saveImageToStream(Bitmap bitmap, OutputStream outputStream) {
if (outputStream != null) {
try {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
Upvotes: 10
Reputation: 2493
Now we have android10 and android11, so here is an updated version, that will work in all android devices.
Make sure you have the WRITE_EXTERNAL_STORAGE
permission before calling this function.
private fun saveMediaToStorage(bitmap: Bitmap) {
val filename = "${System.currentTimeMillis()}.jpg"
var fos: OutputStream? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
contentResolver?.also { resolver ->
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpg")
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
}
val imageUri: Uri? =
resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
fos = imageUri?.let { resolver.openOutputStream(it) }
}
} else {
val imagesDir =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
val image = File(imagesDir, filename)
fos = FileOutputStream(image)
}
fos?.use {
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it)
toast("Saved to Photos")
}
}
Its kotlin, and I think very straight forward and self explaining code. But still if you have a problem, comment below and I will explain.
Reference: Android Save Bitmap to Gallery Tutorial.
Upvotes: 4
Reputation: 2962
Here is a fully working solution in Kotlin:
fun saveToGallery(context: Context, bitmap: Bitmap, albumName: String) {
val filename = "${System.currentTimeMillis()}.png"
val write: (OutputStream) -> Boolean = {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val contentValues = ContentValues().apply {
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
put(MediaStore.MediaColumns.MIME_TYPE, "image/png")
put(MediaStore.MediaColumns.RELATIVE_PATH, "${Environment.DIRECTORY_DCIM}/$albumName")
}
context.contentResolver.let {
it.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)?.let { uri ->
it.openOutputStream(uri)?.let(write)
}
}
} else {
val imagesDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).toString() + File.separator + albumName
val file = File(imagesDir)
if (!file.exists()) {
file.mkdir()
}
val image = File(imagesDir, filename)
write(FileOutputStream(image))
}
}
Upvotes: 6
Reputation: 3615
There were several different ways to do it before API 29 (Android Q) but all of them involved one or a few APIs that are deprecated with Q. In 2019, here's a way to do it that is both backward and forward compatible:
(And since it is 2019 so I will write in Kotlin)
/// @param folderName can be your app's name
private fun saveImage(bitmap: Bitmap, context: Context, folderName: String) {
if (android.os.Build.VERSION.SDK_INT >= 29) {
val values = contentValues()
values.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures/" + folderName)
values.put(MediaStore.Images.Media.IS_PENDING, true)
// RELATIVE_PATH and IS_PENDING are introduced in API 29.
val uri: Uri? = context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
if (uri != null) {
saveImageToStream(bitmap, context.contentResolver.openOutputStream(uri))
values.put(MediaStore.Images.Media.IS_PENDING, false)
context.contentResolver.update(uri, values, null, null)
}
} else {
val directory = File(Environment.getExternalStorageDirectory().toString() + separator + folderName)
// getExternalStorageDirectory is deprecated in API 29
if (!directory.exists()) {
directory.mkdirs()
}
val fileName = System.currentTimeMillis().toString() + ".png"
val file = File(directory, fileName)
saveImageToStream(bitmap, FileOutputStream(file))
if (file.absolutePath != null) {
val values = contentValues()
values.put(MediaStore.Images.Media.DATA, file.absolutePath)
// .DATA is deprecated in API 29
context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)
}
}
}
private fun contentValues() : ContentValues {
val values = ContentValues()
values.put(MediaStore.Images.Media.MIME_TYPE, "image/png")
values.put(MediaStore.Images.Media.DATE_ADDED, System.currentTimeMillis() / 1000);
values.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis());
return values
}
private fun saveImageToStream(bitmap: Bitmap, outputStream: OutputStream?) {
if (outputStream != null) {
try {
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
outputStream.close()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
Also, before calling this, you need to have WRITE_EXTERNAL_STORAGE
first.
Upvotes: 39
Reputation: 1020
For Media Scanning, you can simply do
val intent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
intent.data = Uri.fromFile(path) // path must be of File type
context.sendBroadcast(intent)
Upvotes: 0
Reputation: 363
Use this code this we help you to store images into a particular folder which is saved_images and that folder images show in gallery immediately.
private void SaveImage(Bitmap finalBitmap) {
String root = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES).toString();
File myDir = new File(root + "/saved_images");
myDir.mkdirs();
Random generator = new Random();
int n = 10000;
n = generator.nextInt(n);
String fname = "Image-"+ n +".jpg";
File file = new File (myDir, fname);
if (file.exists ()) file.delete ();
try {
FileOutputStream out = new FileOutputStream(file);
finalBitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
// sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED,
// Uri.parse("file://"+ Environment.getExternalStorageDirectory())));
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
// Tell the media scanner about the new file so that it is
// immediately available to the user.
MediaScannerConnection.scanFile(this, new String[]{file.toString()}, null,
new MediaScannerConnection.OnScanCompletedListener() {
public void onScanCompleted(String path, Uri uri) {
Log.i("ExternalStorage", "Scanned " + path + ":");
Log.i("ExternalStorage", "-> uri=" + uri);
}
});
}
Upvotes: 15
Reputation: 1325
Do it in One Line
MediaStore.Images.Media.insertImage(applicationContext.getContentResolver(), IMAGE ,"nameofimage" , "description");
Upvotes: 5
Reputation: 1794
Use this one:
private void saveImage(Bitmap finalBitmap, String image_name) {
String root = Environment.getExternalStorageDirectory().toString();
File myDir = new File(root);
myDir.mkdirs();
String fname = "Image-" + image_name+ ".jpg";
File file = new File(myDir, fname);
if (file.exists()) file.delete();
Log.i("LOAD", root + fname);
try {
FileOutputStream out = new FileOutputStream(file);
finalBitmap.compress(Bitmap.CompressFormat.JPEG, 90, out);
out.flush();
out.close();
} catch (Exception e) {
e.printStackTrace();
}
}
Upvotes: 17