Pages

Friday, October 11, 2013

Uploader for Android

In this post, you will learn to create an uploader for Android. The uploader app can be used to connect to an FTP server and transfer files between the client and the server. For someone who doesn't want to learn how to create the file uploader, but wants to download and install this app on your devices, you can jump to the bottom of the page. You will find the download link there. It is free.
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.

Uploader for Android-Transfering file interface

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.

Uploader for Android-upload file to server

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.


Uploader for Android-download file from server


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


compass app flashLight app

Monday, October 7, 2013

Keyword Density Checker

There are many keyword density checkers on the web. The novice webmasters always use the keyword density checker to get information about their web page content. A good keyword checker helps the webmaster to have pages that conform to SEO (Search Engine Optimization) guidelines so that their web pages will get high ranks from the search engines. Many SEO experts consider the optimum keyword density to be 1 to 3 percent. The percent that is greater than this will be considered as spam and be penalized by the search engines.

In this post, you will learn to create a simple app to be used to check keywords density of a web page from Android devices. I called this project KeywordDensityChecker. You need to create a project in Eclipse and name it KeywordDensityChecker. The KeywordDensityChecker reads content of web page and analyze it to produce a keyord density table that shows keywords, frequencies, and percentages. The frequency of each keyword represents the number of the same keywords found in the web page. Its percentage is calculated by multiplying 100 to the divisional result of the frequency and the total keywords analyzed.
For the user interface of this simple keyword density checker, we need one EditText, one Button, and one ListView. The EditText allows the user to enter a web page address. The Button will be pushed to read the content of the web page and shows the analyzed result on the ListView. These views or components are defined in the activity_main.xml file that is the resource of the Main_Actity class.

activity_main.xm 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:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity"
    android:orientation="vertical"

     >

  <EditText
        android:id="@+id/txt_input"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="text"
        android:hint="Enter web page adress"
        />
     
<Button
          android:id="@+id/bt_extract"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:text="Check"
          android:onClick="check"
          />

 
        <ListView
            android:id="@+id/density_list"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:paddingBottom="5dp"
            android:paddingTop="5dp"
     
            />    

</LinearLayout>


The ListView displays keywords, frequencies, and percentages. Thus, the ListView needs to be customized to show these three items. The layout file (listlayout.xml) of the ListView will have three TextView components defined. One TextView displays the keyword; another one displays the frequency; and the last one displays the percentage.

keyword density checker



The content of the listlayout.xml file is shown below.

listlayout.xml file

<?xml version="1.0" encoding="utf-8"?>

<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" >

<TextView
    android:id="@+id/keyword"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10sp"
        android:textSize="20sp"
        android:textColor="#0000ff"
        android:textStyle="bold"
       
         >
</TextView>

<TextView
        android:id="@+id/count"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10sp"
        android:textSize="20sp"
        android:textColor="#0000ff"
 
        android:textStyle="bold" >
</TextView>

<TextView
    android:id="@+id/percent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10sp"
        android:textSize="20sp"
        android:textColor="#0000ff"
   
        android:textStyle="bold" >
</TextView>
</LinearLayout>


Now we take a look at the MainActivty.java file that contains the MainActivity class. This will be the start point of our KeywordDensityChecker app. In this class, the check method is defined. The check method will be called when the Check button is pushed. In this method, we reference to te EditText address and the density_list views. The Checker object is created. All methods are used to read content of the web page, analyze, and prepare the necessary data (keywords, frequencies, and percentages) to construct the keyword density table are defined in the Checker class.

MainActivity.java file

package com.example.keyworddensitychecker;

import android.os.AsyncTask;
import android.os.Bundle;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.view.Menu;
import android.view.View;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;


