The API that is used in this app is commons-net. In this API, the FTPClient class is used to connect, and upload files to the server, and download files from the server. You can download this API from Apache website.
Now open the Eclipse and create a new Android Project called AndFTP. You need to extract the package of the commons-net API and copy and paste the commons-net-3.3.jar to the libs directory of your Android project.
For this uploader for Android, you need to create two Fragments. The first fragment is called LoginFragment. This fragment represents a login form. On the log in in form, the user will fill in the ftp domain name (e.g. ftp.worldbestlearningcenter.com), the username, and the password to connect to the ftp server. The Connect button will connect to the ftp server when all required information are filled.
LoginFragment.java file
package com.example.andftp;
//import com.actionbarsherlock.app.SherlockFragment;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class LoginFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.login_fragment, container, false);
}
public static LoginFragment newInstance(String str){
return(new LoginFragment());
}
}
The layout file that is the resource for the LoginFragment is called login_fragment.xml file. Its content is shown below.
login_fragment.xml file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<EditText
android:id="@+id/txt_ftpserver"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/txt_ftpserver"
android:inputType="text" />
<EditText
android:id="@+id/txt_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/txt_username"
android:inputType="text" />
<EditText
android:id="@+id/txt_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/txt_password"
android:inputType="textPassword" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="connectToServer"
android:text="@string/bt_connect"
/>
</LinearLayout>
When you are successfully connect to the server, the second fragment (TransferFragment) will display. On this user interface, the user can select a file from the list of local files and push the upload icon (the arrow with the right-direction head) to upload or transfer the selected file to the server. The right-side list displays the files and directories of the remote server. From the right-side list, you can select a file or navigate to any directory that you want. The selected file can be downloaded from the sever to your device by pressing the download icon ( the arrow with the left-direction head). Above each list (local and server), there is a text box to display the currently selected file or directory. It also allows you to enter the path of your desired file or directory.
TransferFragment.java file
package com.example.andftp;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class TransferFragment extends Fragment{
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.transfer_fragment, container, false);
}
public static TransferFragment newInstance(){
return(new TransferFragment());
}
}
The layout file of the TransferFragment can be written as shown below.
transfer_fragment.xml file
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<EditText
android:id="@+id/txt_local"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:hint="@string/txt_local" />
<ListView
android:id="@+id/localfiles_list"
android:layout_width="200dp"
android:layout_height="wrap_content"
/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_gravity="center"
>
<ImageButton
android:id="@+id/bt_upload"
android:src="@drawable/upload_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="uploadFile"
android:background="#ffffff"
android:layout_marginBottom="30dp"
/>
<ImageButton
android:id="@+id/bt_download"
android:src="@drawable/download_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="downloadFile"
android:background="#ffffff"
android:layout_marginTop="30dp"
/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<EditText
android:id="@+id/txt_server"
android:layout_width="200dp"
android:layout_height="wrap_content"
android:hint="@string/txt_server"
/>
<ListView
android:id="@+id/serverfiles_list"
android:layout_width="200dp"
android:layout_height="wrap_content"
/>
</LinearLayout>
</LinearLayout>
As you see in the screenshot above, each list (either local or server list) has two parts--icon and text. The icon displays the file icon or directory icon. The text part displays the file or directory name. The two lists use the same layout file called listlayout.xml. This is the content of the listlayout.xml file.
listlayout.xml file
<?xml version="1.0" encoding="utf-8"?>
<!-- Single List Item Design -->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="5dip" >
<ImageView
android:id="@+id/icon"
android:layout_width="30dp"
android:layout_height="30dp"
android:padding="5sp"
android:contentDescription="Icon"
/>
<TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10sp"
android:textSize="20sp"
android:textColor="#0000ff"
android:textStyle="bold" >
</TextView>
</LinearLayout>
The ListAdapterModel class that will be used as the data source of each list is written in the ListAdapterModel.java file as shown below.
ListAdapterModel.java file
package com.example.andftp;
import java.io.File;
import java.util.ArrayList;
//import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import android.content.Context;
//import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
public class ListAdapterModel extends ArrayAdapter<String>{
int groupid;
ArrayList<String> filenames;
Context context;
String parentpath;
ArrayList<FTPFile> ftpfiles;
public ListAdapterModel(Context context, int vg, int id,ArrayList<String> filenames, String parentPath,ArrayList<FTPFile> ftpfiles){
super(context,vg, filenames);
this.context=context;
groupid=vg;
this.filenames=filenames;
this.parentpath=parentPath;
this.ftpfiles=ftpfiles;
}
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View itemView = inflater.inflate(groupid, parent, false);
ImageView imageView = (ImageView) itemView.findViewById(R.id.icon);
TextView textView = (TextView) itemView.findViewById(R.id.label);
String item=filenames.get(position);
textView.setText(item);
if(ftpfiles==null){ //Set text and icons to local files and directories
File f=new File(parentpath+"/"+item);
if(f.isDirectory())
imageView.setImageDrawable(context.getResources().getDrawable(R.drawable.diricon));
else
imageView.setImageDrawable(context.getResources().getDrawable(R.drawable.fileicon));
}
else{ ////Set text and icons to files and directories retrieved from server
if(ftpfiles.get(position).isDirectory())
imageView.setImageDrawable(context.getResources().getDrawable(R.drawable.diricon));
else{
imageView.setImageDrawable(context.getResources().getDrawable(R.drawable.fileicon));
}
}
return itemView;
}
}
To learn more about customizing the ListView, you will read the previous post FileChooser, Send Email, or Currency Converter.
Normally, when the Fragment class is used to construct the sub-user interface. The main activity that represents the main interface must be created by using the FragmentActivity class. The layout file (activity_main.xml file) simply contains a FrameLayout view that will be the container of other sub-interface fragments.
activity_main.xml file
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
Now we take a look at the MainActivity.java file. This file is the main part of the uploader for Android app. It contains more than 600 lines of code.
MainActivity.java file
package com.example.andftp;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
//import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
//import android.app.Activity;
import android.content.pm.ActivityInfo;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentTransaction;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Menu;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
//import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends FragmentActivity {
private String localpath;
private String serverpath;
private FTPClient client;
private ProgressDialog pd;
private Context context;
private ArrayList<FTPFile> ftpobjects;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
client= new FTPClient();
context=this;
//prevent the LoginFragment from being recreated when the orientation changes
if(findViewById(R.id.fragment_container)!=null){
if (savedInstanceState != null) {
return;
}
}
LoginFragment lf=new LoginFragment();
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container,lf).commit();
}
public void initLogin(){
String[] loginInfo=readLogin();
if(loginInfo!=null){
EditText txtserver=(EditText) findViewById(R.id.txt_ftpserver);
EditText txtusername=(EditText) findViewById(R.id.txt_username);
EditText txtpassword=(EditText) findViewById(R.id.txt_password);
txtserver.setText(loginInfo[0]);
txtusername.setText(loginInfo[1]);
txtpassword.setText(loginInfo[2]);
}
}
protected void onStart(){
super.onStart();
//default local path
localpath=Environment.getExternalStorageDirectory().toString();
//default server path
serverpath="/www";
//read existing login information
initLogin();
}
protected void onResume(){
super.onResume();
regControls();
}
protected void onDestroy(){
super.onDestroy();
//filenames=client.listNames();
if(pd!=null){
pd.dismiss();
}
if(client.isConnected()){
try {
client.logout();
client.disconnect();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
public void connectToServer(View view){
boolean connected=connect();
if(connected){
showTransferUI();
}
else
Toast.makeText(context,"Unable to connect to the server", Toast.LENGTH_SHORT).show();
}
public boolean connect(){
boolean connected=false;
EditText txtserver=(EditText) findViewById(R.id.txt_ftpserver);
EditText txtusername=(EditText) findViewById(R.id.txt_username);
EditText txtpassword=(EditText) findViewById(R.id.txt_password);
String servername=txtserver.getText().toString();
String username=txtusername.getText().toString();
String password=txtpassword.getText().toString();
if(servername.length()<=0 || username.length()<=0 || password.length()<=0){
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage("Please fill all text boxes");
builder.setCancelable(true);
AlertDialog dialog = builder.create();
dialog.show();
}
else{
//save login info
saveLogin(servername,username,password);
try {
//disconnect the previous connection
if(client.isConnected()){
client.logout();
client.disconnect();
}
//connect to the remove ftp server
client.connect(servername);
//log in to the server with user name and password
client.login(username, password);
//set the passive mode for the file transfer
client.enterLocalPassiveMode();
//upload file to this directory
//client.changeWorkingDirectory("/www");
System.out.println(client.getReplyString());
int reply=client.getReplyCode();
if(FTPReply.isPositiveCompletion(reply)){ //connect an login successfully
connected=true;
}
else{
Toast.makeText(context, "Can't connect to the server", Toast.LENGTH_SHORT).show();
}
} catch (Exception e) {
e.printStackTrace();
}
}
return connected;
}
public void showTransferUI(){
TransferFragment tf=TransferFragment.newInstance();
FragmentTransaction transact=getSupportFragmentManager().beginTransaction();
transact.replace(R.id.fragment_container, tf,"transferf");
transact.addToBackStack(null);
transact.commit();
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
onResume();
//Toast.makeText(this, "count="+count, Toast.LENGTH_SHORT).show();
}
public void regControls(){
//Toast.makeText(this, localpath, Toast.LENGTH_SHORT).show();
EditText txtlocal=(EditText) findViewById(R.id.txt_local);
if(txtlocal!=null){
txtlocal.addTextChangedListener(new TextLocalChangeListener());
txtlocal.setText(localpath);
//Toast.makeText(this, "NULL", Toast.LENGTH_SHORT).show();
}
EditText txtserver=(EditText) findViewById(R.id.txt_server);
if(txtserver!=null){
txtserver.addTextChangedListener(new TextServerChangeListener());
txtserver.setText(serverpath);
}
ListView localFileslist=(ListView) findViewById(R.id.localfiles_list);
if(localFileslist!=null){
localFileslist.setSelector(R.drawable.selection_style);
localFileslist.setOnItemClickListener(new ClickListener());
}
ListView serverFileslist=(ListView) findViewById(R.id.serverfiles_list);
if(serverFileslist!=null){
serverFileslist.setSelector(R.drawable.selection_style);
serverFileslist.setOnItemClickListener(new ServerClickListener());
}
}
class TextLocalChangeListener implements TextWatcher{
public void beforeTextChanged(CharSequence s, int start, int before, int count){
}
public void onTextChanged(CharSequence s, int start, int before, int count){
EditText et=(EditText) findViewById(R.id.txt_local);
localpath=et.getText().toString();
localDirContents();
}
public void afterTextChanged(Editable ed){
}
}
class ClickListener implements OnItemClickListener{
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// selected item
ViewGroup vg=(ViewGroup)view;
TextView tv= (TextView)vg.findViewById(R.id.label);
String selectedItem=tv.getText().toString();
EditText et=(EditText) findViewById(R.id.txt_local);
localpath=localpath+"/"+selectedItem;
et.setText(localpath);
}
public void onNothingSelected(AdapterView<?> parent){
}
}
public void localDirContents(){
ListView localfiles=(ListView) findViewById(R.id.localfiles_list);
if(localpath!=null){
try{
File f=new File(localpath);
if(f!=null){
if(f.isDirectory()){
String[] files=f.list();
ArrayList<String> contents=toArrayList(files);
if(contents.size()>0){
ListAdapterModel lm=new ListAdapterModel(context,R.layout.listlayout,R.id.label,contents,localpath,null);
localfiles.setAdapter(lm);
}
else
{
localpath=f.getParent();
}
}
else{
localpath=f.getParent();
}
}
}catch(Exception e){}
}
}
public ArrayList<String> toArrayList(String[] contents){
ArrayList<String> list=new ArrayList<String>();
for(String f:contents){
list.add(f);
}
return list;
}
class TextServerChangeListener implements TextWatcher{
public void beforeTextChanged(CharSequence s, int start, int before, int count){
}
public void onTextChanged(CharSequence s, int start, int before, int count){
EditText et=(EditText) findViewById(R.id.txt_server);
serverpath=et.getText().toString();
BackTask bt=new BackTask();
bt.execute(null,null,null);
}
public void afterTextChanged(Editable ed){
}
}
class ServerClickListener implements OnItemClickListener{
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// selected item
ViewGroup vg=(ViewGroup)view;
TextView tv= (TextView)vg.findViewById(R.id.label);
String selectedItem=tv.getText().toString();
EditText et=(EditText) findViewById(R.id.txt_server);
serverpath=serverpath+"/"+selectedItem;
et.setText(serverpath);
}
public void onNothingSelected(AdapterView<?> parent){
}
}
public void serverDirContents(){
//get the files and directories from server
if(!isServerFile(serverpath)){
ftpobjects=filecontents(serverpath);
if(ftpobjects.size()<=0){
try {
//record the working directory
serverpath=client.printWorkingDirectory();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
else{
try {
//record the working directory
serverpath=client.printWorkingDirectory();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public ArrayList<FTPFile> filecontents(String path){
ArrayList<FTPFile> fileslist=new ArrayList<FTPFile>();
try {
//set the current working directory
client.changeWorkingDirectory(path);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
//list directories
FTPFile[] dirs=client.listDirectories();
for(FTPFile d:dirs){
fileslist.add(d);
}
//list files
FTPFile[] files=client.listFiles();
for(FTPFile f:files){
fileslist.add(f);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return fileslist;
}
public boolean isServerFile(String spath){
boolean isFile=false;
try {
FTPFile[] files = client.listFiles();
for(FTPFile f:files){
if(f.getName().equals(getFileName(spath))){
if(f.isFile()){ //the specified path is a file
isFile=true;
break;
}
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return isFile;
}
public String getFileName(String path){
String filename=path.substring(path.lastIndexOf("/")+1);
return(filename);
}
//background process for retrieving file and directories from server
private class BackTask extends AsyncTask<Void,Void,Void>{
protected void onPreExecute(){
super.onPreExecute();
//show process dialog
pd = new ProgressDialog(context);
pd.setTitle("Retrieving data");
pd.setMessage("Please wait.");
pd.setCancelable(true);
pd.setIndeterminate(true);
pd.show();
}
protected Void doInBackground(Void...params){
try{
//retrieving files and directories from server
serverDirContents();
}catch(Exception e){
pd.dismiss(); //close the dialog if error occurs
}
return null;
}
protected void onPostExecute(Void result){
//close the progress dialog
pd.dismiss();
//show files and folders in the ListView
ListView serverfiles=(ListView) findViewById(R.id.serverfiles_list);
ArrayList<String> contents=getFTPNames(ftpobjects);
if(contents.size()>0){
ListAdapterModel lm=new ListAdapterModel(context,R.layout.listlayout,R.id.label,contents,"",ftpobjects);
serverfiles.setAdapter(lm);
}
}
}
//get files and directories from the server
public ArrayList<String> getFTPNames(ArrayList<FTPFile> files){
ArrayList<String> list=new ArrayList<String>();
for(FTPFile f:files){
list.add(f.getName());
}
return list;
}
public void uploadFile(View view){
BackTaskUpload btupload=new BackTaskUpload();
btupload.execute(null,null,null);
}
public void upload(){
EditText txtLocalPath=(EditText)findViewById(R.id.txt_local);
String filepath=txtLocalPath.getText().toString();
File f=new File(filepath);
String filename=f.getName();
FileInputStream fis = null;
if(f.isFile()){
try {
fis = new FileInputStream(f);
client.storeFile(filename, fis);
fis.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
try {
fis.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//background process for uploading file to server
private class BackTaskUpload extends AsyncTask<Void,Void,Void>{
protected void onPreExecute(){
super.onPreExecute();
//show process dialog
pd = new ProgressDialog(context);
pd.setTitle("Uploading the file");
pd.setMessage("Please wait.");
pd.setCancelable(true);
pd.setIndeterminate(true);
pd.show();
}
protected Void doInBackground(Void...params){
try{
//upload selected file to server
upload();
}catch(Exception e){
pd.dismiss(); //close the dialog if error occurs
}
return null;
}
protected void onPostExecute(Void result){
pd.dismiss();
}
}
public void downloadFile(View view){
BackTaskDownload btdownload=new BackTaskDownload();
btdownload.execute(null,null,null);
}
public void download(){
FileOutputStream outfile=null;
EditText txtServer=(EditText)findViewById(R.id.txt_server);
String txtpath=txtServer.getText().toString();
if(isServerFile(txtpath)){
String filename=getFileName(txtpath);
try {
outfile=new FileOutputStream(localpath+"/"+filename);
client.retrieveFile(filename, outfile);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
try {
outfile.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//background process for downloading file from server
private class BackTaskDownload extends AsyncTask<Void,Void,Void>{
protected void onPreExecute(){
super.onPreExecute();
//show process dialog
pd = new ProgressDialog(context);
pd.setTitle("Downloading the file");
pd.setMessage("Please wait.");
pd.setCancelable(true);
pd.setIndeterminate(true);
pd.show();
}
protected Void doInBackground(Void...params){
try{
//upload selected file to server
download();
}catch(Exception e){
pd.dismiss(); //close the dialog if error occurs
}
return null;
}
protected void onPostExecute(Void result){
pd.dismiss();
}
}
//save the last login info
public void saveLogin(String domain,String username,String password){
try {
File file = new File(this.getFilesDir(), "ftplogin.info");
FileWriter fw=new FileWriter(file);
BufferedWriter bw=new BufferedWriter(fw);
bw.write(domain+"-"+username+"-"+password);
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//read the previous login info
public String[] readLogin(){
String[] info=null;
try {
File file = new File(this.getFilesDir(), "ftplogin.info");
if(file.exists()){
FileReader fr=new FileReader(file);
BufferedReader br=new BufferedReader(fr);
info=br.readLine().split("-");
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
return info;
}
}
When the uploader app firstly runs, the interface that the user sees is the log in form. So the sub-interface LoginFragment object is created and added to the container.
LoginFragment lf=new LoginFragment();
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container,fl).commit();
The initLogin method will be called when the program starts to read the last log in information that contains domain name, username, and password to place on the log in form so that the user can connect to server without reentering the same log in information again (It saves your time).
The onResume method is overridden to register the controls or components on the TransferFragment sub-interface to text change events (EditText components) and Item click events (ListView components) by calling the regControls method.
The onDestroy method is also overridden to close the progress dialog (if it is still showing) and to disconnect from the remove server before the user leaves the app.
The connectToServer method is called when the Connect button on the log in form is pressed. This method subsequently calls the connect method to connect to the remove server. It returns true if the app successfully connects to the remote server. Otherwise it returns false. The connect method of the FTPClient class is used to connect to the remove server. You need to supply to this method the ftp domain name (e.g. ftp.worldbestlearningcenter.com). To log in to the remote server, you will use the login method. This method takes username and password. You must provide the correct username, and password to login in to the server. Generally, you will use the passive mode data connection to transfer files between the client and the server. To set the passive mode for the data connection, you will use the enterLocalPassiveMode method method.
To test whether you successfully connect to the server, you will use the isPositiveCompletion method of the FTPReply class. This method takes the reply code to be its argument. You can get the reply code by using the getReplyCode method of the FTPClient class.
If the connection is successful, the connectToServer method subsequently calls the showTranserUI method to display the file transfer sub-interface. In the showTranserUI method, the TransferFragment object is created and added to the container. It replaces the first sub-interface (LoginFragment).
The local text box (txt_local) displays the selected local file or directory. It also allows you to enter or edit the file or directory path. Every time the text of the txt_local changes, the contents of the local list also changes by calling the localDirContents method. This text box registers with the text change listener event when the regControls method is called. The ChangeListener extends the TextWatcher interface to handle the text change event of the text box.
When the user selects a file or directory name from the the local list, the file or directory name will be appended to the txt_local text box (making change to the text box) so that the contents of the local list are updated accordingly. The ListView local list (localfiles_list) is registered with the item click event by using the setOnItemClickListener method. This method takes an object of the ClickListener class that implements the OnItemClickListener inerface.
The text box and list for the server side (txt_server and serverfiles_list) work the same as the text box and list of the local side. However, since retrieving the files and directories from the server can take long time, the serverDirContents that is called every time the txt_server changes will be called from the AsyncTask class. This will place the files and directories retrieving task in the background process so that the user interface is not locked or is still responsive when the process goes on. To more explanation about using the AsyncTask to do background task, you will visit the KeywordDensityCheckter app. The ProgressDialog component is used at the beginning of the file and directories retrieving process to inform the user that the process is going on and need to wait.
Each list (either local side or server side) has the same selection style. The layout file that applies the selection style to the list is stored in the drawable directory of the uploader project and written as shown below.
selection_style.xml file
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape>
<gradient
android:startColor="#dfdfdf"
android:endColor="#dfdfdf"
android:angle="180" />
</shape>
</item>
</selector>
The uploadFile method is invoked when the user presses the upload icon to upload the selected file to the remote server. The FTPClient has a method called storeFile that can be used to upload the file to the server. This method takes two arguments. One argument is the filename to be stored on the server. The second argument is the InputStream object that contains the data of the local file. The uploading file process can take long time so it is also placed as a background process by using the AsyncTask class.
The downloadFile method works similar to the uploadFile method except that it transfers the file from server to the local or client side. You will use the retrieveFile method of the FTPClient class to download the file from the server. This method takes two arguments. One is the filename from the server and another one is the OutputStream object to receive the data of the file and write it to the local storage.
Before running the uploader for Android app, you need to make change to the AndroidManifest.xml file to allow the app to use the device storage and internet. This is the content of the AndroidManifest.xm file.
AndroidManifest.xm file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.andftp"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="17" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:allowBackup="true"
android:icon="@drawable/and_ftp"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.example.andftp.MainActivity"
android:label="@string/app_name"
android:configChanges="orientation"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Another file that you need to make change is strings.xml file. It contains global string variables used in the app. Its content is shown below.
strings.xml file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">AndFTP</string>
<string name="action_settings">Settings</string>
<string name="hello_world">Hello world!</string>
<string name="txt_username">Enter username</string>
<string name="txt_password">Enter password</string>
<string name="bt_connect">Connect</string>
<string name="bt_upload">Upload</string>
<string name="bt_download">Download</string>
<string name="txt_local">Enter local path</string>
<string name="txt_server">Enter server path</string>
<string name="txt_ftpserver">Enter ftp server host</string>
</resources>
Now you are ready to run the uploader for Android app. If you have any questions, please leave them at the comments part below.
Download apk file of the Uploader for Android app