osiegmar/FastCSV

More <Storage Access Framework>-friendly CsvWriter builder

martinerrazquin opened this issue · 2 comments

Hello! First of all, i'd like to both thank you and congratulate you, it's a great library and very straightforward to use.
The only not-so-straightforward part of using this to export data to a CSV on my Android application was a combination of:

  • It's encouraged to use the path version of CsvWriter.builder().build() over the Writer one
  • The standard Storage Access Framework's Documents Provider file creation mechanism returns a Uri whose path seems to be of little use, as it's apparently not linked to the file you create (always throws NoSuchFileException). Instead, the documentation promotes use of FileDescriptors and whatnot.

This is my first Android app so maybe I'm making some rookie mistakes but i ended up doing this, based on this method:

    // <Activity code>
    // called from a Button click, prompts the 'save As' window for the user to pick a file name and location
    private void getCSVExportingFile() {
        Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("text/csv");
        intent.putExtra(Intent.EXTRA_TITLE, CSVExporter.buildName());

        return super.onOptionsItemSelected(item);
        startActivityForResult(intent, Constants.CSV_CREATE_FILE_INTENT_CODE);
    }

   // this is called after the 'save As' window exits, with data == null if it fails or != if it succeeds.
   @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == Constants.CSV_CREATE_FILE_INTENT_CODE && resultCode == RESULT_OK) {
            Uri uri = null;
            if (data != null) {
                uri = data.getData();
                try {
                    CSVExporter.exportToCSV(this.getContentResolver().openOutputStream(uri));   // <- this (1/2)
                } catch (FileNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }
    }

   // <I called this CSVExporter, but could be anything>
   //  the actual dumping function
    public static void exportToCSV(OutputStream openOutputStream) {
        new Thread(() -> {
            try (CsvWriter csv = CsvWriter.builder().build(new OutputStreamWriter(openOutputStream))) { // <- this (2/2)
                writeHeader(csv);
                List<SomeRecord> data = someClass.getRecords();
                for(SomeRecord item : data){
                    writeRow(csv,item);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }

So what bothers me is IMO the smoothest you can do is

  1. get the activity, then the content provider and then OutputStream using the Uri
  2. use the Uri to build an OutputStreamWriter
  3. still use the less-preferred (according to documentation), Writer version of the CsvWriter.builder().build()

Looking at the source code, the path version doesn't seem to do things much different than what I did. Now while this may be unnecesary, i think there's a quality of life improvement in adding a builder that takes either the Uri or the OutputStream as the parameter. Also with little change, I think the above code could be used as a more realistic version of the CsvWriter file() example, making this library suuuuper plug-n-play. Going from the current example to the final version of this code was a big leap IMO.

Thanks again!

For some weird reason my brain never caught the "Android is not officially supported" bit until right now, i'm sorry if this was a waste of time to any of you. I'll still leave it open in case you want to do something with it, but will delete it if you want.

Thanks for your feedback! Although, Android is not officially supported, it's great so see, that it seems to work. :-)

Looking at the source code, the path version doesn't seem to do things much different than what I did.

Oh, it does! The Path version does not sync its internal buffer on every single record as the Writer version does. But unless you're writing millions of rows you'll hardly find any performance difference – but you should wrap your OutputStreamWriter in a BufferedWriter to reduce disk access. You might also want to add the charset argument to the OutputStreamWriter constructor call.

After searching the web a little there does not seem to be a way to make this library more "Storage Access Framework"-friendly without having a hard Android dependency. Currently I don't want to publish an official Android example because then I'd have to maintain it which I'm not able to do.