4

I am using codes given by Bao Lei with the name private fun saveImage at how to save bitmap to android gallery to save bitmap

To solve deprecated problems in Kotlin, I removed these lines:

 //values.put(MediaStore.Images.Media.DATA, file.absolutePath) 
//context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)

and added this:

 val contentValues = ContentValues()

 contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, folderName)

 this.applicationContext.contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)

Then, I called the function (in DidtodayActivity ) as:

saveImage(bitmapImg, this.applicationContext)

The image is downloaded to Phone>Android>Data>com.example.MyApp1>files>MyFiles. I can see the downloaded image in my device.

But, the activity file is closed suddenly (and activity_main.xml is opened directly) due to the runTime error at this line:

this.applicationContext.contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)

Cause:

java.lang.ClassNotFoundException: Didn't find class "android.provider.MediaStore$Downloads" on path: DexPathList[[zip file "/data/app/com.example.MyApp1-rvQQjSAj4NilLch2h12faQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.example.MyApp1-rvQQjSAj4NilLch2h12faQ==/lib/arm64, /data/app/com.example.MyApp1-rvQQjSAj4NilLch2h12faQ==/base.apk!/lib/arm64-v8a, /system/lib64]]

Detailmessage:

**Didn't find class "android.provider.MediaStore$Downloads"** on path: DexPathList[[zip file "/data/app/com.example.MyApp1-rvQQjSAj4NilLch2h12faQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.example.MyApp1-rvQQjSAj4NilLch2h12faQ==/lib/arm64, /data/app/com.example.MyApp1-rvQQjSAj4NilLch2h12faQ==/base.apk!/lib/arm64-v8a, /system/lib64]]

Failed resolution of: Landroid/provider/MediaStore$Downloads;

Module level build.gradle is:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'com.google.gms.google-services'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.2"

    defaultConfig {
        applicationId "com.example.MyApp1"
        minSdkVersion 16
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

        multiDexEnabled true

    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    aaptOptions{
        noCompress "tflite"
        noCompress "lite"
    }
}

Also, the following code was added into the dependencies part in the build.gradle file:

 implementation "com.android.support:multidex:2.0.1"

And, I added this code in the Manifest file:

  android:name="androidx.multidex.MultiDexApplication">

How to solve the run time error problem ?

I tried this;

this.applicationContext.contentResolver.insert( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)

instead of this;

this.applicationContext.contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)

But, it crashes again with this runtime error;

java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=203, result=-1, data=Intent { (has extras) }} to 
activity {com.example.MyApp1/com.example.MyApp1.DidtodayActivity}: java.lang.SecurityException: Permission Denial: writing com.android.providers.media.MediaProvider uri 
content://media/external/images/media from pid=3812, uid=10270 
requires android.permission.WRITE_EXTERNAL_STORAGE, or grantUriPermission()

However, the following code is already in the Manifest file;

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

So, is there any other alternative to use instead of MediaStore.Downloads?

Member
  • 87
  • 2
  • 9

2 Answers2

7

First of all you need to use at least targetSdkVersion 29

https://developer.android.com/training/data-storage/shared/media
    
Downloaded files, which are stored in the Download/ directory. On devices that run
Android 10 (API level 29) and higher, these files are stored in the MediaStore. 
Downloads table. **This table isn't available on Android 9 (API level 28) and lower.**

As per docs, this api is available starting from Android 10, api 29.

Below api 29 you would have to use the old way of saving a file in the Downloads folder, using Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)

Complete solution:

private val FOLDER = "MyFolder"

private fun saveInDownloads(appContext: Context, fromFile: File) {
    val dst = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        saveInDownloadsForQ(appContext, fromFile.name)
    } else {
        //dont forget to check if you have the permission 
        //to WRITE_EXTERNAL_STORAGE, and ask for it if you don't
        saveInDownloadsBelowQ(appContext, fromFile.name)
    }

    dst?.let {
        try {
            val src = FileInputStream(fromFile)
            dst.channel.transferFrom(src.channel, 0, src.channel.size())
            src.close()
            dst.close()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
}

private fun saveInDownloadsForQ(appContext: Context, fileName: String): FileOutputStream? {
    val contentValues = ContentValues().apply {
        put(MediaStore.Downloads.DISPLAY_NAME, fileName)
        put(MediaStore.Downloads.MIME_TYPE, "application/pdf")
        put(MediaStore.Downloads.DATE_ADDED, (System.currentTimeMillis() / 1000).toInt())
        put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + File.separator + FOLDER)
    }

    val resolver = appContext.contentResolver
    val uri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
    return uri?.let {
        resolver.openOutputStream(uri) as FileOutputStream
    }
}

private fun saveInDownloadsBelowQ(appContext: Context, fileName: String): FileOutputStream {
    val path = Environment.getExternalStoragePublicDirectory(
        Environment.DIRECTORY_DOWNLOADS).toString()

    val dir = File(path, FOLDER)
    val dirFlag = dir.mkdirs()

    val file = File(dir, fileName)
    return FileOutputStream(file)
}
Goran Horia Mihail
  • 3,536
  • 2
  • 29
  • 40
-1

After Android 5.0, multidexing is enabled by default.

Taken from the documentation:

Multidex support for Android 5.0 and higher Android 5.0 (API level 21) and higher uses a runtime called ART which natively supports loading multiple DEX files from APK files. ART performs pre-compilation at app install time which scans for classesN.dex files and compiles them into a single .oat file for execution by the Android device. Therefore, if your minSdkVersion is 21 or higher multidex is enabled by default, and you do not need the multidex support library.

Regardless, you need to do the following:

In build.gradle:

android {
defaultConfig {
    ...
    minSdkVersion 15 
    targetSdkVersion 28
    multiDexEnabled true. <----
  }
...
}

AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
  <application
        android:name="android.support.multidex.MultiDexApplication" >
    ...
</application>

If you are not using AndroidX library suite:

dependencies {
  implementation 'com.android.support:multidex:1.0.3'
}

If you are using AndroidX:

dependencies {
  def multidex_version = "2.0.1"
  implementation 'androidx.multidex:multidex:$multidex_version'
 }
tomerpacific
  • 4,704
  • 13
  • 34
  • 52
  • Thanks, I am using Android 9 (Android SDK tools version: 26.1.1) and already done these apdates in your comment. But, I have still the same problem. – Member Feb 24 '20 at 12:41
  • @Member - you removed all the necessary code snippets that were useful to understand what you are doing. I am sure that I did not see the multidexing configuration in your build.gradle file. Do you mind sharing all these files with us, just to make sure? – tomerpacific Feb 24 '20 at 13:19
  • thank you for your interest. Please look at the edited question. I still need help :(( – Member Feb 24 '20 at 14:34
  • 1
    Downloads class has been added API level29 according to this doc.=> https://developer.android.google.cn/reference/kotlin/android/provider/MediaStore.Downloads#EXTERNAL_CONTENT_URI:android.net.Uri. I am using Android 9 API level 28. So, how can I use Mediastore's download class to solve the above problem given in the question – Member Feb 25 '20 at 12:16
  • The question and solution has nothing to do with MultiDexApplication. – Goran Horia Mihail Jul 22 '20 at 10:12