Simple Android File Chooser

By , 3 June 2015

Simple Android File Chooser
Simple Android File Chooser

Surprisingly, the Android API doesn't include a file chooser. I'm not sure why that is, I guess the developers don't want to make the assumption that the device has a user filesystem available. Anyway, it's not my job to speculate on what the Android developers talk about over lunch. I just need a file chooser.

It seems like there are a few options out there but I wanted something uber simple and ultra light. It's just for users to select an import file.

My solution is a single Java class utilising regular android Dialog and ListViews. It just wires up the event handlers to read from the disk and refresh the list. You provide a FileSelectedListener implementation to handle the file selection event. Menial stuff that you don't want to bother implementing yourself. It can be used like this:

new FileChooser(activity).setFileListener(new FileSelectedListener() {
    @Override public void fileSelected(final File file) {
        // do something with the file
    }).showDialog();

Here is the FileChooser class which you can cut and paste into your project. Public domain code so do what you want with it.

Peace.

Simple Android File Chooser
package au.com.ninthavenue.patterns.android.dialogs;

import android.app.Activity;
import android.app.Dialog;
import android.os.Environment;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager.LayoutParams;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import java.io.File;
import java.io.FileFilter;
import java.util.Arrays;

public class FileChooser {
    private static final String PARENT_DIR = "..";

    private final Activity activity;
    private ListView list;
    private Dialog dialog;
    private File currentPath;

    // filter on file extension
    private String extension = null;
    public void setExtension(String extension) {
        this.extension = (extension == null) ? null :
                extension.toLowerCase();
    }

    // file selection event handling
    public interface FileSelectedListener {
        void fileSelected(File file);
    }
    public FileChooser setFileListener(FileSelectedListener fileListener) {
        this.fileListener = fileListener;
        return this;
    }
    private FileSelectedListener fileListener;

    public FileChooser(Activity activity) {
        this.activity = activity;
        dialog = new Dialog(activity);
        list = new ListView(activity);
        list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override public void onItemClick(AdapterView<?> parent, View view, int which, long id) {
                String fileChosen = (String) list.getItemAtPosition(which);
                File chosenFile = getChosenFile(fileChosen);
                if (chosenFile.isDirectory()) {
                    refresh(chosenFile);
                } else {
                    if (fileListener != null) {
                        fileListener.fileSelected(chosenFile);
                    }
                    dialog.dismiss();
                }
            }
        });
        dialog.setContentView(list);
        dialog.getWindow().setLayout(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
        refresh(Environment.getExternalStorageDirectory());
    }

    public void showDialog() {
        dialog.show();
    }


    /**
     * Sort, filter and display the files for the given path.
     */
    private void refresh(File path) {
        this.currentPath = path;
        if (path.exists()) {
            File[] dirs = path.listFiles(new FileFilter() {
                @Override public boolean accept(File file) {
                    return (file.isDirectory() && file.canRead());
                }
            });
            File[] files = path.listFiles(new FileFilter() {
                @Override public boolean accept(File file) {
                    if (!file.isDirectory()) {
                        if (!file.canRead()) {
                            return false;
                        } else if (extension == null) {
                            return true;
                        } else {
                            return file.getName().toLowerCase().endsWith(extension);
                        }
                    } else {
                        return false;
                    }
                }
            });

            // convert to an array
            int i = 0;
            String[] fileList;
            if (path.getParentFile() == null) {
                fileList = new String[dirs.length + files.length];
            } else {
                fileList = new String[dirs.length + files.length + 1];
                fileList[i++] = PARENT_DIR;
            }
            Arrays.sort(dirs);
            Arrays.sort(files);
            for (File dir : dirs) { fileList[i++] = dir.getName(); }
            for (File file : files ) { fileList[i++] = file.getName(); }

            // refresh the user interface
            dialog.setTitle(currentPath.getPath());
            list.setAdapter(new ArrayAdapter(activity, 
                   android.R.layout.simple_list_item_1, fileList) {
                       @Override public View getView(int pos, View view, ViewGroup parent) {
                           view = super.getView(pos, view, parent);
                           ((TextView) view).setSingleLine(true);
                           return view;
                       }
                   });
        }
    }


    /**
     * Convert a relative filename into an actual File object.
     */
    private File getChosenFile(String fileChosen) {
        if (fileChosen.equals(PARENT_DIR)) {
            return currentPath.getParentFile();
        } else {
            return new File(currentPath, fileChosen);
        }
    }
}
 

