6

I want the TXT files (and the folder containing them) generated by my App to be accessible by my phone's file browser, or even from a PC when I plug the phone via USB. Android's official documentation describes the different types of storing depending if it is "internal" or "external" (Google's terminology). And of course, I don't want those TXT files to be deleted when I uninstall the App.

To avoid any confusions:

The fact that I want to hardcode the file location in the App, doesn't imply that I would use neccessarily the complete absolute filepath as a parameter. What i mean is the relative filepath will be hardcoded (same task, same file, same place).

...that said...

The app is for personal use (I mean to use it myself), so fulfilling Google Play Store's requirements is not a priority for now.

Code example (what I tried)

 try {

     // Dedicated folder would be better...
     // ...but not even in the root folder works...
     // FIXME T_T
     FileWriter writer = new FileWriter("/storage/emulated/0/myfile.txt");
     writer.write(my_string_for_file);
     writer.flush();
     writer.close();
 } catch (IOException e) {
     e.printStackTrace();
 }
// ...always throws FileNotFoundException with Permission DENIED

It would be Python on PC, stuff's done in 5 minutes!! 3 hours and I still can't figure it out?? I wanna cry...

So, I want to save some output always in the same file, in the same path. And I want that TXT file to be accessible from my PC when I plug it in via USB, as well as from my phone's file browser. In Python, for example, for a PC app, I would achieve this with 3 single lines of code! It can't be much harder in Android, right? (or so I hope)

I am aware that, for what I describe here, it is technically "documents and other kind of files", and what Google or Android Developers consider "external storage" (but what Android Users consider Internal Storage!). Well, Android Developers Documentation has a section for this in this link.

But the example's Code Snippet is overkill for my needs: I don't want the User to choose filepath: I want to hardcode the TXT files' paths into /storage/emulated/0/myapp/ directory. Instead, the example seems to provide some way to open some kind of "Save As" file dialog, which I don't want.

According to this article, it suggests me to use Environment.getExternalStoragePublicDirectory, and if I look to this method's documentation it says this method is deprecated from API 28. I have been looking other docs about the subject, but I am still confused about how to proceed. Supposing that I already have the text I want to write into a String variable:

  1. How would I make sure that files would be written to some dedicated folder like /storage/emulated/0/myapp or /storage/emulated/0/myfolder instead of the root folder /storage/emulated/0? Will getExternalFilesDir() return /storage/emulated/0 or is there a better method?

  2. Which is the simplest code to just write the file containing the String variable's text with a hardcoded filename into the dedicated directory, but it will still persist after desinstalling the app?

If it is either /storage/emulated/0 or /storage/emulated/0/Documents, any of both is fine by me. The most important thing is that I can access those files from outside my App that is gonna make them and, if possible, that uninstalling the mentioned App won't delete them.

SebasSBM
  • 860
  • 2
  • 8
  • 32
  • "I want to hardcode the TXT files' paths into /storage/emulated/0/myapp/ directory" -- that is not an option on Android 11+. "How would I make sure that files would be written to some dedicated folder like /storage/emulated/0/myapp or /storage/emulated/0/myfolder instead of the root folder /storage/emulated/0?" -- none of those are an option on Android 11+. "Will getExternalFilesDir() return /storage/emulated/0" -- no. It will return a directory that your app is free to use. However, that directory will be removed when your app is uninstalled. – CommonsWare Oct 16 '21 at 17:01
  • 1
    Write your files to /storage/emulated/0/Documents/myfolde for Android 11+ devices. For lower versions just write to /storage/emulated/0/myfolder. getExternalStorageDirectory() returns /storage/emulated/0. – blackapps Oct 16 '21 at 17:12
  • @CommonsWare: Goog to know, thank you. However, I am not targeting Android 11 yet – SebasSBM Oct 16 '21 at 17:12
  • 1
    You cannot ship your app on the Play Store without a `targetSdkVersion` of at least 30 as of November 2021, which is just a couple of weeks away. If you are using other app distribution channels, you will need to research what their requirements are. – CommonsWare Oct 16 '21 at 17:14
  • @CommosWare once again, thanks, good to know. For now I am not shipping it, the app is for personal use. – SebasSBM Oct 16 '21 at 17:26
  • I think only the first SD card is called `/storage/emulated/0`... and hard-coding something that the system already knows is indeed questionable (it has zero purpose and might break at any given time / or API level). – Martin Zeitler Oct 16 '21 at 21:59
  • @Martin Zeitler Ok, Martin, you have a point: I didn't expect anyone to interpret the expression "hardcoded" to the letter. It could be something like `String filepath = methodThatReturnsRootFolder() + myAppSubfolder() + "myfile.txt"`. What I mean is that I will always write that single file with some output in the same place! So I want the minimum genericity possible to just do the job. No more, no less. – SebasSBM Oct 16 '21 at 22:42
  • @Martin Zeitler I am sorry to disagree. In every Android phone I've ever had, `/storage/emulated/0` is the root folder for "Internal Storage" (User Terminology). No SD Cards involved. – SebasSBM Oct 18 '21 at 20:50
  • Nearly ten days ago i told you what to do to get all you wanted. Pretty strange that you still not closed your question. – blackapps Oct 25 '21 at 09:09
  • @blackapps Well, I am a strange person. I want this question to lead to the most "cannonical" answer possible (as most of the questions I make); so, I take my time reviewing them, and I might take time to finally decide which answer to accept. – SebasSBM Oct 26 '21 at 23:58

4 Answers4

3

Context.getExternalFilesDir(null) is the new alternative to Environment.getExternalStoragePublicDirectory() but as you mentioned this will return a directory that will get deleted once you uninstall the app. For your use case you need to use MediaStore and I completely agree with you: this has become terribly complicated (i think android has done this in order to strengten security and so that other apps can more easyly browse the files you place in shared directories).Here are some methods to do what you want using Media Store:

private void saveTxtFile(String fileName, String fileContent, Context context) throws IOException {
        OutputStream fos;
        try{
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                
                ContentValues values = new ContentValues();

                values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);       //file name
                values.put(MediaStore.MediaColumns.MIME_TYPE, "text/plain");        //file extension, will automatically add to file
                values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOCUMENTS + "/yourFolder/");     //end "/" is not mandatory

                Uri uri = context.getContentResolver().insert(MediaStore.Files.getContentUri("external"), values);      //important!

                fos = context.getContentResolver().openOutputStream(uri);
            } else {
                String docsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).toString();
                File file = new File(docsDir, fileName);
                fos=new FileOutputStream(file);
            }
            try {
                fos.write(fileContent.getBytes());
            } finally {
                fos.close();
            }
        }catch (IOException e){
            e.printStackTrace();
        }
       
    }

