9

Can anyone provide an accurate tutorial on how to save files on Android Q publicily available, since getExternalFilesDir() is unavailable to use in Android Q+.

I've been dealing with this issue for some days, and can't figure out any solution.

I am trying to download some css, js, and HTML files for loading them on a Webview.

Right now, I have the following code, written in Kotlin:

companion object {
    private const val TAG = "MainActivity"
    private var rootDir: Path? = null
}

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    ...

    rootDir = getExternalFilesDir(null)
    Log.v(TAG, "RootDir: ${rootDir!!.path}")

    val cacheDir = File(rootDir!!, "cache")
    if(!cacheDir.exists()) {
         Log.v(TAG, "Creating cache dir (${cacheDir.path})")
         try {
            if (cacheDir.mkdirs())
                Log.v(TAG, "Created cache dir correctly")
            else
                Log.v(TAG, "Could not create cache dir")
        }catch (ex: IOException){
            Log.e(TAG, "Could not create cache dir: ", ex)
        }catch (ex: SecurityException) {
            Log.e(TAG, "Not allowed to create cache dir: ", ex)
        }
    }

    val cssDir = File(cacheDir, "css")
    if(!cssDir.exists()){
        Log.v(TAG, "Creating css dir (${cssDir.path})")
        try {
            if (cacheDir.mkdirs())
                Log.v(TAG, "Created css dir correctly")
            else
                Log.v(TAG, "Could not create css dir")
        }catch (ex: IOException){
            Log.e(TAG, "Could not create css dir: ", ex)
        }catch (ex: SecurityException) {
            Log.e(TAG, "Not allowed to create css dir: ", ex)
        }
    }

    val siteGlobal = File(cssDir, "site_global.css")
    if(!siteGlobal.exists())
        DownloadFileAsyncTask().execute(DownloadFileAsyncTask.DownloadArgs("...", siteGlobal.path))
}

And DownloadFileAsyncTask:

    private class DownloadFileAsyncTask :
        AsyncTask<DownloadFileAsyncTask.DownloadArgs, Int, Void?>() {

        data class DownloadArgs(val url : String, val savePath : String)

        override fun doInBackground(vararg downloadFiles: DownloadArgs?): Void? {
            for(downloadFile in downloadFiles) {
                val urlStr = downloadFile!!.url
                val outputFilePath = downloadFile.savePath
                val outputFile = File(outputFilePath)
                val url = URL(urlStr)

                Log.v(TAG, "Connecting...")
                val connection = url.openConnection() as HttpURLConnection
                connection.connect()

                if (connection.responseCode != HttpURLConnection.HTTP_OK) {
                    throw InterruptedException("Server returned HTTP ${connection.responseCode} ${connection.responseMessage}")
                }

                Log.v(TAG, "Connection correct")
                if (!outputFile.exists()) {
                    Log.v(TAG, "Creating ${outputFile.path}")
                    outputFile.createNewFile()
                }

                val fileLength = connection.contentLength

                val input = connection.inputStream
                val output = FileOutputStream(outputFile)

                val data = ByteArray(4096)
                var total: Long = 0
                var count: Int
                Log.v(TAG, "Starting download...")
                while (true) {
                    count = input.read(data)
                    if (count < 0) break
                    total += count
                    if (fileLength > 0)
                        publishProgress((total * 10 / fileLength).toInt())
                    output.write(data, 0, count)
                }

                output.close()
                input?.close()
                connection.disconnect()
            }

            return null
        }
    }

And my response on Logcat is:

V/MainActivity: RootDir: /storage/emulated/0/Android/data/.../files
V/MainActivity: Creating cache dir (/storage/emulated/0/Android/data/.../files/cache)
V/MainActivity: Created cache dir correctly
V/MainActivity: Creating css dir (/storage/emulated/0/Android/data/.../files/cache/css)
V/MainActivity: Could not create css dir
V/MainActivity: Connecting...
V/MainActivity: Connection correct
V/MainActivity: Creating /storage/emulated/0/Android/data/.../files/cache/css/site_global.css

And obviously, it crashes with java.io.IOException: No such file or directory at outputFile.createNewFile() in AsyncTask, since the directory doesn't exist.