About Roger Keays

Simple Android File Chooser

Roger Keays is an artist, an engineer, and a student of life. Since he left Australia in 2009, he has been living as a digital nomad in over 40 different countries around the world. Roger is addicted to surfing. His other interests are music, psychology, languages, and finding good food.

Leave a Comment

Please visit https://rogerkeays.com/simple-android-file-chooser to add your comments.

Comment posted by: Jeff Jennings, 3 months ago
Hi, My name is Jeff, I work as a Internet Marketing Consultant, and was doing research for another client when I came across your site rogerkeays.com. and thought I would message you on your contact form. After doing a quick analysis of your website, I noticed a couple of issues that are most likely causing people to leave without making contact. I really liked your website but noticed you weren't getting a lot of traffic to your site and your Alexa ranking wasn't as strong as it could be. http://OnTargetSEO.us I can get 1,000’s of visitors looking at rogerkeays.com, ready to buy your product, service or signup for your offer. Our advertising network of over 9000 websites provides a low cost and effective online marketing solution that actually works. We can help your business get more online quality traffic by advertising your business on websites that are targeted to your market. The internet is a vast entity and kick starting your online business doesn’t have to take a ridiculous amount of cash. We’ll send real people to see your web site starting almost immediately! In fact, I can get 10,000 highly targeted visitors to your website for as little as $29 just so you can test out our service. Right now to make things really exciting you can get 200,000 Targeted visitors to you site in 30 days for only $199. If you'd like to talk personally please give me a call at 480-457-0165 9 to 5 MST USA. I have a short video here that explains how everything works http://OnTargetSEO.us Best Regards, Jeff http://OnTargetSEO.us
Comment posted by: Rasta, 4 months ago

1st time that i just copy/paste sth and its work ! thanks a lot

Comment posted by: Xerus, 10 months ago

I made a few tweaks, because the original method was a bit unreadable and called path.listFiles() twice for no apparent reason:

if (path.exists() && path.canRead()) {
    // find em all
    TreeSet<String> dirs = new TreeSet<>();
    TreeSet<String> files = new TreeSet<>();
    for(File file : path.listFiles()) {
        if(!file.canRead())
            continue;
        if(file.isDirectory()) {
            dirs.add(file.getName());
        } else {
            if(extension == null || file.getName().toLowerCase().endsWith(extension))
                files.add(file.getName());
        }
    }

    // convert to an array
    ArrayList<String> fileList = new ArrayList<>(dirs.size() + files.size());
    if (path.getParentFile() != null)
        fileList.add(PARENT_DIR);
    fileList.addAll(dirs);
    fileList.addAll(files);

    // refresh the user interface
    dialog.setTitle(currentPath.getPath());
    list.setAdapter(new ArrayAdapter<String>(activity, android.R.layout.simple_list_item_1, fileList) {
        @Override public View getView(int pos, View view, ViewGroup parent) {
            view = super.getView(pos, view, parent);
            ((TextView) view).setSingleLine(true);
            return view;
        }
    });
} else {
    list.setAdapter(new ArrayAdapter<>(activity, android.R.layout.simple_list_item_1, new String[]{"Can't access "+path}));
}

Oh and by the way, you should address the new permission issues with Android 6.0

Comment posted by: iliyan s, 10 months ago

Brian Young,

have you got this in manifest?

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

Comment posted by: Godwin, last year
Nice! Really help me.
Comment posted by: Marcin Szydłowski, last year

Many thanks for this topic!

Comment posted by: }}}}}}}}}}}}}}}}}}}}, last year

Ai uitat o acolada pisa-m-as pe mortii matii

Comment posted by: Giancarlo, last year

Just had to say:

Thank you for sharing, helped me a lot.

Comment posted by: fredchamp, last year