you will also need the following method to be able to acertain if the file already exists. I made it it so it returns an Uri but you should change that to suit your needs

  private Uri isFilePresent(String fileName, Context context) {
    Uri contentUri = MediaStore.Files.getContentUri("external");

    String selection = MediaStore.MediaColumns.RELATIVE_PATH + "=?";

    String[] selectionArgs = new String[]{Environment.DIRECTORY_DOCUMENTS + "/yourFolder/"};

    Cursor cursor = context.getContentResolver().query(contentUri, null, selection, selectionArgs, null);

    Uri uri = null;

    if (cursor.getCount() == 0) {
        Toast.makeText(context, "No file found in " + Environment.DIRECTORY_DOCUMENTS + "yourFolder", Toast.LENGTH_LONG).show();
    } else {
        while (cursor.moveToNext()) {
            String file = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME));

            if (fileName.equals(fileName)) {
                long id = cursor.getLong(cursor.getColumnIndex(MediaStore.MediaColumns._ID));

                uri = ContentUris.withAppendedId(contentUri, id);

                break;
            }
        }
    }
return uri;
}

please do not forget to add the corresponding permissions to the manifest fro this to work:

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

btw this answer does something similar

quealegriamasalegre
  • 2,887
  • 1
  • 13
  • 35
  • Thank you for the answer. Once I get home and use my PC again, I want to check this out. – SebasSBM Oct 20 '21 at 19:57
  • It worked: creates the file, and, after uninstalling the App, the file persists. This solution covers Android 9 and 10. Good. – SebasSBM Oct 20 '21 at 23:08
  • Btw I noticed that the variable `ContentResolver resolver` is actually not being used at all ;-) – SebasSBM Oct 20 '21 at 23:09
  • Oh you are right, copied by mistake i guess, will edit – quealegriamasalegre Oct 21 '21 at 17:08
  • Hi did this end up working for you. let me know if you need me to change something in order to mark this as the correct answer – quealegriamasalegre Oct 26 '21 at 23:28
  • I implemented your code in a new App from scratch and worked as expected, thank you. Before receiving your answer I ended up workarounding the saving file issue by saving it on internal storage. Even though I am procastinating to implement it in my app, your solution has proven to work and I keep it for future reference. If no better answer appears today, I will make sure to award you the bounty ;-) – SebasSBM Oct 26 '21 at 23:38