Since no error on the folder creation is shown, it only tells that the directory couldn't be created, I don't know what am I doing wrong.

I've added and granted <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> in case of it was the issue, and it didn't solve anything.

Arnyminer Z
  • 5,784
  • 5
  • 18
  • 32
  • "since getExternalFilesDir() is unavailable to use in Android Q+" -- `getExternalFilesDir()` is definitely available for use on Android Q+. – CommonsWare Sep 22 '19 at 13:57
  • CommonsWare But I amb still not able to create any directories nor files by using `getExternalFilesDir(null)`, for example. – Arnyminer Z Sep 22 '19 at 13:59
  • 1
    Nothing changed about `getExternalFilesDir()` on Android 10. It works the same as it has since it was added in Android 4.4. It works using standard Java file I/O, such as `File` and `OutputStream`. You might consider editing your question and providing a [mcve] showing what you tried and explaining your symptoms **in detail**. – CommonsWare Sep 22 '19 at 14:02
  • FWIW, [this sample Java app](https://gitlab.com/commonsguy/cw-jetpack-java/tree/v0.5/ContentEditor) and [its Kotlin equivalent](https://gitlab.com/commonsguy/cw-jetpack-kotlin/tree/v0.5/ContentEditor) show the use of `getExternalFilesDir()`. – CommonsWare Sep 22 '19 at 14:08
  • CommonsWare I've now added my code – Arnyminer Z Sep 22 '19 at 14:11
  • Your code does not match your output, as there should be something between the two two output lines showing the success or failure of your `cacheDir.mkdir()` call. I recommend that you switch from `mkdir()` to `mkdirs()`. I also recommend that you stop using string concatenation to create filesystem paths. IOW, replace `val cacheDir = File(rootDirPath!! + "/cache")` with `val cacheDir = File(getExternalFilesDir(), "cache")`. – CommonsWare Sep 22 '19 at 14:16
  • CommonsWare I've updated the post with your suggesions, and everying does the same. The `cache` folder is created correctly, that's why on the previous console output it didn't show up, the issue comes when creating the `cache/css` folder. – Arnyminer Z Sep 22 '19 at 14:22
  • Personally, I would skip most of that code. Just call `cacheDir.mkdirs()`, as it creates all parent directories, ignoring those that already exist. Everything that you do before that is just duplicating work that `mkdirs()` will do. And you might want to use the Device File Explorer in Android Studio, or `adb shell ls`, to see whether your directories are being created. – CommonsWare Sep 22 '19 at 14:26
  • CommonsWare OMG found the frckn issue. It was just a stupid copy-paste error. I was calling `cacheDir.mkdirs()` in the cssDir creation process. So `cssDir.mkdirs()` was obviously never created. Sorry for being this stupid . Really thank you – Arnyminer Z Sep 22 '19 at 14:29
  • 1
    I am facing a similar problem with API29. still not solved.cant able to create folder or files – Prashant Jan 11 '20 at 21:21

1 Answers1

6

getExternalStorageDirectory() is deprected in API-29

Check the step

  1. Download and upload file from webserver "Using FTP connection. (It is a way but the not only way)

How to download a file from FTP server to Android device?

  1. Save it to external storage
    //private static void createFile(Context ctx, String fileName, byte text)
    private static void createFile(Context ctx, String fileName, String text) {
        File filesDir = ctx.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS);
        assert filesDir != null;
        if (!filesDir.exists()){
           if(filesDir.mkdirs()){
            }
        }
        File file = new File(filesDir, fileName);
        try {
            if (!file.exists()) {
                if (!file.createNewFile()) {
                    throw new IOException("Cant able to create file");
                }
            }    
            OutputStream os = new FileOutputStream(file);
            byte[] data = text.getBytes();
            os.write(data);
            os.close();
            Log.e("TAG", "File Path= " + file.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }    
    }
  1. Use load URL to load files in webview and inject like this question Android Web-View : Inject local Javascript file to Remote Webpage and Android - How to inject custom css into external webpage in webview
Prashant
  • 394
  • 1
  • 6
  • 18