public class MainActivity extends Activity {

private Context context;
private ListView list;
private Checker checker;
ProgressDialog pd;
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        context=this;      
    }
 
    @Override
    protected void onDestroy() {
    if (pd!=null) {
pd.dismiss();

}
    super.onDestroy();
    }

    @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 check(View view){
    EditText txtaddress=(EditText)findViewById(R.id.txt_input);
    String address=txtaddress.getText().toString();
    list=(ListView)findViewById(R.id.density_list);
        checker=new Checker();
    if(address.length()>0){
    BackTask bt=new BackTask();
    bt.execute(address,null,null);
    }
    else
    Toast.makeText(context, "Please input web page address", Toast.LENGTH_SHORT).show();
    }
 
    private class BackTask extends AsyncTask<String,Void,Void>{  
   
   
    protected void onPreExecute(){
    super.onPreExecute();
    //show process dialog
    pd = new ProgressDialog(context);
pd.setTitle("Checking...");
pd.setMessage("Please wait.");
pd.setCancelable(true);
pd.setIndeterminate(true);
pd.show();
   
   
    }
    protected Void doInBackground(String...params){    
    try{
    //start analyzing the web page
    checker.processChecking(params[0]);
    }catch(Exception e){
    pd.dismiss();   //close the dialog if error occurs
    }
return null;
   
    }
   
   
   
    protected void onPostExecute(Void result){
    //close the progress dialog
    pd.dismiss();
    //create ListAdapterModel object for the ListView so that
    //the keyword density table can be displayed
    ListAdapterModel lam=new ListAdapterModel(context,R.layout.listlayout,checker.getWords(),checker.getCounts(),checker.getPercents());
    list.setAdapter(lam);
    }
   
    }



 
}


Reading content of the web page, analyzing it, and displaying the result can take long time. So it is a good idea to place these processed in background thread. In the previous post (Web Downloader), you learn to use the IntentService class to put the web downloading process in background thread so that it runs without locking the user interface. In this KeywordDensityCheck, you learn another tool to do the background process. This tool is called AsyncTask. The AsyncTask has three important methods--onPreExecute, doInBackground, and onPostExecute. Int the onPreExecute mehtod, you can write code to do something before the background process starts. In this app, the ProgressDialog is displayed to tell the user to wait.

Progress Dialog to wait while analyzing the web page


In the doInBackground method, you will place code that represents the background process. In this case, the proessChecking method of the Checker class is called to read the content of the web page, analyze it, and prepare the necessary data to construct the keyword density table. After the background process completes you can write code to do something in the onPostExecute method. In our KeywordDensityChecker app, the task to do after the background process completes is closing the progress dialog and constructing the ListAdapterModel object for the ListView component so that the keyword density table can be shown to the user.
The ListView component needs the ArrayAdapter object be its data source. In default, the ListView can display only a single TextView component. In this app, the ListView needs three TextView components. The first step to customize the ListView is to define the three TextView components in the layout file as i mentioned above. The additional step to customize the ArrayAdapter to enable the ListView to show the keyword density table that consists of keywords, frequencies, and percentages. Generally, to customize the ArrayAdapter class, you need to extend it. The ListAdapterModel extends the ArrayAdapter class. The important part of the ArrayAdapter class that must be overridden is the getView method. In this method, code is written to allow the ListView to show the keyword on the keyword TextView, frequency on the count TextView, and the percentage on the percent Textview. Below is the content of the ListAdapterModel class.

ListAdapterModel.java file

package com.example.keyworddensitychecker;

import java.util.ArrayList;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class ListAdapterModel extends ArrayAdapter<String>{
int groupid;
ArrayList<String> words;
Context context;
ArrayList<String> counts;
ArrayList<String> percents;

public ListAdapterModel(Context context, int vg,ArrayList<String> words,ArrayList<String> counts,ArrayList<String> percents){
super(context,vg, words);
this.context=context;
groupid=vg;
this.words=words;
this.counts=counts;
this.percents=percents;
}
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);
        TextView textKeyword = (TextView) itemView.findViewById(R.id.keyword);
        TextView textCount= (TextView) itemView.findViewById(R.id.count);
        TextView textPercent= (TextView) itemView.findViewById(R.id.percent);
        textKeyword.setText(words.get(position));
        textCount.setText(counts.get(position));
        textPercent.setText(percents.get(position));
        return itemView;
     
}

}


