Introduction
Every time you need to choose a certain SD card directory in your Android application you need to load a kind of directory chooser dialog that will offer a graphical interface to select a required directory.
Unfortunately Android does not provide any built in directory chooser dialog as is expected by Android developers. Thereby developers have to write their own directory chooser dialog. In this article I present an implementation of simple directory chooser dialog for Android
SD card enhanced with ability to create directories. The implementation is contained in a single file and does not use any extra resources except for the pre-defined Android resources. This assumes very easy integration of the dialog in Android applications.
The implementation code
The directory chooser dialog is based on the AlertDialog
supplied with the ListView
of sub-directories. The current directory path is displayed in the AlertDialog
title. The navigation forth to a directory is accomplished by tapping a sub-directory item in the ListView and back by pressing the back button. The sub-directories in the list are sorted by name. When required directory is chosen by pressing OK button a registered callback is invoked supplied with full path of the chosen directory. Note that long current directory paths and sub-directories names are correctly displayed in multiline view.
The code is contained in a single file DirectoryChooserDialog.java.
It loads AlertDialog
with ListView
of sub-directories of a current directory and keeps track of directories navigation.
The implementation DirectoryChooserDialog
class defines the following callback interface.
public interface ChosenDirectoryListener
{ public void onChosenDir(String chosenDir);
}
A callback can be registered in the DirectoryChooserDialog
class constructor.
public DirectoryChooserDialog(Context context, ChosenDirectoryListener chosenDirectoryListener);
By default the ability to create new directories is enabled and can be applied by clicking on the New folder button. It can be switched off by means
of setNewFolderEnabled
method. When disabled the New folder button gets hidden.
public void setNewFolderEnabled(boolean isNewFolderEnabled)
{
m_isNewFolderEnabled = isNewFolderEnabled;
}
public boolean getNewFolderEnabled()
{
return m_isNewFolderEnabled;
}
The DirectoryChooserDialog
class specifies two public chooseDirectory
methods for loading
directory chooser dialog, one without and another with an initial directory parameter. The default initial directory is
SD card root directory.
public void chooseDirectory();
public void chooseDirectory(String dir);
The DirectoryChooserDialog
class full implementation is shown below.
package com.example.directorychooser;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.DialogInterface.OnKeyListener;
import android.os.Environment;
import android.text.Editable;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
public class DirectoryChooserDialog
{
private boolean m_isNewFolderEnabled = true;
private String m_sdcardDirectory = "";
private Context m_context;
private TextView m_titleView;
private String m_dir = "";
private List<String> m_subdirs = null;
private ChosenDirectoryListener m_chosenDirectoryListener = null;
private ArrayAdapter<String> m_listAdapter = null;
public interface ChosenDirectoryListener
{
public void onChosenDir(String chosenDir);
}
public DirectoryChooserDialog(Context context, ChosenDirectoryListener chosenDirectoryListener)
{
m_context = context;
m_sdcardDirectory = Environment.getExternalStorageDirectory().getAbsolutePath();
m_chosenDirectoryListener = chosenDirectoryListener;
try
{
m_sdcardDirectory = new File(m_sdcardDirectory).getCanonicalPath();
}
catch (IOException ioe)
{
}
}
public void setNewFolderEnabled(boolean isNewFolderEnabled)
{
m_isNewFolderEnabled = isNewFolderEnabled;
}
public boolean getNewFolderEnabled()
{
return m_isNewFolderEnabled;
}
public void chooseDirectory()
{
chooseDirectory(m_sdcardDirectory);
}
public void chooseDirectory(String dir)
{
File dirFile = new File(dir);
if (! dirFile.exists() || ! dirFile.isDirectory())
{
dir = m_sdcardDirectory;
}
try
{
dir = new File(dir).getCanonicalPath();
}
catch (IOException ioe)
{
return;
}
m_dir = dir;
m_subdirs = getDirectories(dir);
class DirectoryOnClickListener implements DialogInterface.OnClickListener
{
public void onClick(DialogInterface dialog, int item)
{
m_dir += "/" + ((AlertDialog) dialog).getListView().getAdapter().getItem(item);
updateDirectory();
}
}
AlertDialog.Builder dialogBuilder =
createDirectoryChooserDialog(dir, m_subdirs, new DirectoryOnClickListener());
dialogBuilder.setPositiveButton("OK", new OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
if (m_chosenDirectoryListener != null)
{
m_chosenDirectoryListener.onChosenDir(m_dir);
}
}
}).setNegativeButton("Cancel", null);
final AlertDialog dirsDialog = dialogBuilder.create();
dirsDialog.setOnKeyListener(new OnKeyListener()
{
@Override
public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event)
{
if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN)
{
if ( m_dir.equals(m_sdcardDirectory) )
{
return false;
}
else
{
m_dir = new File(m_dir).getParent();
updateDirectory();
}
return true;
}
else
{
return false;
}
}
});
dirsDialog.show();
}
private boolean createSubDir(String newDir)
{
File newDirFile = new File(newDir);
if (! newDirFile.exists() )
{
return newDirFile.mkdir();
}
return false;
}
private List<String> getDirectories(String dir)
{
List<String> dirs = new ArrayList<String>();
try
{
File dirFile = new File(dir);
if (! dirFile.exists() || ! dirFile.isDirectory())
{
return dirs;
}
for (File file : dirFile.listFiles())
{
if ( file.isDirectory() )
{
dirs.add( file.getName() );
}
}
}
catch (Exception e)
{
}
Collections.sort(dirs, new Comparator<String>()
{
public int compare(String o1, String o2)
{
return o1.compareTo(o2);
}
});
return dirs;
}
private AlertDialog.Builder createDirectoryChooserDialog(String title, List<String> listItems,
DialogInterface.OnClickListener onClickListener)
{
AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(m_context);
LinearLayout titleLayout = new LinearLayout(m_context);
titleLayout.setOrientation(LinearLayout.VERTICAL);
m_titleView = new TextView(m_context);
m_titleView.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
m_titleView.setTextAppearance(m_context, android.R.style.TextAppearance_Large);
m_titleView.setTextColor( m_context.getResources().getColor(android.R.color.white) );
m_titleView.setGravity(Gravity.CENTER_VERTICAL | Gravity.CENTER_HORIZONTAL);
m_titleView.setText(title);
Button newDirButton = new Button(m_context);
newDirButton.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
newDirButton.setText("New folder");
newDirButton.setOnClickListener(new View.OnClickListener()
{
@Override
public void onClick(View v)
{
final EditText input = new EditText(m_context);
new AlertDialog.Builder(m_context).
setTitle("New folder name").
setView(input).setPositiveButton("OK", new DialogInterface.OnClickListener()
{
public void onClick(DialogInterface dialog, int whichButton)
{
Editable newDir = input.getText();
String newDirName = newDir.toString();
if ( createSubDir(m_dir + "/" + newDirName) )
{
m_dir += "/" + newDirName;
updateDirectory();
}
else
{
Toast.makeText(
m_context, "Failed to create '" + newDirName +
"' folder", Toast.LENGTH_SHORT).show();
}
}
}).setNegativeButton("Cancel", null).show();
}
});
if (! m_isNewFolderEnabled)
{
newDirButton.setVisibility(View.GONE);
}
titleLayout.addView(m_titleView);
titleLayout.addView(newDirButton);
dialogBuilder.setCustomTitle(titleLayout);
m_listAdapter = createListAdapter(listItems);
dialogBuilder.setSingleChoiceItems(m_listAdapter, -1, onClickListener);
dialogBuilder.setCancelable(false);
return dialogBuilder;
}
private void updateDirectory()
{
m_subdirs.clear();
m_subdirs.addAll( getDirectories(m_dir) );
m_titleView.setText(m_dir);
m_listAdapter.notifyDataSetChanged();
}
private ArrayAdapter<String> createListAdapter(List<String> items)
{
return new ArrayAdapter<String>(m_context,
android.R.layout.select_dialog_item, android.R.id.text1, items)
{
@Override
public View getView(int position, View convertView,
ViewGroup parent)
{
View v = super.getView(position, convertView, parent);
if (v instanceof TextView)
{
TextView tv = (TextView) v;
tv.getLayoutParams().height = LayoutParams.WRAP_CONTENT;
tv.setEllipsize(null);
}
return v;
}
};
}
}
Usage example
The following example shows how to load directory chooser dialog on button click. The ability to create new directories is toggled between
dialog appearances. The previous chosen directory turns into initial directory for the next dialog invocation.
package com.example.directorychooser;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
public class DirectoryChooserActivity extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_directory_chooser);
Button dirChooserButton = (Button) findViewById(R.id.chooseDirButton);
dirChooserButton.setOnClickListener(new OnClickListener()
{
private String m_chosenDir = "";
private boolean m_newFolderEnabled = true;
@Override
public void onClick(View v)
{
DirectoryChooserDialog directoryChooserDialog =
new DirectoryChooserDialog(DirectoryChooserActivity.this,
new DirectoryChooserDialog.ChosenDirectoryListener()
{
@Override
public void onChosenDir(String chosenDir)
{
m_chosenDir = chosenDir;
Toast.makeText(
DirectoryChooserActivity.this, "Chosen directory: " +
chosenDir, Toast.LENGTH_LONG).show();
}
});
directoryChooserDialog.setNewFolderEnabled(m_newFolderEnabled);
directoryChooserDialog.chooseDirectory(m_chosenDir);
m_newFolderEnabled = ! m_newFolderEnabled;
}
});
}
}
Conclusion
This article presented an implementation of a directory chooser dialog enhanced with new directories creation Java class that can be used as is in Android applications. Enjoy!