Page 1 of 1

Android Socket Tutorial - Part 2

#1 EndLessMind  Icon User is offline

  • Android Expert
  • member icon

Reputation: 266
  • View blog
  • Posts: 1,236
  • Joined: 13-March 09

Posted 13 August 2016 - 02:49 PM

Welcome back!
Oh, I see that you're back for Part 2 of the Android Socket Tutorial :)/>

As said in Part 1, in this part we'll be covering:

  • Data structure examples for sending over network.
  • Listener to notify the clients parent(Activity or Service) about events(incoming data or errors)
  • Talk about how we are reducing the load of the CPU by interrupting the threads
  • Creating file server browser using code from Part 1




I'll try to make this part shorter than Part 1, and will only go into details on the first 2 sections.

So, without further ado let's dive into it!



The structure of the data
So, we're going to start communication between the client and the server.
To do so, we need to send some kind of data from one to another.

For this, we'll be using JSON as some java-classes to create it.
There is no perfect answer to why we're going to use JSON other than that
it's what I prefer to use. We could also have done with XML or any other human-readable type of
data structure. We could even make up our own if we wanted to.

We'll use 2 base classes to format the data into JSON.
These classes will be extended as needed to alter the data the JSON-string will contain.

We'll be using a base command class, and a base data class.

The base command will hold the command as a string, and a base data object to hold any other data we want to send.

The base command class will be called BaseSocketObject, it has 2 instance variables: request as a string and data as a base data class.
It also has a public method that converts the object to a JSON string, and one that converts that JSON-string into a byte-array.

That class will look like this:
public class BaseSocketObject {