Well, just to say thanks.

Simple and efficient

 

Comment posted by: , last year

Hey @Robot Maxwell. Thanks for the patch. It looks like path.listFiles() is returning null in your case. The javadoc says "The array will be empty if the directory is empty. Returns null if this abstract pathname does not denote a directory, or if an I/O error occurs."

So I think this code should work if the directory is empty, but will probably crash if Environment.getExternalStorageDirectory() does not return a valid directory. What happens if you change:

        if (path.exists()) {

to

        if (path.exists() && path.isDirectory()) {

?

 

Comment posted by: Robot Maxwell, last year

Roger you have a nasty error in your code, when the sdcard is "empty" it will crash. I had to bolster it up with a bit of checking thus:

 

~            // convert to an array
            int i = 0;
            int j = 0;
            int k = 0;
         if(dirs!=null && dirs.length>0)
             j = dirs.length;
         if(files!=null && files.length>0)
             k = files.length;
            String[] fileList;
            if (path.getParentFile() == null) {
                fileList = new String[j + k];
            } else {
                //fileList = new String[dirs.length + files.length + 1];
                fileList = new String[j + k + 1];
                fileList[i++] = PARENT_DIR;
            }
            if(dirs!=null && dirs.length>0) {
                Arrays.sort(dirs);
             for (File dir : dirs) { fileList[i++] = dir.getName(); }}
         if(files!=null && files.length>0){
                Arrays.sort(files);
              for (File file : files ) { fileList[i++] = file.getName(); }}
 

 

Comment posted by: Prathap, last year

Really Useful code for many Android Developers.

Great step by step solution, thanks for the help!

Comment posted by: Shadilan, last year

Great! Thanks alot you save my time!

Comment posted by: alessandro, 2 years ago

Marthono, it gave the same error to me. Just substitute this line:

FileChooser(this.getParent())

with this:

FileChooser(this)

and it worked for me.

Comment posted by: Martono, 2 years ago

it gives error :

06-15 03:43:31.038 24606-24606/on4shop.androidapp E/AndroidRuntime: FATAL EXCEPTION: main
                                                                    Process: on4shop.androidapp, PID: 24606
                                                                    java.lang.IllegalStateException: Could not execute method for android:onClick
                                                                        at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:293)
                                                                        at android.view.View.performClick(View.java:4438)
                                                                        at android.view.View$PerformClick.run(View.java:18422)
                                                                        at android.os.Handler.handleCallback(Handler.java:733)
                                                                        at android.os.Handler.dispatchMessage(Handler.java:95)
                                                                        at android.os.Looper.loop(Looper.java:136)
                                                                        at android.app.ActivityThread.main(ActivityThread.java:5001)
                                                                        at java.lang.reflect.Method.invokeNative(Native Method)
                                                                        at java.lang.reflect.Method.invoke(Method.java:515)
                                                                        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
                                                                        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
                                                                        at dalvik.system.NativeStart.main(Native Method)
                                                                     Caused by: java.lang.reflect.InvocationTargetException
                                                                        at java.lang.reflect.Method.invokeNative(Native Method)
                                                                        at java.lang.reflect.Method.invoke(Method.java:515)
                                                                        at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288)
                                                                        at android.view.View.performClick(View.java:4438) 
                                                                        at android.view.View$PerformClick.run(View.java:18422) 
                                                                        at android.os.Handler.handleCallback(Handler.java:733) 
                                                                        at android.os.Handler.dispatchMessage(Handler.java:95) 
                                                                        at android.os.Looper.loop(Looper.java:136) 
                                                                        at android.app.ActivityThread.main(ActivityThread.java:5001) 
                                                                        at java.lang.reflect.Method.invokeNative(Native Method) 
                                                                        at java.lang.reflect.Method.invoke(Method.java:515) 
                                                                        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785) 
                                                                        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601) 
                                                                        at dalvik.system.NativeStart.main(Native Method) 
                                                                     Caused by: java.lang.NullPointerException
                                                                        at on4shop.androidapp.DialogClassCollection.clsSaveFileDialog.refresh(clsSaveFileDialog.java:99)
                                                                        at on4shop.androidapp.DialogClassCollection.clsSaveFileDialog.<init>(clsSaveFileDialog.java:62)
                                                                        at on4shop.androidapp.frm_file_act.WriteExternalFile(frm_file_act.java:67)
                                                                        at java.lang.reflect.Method.invokeNative(Native Method) 
                                                                        at java.lang.reflect.Method.invoke(Method.java:515) 
                                                                        at android.support.v7.app.AppCompatViewInflater$DeclaredOnClickListener.onClick(AppCompatViewInflater.java:288) 
                                                                        at android.view.View.performClick(View.java:4438) 
                                                                        at android.view.View$PerformClick.run(View.java:18422) 
                                                                        at android.os.Handler.handleCallback(Handler.java:733) 
                                                                        at android.os.Handler.dispatchMessage(Handler.java:95) 
                                                                        at android.os.Looper.loop(Looper.java:136) 
                                                                        at android.app.ActivityThread.main(ActivityThread.java:5001) 
                                                                        at java.lang.reflect.Method.invokeNative(Native Method) 
                                                                        at java.lang.reflect.Method.invoke(Method.java:515) 
                                                                        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785) 
                                                                        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601) 
                                                                        at dalvik.system.NativeStart.main(Native Method) 

