How to Download Image from the Web in Android using Kotlin

In this tutorial, I’m going to show you how to download an image from the Web and save it in your Phone Storage (not the SD card), using the DownloadManager on Android.

As an example, we are going to download the following image I found on the Internet and uploaded on

Creating the Image Downloader

Go to your AndroidManifest.xml file (manifests > AndroidManifest.xml) and add the following permissions:

<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Code language: HTML, XML (xml)

Go to your Activity file and create a method with a parameter url and type String.

Also, set the directory you want to save the image.

In this example, we’re using the Pictures folder in our Phone Storage.

fun downloadImage(url: String) { val directory = File(Environment.DIRECTORY_PICTURES) if (!directory.exists()) { directory.mkdirs() } }
Code language: Kotlin (kotlin)

Create a DownloadManager.Request

private fun downloadImage(url: String) { // ... val downloadManager = this.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager val downloadUri = Uri.parse(url) val request = DownloadManager.Request(downloadUri).apply { setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE) .setAllowedOverRoaming(false) .setTitle(url.substring(url.lastIndexOf("/") + 1)) .setDescription("") .setDestinationInExternalPublicDir( directory.toString(), url.substring(url.lastIndexOf("/") + 1) ) } // ... } }
Code language: Kotlin (kotlin)

In request‘s options, we set as a title of the image to be the URL name.

For example, if we download an image from the following link.

Then, the download image will have funnycat.png as a name.

Now, check the status of your download and show it in Toast.

var msg: String? = "" var lastMsg = "" private fun downloadImage(url: String) { // ... val downloadId = downloadManager.enqueue(request) val query = DownloadManager.Query().setFilterById(downloadId) Thread(Runnable { var downloading = true while (downloading) { val cursor: Cursor = downloadManager.query(query) cursor.moveToFirst() if (cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) == DownloadManager.STATUS_SUCCESSFUL) { downloading = false } val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) msg = statusMessage(url, directory, status) if (msg != lastMsg) { this.runOnUiThread { Toast.makeText(this, msg, Toast.LENGTH_SHORT).show() } lastMsg = msg ?: "" } cursor.close() } }).start() } private fun statusMessage(url: String, directory: File, status: Int): String? { var msg = "" msg = when (status) { DownloadManager.STATUS_FAILED -> "Download has been failed, please try again" DownloadManager.STATUS_PAUSED -> "Paused" DownloadManager.STATUS_PENDING -> "Pending" DownloadManager.STATUS_RUNNING -> "Downloading..." DownloadManager.STATUS_SUCCESSFUL -> "Image downloaded successfully in $directory" + File.separator + url.substring( url.lastIndexOf("/") + 1 ) else -> "There's nothing to download" } return msg }
Code language: Kotlin (kotlin)

Using the Image Downloader

To use the downloader, just call the method downloadImage and pass the URL of your image, like that:

override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) download_btn.setOnClickListener { downloadImage(imageUrl) } }
Code language: Kotlin (kotlin)

Asking for Permissions

If your app supports Android 9 (API level 28) and lower, you need to ask the user for permission before downloading the image on their device.

In Android 10 (API level 29) and above, you don’t need to do that.

Go to your AndroidManifest.xml file (manifests > AndroidManifest.xml) and add the following permission:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Code language: HTML, XML (xml)

Create a method askPermissions() and ask the user if they want to allow app to store images in their phone.

If they Deny, the next time an AlertDialog will show up and tell them why your app needs these permissions.

@TargetApi(Build.VERSION_CODES.M) fun askPermissions() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { // Permission is not granted // Should we show an explanation? if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { // Show an explanation to the user *asynchronously* -- don't block // this thread waiting for the user's response! After the user // sees the explanation, try again to request the permission. AlertDialog.Builder(this) .setTitle("Permission required") .setMessage("Permission required to save photos from the Web.") .setPositiveButton("Accept") { dialog, id -> ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE) finish() } .setNegativeButton("Deny") { dialog, id -> dialog.cancel() } .show() } else { // No explanation needed, we can request the permission. ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE) // MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE is an // app-defined int constant. The callback method gets the // result of the request. } } else { // Permission has already been granted downloadImage(imageUrl) } } companion object { private const val MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1 }
Code language: Kotlin (kotlin)

After getting the permission results (Allow or Deny), the onRequestPermissionsResult method called.

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) when (requestCode) { MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE -> { // If request is cancelled, the result arrays are empty. if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) { // permission was granted, yay! // Download the Image downloadImage(imageUrl) } else { // permission denied, boo! Disable the // functionality that depends on this permission. } return } // Add other 'when' lines to check for other // permissions this app might request. else -> { // Ignore all other requests. } } }
Code language: Kotlin (kotlin)

Now, you can use the image downloader like that:

override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) download_btn.setOnClickListener { // After API 23 (Marshmallow) and lower Android 10 you need to ask for permission first before save in External Storage(Micro SD) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { askPermissions() } else { downloadImage(imageUrl) } } }
Code language: Kotlin (kotlin)
You can find the final project here

If you have any questionsplease feel free to leave a comment below

Notify of
Newest Most Voted
Inline Feedbacks
View all comments

Get once a week my latest tutorials right in your inbox

Check your inbox to confirm your email