Things are changing very fast in the technology. And if you want to be an Android Developer, then be ready to keep upgrading yourself. This learning curve is the best thing I like about this industry (In my opinion). So, here is a new about “Bitmap saved in Android Gallery”.
Saving files to internal storage is required so many times in our projects. We have done this so many times before, but the method for saving files to internal storage has been changed completely for devices running with OS >= Android10.
If your previous way of saving files will not work in new versions of Android, Starting with android10. You can still go the previous way, but it is a temporary solution. In this post, Down Tips going to talk about all these things.
File Storage in Android
Now, DownTips provides you two types of storage to store files.
- App-specific storage:
- The files are for your application only. Files stored here cannot be accessed outside your application. And you do not need any permission to access this storage.
- Shared Storage:
- The files can be shared with other apps as well. For example Media Files (Images, Videos, Audios), Documents, and other files.
To demonstrate saving files to internal storage in Android10.
Start with the main function, that we need to Bitmap saved in Android Gallery as an Image File.
Here is the perfect and easy code:
fun saveMediaToStorage(bitmap: Bitmap) {
//Generating a file name
val filename = “${System.currentTimeMillis()}.jpg”
//Output stream
var fos: OutputStream? = null
//For devices running android >= Q
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
//getting the contentResolver
context?.contentResolver?.also { resolver ->
//Content resolver will process the contentvalues
val contentValues = ContentValues().apply {
//putting file information in content values
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
put(MediaStore.MediaColumns.MIME_TYPE, “image/jpg”)
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
}
//Inserting the contentValues to contentResolver and getting the Uri
val imageUri: Uri? =
resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
//Opening an outputstream with the Uri that we got
fos = imageUri?.let { resolver.openOutputStream(it) }
}
} else {
//These for devices running on android < Q
//So I don’t think an explanation is needed here
val imagesDir =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
val image = File(imagesDir, filename)
fos = FileOutputStream(image)
}
fos?.use {
//Finally writing the bitmap to the output stream that we opened
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, it)
context?.toast(“Saved to Photos”)
}
}
Now discuss and explain this code and how it works:
- File Name Generation:
- Generates a unique file name for the image using the current system time in milliseconds.
- Output Stream Initialization:
- Initializes an output stream variable
fos
which will be used to write the bitmap data.
- Initializes an output stream variable
- Handling Android Versions:
- Check the Android version using
Build.VERSION.SDK_INT
. - For devices running Android Q (API level 29) or later:
- Retrieves the
ContentResolver
from the context. - Constructs
ContentValues
to store metadata such as display name, MIME type, and relative path. - Inserts the
ContentValues
intoMediaStore.Images.Media.EXTERNAL_CONTENT_URI
to get a URI for the image. - Opens an output stream using the obtained URI.
- Retrieves the
- For devices running on versions earlier than Android Q:
- Retrieves the public external storage directory for pictures.
- Creates a
File
object using the directory and generated filename. - Initializes the output stream using
FileOutputStream
.
- Check the Android version using
- Bitmap Compression and Writing:
- Uses the output stream (
fos
) to compress the bitmap image into JPEG format with maximum quality (100). - Displays a toast message indicating that the image has been saved to the device’s photos.
- Uses the output stream (
- Resource Management:
- Utilizes Kot Lin’s
use
function to ensure proper closing of the output stream after use, preventing resource leaks.
- Utilizes Kot Lin’s
Downloading and Bitmap saving to Gallery
Now we just need to add the functionality in our MainActivity.kt file. The code is very straight forward so we are directly showing the whole MainActivity here.
class MainActivity : AppCompatActivity() {
//ImageLoader instance
private lateinit var imageLoader: ImageLoader
//Permission Request Handler
private lateinit var requestPermissionLauncher: ActivityResultLauncher<String>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//Setting up Activity Permission Request Handler
setPermissionCallback()
progressbar.visible(false)
//getting imageloader instance
imageLoader = Coil.imageLoader(this)
button_paste_link.setOnClickListener {
pasteLink()
}
button_download.setOnClickListener {
val bitmapURL = edit_text_image_url.text.toString().trim()
//when download is pressed check permission and save bitmap from url
checkPermissionAndDownloadBitmap(bitmapURL)
}
edit_text_image_url.addTextChangedListener {
button_download.enable(it.toString().isNotEmpty())
}
}
//Allowing activity to automatically handle permission request
private fun setPermissionCallback() {
requestPermissionLauncher =
registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
getBitmapFromUrl(edit_text_image_url.text.toString().trim())
}
}
}
//function to check and request storage permission
private fun checkPermissionAndDownloadBitmap(bitmapURL: String) {
when {
ContextCompat.checkSelfPermission(
this,
Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED -> {
getBitmapFromUrl(bitmapURL)
}
shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE) -> {
showPermissionRequestDialog(
getString(R.string.permission_title),
getString(R.string.write_permission_request)
) {
requestPermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
}
else -> {
requestPermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
}
}
//this function will fetch the Bitmap from the given URL
private fun getBitmapFromUrl(bitmapURL: String) = lifecycleScope.launch {
progressbar.visible(true)
image_view.load(bitmapURL)
val request = ImageRequest.Builder(this@MainActivity)
.data(bitmapURL)
.build()
try {
val downloadedBitmap = (imageLoader.execute(request).drawable as BitmapDrawable).bitmap
image_view.setImageBitmap(downloadedBitmap)
saveMediaToStorage(downloadedBitmap)
} catch (e: Exception) {
toast(e.message)
}
progressbar.visible(false)
}
//the function I already explained, it is used to save the Bitmap to external storage
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”)
}
}
//Pasting the value from Clipboard to EditText
private fun pasteLink() {
val clipboard: ClipboardManager? =
getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager?
if (clipboard?.hasPrimaryClip() == true) {
edit_text_image_url.setText(clipboard.primaryClip?.getItemAt(0)?.text.toString())
}
}
}
This code is very simple and straight just copy and use this code for your bitmap saving in Android Gallery.
How to set SDK location at the filesystem root in Android