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:

  1. File Name Generation:
    • Generates a unique file name for the image using the current system time in milliseconds.
  2. Output Stream Initialization:
    • Initializes an output stream variable fos which will be used to write the bitmap data.
  3. 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 into MediaStore.Images.Media.EXTERNAL_CONTENT_URI to get a URI for the image.
      • Opens an output stream using the obtained URI.
    • 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.
  4. 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.
  5. Resource Management:
    • Utilizes Kot Lin’s use function to ensure proper closing of the output stream after use, preventing resource leaks.

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

By Admin