Comment posted by: , 2 years ago

Hey Brian, the stack trace might tell you if this.getParent() is the cause of the problem. Couldn't you just use 'this'?

Comment posted by: Brian Young, 2 years ago

I'm trying to use your file picker to pick a file for the XML parser to parse... I have the FileChooser Class and everything compiling, but my APP blows up when I include this code to present the file chooser.  I only pick an XML file to parse on the initial startup of the app.  It's likely the "this.getParent()" to get the activity, but I cannot be sure.

 

public class XMLPullParserActivity extends Activity {
...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

...
        if (savedInstanceState != null) {
...
 } else {

            new FileChooser(this.getParent()).setFileListener(new FileChooser.FileSelectedListener() {
                @Override public void fileSelected(final File file) {
                    // do something with the file
                    Application application=(Application)CBA_Application.getContext();
                    CBA_Application app = (CBA_Application)application;
                    Context context=getApplicationContext();
                    CharSequence mytext;
                    Integer myduration;
                    Toast mytoast;
                    mytext = "Root Directory for selected File " + file.getAbsolutePath();
                    myduration = Toast.LENGTH_LONG;
                    mytoast = Toast.makeText(context,mytext,myduration);
                    mytoast.show();
                }}).showDialog();
...

Comment posted by: Shipmodeller, 2 years ago

Thanks .. that made my life easy ..  Made a few minor tweaks.. but I had some issues with the extensions.  So, since I know what the extension is ahead of time, I  just added it to the construction methods.  I could add multiple extension filters on... your skeleton was what I really needed.. So, I use it like 

private void processFile(){
    filechooser = new FileChooser(ImportActivity.this);
    filechooser.setFileListener(new FileChooser.FileSelectedListener() {
        @Override
        public void fileSelected(final File file) {
            // ....do something with the file
            String filename = file.getAbsolutePath();
            Log.d("File", filename);
            // then actually do something in another module

        }
    });
// Set up and filter my extension I am looking for
    filechooser.setExtension("pdf");
    filechooser.showDialog();
}
Comment posted by: Choudry, 2 years ago

Hello, i already have a button in main activity to access this class, how i implement my button actionListener with this class?

Comment posted by: Kirintal, 3 years ago

Hi,

Thank you. This is absolutely brilliant and saved me a lot of time.

I noticed one issue with the processing order. When using "setExtension", the "refresh" method is not called. This results in the first screen displaying all file formats not just directories and the designated extension.

Thank you once again

Comment posted by: haruto, 3 years ago

I don't understand with this part:

// do something with the file

can you give me an example what i can do with that line?

Comment posted by: Tonny, 3 years ago

How do i implement the class within a button click of another class

Comment posted by: Felix Zaulda Jr, 3 years ago

Hi Roger,

Thank you so much for this code... I am so thankful that you allow this code to be used on our projects.