1

Try this code, it saves data to file, but on internal storage. It's more convenient, because a lot of devices now don't have external card. You can extract that file at least using android emulator tools and save it to your PC:

    import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;

public class FileManager {

    public static void writeToFile(String fileName, String data, Context context) {
        try {
            OutputStreamWriter outputStreamWriter = new OutputStreamWriter(context.openFileOutput(fileName, Context.MODE_PRIVATE));
            outputStreamWriter.write(data);
            outputStreamWriter.close();
        } catch (FileNotFoundException e) {
            LLog.e(e, "File not found");
        } catch (IOException e) {
            LLog.e(e, "File write failed");
        }
    }

    public static void addToFile(String fileName, String data, Context context) {
        try {
            OutputStreamWriter outputStreamWriter = new OutputStreamWriter(context.openFileOutput(fileName, Context.MODE_APPEND));
            outputStreamWriter.append(data);
            outputStreamWriter.close();
        } catch (FileNotFoundException e) {
            LLog.e(e, "File not found");
        } catch (IOException e) {
            LLog.e(e, "File write failed");
        }
    }

    public static String readFromFile(String fileName, Context context) {
        String resultStr = "";

        try {
            InputStream inputStream = context.openFileInput(fileName);

            if (inputStream != null) {
                InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
                BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
                String receiveString = "";
                StringBuilder stringBuilder = new StringBuilder();

                while ((receiveString = bufferedReader.readLine()) != null) {
                    stringBuilder.append(receiveString);
                }

                inputStream.close();
                bufferedReader.close();
                resultStr = stringBuilder.toString();
            }
        } catch (FileNotFoundException e) {
            LLog.e(e, "File not found");
        } catch (IOException e) {
            LLog.e(e, "File write failed");
        }

        return resultStr;
    }

    public static String readFromFileInRawFolder(String fileName, Context context) {
        String resultStr = "";

        try {
            InputStream inputStream = context.getResources().openRawResource(
                    context.getResources().getIdentifier(fileName, "raw", context.getPackageName()));

            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String receiveString;
            StringBuilder stringBuilder = new StringBuilder();

            while ((receiveString = bufferedReader.readLine()) != null) {
                stringBuilder.append(receiveString);
            }

            inputStream.close();
            bufferedReader.close();
            resultStr = stringBuilder.toString();
        } catch (FileNotFoundException e) {
            LLog.e(e, "File not found");
        } catch (IOException e) {
            LLog.e(e, "File write failed");
        }

        return resultStr;
    }

    public static boolean deleteFile(String fileName, Context context) {
        File dir = context.getFilesDir();
        File file = new File(dir, fileName);
        return file.delete();
    }
}
yozhik
  • 4,644
  • 14
  • 65
  • 98
  • 1
    Actually, after making this question, I ended up implementing internal storage saving before you answered. A workaround most people who might land in this question might be interested on, so you have my +1 – SebasSBM Oct 26 '21 at 23:47
0

My experience in this area is limited however, I believe I have an answer. Firstly, when using an android device and wishing to install anything to a root folder, you are required to have a rooted device. Another option (what I have done on my android 10 Lenovo Tab M10 FHD+) is forcing the app to store the information externally through the app itself and using a file manager (with root capabilities such as X-plore or others) to manually move the text files.

Ella
  • 31
  • 10
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). –  Oct 25 '21 at 11:30
0

it is simple. you are getting file not found that exception because:

  1. you did not create the file yourself.
  2. you did not pass the CreateNew or CreateAppend parameter to the unused FileStream...

Add these to your code:

To fix the FileNotFoundException:

FileStream fs = new FileStream(filePath, FileMode.CreateNew);

To fix the Permission Issue:

FileIOPermission f2 = new FileIOPermission(FileIOPermissionAccess.Read, "your file's name plus extension(.txt)");

To create different files for you not to get lost between which file contains what link!, maybe use the name of the website you visited through the Split([4]) guessing you have www.websitename.domain, you can split the link at index 4 and get rid of the rest of the link! or depend on your memory and remember when you created what file.

string filename = DatTime.Now.ToShortDate.ToString() + ".txt";

Then use the filename in the filepath you've set.

Congrats your app is running, and it is fun

Best of luck mate ✌

UnkownReality
  • 130
  • 13