The last code fragment that defines the backgroud process is in the Checker class. In this class, there are four methods. The first method ,readWords will be called to read the content of the web page, split the content in to words. Each word is separated by a single space or many spaces. The split method is used to split the content in to words. A word might contain symbols. The regular expression is used to remove all symbols from the words. The Pattern class is used to define string pattern ("\\W+") to match all symbols (except underscore) and the replaceAll method of the Matcher is called to remove all the symbols from the words. To learn more about regular expression in Java, read this page. The words are filtered again to ignore the words that are not keywords. The filtering task is performed by invoking the addToList method. The addToList method adds every keyword to the LinkedList, keywordsList. The countWords method is invoked to count the frequency of each keyword. This method also adds the every keyword and its frequency to the HashMap, denMap. The prepareTable method prepares data necessary to construct the keyword density table. The keywords will be stored in the ArrayList, keywords; the frequencies are stored in the ArrayList, counts; and the percentages are stored int he ArrayList, percents. The processChecking method wraps the readWords, countWords, and prepareTable methods so that they can be called at once. The getWords, getCounts, and getPercents methods are called from the MainActivity class to return the list of keywords, the list of frequencies, and the list of percents to be used in the ListAdapterModel class. The content of the Checker class is shown below.

Checker.java file

package com.example.keyworddensitychecker;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


class Checker{
private String address;
private TreeMap<String, Integer> denMap;
private LinkedList<String> keywordsList;
private ArrayList<String> keywords;
private ArrayList<String> counts;
private ArrayList<String> percents;

Checker(){
denMap=new TreeMap<String,Integer>();
keywordsList=new LinkedList<String>();
keywords=new ArrayList<String>();
counts=new ArrayList<String>();
percents=new ArrayList<String>();
}

public void readKeywords(){
Pattern pattern=Pattern.compile("\\W+");
try {
//read the web page content
URL url=new URL(address);
HttpURLConnection con=(HttpURLConnection)url.openConnection();
InputStream is=con.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
String strLine;
while((strLine=br.readLine())!=null){
String[] words=strLine.split("[ ]+"); //split content in to words
for(String word:words){
Matcher mat=pattern.matcher(word); //remove all symbols except underscore
word=mat.replaceAll("");
addToList(word.toLowerCase());
}
}

br.close();
} catch (Exception ex) {
// TODO Auto-generated catch block
ex.printStackTrace();
}
}

public void addToList(String word){

//filter non-keywords
//there are more non-keywords that are not filtered in this simple program
//you can add those non-keywords in the nonKeyword array below.
boolean isNonKey=false;
String[] nonKeywords={"a","an","after","and","are","as","at","above","before"
,"below","beyond","br","div","for","in","is","li","of","on","p","the"
,"span","that","this","those","tr","td","to","ul","under","when","where","you"};
for(String nonKey:nonKeywords){
if(nonKey.equals(word)){
isNonKey=true;
break;
}
}
//add only the keyword to the list
if(!isNonKey)
keywordsList.add(word);
}


public void countKeywords(){
int count=1;
String word="";
for(int i=0;i<keywordsList.size();i++){
word=keywordsList.get(i);
for(int j=i+1;j<keywordsList.size();j++){
if(word.equals(keywordsList.get(j))){
count++;
}
}

addToMap(word,count);
count=1;
}

}

public void addToMap(String word, int count){
//place keyword and its frequency in TreeMap
if(!denMap.containsKey(word) && word.length()>=1){
denMap.put(word, count);
}

}


public void prepareTable(){

Set<String> keys=denMap.keySet();
int numWord=keys.size();
Iterator<String> iterator=keys.iterator();
while(iterator.hasNext()){
String word=iterator.next();
int count=denMap.get(word);
int p=100*count/numWord;
if(p>0){
keywords.add(word);
counts.add(String.valueOf(count));
percents.add(p+"%");

}
}



}

public void processChecking(String address){

this.address=address;
readKeywords();
countKeywords();
prepareTable();
}

public ArrayList<String> getWords(){
return keywords;
}
public ArrayList<String> getCounts(){
return counts;
}
public ArrayList<String> getPercents(){
return percents;
}


}


Before starting to run the KeywordDensityChecker app, you need to allow Android to use the internet by placing the below code to the AndroidManifest.xml file.

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

Download the apk file of the KeywordDensityChecker app