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

Leave a Comment

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

Comment posted by: Godwin, 5 months ago
Nice! Really help me.
Comment posted by: Marcin Szydłowski, 7 months ago

Many thanks for this topic!

Comment posted by: }}}}}}}}}}}}}}}}}}}}, 8 months ago

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

Comment posted by: Giancarlo, 8 months ago

Just had to say:

Thank you for sharing, helped me a lot.

Comment posted by: fredchamp, 8 months ago

Well, just to say thanks.

Simple and efficient

 

Comment posted by: , 10 months 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, 11 months 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, 11 months ago

Really Useful code for many Android Developers.

Great step by step solution, thanks for the help!

Comment posted by: Shadilan, 11 months ago

Great! Thanks alot you save my time!

Comment posted by: alessandro, last year

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

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

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

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

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

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, 2 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, 2 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, 2 years ago

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

Comment posted by: Felix Zaulda Jr, 2 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.