I think, therefore I blog

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. Click here to subscribe to his weekly blog, or stalk him on Facebook and Twitter.

Leave a Comment

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

Comment posted by: , 1 month ago

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, 1 month ago

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, 1 month ago

Really Useful code for many Android Developers.

Great step by step solution, thanks for the help!

Comment posted by: Shadilan, 1 month ago

Great! Thanks alot you save my time!

Comment posted by: alessandro, 3 months 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, 5 months 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: , 7 months 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, 7 months 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, 8 months 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, 8 months 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, last year

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, last year

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, last year

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

Comment posted by: Felix Zaulda Jr, last year

Hi Roger,

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

Join 3,559 People Who Think Outside The Box

I write every Sunday about travel, psychology, technology, and all sorts of interesting stuff. It's completely free, and you can subscribe for as long as you like. Do it now, so you don't miss a single post.

Chat For A While

Your Vote Matters

Which animal will take over when humans go extinct?
Lions
Dogs
Pigs
Octopuses
Cockroaches
Rats
Dolphins
Sharks
Jellyfish
Cows

Read a Good Book

“A spellbinding true story of love, passion and adventure. One can’t help but be swept away by 100% Love Guaranteed.” —Dr Tammie Matson, author of Elephant Dance.