	public String request;
	public BaseData data;
	
	
	public String toJSON() {
		JSONObject json= new JSONObject();
		try {
			json.put("request", request);
			if (data != null)
				json.put("data", data.toJSON());
			return json.toString();
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
	public byte[] toByte() {
		return toJSON().getBytes();
	}

}


the toJSON() creates a new JSONObject and puts the data into it and then return the JSON-string.

Next, we need the base data class. We'll name it BaseData.
This will just be a placeholder, as we'll only be using classes that extends it.
The only thing this class needs, is a toJSON method. This method will return null as any class extending BaseData will override it anyway.

This is what the BaseData class will look like:
public class BaseData {
	//This class is just a placeholder
	public JSONObject toJSON() {
		return null;
	}
}


The reason the the variable data in BaseSocketObject is of type BaseData, is because it will let us assign any class the extends BaseData to it.
Because they will be extending BaseData, they will all have the toJSON method. If data was of type Object, we would need to check what class it
belongs to, cast it to that class and then call methods.

For example
If data as if type Object and we had 2 classes named DataClass1 and DataClass2. Non of which extends any other class but both having a toJSON method.
This is what we would have to do to call the toJSON on any of them inside the toJSON method of BaseSocketObject-class.
	public String toJSON() {
		JSONObject json= new JSONObject();
		try {
			json.put("request", request);
			if (data != null)
				if(data instanceof DataClass1) {
					DataClass1 d1 = (DataClass1)data;
					json.put("data", d1.toJSON());
				}else if(data instanceof DataClass2) {
					DataClass2 d2 = (DataClass2)data;
					json.put("data", d2.toJSON());
				}
			return json.toString();
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}


So, by extending a base class that has the toJSON method, we don't need to worry about the class.

So, what classes do we need that extends BaseData?
For this tutorial we're going to use 2 classes that extends BaseData:
The first one is FileData. This will hold information about the files sent by the server.
For this tutorial, we'll create an array of FileData from the data the server sends back when we request a list of files.

We'll also use this class for when we want the server to transfer a specific file, this will be the only time
we use it for sending data to a server.


The class will hold filename, file path, file size and file type (File type will only be used to indicate if it's a directory or not).
As said before, it will extended BaseData.
First,we need to create instance variables for each piece of data:
	public String fileName;
	public String Path;
	public long Size;
	public int Type;


We're going to be using 2 constructor, one with an argument for each piece of data and one with only an argument for the string Path
The single-argument constructor will be used when we request a file to be transferred. For that we'll only need to tell the server the path
of the file.

The other constructor will be used when we parse the filelist from the server.

The constructors will look like this:
	public FileData(String ph) {
		Path = ph;
	}
	
	public FileData(String fn, String ph, long s, int t) {
		fileName = fn;
		Path = ph;
		Size = s;
		Type = t;
	}


Lastly, we need to override the toJSON to input the data that this class will send to the server.
In this case, that would be only the Path.
So that would look like this:
	@Override
	public JSONObject toJSON() {
		JSONObject json= new JSONObject();
		try {
			json.put("filePath", Path);
			return json;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}



Final result will look like this:
public class FileData extends BaseData {

	public String fileName;
	public String Path;
	public long Size;
	public int Type;
	
	public FileData(String ph) {
		Path = ph;
	}
	
	public FileData(String fn, String ph, long s, int t) {
		fileName = fn;
		Path = ph;
		Size = s;
		Type = t;
	}
	 
	@Override
	public JSONObject toJSON() {
		JSONObject json= new JSONObject();
		try {
			json.put("filePath", Path);
			return json;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
}



Next class we need that extends BaseData will be named FolderData.
This will be used when navigating into a subfolder and we want to get the files in that directory.
This class will only contain one instance variable, a string named folder.
The FolderData will always be the data-class for when we request a list of files from the server.

It will only have one constructor, and that will have 1 string as argument.
As this class will be used to send data to the server, we'll override toJSON.

That class will look like this:
public class FolderData extends BaseData {

	public String folder;

	public FolderData(String f) {
		folder = f;
	}
	
	@Override
	public JSONObject toJSON() {
		JSONObject json= new JSONObject();
		try {
			json.put("folder", folder);
			return json;
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
}


The string folder will not contain the complete path to the directory,
it will only contain all the subfolders name from the root-directory up intill (and including)
the directory we want to "move into"

Example:
If the root-directory is C:\Windows\ and we want to get into C:\Windows\System32\drivers\etc\
then the the variable folder will be root\System32\drivers\etc\
That way, we can't access the parent of the root-directory, C:\ in this example

Note: root-directory is the directory from where we'll start browsing. This is set in the server application.


The JSON-string that the above example will produce will look like this:
{
  "cmd": "server_get_files",
  "data": {
    "folder": "root\\System32\\drivers\\etc\\"
  }
}


and the response from the server would look like this:
{
  "Request": "server_get_files_response",
  "Data": {
    "allFiles": [
      {
        "Size": 946,
        "Path": "C:\\Windows\\System32\\drivers\\etc\\host",
        "FileName": "12.png"
      },
      {
        "Size": 3683,
        "Path": "C:\\Windows\\System32\\drivers\\etc\\lmhosts.sam",
        "FileName": "16.png"
      }
      ,
      {
        "Size": 407,
        "Path": "C:\\Windows\\System32\\drivers\\etc\\networks",
        "FileName": "16.png"
      }
      ,
      {
        "Size": 1358,
        "Path": "C:\\Windows\\System32\\drivers\\etc\\protocol",
        "FileName": "16.png"
      }
      ,
      {
        "Size": 17463,
        "Path": "C:\\Windows\\System32\\drivers\\etc\\services",
        "FileName": "16.png"
      }
    ]
  }
}

Note: Some of the files in the etc folder on windows don't have file extensions, that's why the don't have it in the example.




React to events using callbacks
When we receive data, or when something goes wrong, we want to do something.
Using interface we can create a callback (aka listener) to be called when
something happens. This will make a callback to what ever context is holding the
instance of the interface.

We're going to create a new class-file, name it MessageReceivedListener and change "class" to "interface".
Then we're going to create 5 methods with empty bodys. Each one will be a specific event.

Here is the 5 methods we're going to create.
	public void OnMessageReceived(String msg);
	public void OnFileIncoming(int length, int downloaded);
	public void OnFileDataReceived(byte[] data,int read, int length, int downloaded);
	public void OnFileComplete(int got, int expected);
	public void OnConnectionerror();


So, what do they do:
OnMessageReceived - Called when new command/message received
OnFileIncoming - Called when new file is incoming.
OnFileDataReceived - Called when more data of a file has been transfered
OnFileComplete - Called when file transfer is complete
OnConnectionerror - Called when an error occur
OnConnectSuccess - Called when socket has connected to the server

The resulting code (including method descriptions):
public interface MessageReceivedListener {
	
	/**
	 * Called when new command/message received
	 * @param msg Command/message as String
	 */
	public void OnMessageReceived(String msg);
	
	/**
	 * Called when new file is incoming.
	 * @param length Total length of data expected to be received
	 * @param downloaded Total length of data actually received so far
	 */
	public void OnFileIncoming(int length);
	
	/**
	 * Called when more data of a file has been transfered
	 * @param data Byte array of data received
	 * @param read The lenght of the data received as int
	 * @param length Total length of data expected to be received
	 * @param downloaded Total length of data actually received so far
	 */
	public void OnFileDataReceived(byte[] data,int read, int length, int downloaded);
	
	/**
	 * Called when file transfer is complete
	 */
	public void OnFileComplete();
	
	/**
	 * Called when an error occur
	 */
	public void OnConnectionerror();
	
	/**
	 * Called when socket has connected to the server
	 */
	public void OnConnectSuccess();
}



Now, go into the TCPClient class and add a instance variable for the MessageReceivedListener:
private MessageReceivedListener mList;

Now we need to create a constructor for the TCPClient that accepts MessageReceivedListener as argument.

	public TCPClient(MessageReceivedListener l) {
		mList = l;
	}



Go into the ConnectRunnable and in the catch-block, we need to call the OnConnectionerror in the listener (always do null-check first ;)/> )
Right before the catch-block, we'll add:
	if (mList != null)
		mList.OnConnectSuccess();


Then, inside the catch-block, we'll add:
	if (mList !=null)
		mList.OnConnectionerror();


This should also be added to the catch-block for the SendRunnable constructor and the catch-block in the run-method of ReceiveRunnable.
While we're in the ReceiveRunnable, find the while-loop for TYPE_CMD.

After it, we need to call OnMessageReceived with the string in the StringBuilder as argument.
	if (mList != null)
		mList.OnMessageReceived(sb.toString());


Now, move down to the TYPE_FILE_CONTENT part of the if-statement (still in the ReceiveRunnable).
As the first thing here, add a call to OnFileIncoming and pass the variables length and downloaded into it.
	if (mList != null)
		mList.OnFileIncoming(length, downloaded); 



This will be used to open a FileOutputStream so that we can save the file.


Now, go down to the while-loop for TYPE_FILE_CONTENT, right under downloaded += read
make a call to OnFileDataReceived and pass the there variables: inputData, read, length and downloaded.
	if (mList != null)
		mList.OnFileDataReceived(inputData, read, length, downloaded);



This will be used to write the newly feached data to a file


Lastly, right after the while-loop, make call to OnFileComplete.
	if (mList != null) 
		mList.OnFileComplete();


This will be used to close the FileOutputStream that saved the file.


Now, when we create a new instance of TCPClient, we need to pass on a reference to a MessageReceivedListener.
When events are called, we can do stuff, like update a listview with a new set of files received from the server.


Reducing the load of the CPU
Okay, so this will be short, as the solution is very simple.

Most tutorials out there want you to use loops in a thread.
That's great, but never ending loops will just eat up CPU-cycles, make the device run hot and might cause
other applications to lagg, even if the loops are in a separate thread.

So, how to we solve that.
We, that's what we've got stopThreads(), startSending() and startReceiving() for.
When're done for the moment, we just stop the threads but keep the connection open.

So next time we want to transfer data over the connection, we just start the threads again.
By interrupting the threads, we break out of the loops and the threads finishes and we're freeing
up the CPU.

Well, then you might think: "Well, why don't you just skip the loops, then the thread will exit
them self when data has been sent/received."

Yes, that is's true. But then you wouldn't have this section to read and might not have an idea
of how to fix the issue in an other application.

For this application, it would work fine without the main loops inside the runnables.
But for a chat-application, you might want to be able to at least receive message as soon as they
are available in the InputStream.

That's one way to do it. An other is to not use the main loops, and instead try to see if there is any data
available every 1000ms or so.

If you wish to modify your SendRunnable and receivedRunnable to not use the main loops, you basically need
to remove this:
	while (!Thread.currentThread().isInterrupted() && isConnected()) {
	}



Putting it to use (file browser)
In this part, we'll be creating an application using the the code we've written.
When you created the android project, an activity should have been make.
If not, create a class file named MainActivity. This should extended Activity, and
we should also have an xml file for the layout.
We're also going to be using a class named FileAdapter and one named Utils.


I'll jump through this quite quickly and only explain some of the code.
If you've been using activitys, buttons and listviews before, most of this wont need
and explanation.

Let's just move through the xml files real quick:

In layout folder:

First, file_item.xml for the listview items
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@drawable/item_background"
        android:paddingTop="5dp"
        android:paddingBottom="5dp"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/tvFileName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceMedium" />

        <TextView
            android:id="@+id/tvFileSize"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceSmall" />

    </LinearLayout>

</LinearLayout>



Then, the activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center_horizontal|top" >

        <Button
            android:id="@+id/btnConnect"
            android:layout_width="180dp"
            android:layout_height="wrap_content"
            android:text="@string/btnConnect_text" />

    </LinearLayout>

    <ListView
        android:id="@+id/lvFiles"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
    </ListView>

</LinearLayout>





Then, make a new resources folder name drawable
In there, create an xml-file named item_background
It will look like this:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
	<item>
		<shape android:shape="rectangle"
			android:dither="true">


			<corners android:radius="2dp"/>

			<solid android:color="#ccc" />

		</shape>
	</item>

	<item android:bottom="2dp">
		<shape android:shape="rectangle"
			android:dither="true">

			<corners android:radius="2dp" />

			<solid android:color="@android:color/white" />

		</shape>
	</item>
</layer-list>


Lastly, go into the values-folder and open strings.xml.
In here, we're going to add the following lines
    <string name="btnConnect_text">Connect to server</string>
    <string name="dialog_file_confirm">Are you sure you want to download %1$s?</string>
    <string name="dialog_option_yes">Yes</string>
    <string name="dialog_option_no">No</string>
    <string name="dialog_file_confirm_title">Are you sure?</string>
    <string name="dialog_transfer_file_msg">Transferring %1$s</string>
    <string name="dialog_transfer_file_title">Transferring file</string>
    <string name="dialog_loading_title">Loading..</string>
    <string name="dialog_loading_msg">Loading, please wait..</string>



Now, moving over to the Java-code
Let's start out with the Utils.java file

public class Utils {

	/**
	 * Takes a subfolder array and converts it to a single string.
	 * @param data
	 * @return
	 */
	public static String createFolderPathFromArray(ArrayList<String> data) {
		String formatted = "";
		for (String s : data) {
			formatted += "\\" + s;
		}
		
		return formatted;
	}
	
	/**
	 * Format double to 2 decimals
	 * @param dub Double value to reduce to 2 decimals
	 * @return Double with 2 decimal as String
	 */
	private static String formateDouble(double dub) {
		String value = dub + "";
		if (value.length() > value.indexOf('.') +2){
			value = value.substring(0, value.indexOf('.') +2);
		}
		
		return value;
	}
	
	/**
	 * Format bytes to nearest unit prefix
	 * @param size File size in bytes
	 * @return Formatted string
	 */
	public static String FormatFileSize(double size) {
		long KB = 1024;
		long MB = KB*KB;
		long GB = MB * KB;
		if (size < KB) {
			if (size > -1) {
			return (int)size + "b";
			} else {
				return "0b";
			}
		}
		long pre_value = (long) (size / KB);

		if (pre_value < 1000) {
			//Less than 1000KB
			return formateDouble(size/KB) + "Kb";
		} else if (pre_value < 1000000) {
			//Less than 1000MB
			return formateDouble(size/MB) + "Mb";
		} else if (pre_value > 1000000 && pre_value < 100000000) {
			//Less than 1000GB
			return formateDouble(size/MB/KB) + "Gb";
		} else if (pre_value < 10000000000L) {
			//Less than 1000TB
			return formateDouble(size/GB/MB/KB) + "Tb";
		} else {
			return size + "b";
		}

	}
	
}


Quick explanation for createFolderPathFromArray.
When we move into a subfolder, we just take it's name and add it to a ArrayList.
This method just loops through and appends them to a string with "\\" as a divider.
First foldername will always be "root"


Next, the FileAdapter.java

public class FileAdapter extends BaseAdapter {

	private LayoutInflater inflator = null;
	private ArrayList<FileData> files;

	 class ViewHolder {
		 public TextView tvFileName;
		 public TextView tvFileSize;
	}
	 
	public FileAdapter(Context context, int resource, ArrayList<FileData> s) {
		this.files = s;
		inflator = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
	            
	}
	 
	
	@Override
	public int getCount() {
		return files.size();
	}

	@Override
	public Object getItem(int position) {
		return files.get(position);
	}

	@Override
	public long getItemId(int position) {
		return position;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		final ViewHolder holder;
		
    	if(convertView == null){
    		int layout =  R.layout.file_item;
    		convertView = inflator.inflate(layout, null);

    		holder = new ViewHolder();
    		holder.tvFileName = (TextView) convertView.findViewById(R.id.tvFileName);
    		holder.tvFileSize = (TextView) convertView.findViewById(R.id.tvFileSize);

    		convertView.setTag(holder);
    		
    	} else {
    		holder = (ViewHolder) convertView.getTag();
    		
    	}
    	
    	FileData fd = files.get(position);
    	
    	holder.tvFileName.setText(fd.fileName);
    	if (fd.Type == 1)
    		holder.tvFileSize.setText(Utils.FormatFileSize(fd.Size));
    	else if (fd.Type == 2)
    		holder.tvFileSize.setText("Folder");
    	else if (fd.Type == -1)
    		holder.tvFileSize.setText("..."); //The "go back" item
		
		
		return convertView;
	}

}



Basically a standard BaseAdapter. We just use our custom layout (file_item.xml).
Very basic stuff.


Next, the MainActivity.java.
public class MainActivity extends Activity implements MessageReceivedListener, onclickListener, OnItemClickListener {

	public static String TAG = "MainActivity";
	FileOutputStream fileOutputStream;
	ArrayList<FileData> filesOnServer;
	ArrayList<String> folderStruc = new ArrayList<String>(Arrays.asList("root")) ;
	FileData fileDownload;
	
	TCPClient client;
	FileAdapter fileAdapt;
	ListView lvFiles;
	Button btnConnect;
	ProgressDialog progressDialog;
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		btnConnect = (Button) findViewById(R.id.btnConnect);
		lvFiles = (ListView) findViewById(R.id.lvFiles);
		
		lvFiles.setOnItemClickListener(this);
		btnConnect.setonclickListener(this);
		
		client = new TCPClient(this);
	}
	
	@Override
	public void onDestroy() {
		super.onDestroy();
		if (client != null && client.isConnected())
			client.Disconnect();
	}
	
	/**
	 * Get files from server
	 */
	private void GetFilesFromServer() {
		//Show loading window
		showLoadingDialog();
		//Create the request and send it to the server
		BaseSocketObject bso = new BaseSocketObject();
		bso.request = TCPCommands.CMD_REQUEST_FILES;
		bso.data = new FolderData(Utils.createFolderPathFromArray(folderStruc));
		client.WriteCommand(bso.toJSON());
	}
	
	/**
	 * Show dialog for when loading data from server
	 */
	private void showLoadingDialog() {
		MainActivity.this.runOnUiThread(new Runnable() {
			@Override
			public void run() {
				Resources re = getResources();
				progressDialog = ProgressDialog.show(MainActivity.this, 
						re.getText(R.string.dialog_loading_title),
						re.getText(R.string.dialog_loading_msg), true);
			} 
		});
	}
	
	/**
	 * Show dialog for file transfer progress
	 * @param value
	 * @param max
	 * @param fileName
	 */
	private void showTransferringDialog(final int max, final String fileName) {
		MainActivity.this.runOnUiThread(new Runnable() {
			@Override
			public void run() {
				Resources re = getResources();
				progressDialog = new ProgressDialog(MainActivity.this);
				progressDialog.setTitle(R.string.dialog_transfer_file_title);
				progressDialog.setMessage(re.getString(R.string.dialog_transfer_file_msg, fileName));
				progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
				progressDialog.setIndeterminate(false);
				progressDialog.setProgress(0);
				progressDialog.setMax(max);
				//Hide the numbers.
				progressDialog.setProgressNumberFormat(null);
				progressDialog.show();
			} 
		});
	}
	
	/**
	 * Dismiss the progress dialog if possible
	 * We use this as we access the same ProgressDialog variable for 2 purposes
	 * and this function reduces code
	 */
	private void dismissProgressDialog() {
		MainActivity.this.runOnUiThread(new Runnable() {
			@Override
			public void run() {
				if (progressDialog != null && progressDialog.isShowing())
					progressDialog.dismiss();
			} 
		});
	}
	
	/**
	 * Show dialog to confirm file transfer
	 */
	private void showTransferConfirmationDialog() {
		Resources re = getResources();
		AlertDialog alertDialog = new AlertDialog.Builder(MainActivity.this).create();
		alertDialog.setTitle(R.string.dialog_file_confirm_title);
		alertDialog.setMessage(re.getString(R.string.dialog_file_confirm, fileDownload.fileName));
		
		alertDialog.setButton(AlertDialog.BUTTON_NEGATIVE, re.getString(R.string.dialog_option_no),
		    new DialogInterface.onclickListener() {
		        public void onclick(DialogInterface dialog, int which) {
		        	//Nope, transfer not confirmed. Just cloes this dialog
		            dialog.dismiss();
		        }
		    });
		
		alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, re.getString(R.string.dialog_option_yes),
			new DialogInterface.onclickListener() {
	        	public void onclick(DialogInterface dialog, int which) {
	        		//Yep, transfer confirmed. Request the server to send the file.
	    			BaseSocketObject bso = new BaseSocketObject();
	    			bso.request = TCPCommands.CMD_REQUEST_FILE_DOWNLOAD;
	    			bso.data = new FileData(fileDownload.Path);
	    			client.WriteCommand(bso.toJSON());
	    			//Close the dialog
	    			dialog.dismiss();
	        	}
	    	});
		
		alertDialog.show();
	}

	@Override
	public void onclick(View v) {
		int id = v.getId();
		if (id == btnConnect.getId()) {
			//Connect to the server
			client.Connect("178.174.161.182", 7462);
			btnConnect.setEnabled(false);
		}
	}

	@Override
	public void onItemClick(AdapterView<?> parent, View view, int position,long id) {
		FileData fd = (FileData) fileAdapt.getItem(position);
		
		if (fd.Type == 1) {
			//Download file
			fileDownload = fd;
			//AlertDialog. are you sure you want to download this file..
			showTransferConfirmationDialog();
		} else if (fd.Type == 2) {
			//Add folder to structure
			folderStruc.add(fd.fileName);
			//Get file list from new path
			GetFilesFromServer();
		} else if (fd.Type == -1) {
			//Remove last folder
			if (folderStruc.size() < 2)
				return;
			folderStruc.remove(folderStruc.size() -1);
			//Get file list from new path
			GetFilesFromServer();
		}
	}

	@Override
	public void OnMessageReceived(String msg) {
		try {
			JSONObject base =  new JSONObject(msg);
			String request = base.getString("Request");
			if (request.equals(TCPCommands.CMD_REQUEST_FILES_RESPONSE)) {
				//Here we parse the list of files from the server
				JSONObject jData = base.getJSONObject("Data"); 
				JSONArray jFiles = jData.getJSONArray("allFiles");
				if (filesOnServer != null)
					filesOnServer.clear();
				
				filesOnServer = new ArrayList<FileData>();
				
				if (folderStruc.size() > 1) //If this is not the root folder then
					filesOnServer.add(new FileData("Go back", "", -1, -1)); //Go back
				
				//Add all files to a list
				for (int i = 0; i < jFiles.length(); i++) {
					JSONObject obj = (JSONObject) jFiles.get(i);
					filesOnServer.add(new FileData(
							obj.getString("FileName"),
							obj.getString("Path"),
							obj.getLong("Size"),
							obj.getInt("Type")));
				}
				
				MainActivity.this.runOnUiThread(new Runnable() {
					@Override
					public void run() {
						//Create a new adapter with the files
						fileAdapt = new FileAdapter(MainActivity.this, 0, filesOnServer);
						//Set the adapter to the listview
						lvFiles.setAdapter(fileAdapt);
						//Dismiss loading dialog
						dismissProgressDialog();
					} });
			}
		} catch (JSONException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void OnFileIncoming(int length) {
		try {
			Log.e(TAG, "File incoming");
			//Show progress dialog
			showTransferringDialog(length, fileDownload.fileName);
			//Create and open a filestream
			fileOutputStream = new FileOutputStream(new File(Environment.getExternalStorageDirectory(), fileDownload.fileName), false);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void OnFileDataReceived(byte[] data, int read, int length,final int downloaded) {
		try {
			//We write the new data to the filestream
			fileOutputStream.write(data, 0, read);
			if (progressDialog != null && progressDialog.isShowing()) {
				//Then we update the progress
				progressDialog.setProgress(downloaded);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void OnFileComplete() {
		try {
			//We close the filestream
			fileOutputStream.flush();
			fileOutputStream.close();
			dismissProgressDialog();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	
	@Override
	public void OnConnectionerror() {
		Log.e(TAG, "Something went wrong");
		btnConnect.setEnabled(true);
	}
	
	@Override
	public void OnConnectSuccess() {
		//Get files from server
		GetFilesFromServer();
	}

}


We have some methods for showing dialogs, other than that we're just implementing some interfaces
like our own MessageReceivedListener interface. Then we take the appropriate for each of the callback methods.

I've commented the code for the most part, and as this tutorial series is on the socket communication it self,
i'm not going to go into details on how the activity and adapter is working.
I would consider this an advanced tutorial, so you should be familiar with activitys and adapters already.



Lastly, we're going to go into our manifest and add permissions for internet and writing to external storage
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />




That all! It to a long time to read, and even longer to write. But it's finally done!
A total of 42 A4 paged with font Courier New and size 10 when including the code.
Or, 2167 lines of text, that's just crazy :)/>

Source for both the android project and the C#.net server will be attached.
Both of them will also be uploaded to my github shortly after this tutorial has been
uploaded, so that I can update it if needed.

Thanks for reading, and enjoy your newly found knowledge!

Attached File(s)



Is This A Good Question/Topic? 1
  • +

Page 1 of 1