Monthly Archives: January 2015

Network Service Discovery – Bonus 1 – Receive Text

Changes to Client:

main.xml

<?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="fill_parent"
    android:gravity="center"
    android:orientation="vertical" >

    <Button
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:onClick="onClick"
		android:text="Send Message" />

	<EditText 
		android:id="@+id/mess"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
	/>

</LinearLayout>

MainActivity.java
**** Add to top with other declarations in the MainActivity class

private static final String REQUEST_TOAST_ME = "say_this";
EditText message;

**** Add inside onCreate() method

message = (EditText) findViewById(R.id.mess);

**** Add as stand alone method() anywhere in MainActivity class

	public void onClick(View view) {
		
		JSONObject jsonData = new JSONObject();

        try {
            jsonData.put("request", REQUEST_TOAST_ME);
            jsonData.put("ipAddress", message.getText().toString());
        } catch (JSONException e) {
            e.printStackTrace();
            Log.e(TAG, "can't put request");
            return;
        }
		new  SocketServerTask().execute(jsonData);
	}

Server Changes:

**** Add to top with other declarations in the MainActivity class

private static final String REQUEST_TOAST_ME = "say_this";

**** Replace Bonus 1 line with following code in the run() section of Network Service Discovery / Bonjour – Server

						} else if (request.equals(REQUEST_TOAST_ME)) {
							//runMe();
							
							String mess = jsondata.getString("ipAddress");
							showToast(mess);
							
							messageToClient = "Connection Accepted";
                            dataOutputStream.writeUTF(messageToClient);
							

**** Bonus 2

Find your code on the hard drive of your PC and add a /raw folder to your /res folder (/res/raw) and place a short .wav or .mp3 in the folder (If you have not played this .wav or .mp3 file on your phone before, try playing it first, I had issues with corrupt sound files that made it appear my code was bad, but it was the .wav file instead).

Then add the following method() to your server code, change sound file name accordingly (master_biding) file extension is not needed in code.

public void runMe(){
        MediaPlayer windchime = MediaPlayer.create(MainActivity.this, R.raw.master_bidding);
        windchime.start();
         /* Bonus 3
         Intent intent = new Intent(Intent.ACTION_MAIN);
         intent.setComponent(new ComponentName("com.damageinc.usaradar","com.damageinc.usaradar.MainActivity"));
         startActivity(intent);
        */
         
    }

**** Finally add this in your run() method on the server in the if statement that checks for if (request.equals(REQUEST_TOAST_ME))

runMe():

**** Bonus 3

Intents

Intents are what you use to ask the system to run other Activity’s. Activity’s that are in your app or other apps that are of course Activity’s themselves. Most of the time you send a Intent to the system with a general request (i.e. share this file) and Android comes back with a list of Activity’s (apps) that have the ability to do this and you pick the one you want. But you also have the ability to have your intent ask specifically what app you want to run. Most developers won’t give out this information freely about their app so to do it you have to use one of your own so you can get the info you need from your Manifest file. So for Bonus 3 you will need to have another app that you created, hopefully something short that finish()(s) itself. To find out the “path” you need to run your app

ComponentName(“com.damageinc.usaradar”,”com.damageinc.usaradar.MainActivity”)

You will need to go to your Manifest file for the app… We’ll just make one, it will do the same thing as the Bonus 2 but differently.

Make a new app, mine will be called thybidding.

Again make a /res/raw folder and copy the sound file to it.

We can leave the main.xml file stock.

In the MainActivity class in the onCreate() method just after setContentView(R.layout.main);

add this:

MediaPlayer sound = MediaPlayer.create(MainActivity.this, R.raw.master_bidding);
sound.start();
finish();

Then go to your manifest file for this little app, we don’t need to change it, but we will need to get some info out of it.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.thybidding"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk 
        android:minSdkVersion="8" 
        android:targetSdkVersion="21" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:label="@string/app_name"
            android:name=".MainActivity" >
            <intent-filter >
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

And let’s look at our code in the runMe() method from the server app

public void runMe(){
        MediaPlayer windchime = MediaPlayer.create(MainActivity.this, R.raw.master_bidding);
        windchime.start();
         /* Bonus 3
         Intent intent = new Intent(Intent.ACTION_MAIN);
         intent.setComponent(new ComponentName("com.damageinc.usaradar","com.damageinc.usaradar.MainActivity"));
         startActivity(intent);
        */
         
    }

First add /* and */ around the first 2 lines, just above Bonus 3 and un-remark the last lines, get rid of Bonus 3 text too, so we have

public void runMe(){
/*
		MediaPlayer windchime = MediaPlayer.create(MainActivity.this, R.raw.master_bidding);
		windchime.start();
*/
		 Intent intent = new Intent(Intent.ACTION_MAIN);
		 intent.setComponent(new ComponentName("com.damageinc.usaradar","com.damageinc.usaradar.MainActivity"));
		 startActivity(intent);
	}

Now in the intent.setComponent line we need to change the code to call our little app we just created.

We need the package line from the Manifest:

package=”com.example.thybidding”

and the android:name line under Activity in the Manifest:

android:name=”.MainActivity”

new ComponentName takes two parameters, the package name and the name of the Activity that is called by:

Which is MainActivity in this case, preceded by the package name, which gives us a full path to the application:

“com.example.thybidding.MainActivity”

Now we create our Intent and give it an action (Intent.ACTION_MAIN) this means call the Activity in our app that receives intents for android.intent.action.MAIN which is our MainActivity.

Then we set the path to the application that we want to call the Intent.ACTION_MAIN on which is:

new ComponentName(“com.example.thybidding”,”com.example.thybidding.MainActivity”)

and then we make the call to run our app

startActivity(intent);

So our runMe() method should look like:

public void runMe(){
/*
		windchime = MediaPlayer.create(MainActivity.this, R.raw.master_bidding);
		windchime.start();
*/
		 Intent intent = new Intent(Intent.ACTION_MAIN);
		 intent.setComponent(new ComponentName("com.example.thybidding","com.example.thybidding.MainActivity"));
		 startActivity(intent);
	}

Now when you send a message to the server you should see the screen flash and hear your sound file and see the Toast then it will come back to the server app.

**** Bonus 4 (Somebody STOP ME!)

We are almost there let’s make this thing play music.

At the top of the MainActivity class on the server where you declare everything add:

boolean isPlaying = false;
MediaPlayer playThis = null;

Then remark out everything in the runMe() method and add this:

Uri daSong = null;
daSong = Uri.parse("file:/sdcard/Music/" + request + ".mp3");
//You can remark out the above line and un-remark the line below to play Apple music files.
//daSong = Uri.parse("file:/sdcard/Music/" + request + ".m4a");

		if(isPlaying){
			playThis.stop();
			} else {
				playThis = MediaPlayer.create(MainActivity.this,daSong);
				playThis.start();
				isPlaying=true;
			}
		//Song was picked for first time or button in bonus 5 was clicked
		} else {
			playThis = MediaPlayer.create(MainActivity.this,daSong);
			playThis.start();
			isPlaying=true;
		}

Now if you have some .mp3’s or .m4a’s in your Music directory, and you type the name correct without the extension (or I guess you could remove the extension in the code above and type the whole file name in if you wanted to play both types of files) on the client and click the button. The music will play on the server, if you type the name wrong it will crash because I have no error protection to complicate things, you will have to add it if you really want it.

**** Bonus 5

If you want to put a stop button on the app do this

Add this to the server:

//up where you declare everything

private static final String REQUEST_STOP_IT = "stop_it";

//squeeze this in just above the last else statement in the run() method:

} else if (request.equals(REQUEST_STOP_IT)) {
							if(isPlaying){
								playThis.stop();
								isPlaying=false;
}

                            messageToClient = "Connection Accepted";
                            dataOutputStream.writeUTF(messageToClient);

And on the Client:

//Add this just above the first button in the main.xml file
<Button
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="onStop"
        android:text="Stop" />
//Add this above or below the onClick() method in the MainActivity.java file.
public void onStop(View view) {

        JSONObject jsonData = new JSONObject();

        try {
            jsonData.put("request", REQUEST_STOP_IT);
            jsonData.put("ipAddress", message.getText().toString());
        } catch (JSONException e) {
            e.printStackTrace();
            Log.e(TAG, "can't put request");
            return;
        }
        new  SocketServerTask().execute(jsonData);
    
	
}

Extra Credit:

I use the following to run all kinds of little apps on my tablet from my phone, adjust volume, adjust screen brightness, screen timeout, show weather radar, announce forecast. It’s pretty cool although not necessarily practical when you have the phone that can do the same thing.

This is pretty good stuff right here, we are on our way to the Internet of Things (IoT) with what we just learned here, but let’s take it another step something that I don’t think Steve Jobs would be too happy with we will learn how to control a iPhone with a Android device.

Thats what’s up for the next post……

Network Service Discovery / Bonjour – Client

Last post we set up a Android Network Service Discovery server that will receive text messages from our client. Now we are ready to create the client that we will use to create the text messages and send to the server.

When creating the project choose Sdk version 16, Jelly Bean 4.1.

or

Edit the Manifest first to avoid errors when creating the MainActivity class.

Replace minimum with android:minSdkVersion=”16″

and add

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

This will all be done in one file for simplicity.

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;

import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.content.Context;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.net.wifi.WifiManager;
import android.os.AsyncTask;
import android.os.Bundle;
import android.text.format.Formatter;
import android.util.Log;
import android.view.Menu;
import android.widget.Toast;

public class MainActivity extends Activity {

	private String SERVICE_NAME = "Client Device";
    private String SERVICE_TYPE = "_letstalk._tcp.";

    private InetAddress hostAddress;
    private int hostPort;
    private NsdManager mNsdManager;

    private int SocketServerPort = 6000;
    private static final String REQUEST_CONNECT_CLIENT = "request-connect-client";

    private static final String TAG = "NSDClient";

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		mNsdManager = (NsdManager) getSystemService(Context.NSD_SERVICE);
        mNsdManager.discoverServices(SERVICE_TYPE,
									 NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
	}

	NsdManager.DiscoveryListener mDiscoveryListener = new NsdManager.DiscoveryListener() {

        // Called as soon as service discovery begins.
        @Override
        public void onDiscoveryStarted(String regType) {
            Log.d(TAG, "Service discovery started");
        }

        @Override
        public void onServiceFound(NsdServiceInfo service) {
			// A service was found! Do something with it.
			Log.d(TAG, "Service discovery success : " + service);
			Log.d(TAG, "Host = "+ service.getServiceName());
			Log.d(TAG, "port = " + String.valueOf(service.getPort()));

            if (!service.getServiceType().equals(SERVICE_TYPE)) {
                // Service type is the string containing the protocol and
                // transport layer for this service.
                Log.d(TAG, "Unknown Service Type: " + service.getServiceType());
            } else if (service.getServiceName().equals(SERVICE_NAME)) {
                // The name of the service tells the user what they'd be
                // connecting to. It could be "Bob's Chat App".
                Log.d(TAG, "Same machine: " + SERVICE_NAME);
            } else {
                Log.d(TAG, "Diff Machine : " + service.getServiceName());
                // connect to the service and obtain serviceInfo
                mNsdManager.resolveService(service, mResolveListener);
            }
        }

        @Override
        public void onServiceLost(NsdServiceInfo service) {
            // When the network service is no longer available.
            // Internal bookkeeping code goes here.
            Log.e(TAG, "service lost" + service);
        }

        @Override
        public void onDiscoveryStopped(String serviceType) {
            Log.i(TAG, "Discovery stopped: " + serviceType);
        }

        @Override
        public void onStartDiscoveryFailed(String serviceType, int errorCode) {
            Log.e(TAG, "Discovery failed: Error code:" + errorCode);
            mNsdManager.stopServiceDiscovery(this);
        }

        @Override
        public void onStopDiscoveryFailed(String serviceType, int errorCode) {
            Log.e(TAG, "Discovery failed: Error code:" + errorCode);
            mNsdManager.stopServiceDiscovery(this);
        }
    };

    NsdManager.ResolveListener mResolveListener = new NsdManager.ResolveListener() {

        @Override
        public void onServiceResolved(NsdServiceInfo serviceInfo) {
            Log.d(TAG, "Resolve Succeeded. " + serviceInfo);

            if (serviceInfo.getServiceName().equals(SERVICE_NAME)) {
                Log.d(TAG, "Same IP.");
                return;
            }

            // Obtain port and IP
            hostPort = serviceInfo.getPort();
            hostAddress = serviceInfo.getHost();

            /* Once the client device resolves the service and obtains
             * server's ip address, connect to the server and send data
             */

            connectToHost();
        }


        @Override
        public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
            // Called when the resolve fails. Use the error code to debug.
            Log.e(TAG, "Resolve failed " + errorCode);
            Log.e(TAG, "serivce = " + serviceInfo);
        }
    };

    private void connectToHost() {

        if (hostAddress == null) {
            Log.e(TAG, "Host Address is null");
            return;
        }

        String ipAddress = getLocalIpAddress();
        JSONObject jsonData = new JSONObject();

        try {
            jsonData.put("request", REQUEST_CONNECT_CLIENT);
            jsonData.put("ipAddress", ipAddress);
        } catch (JSONException e) {
            e.printStackTrace();
            Log.e(TAG, "can't put request");
            return;
        }

        new SocketServerTask().execute(jsonData);
    }

    private String getLocalIpAddress() {
        WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
        String ip = Formatter.formatIpAddress(wm.getConnectionInfo().getIpAddress());
        return ip;
    }

    private class SocketServerTask extends AsyncTask<JSONObject, Void, Void> {
        private JSONObject jsonData;
        private boolean success;

        @Override
        protected Void doInBackground(JSONObject... params) {
            Socket socket = null;
            DataInputStream dataInputStream = null;
            DataOutputStream dataOutputStream = null;
            jsonData = params[0];

            try {
                // Create a new Socket instance and connect to host
                socket = new Socket(hostAddress, SocketServerPort);

                dataOutputStream = new DataOutputStream(
					socket.getOutputStream());
                dataInputStream = new DataInputStream(socket.getInputStream());

                // transfer JSONObject as String to the server
                dataOutputStream.writeUTF(jsonData.toString());
                Log.i(TAG, "waiting for response from host");

                // Thread will wait till server replies
                String response = dataInputStream.readUTF();
                if (response != null && response.equals("Connection Accepted")) {
                    success = true;
                } else {
                    success = false;    
                }

            } catch (IOException e) {
                e.printStackTrace();
                success = false;
            } finally {

                // close socket
                if (socket != null) {
                    try {
                        Log.i(TAG, "closing the socket");
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                // close input stream
                if (dataInputStream != null) {
                    try {
                        dataInputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                // close output stream
                if (dataOutputStream != null) {
                    try {
                        dataOutputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
			return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            if (success) {
                Toast.makeText(MainActivity.this, "Connection Done", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(MainActivity.this, "Unable to connect", Toast.LENGTH_SHORT).show();
            }
        }
    }

    protected void onPuase() {
        if (mNsdManager != null) {
            mNsdManager.stopServiceDiscovery(mDiscoveryListener);
        }
        super.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mNsdManager != null) {
            mNsdManager.discoverServices(
                SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, mDiscoveryListener);
        }

    }

    @Override
    protected void onDestroy() {
        if (mNsdManager != null) {
            mNsdManager.stopServiceDiscovery(mDiscoveryListener);
        }
        super.onDestroy();
    }

}

SERVICE_NAME is a constant that we use to set the device name, the Google example doesn’t use these it picks the name for you which is the service name if more than one device has the same service name, which it will, the 2nd device has a (1) added to it and the 3rd would have (2) added to it and so on. Not sure if this is what made the Google version so flaky so I don’t use it, I give the server a name and the client a name, and use that.

SERVICE_TYPE is a custom name you make up (that must conform to a standard). This is the name of your app on the network, it doesn’t have to be the same name as your app, just every device that you want to see each other must use the same one. The service type specifies which protocol and transport layer the application uses. The syntax is “_[protocol]._[transportlayer].” You can name the protocol anything you want but leave the transportlayer the way it is.

Note: If you plan on publishing an app to the app store that uses NSD you should register your protocol to the International Assigned Numbers Authority (IANA). They manage a centralized, authoritative list of service types used by service discovery protocols such as NSD and Bonjour. If you intend to use a new service type, you should reserve it by filling out the IANA Ports and Service registration form.

REQUEST_CONNECT_CLIENT is a constant we use to tell the server what we want to do. At first this will be the only option, but later we will add a display message option.

Now with our tools laid out let’s walk through the logic.

We display our screen with setContentView(R.layout.main);

We create an instance of NsdManager called mNsdManager to use to discover and connect to our server.

We then use NsdManager to discover our service (and server) on the network using our SERVICE_TYPE, we define what type of protocol we are using (for NSD you use NsdManager.PROTOCOL_DNS_SD) and where to go after the service is successfully registered (a interface callback we create called mDiscoveryListener).

Next we define, initialize and implement the DiscoveryListener interface call back. We use this to get all the devices that have registered the service. Since it is an interface we have methods() we must implement (@Override) to receive the NsdServiceInfo for all the servers we discovered.

The methods are:

public void onDiscoveryStarted(String regType) {}
public void onServiceFound(NsdServiceInfo service) {}
public void onServiceLost(NsdServiceInfo service) {}
public void onDiscoveryStopped(String serviceType) {}
public void onStartDiscoveryFailed(String serviceType, int errorCode) {}
public void onStopDiscoveryFailed(String serviceType, int errorCode) {}

The second one is the only one we will really use, the rest are used for logging purposes.

@Override
        public void onServiceFound(NsdServiceInfo service) {
			// A service was found! Do something with it.
			Log.d(TAG, "Service discovery success : " + service);
			Log.d(TAG, "Host = "+ service.getServiceName());
			Log.d(TAG, "port = " + String.valueOf(service.getPort()));

            if (!service.getServiceType().equals(SERVICE_TYPE)) {
                // Service type is the string containing the protocol and
                // transport layer for this service.
                Log.d(TAG, "Unknown Service Type: " + service.getServiceType());
            } else if (service.getServiceName().equals(SERVICE_NAME)) {
                // The name of the service tells the user what they'd be
                // connecting to. It could be "Bob's Chat App".
                Log.d(TAG, "Same machine: " + SERVICE_NAME);
            } else {
                Log.d(TAG, "Diff Machine : " + service.getServiceName());
                // connect to the service and obtain serviceInfo
                mNsdManager.resolveService(service, mResolveListener);
            }
        }

When any service is found (even if it is not ours we show all the NsdServiceInfo that we discovered and then we point out the service name (which is “Server Device” for what we are looking for) and port number for the service, even if it is not ours (if you have printers on the network or webcams you will see those here too).

Now we start parsing the NsdServiceInfo to find what we want. First we check the SERVICE_TYPE if it is NOT (notice the ! at the beginning of the if statement) _letstalk._tcp. then we log it and keep discovering.

If the SERVICE_TYPE IS _letstalk._tcp. we check the SERVICE_NAME. If the SERVICE_NAME is the same as ours, Client Device, we know we have discovered ourself, so we keep looking.

If the SERVICE_NAME is different than we have found another device that we can connect to. In our case it should be the server. This is where you would create a List if you wanted to make a list of several devices you could connect to, but for simplicity sake if we find our server we immediately resolve the service so we can connect.

To resolve the service we use our instance of NsdManager to start the resolveService() method which takes two parameters the first is the NsdServiceInfo of the device we are wanting to resolve(get IPAddress for), and the second parameter is where to send that information when we get it, which is the ResolveListener interface call back.

Now we define, initialize and implement the ResolveListener interface call back. It has two methods we must implement.

public void onServiceResolved(NsdServiceInfo serviceInfo) {}
public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {}

onServiceResolved(NsdServiceInfo serviceInfo) is the one we care about, it gives us the NsdServiceInfo that includes the server IP address this time.

First we double check to make sure we didn’t get our own device’s address by checking the SERVICE_NAME, if we did we return, which goes back to resolving the next device we found.

If we did get the information for our server, we set our local variables that we will use to connect to the server, hostPort is the server port number and hostAddress is the servers IP address.

            // Obtain port and IP
            hostPort = serviceInfo.getPort();
            hostAddress = serviceInfo.getHost();

We then run a custom method(), connectToHost();

private void connectToHost() {

        if (hostAddress == null) {
            Log.e(TAG, "Host Address is null");
            return;
        }

        String ipAddress = getLocalIpAddress();
        JSONObject jsonData = new JSONObject();

        try {
            jsonData.put("request", REQUEST_CONNECT_CLIENT);
            jsonData.put("ipAddress", ipAddress);
        } catch (JSONException e) {
            e.printStackTrace();
            Log.e(TAG, "can't put request");
            return;
        }

        new SocketServerTask().execute(jsonData);
    }

We double check to make sure we have a IP address to connect to, if we don’t we jump out of the connectToHost() method so we don’t try to connect and crash.

If we do have a IP Address we run another custom method(), getLocalIpAddress(); Let’s jump there and then come back.

    private String getLocalIpAddress() {
        WifiManager wm = (WifiManager) getSystemService(WIFI_SERVICE);
        String ip = Formatter.formatIpAddress(wm.getConnectionInfo().getIpAddress());
        return ip;
    }

First of all when you get any kind of Wifi information you are going to need request permissions from the user which means we will need a permission in the Manifest file.

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

Then in the getLocalIpAddress() method we initialize our WifiManager which we will use to get our local IP address, wm.getConnectionInfo().getIpAddress().

Then we take that IP address that is not in a format we can use and run it through the Formatter class which contains a method() that turns the IP Address we retrieved into a String that we can use, Formatter.formatIpAddress().

And we send it back to the connectToHost() method and save it as ipAddress.

Now we are back in the connectToHost() method, we create a JSONObject that we can use to send data to the server.

There are lots of things we could do wrong, so we surround our actions with a try and catch and then start to put data in our JSONObject (jsonData). We use (key, value) pairs to do this so the command ends up looking like this:

            jsonData.put("request", REQUEST_CONNECT_CLIENT);
            jsonData.put("ipAddress", ipAddress);

The request “key” contains our REQUEST_CONNECT_CLIENT “value” which is request-connect-client. This will tell the server that we want to connect.

We also send a ipAddress “key” that contains our ipAddress “value” which is the IP address of our device.

We catch our JSONObject errors and print out our stack trace (e.printStackTrace();) if there were any, and jump out of our try with return;

But for fun’s sake, let’s say it worked and then we create a new custom Thread called SocketServerTask() and run it with our jsonData (.execute(jsonData);).

new SocketServerTask().execute(jsonData);

makes this:

private class SocketServerTask extends AsyncTask<JSONObject, Void, Void> {
        private JSONObject jsonData;
        private boolean success;

        @Override
        protected Void doInBackground(JSONObject... params) {
            Socket socket = null;
            DataInputStream dataInputStream = null;
            DataOutputStream dataOutputStream = null;
            jsonData = params[0];

            try {
                // Create a new Socket instance and connect to host
                socket = new Socket(hostAddress, SocketServerPort);

                dataOutputStream = new DataOutputStream(
					socket.getOutputStream());
                dataInputStream = new DataInputStream(socket.getInputStream());

                // transfer JSONObject as String to the server
                dataOutputStream.writeUTF(jsonData.toString());
                Log.i(TAG, "waiting for response from host");

                // Thread will wait till server replies
                String response = dataInputStream.readUTF();
                if (response != null && response.equals("Connection Accepted")) {
                    success = true;
                } else {
                    success = false;    
                }

            } catch (IOException e) {
                e.printStackTrace();
                success = false;
            } finally {

                // close socket
                if (socket != null) {
                    try {
                        Log.i(TAG, "closing the socket");
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                // close input stream
                if (dataInputStream != null) {
                    try {
                        dataInputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                // close output stream
                if (dataOutputStream != null) {
                    try {
                        dataOutputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
			return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            if (success) {
                Toast.makeText(MainActivity.this, "Connection Done", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(MainActivity.this, "Unable to connect", Toast.LENGTH_SHORT).show();
            }
        }
    }

As you noticed probably quite quickly we are using a AsyncTask class to send the data. AsyncTask is not a bare bones Thread it is actually a “helper” class that uses a Thread to do it’s work

Some detailed, but very good documentation from googles AsyncTask web page.

AsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.

AsyncTask is designed to be a helper class around Thread and Handler and does not constitute a generic threading framework. AsyncTasks should ideally be used for short operations (a few seconds at the most.) If you need to keep threads running for long periods of time, it is highly recommended you use the various APIs provided by the java.util.concurrent package such as Executor, ThreadPoolExecutor and FutureTask.

An asynchronous task is defined by a computation that runs on a background thread and whose result is published on the UI thread. An asynchronous task is defined by 3 generic types, called Params, Progress and Result, and 4 steps, called onPreExecute, doInBackground, onProgressUpdate and onPostExecute.

So in summary, AsyncTask uses a Thread and Handler, so you don’t have to, and posts it’s results to the UI Thread. It is also best for short tasks, like sending data, not listening for connections, like the server does.

private class SocketServerTask extends AsyncTask<JSONObject, Void, Void> {

The three parameters are used like this:

JSONObject is the type of Object we are passing in.
The first Void is the type of value you want to pass back to calculate the progress bar.
The second Void is the type of value you want to send back to the UI Thread to display.

Since we are using the AsyncTask to only do work, because what we are doing is going to happen so fast, we won’t be returning any values for the progress bar, and we don’t need to update the UI Thread because we will know it’s complete when the song starts playing.

We use the boolean value (true or false) success in multiple methods() in the SocketServerTask class so we define the variable here so we can get to it. We set it in our doInBackground() method and we retrieve it in our onPostExecute() method.

When we run the .execute() method we are actually running:

protected Void doInBackground(JSONObject... params) {}

protected means that it can not be called from outside the SocketServerTask class.

Void means we will not be passing any values to the onPostExecute() method. And also means we will be returning null from the doInBackground() method.

JSONObject… The … means array, so the doInBackground() method can take several JSONObjects in an array format.

params is the JSONObject (jsonData) we passed in from the connectToHost() method in the .execute(jsonData); command.

Now we do the same think like we did in our Server Thread:

We initialize our Socket, used to communicate with the server, and set it’s value to null. You may wonder why we initialize it here instead of farther down when we give it an actual value. When we are done using the socket we are going to try to close it from the finally {} section and if we don’t initialize it here we won’t be able to “see” it.

We initialize our DataInputStream, that receives incoming data, and set it to null.

We initialize our DataOutputStream, used to send data to clients, and set it to null.

We create a JSONObject jsonData and set it to the value in index 0 (the first value), which was passed in the params array of JSONObjects.

protected Void doInBackground(JSONObject... params) {}

Now we are about to do some pretty complicated stuff, Android makes it pretty easy, but if everything is not set up just right, it could easily fail, so we use a try and catch.

We create a new socket to talk to the server using the server IP Address we acquired and a port that we manually agreed upon.

Once we have the socket we can use it to create a DataInputStream and a DataOutputStream.

Now we can send data to the server.

dataOutputStream.writeUTF(jsonData.toString());

We take our dataOutputStream and write to the socket in a UTF-8 format which is a way to format text similar to the .xml files used for our layout objects, you will notice in the main.xml file at the beginning:

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

We convert our jsonData Object to a String, not exactly sure what it would look like, but it will contain two key/value pairs something like request request-connect-client ipAddress 192.168.1.98

then we run

String response = dataInputStream.readUTF();

Which blocks the Thread (which would cause our UI Thread to crash if we were running it there) and we wait until the server responds. Once we have a response, since we know it is only returning one string, not a JSONObject or any other object we can just compare the response to a text string.

if (response != null && response.equals(“Connection Accepted”)) {

We could have used a constant like:

final String CONNECTION_VALUE = “Connection Accepted” and then we could check if:

if (response != null && response.equals(CONNECTION_VALUE)) {

but that is a little more than we need at this point.

If the response was “Connection Accepted” then everything was successful and we can set the boolean value success to true, if the response didn’t equal “Connection Accepted” then we set the boolean value success to false.

Then we have our catch statement if something went wrong while performing I/O Input/Output and we can print our stackTrace if needed.

Finally which means after the Thread has performed it’s task we can try to close our socket, DataInputStream and our DataOutputStream. If we run into issues we catch the IOExceptions and print out our stackTrace for debugging.

After all that we have to return something because doInBackground() must have a return value, we are able to set it to Void like we did in this instance as a work around, but we still have to return something so we return null; which doesn’t go anywhere. Since we are returning Void the parameter we take in on the onPostExecute() method needs to be Void so it doesn’t expect a real value.

@Override
        protected void onPostExecute(Void result) {
            if (success) {
                Toast.makeText(MainActivity.this, "Connection Done", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(MainActivity.this, "Unable to connect", Toast.LENGTH_SHORT).show();
            }
        }

Then we check our success variable and Toast a message to the screen accordingly.

For a better example of the onPostExecute() method let’s say that we are returning a Bitmap from the doInBackground() method.

This:

private class SocketServerTask extends AsyncTask<JSONObject, Void, Void> {

Would become this:

private class SocketServerTask extends AsyncTask<JSONObject, Void, Bitmap> {

This:

protected Void doInBackground(JSONObject... params) {

Would become this:

protected Bitmap doInBackground(JSONObject... params) {

This:

return null;

Would become this:

return bitmap; //bitmap being a Bitmap Object

This:

protected void onPostExecute(Void result) {

Would become this:

protected void onPostExecute(Bitmap result) {

To finish out this project we define the onPause(), onResume(), onDestroy() methods. These are all protected so you can only run them from this class. They all make sure mNsdManager has been created because if you try to stop a service when it doesn’t exist the program will crash.

onPause() will stop service discovery because if the app is not running there is no need to waste resources discovering things we won’t use (memory leak). Then the default onPause() method will run.

onResume() will continue discovering services, and reconnect to our server in this case, on resuming, after the default onResume() method runs.

onDestroy() will stop service discovery because if the app is not running there is no need to waste resources discovering things we won’t use (memory leak). Then the default onDestroy() method will run.

Here is the main.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="fill_parent"
    android:gravity="center"
    android:orientation="vertical" >

</LinearLayout>

Now you can compile and run the server on one device and the client on the other, make sure both devices are on the same network. When you run the client you should see Toasts appear on both devices.

For my next post I will add the ability to send text from the client to the server to this project.

Network Service Discovery / Bonjour – Server

Before we get to the heart of this app, I will do a couple little apps that help you understand how to use NSD a little easier, Google’s example (nsdchat) is a full blown app with both the server and the client in the same one, along with fragments, classes buried in classes so the server can use the same logic as the client, it uses a Runnable Thread class which doesn’t handle reconnections (when orientations changes for instance) and for the most part it doesn’t work (flaky at best) and no documentation. You would think you would click on advertise on the server and detect and connect on the client, but that usually doesn’t work, you have to make both clients servers and then you can usually chat back and forth.

This little side app will strip out all that extra stuff and give you 2 apps (one client app and one server app).

You will start the server app and it will instantly start advertising itself.

You will start the client app and it will instantly detect the server and resolve it’s connection info and connect to the server.

Then you will be able to pass messages to the server which will display them.

Then we will dive into our first Apple code, Requires a Mac computer of some sort, which allows you to run Xcode, and you also need a Apple Developer License which you can get for free or they are $99 a year. The Apple code will allow us to act as a server and receive messages from the Android client.

This is what the MP3 player app will do also when we return to it, because all it does is send the song db number to the server which receives the number and plays the song it belongs to.

First, to use NSD you need to use a minimum of OS version of 16, JellyBean 4.1. This tutorial was tested on a JellyBean ASUS T700, KitKat Samsung S3, and a KitKat Samsung Note 3. The only issue seems to be the Note 3’s WIFI radio seems to drop connection more than it should, luckily NSD reconnects for you.

When creating the project choose Sdk version 16, Jelly Bean 4.1.

or

Edit the Manifest first to avoid errors when creating the MainActivity class.

Replace minimum with android:minSdkVersion=”16″

and add

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

android.permission.INTERNET – let’s the user know that our application uses network sockets.
android.permission.ACCESS_WIFI_STATE – let’s the user know we are going to extract the WIFI IP Address.

Android NSD Server

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

import org.json.JSONException;
import org.json.JSONObject;

import android.app.Activity;
import android.content.Context;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdManager.RegistrationListener;
import android.net.nsd.NsdServiceInfo;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.widget.*;

public class MainActivity extends Activity {

	private String SERVICE_NAME = "Server Device";
    private String SERVICE_TYPE = "_letstalk._tcp.";
    private static final String REQUEST_CONNECT_CLIENT = "request-connect-client";
    private SocketServerThread socketServerThread;
    private NsdManager mNsdManager;

    private int SocketServerPort = 6000;

    private List<String> clientIPs;

    private static final String TAG = "NSDServer";
	
	public void showToast(final String toast){
		MainActivity.this.runOnUiThread(new Runnable(){
				public void run(){
					Toast.makeText(MainActivity.this,toast,Toast.LENGTH_LONG).show();
				}
			});
	}

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mNsdManager = (NsdManager) getSystemService(Context.NSD_SERVICE);
        registerService(9000);

        clientIPs = new ArrayList<String>();
        socketServerThread = new SocketServerThread();
        socketServerThread.start();
    }

    public void registerService(int port) {
        NsdServiceInfo serviceInfo = new NsdServiceInfo();
        serviceInfo.setServiceName(SERVICE_NAME);
        serviceInfo.setServiceType(SERVICE_TYPE);
        serviceInfo.setPort(port);

        mNsdManager.registerService(serviceInfo,NsdManager.PROTOCOL_DNS_SD,mRegistrationListener);
    }

    RegistrationListener mRegistrationListener = new NsdManager.RegistrationListener() {

        @Override
        public void onServiceRegistered(NsdServiceInfo NsdServiceInfo) {
            String mServiceName = NsdServiceInfo.getServiceName();
            SERVICE_NAME = mServiceName;
            Log.d(TAG, "Registered name : " + mServiceName);
        }

        @Override
        public void onRegistrationFailed(NsdServiceInfo serviceInfo,
										 int errorCode) {
            // Registration failed! Put debugging code here to determine
            // why.
        }

        @Override
        public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
            // Service has been unregistered. This only happens when you
            // call
            // NsdManager.unregisterService() and pass in this listener.
            Log.d(TAG,
        		  "Service Unregistered : " + serviceInfo.getServiceName());
        }

        @Override
        public void onUnregistrationFailed(NsdServiceInfo serviceInfo,
										   int errorCode) {
            // Unregistration failed. Put debugging code here to determine
            // why.
        }
    };

	private class SocketServerThread extends Thread {

        @Override
        public void run() {

            Socket socket = null;
            ServerSocket serverSocket = null;
            DataInputStream dataInputStream = null;
            DataOutputStream dataOutputStream = null;

            try {                
                Log.i(TAG, "Creating server socket");                
                serverSocket = new ServerSocket(SocketServerPort);

                while (true) {
                    socket = serverSocket.accept();
                    dataInputStream = new DataInputStream(
						socket.getInputStream());
                    dataOutputStream = new DataOutputStream(
						socket.getOutputStream());

                    String messageFromClient, messageToClient, request;

                    //If no message sent from client, this code will block the Thread
                    messageFromClient = dataInputStream.readUTF();

                    final JSONObject jsondata;

                    try {
                    	jsondata = new JSONObject(messageFromClient);
                        request = jsondata.getString("request");

                        if (request.equals(REQUEST_CONNECT_CLIENT)) {
                            String clientIPAddress = jsondata.getString("ipAddress");

                            // Add client IP to a list
                            clientIPs.add(clientIPAddress);
							showToast("Accepted");

                            messageToClient = "Connection Accepted";


// Important command makes client able to send message
                            dataOutputStream.writeUTF(messageToClient);
// ****** Paste here  Bonus 1

// ****** Paste here  Bonus 1
                        } else {
                            // There might be other queries, but as of now nothing.
                            dataOutputStream.flush();
                        }

                    } catch (JSONException e) {
                        e.printStackTrace();
                        Log.e(TAG, "Unable to get request");
                        dataOutputStream.flush();
                    }
                }

            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (socket != null) {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }   

                if (dataInputStream != null) {
                    try {
                        dataInputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }

                if (dataOutputStream != null) {
                    try {
                        dataOutputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }

        }       

    }

	protected void onPuase() {
        if (mNsdManager != null) {
            mNsdManager.unregisterService(mRegistrationListener);
        }
        super.onPause();
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mNsdManager != null) {
            registerService(9000);
        }

    }

    @Override
    protected void onDestroy() {
        if (mNsdManager != null) {
            mNsdManager.unregisterService(mRegistrationListener);
        }
        super.onDestroy();
    }

}

SERVICE_NAME is a constant that we use to set the device name. If more than one device has the same service name, the 2nd device has a (1) added to it and the 3rd would have (2) added to it and so on. Not sure if this is what made the nsdChat version so flaky so I don’t use it, I give the server a name and the client a name, and use that.

SERVICE_TYPE is a custom name you make up (that must conform to a standard). This is the name of your app on the network, it doesn’t have to be the same name as your app, just every device that you want to see each other must use the same one. The service type specifies which protocol and transport layer the application uses. The syntax is “_[protocol]._[transportlayer].” You can name the protocol anything you want but leave the transportlayer the way it is.

Note: If you plan on publishing an app to the app store that uses NSD you should register your protocol to the International Assigned Numbers Authority (IANA). They manage a centralized, authoritative list of service types used by service discovery protocols such as NSD and Bonjour. If you intend to use a new service type, you should reserve it by filling out the IANA Ports and Service registration form.

REQUEST_CONNECT_CLIENT is a static constant we use to detect what the client wants to do. At first this will be the only option, but later we will add a display message option. Static means that you can access the constant value from other classes without having to instantiate the class. A class is like blue prints on how to build a house, if you use the blue prints to instantiate the house, you build the house. A static value is like a equation on how to figure square footage that happens to also be written on the blue prints. Just because the equation is written on the blue prints it doesn’t mean you have to build the house to use them.

Now we layout our tools we want to use.

SocketServerThread is a custom class that we use to add functionality to the Thread class. The functionality that we add listen’s for incoming connections and receive’s data from the client.

Threads allow us to perform work simultaneously along with the main Thread. The main Thread is what the app display runs on and what Android watches to detect misbehaving apps that it needs to shut down to keep the phone performing in a user friendly way. The server will be waiting for a connection to be requested and established and it can not do this on the main Thread or Android will shut it down.

NsdManager is what we use to make the server discoverable by other devices. The client devices use NsdManager to discover us and then resolve (connect) to us.

List clientIPs is a list of Strings that will hold the IP Addresses of all the connected clients. This is not required to make the app work, and will not be used by me, but might be helpful to you if you need to know that information.

A TAG file is used for logging, it helps catch your eye when looking at logcat while your app is running to make sure it is making it through all steps of your program. This was one of the few concepts that I had to use logging for, especially with the nsdchat app, but that was before I found the showToast method on stackOverflow, which I show you next.

public void showToast(final String toast){
		MainActivity.this.runOnUiThread(new Runnable(){
				public void run(){
					Toast.makeText(MainActivity.this,"Accepted",Toast.LENGTH_LONG).show();
				}
			});
	}

This is one of the best tools ever! Especially if you are debugging on your phone. This allows you to Toast whatever you want to the screen from anywhere in the app. If you remember earlier when I was discussing Context I said you had to have access to the main screen to be able to see a Toast, and you can’t always get Context. But with this tool you don’t need to. You can pass any string you like to this method() but for this app I just Toast a static message of Accepted.

Now with our tools laid out let’s walk through the logic.

We display our screen with setContentView(R.layout.main); – Displayed at the bottom.

We create an instance of NsdManager called mNsdManager to use to make our server discoverable.

We run a custom method registerService().

This method initializes an instance of NsdServiceInfo that we use to assign our service name, service type, and service port.

We then use NsdManager to register our service (server) on the network using our NSDServiceInfo (which will be passed to the client during discovery and resolve, we define what type of protocol we are using (for NSD you use NsdManager.PROTOCOL_DNS_SD) and where to go after the service is successfully registered (a interface we create for callbacks called mRegistrationListener).

Next we define, initialize and implement the RegistrationListener interface. We use this to tell if we successfully registered the service. Since it is an interface we have methods() we must implement (@Override) to receive service status change call backs.

public void onServiceRegistered(NsdServiceInfo NsdServiceInfo) {

Is executed when a the service is successfully registered. We use the NsdServiceInfo we receive to Log the service name that was registered.(This would be helpful if you were registering multiple different services).

public void onRegistrationFailed(NsdServiceInfo serviceInfo,
int errorCode) {

Is executed when the registration fails, you could log the error if you need to, but we don’t in this example.

public void onServiceUnregistered(NsdServiceInfo serviceInfo) {

This is run if the client is turned off.

public void onUnregistrationFailed(NsdServiceInfo serviceInfo,
int errorCode) {

This is run if registration fails.

Next we define our custom background Thread that we use to accept connections, receive and send data.

We initialize our Socket, used to communicate with the clients, and set it’s value to null.

We initialize our ServerSocket, used to accept connections, and set it to null.

We initialize our DataInputStream, that receives incoming data, and set it to null.

We initialize our DataOutputStream, used to send data to clients, and set it to null.

We use a try and catch because a port may not be available or configured wrong.

We then create our ServerSocket that we use to listen for incoming connections. Which is a pre-defined integer variable (SocketServerPort = 6000).

while(true){ – is known as an infinite loop, while remains true as long as the app is running.

Now comes the main reason why we use a Thread.

socket = serverSocket.accept();

This command sits and waits till a connection comes in, if this were on the main thread it would cause the program to time out and crash. It’s called a blocking method.

When a connection does come in you can not use the same socket to talk with the client, because the ServerSocket is always waiting and listening for just incoming calls, so when a new connection is made a new socket is created and assigned to the socket variable

We can then use this socket to get a InputStream and OutputStream for the socket.

Then we wait for the client to send some data.

messageFromClient = dataInputStream.readUTF();

This is another blocking method that doesn’t allow the code to continue until data is received.

The data that the client sends us in this app is in a JSONObject format which is similar to a HashMap format, or a string value pair. It is essentially a variable name with a value. You can check for the existence of the variable, and if it exists get the value. The variable is the name for the value.

Since the client will be sending a JSONObject we create one to store the data passed in.

Since the data passed in might not end up being a JSONObject we surround it with a try and catch to debug issues if there are any.

We initialize the JSONObject by putting the data passed in from the client in it.

We then get the value (which is a String) out of the variable request.

If the value stored in request equals the constant stored in REQUEST_CONNECT_CLIENT, which is “request-connect-client”

Then we get the value stored in ipAddress (which is a String).

Then we take that value and add it to our List of client IP Adresses (which we do nothing with).

We then display a toast so we can tell the server received our message from the client.(to help with debugging)

We then create a message to send back to the client “Connection Accepted”.

We then send the message using writeUTF to send data out the OutputStream using the socket.

dataOutputStream.writeUTF(messageToClient);

if (request.equals(REQUEST_CONNECT_CLIENT)) is FALSE.

dataOutputStream.flush();

Which removes all data from the OutputStream, this will be useful for our MP3 player because we will be listening for a certain amount of data (20 characters) and if there are data remnants left in the pipe this could really mess up our data flow.

First we catch our last try statement which tries to pull data out of the JSONObject so we catch a JSONException. If there is an JSONException we do a printStackTrace() and flush any data that might have made it in the pipe.

Next we catch a IOException in case we have issues creating the Socket, InputStream, or OutputStream.

After making it through the Thread and only after making it through the Thread do we try to close the socket, close the OutputStream, and close the InputStream. (if they have been used( i.e. !=null))

Finally if another application is opened over this app.

protected void onPuase() {

If NsdManager is being used, we can stop advertising it’s existence.

mNsdManager.unregisterService(mRegistrationListener);

If the app is re-opened.

protected void onResume() {

and NsdManager is still available we can re-advertise our service.

registerService(9000);

Or if our app is completely removed from memory by the system, we can stop advertising it’s existence.

mNsdManager.unregisterService(mRegistrationListener);

That’s everything you need all in one file.

Except for the layout/main.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="fill_parent"
    android:gravity="center"
    android:orientation="vertical" >

</LinearLayout>

For this example we are connecting a Android client to a Android server using Network Service Discovery. We have set up the server. In my next post we will set up the client.

Android – Gestures and Interfaces

Here is the part I love about Fragments to use them you also need to use interfaces, which are pretty simple once you know them, but for a beginner there is a lot of magic and a lot of pointing at other classes in both directions.

Interfaces

Interfaces are kind of like American football timing plays, the receiver runs blindly to a agreed upon spot on the field, and the quarterback throws the ball to the spot. If everything goes as planned the ball is delivered to the spot at the right time.

Interfaces aren’t as much timing as an agreed upon spot. You agree on a method name and one class throws the data to the other class to the same method name and it catches the data.

The class throwing the data defines the interface and method, creates an instance of the interface that points to the catching class and uses it to throw the data to the class catching the data.

The class catching the data implements the interface and the method and uses the method to catch the data.

    Gestures

Gestures allow you to do anything you want when it detects any contact with the screen. We are going to to use a small subset of gestures to do what we need, but it will be enough.

We need interfaces to transfer the gestures from the FragmentArt class to the MainActivity class, and to send songs we select from the FragmentLst class to the MainActiviy. So we will start with interfaces first, sorry.

I don’t want to scare you, I was going to just post a snippet at first, but if your just going to copy and paste it first to see it work here it is:

import android.app.*;
import android.net.*;
import android.os.*;
import android.view.*;
import android.view.GestureDetector.*;
import android.view.View.*;
import android.widget.*;

public class FragmentArt extends Fragment
{
	private ImageView iv = null;

	OnSwipePerformedListener swipePerformedListener;

	public interface OnSwipePerformedListener {
		public void onSwipePerformed(String swipeDirection);
	}

	@Override
	public void onAttach(Activity activity) {
		super.onAttach(activity);
		try{
			swipePerformedListener = (OnSwipePerformedListener) activity;
		} catch (ClassCastException e) {
			throw new ClassCastException( activity.toString()
										 + " must implement OnSwipePerformedListener");
		}
	}

	 @Override
	 public void onDetach(){
	 super.onDetach();
	 swipePerformedListener = null;
	 }
	 
	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
		iv = new ImageView(getActivity());
		iv.setImageURI(Uri.parse("file:/sdcard/CoverArt/1984.jpg"));
		
		iv.setOnTouchListener(new OnSwipeTouchListener());

		return iv;
	}

	public class OnSwipeTouchListener implements OnTouchListener
	{

		private final GestureDetector gestureDetector 
			= new GestureDetector(new GestureListener());

		public boolean onTouch(final View view, final MotionEvent motionEvent) {
			return gestureDetector.onTouchEvent(motionEvent);
		}
		
		
		

		private final class GestureListener extends SimpleOnGestureListener {

			private static final int SWIPE_THRESHOLD = 100;
			private static final int SWIPE_VELOCITY_THRESHOLD = 100;
			
			//Hidden onTouchEvent(MotionEvent event);

			@Override
			public boolean onDown(MotionEvent e) {
				return true;
			}

			@Override
			public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
				boolean result = false;
				try {
					float diffY = e2.getY() - e1.getY();
					float diffX = e2.getX() - e1.getX();
					if (Math.abs(diffX) > Math.abs(diffY)) {
						if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
							if (diffX > 0) {
								//SwipeRight
								swipePerformedListener.onSwipePerformed("right");
							} else {
								//SwipeLeft
								swipePerformedListener.onSwipePerformed("left");
							}
						}
					} else {
						if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
							if (diffY > 0) {
								//SwipeBottom
								swipePerformedListener.onSwipePerformed("bottom");
							} else {
								//SwipeTop
								swipePerformedListener.onSwipePerformed("top");
							}
						}
					}
				} catch (Exception exception) {
					exception.printStackTrace();
				}
				return result;
			}
		}
		
	}
	
}

Let’s walk through this the way the system does. First BIG MAGIC! the onAttach() method.

OnSwipePerformedListener swipePerformedListener;

@Override
	public void onAttach(Activity activity) {
		super.onAttach(activity);
		try{
			swipePerformedListener = (OnSwipePerformedListener) activity;
		} catch (ClassCastException e) {
			throw new ClassCastException( activity.toString()
										 + " must implement OnSwipePerformedListener");
		}
	}

When the system goes to the main.xml file and it finds paths to fragments, it goes to the fragment and it associates the fragment to the MainActivity, when it does this it passes the MainActivity to the onAttach() method. This must act as a Context, but I have a sneaking suspicion that it provides more, but Google doesn’t tell you.

@Override tells us that it’s a built in method.

The parameter is an Activity that get’s passed in.

Since we are overriding we need to pass the MainActivity on to the default onAttach() method.

There is a good chance that humans will mess up this procedure so you need to surround it with a try and catch so we can catch errors if we do mess it up.

swipePerformedListener is a interface Object, of type OnSwipePerformedListener.
We cast (OnSwipePerformedListener) for the MainActivity, which means we are passing in the OnSwipePerformedListener Object for MainActivity and saving it as swipePerformedListener.
When you are using swipePerformedListener in FragmentArt.java it is actually pointing to MainActivity.java.
The method defined in the interface is what we use to agree on where to send the data.

public void onSwipePerformed(String swipeDirection);

This passes the data and catches the data.

The catch section is actually pretty cool on this one, when you implement this interface in another class if you don’t have the onSwipePerformed method defined it will throw an exception to that class, MainActivity in our case, and it will remind you that you need to define the onSwipePerformed method before it will compile. That’s one of the magic tricks that I actually like.

Let’s take a closer look at the interface.

	public interface OnSwipePerformedListener {
		public void onSwipePerformed(String swipeDirection);
	}

Notice the interface is not capitalized. And notice there is no body to the method of the interface because we are just passing data, not regulating how the data is used. OnSwipePerformedListener is the interface Object type which you will have to implement (similar to extend) in the MainActivity. and onSwipePerformed is the method that must exist in both classes. The String that you are passing from one class to the other will go out the onSwipePerformed(String swipeDirection); parameter in the FragmentArt class and come in through the onSwipePerformed(String swipeDirection); parameter in the MainActivity class.

Remember the interface is defined in the class sending the data, implemented in the class receiving the data.

I’m going to skip the gesture logic and just pass the string from one of the gestures.

swipePerformedListener.onSwipePerformed(“bottom”);

swipePerformedListener is the Object in the FragmentArt class that points to the OnSwipePerformedListener Object in the MainActivity class.

onSwipePerformed() is the method that exists in both classes and I think of as a portal from one class to the other.

“bottom” is the String that goes out the onSwipePerformed() method in the FragmentArt class and goes in the onSwipePerformed() method in MainActivity class.

Finally some house cleaning.

 @Override
	 public void onDetach(){
	 super.onDetach();
	 swipePerformedListener = null;
	 }

The Fragment get’s destroyed when the app is being shut down, or if you were doing something more advanced like removing one Fragment and replacing it with another. If the Fragment is gone we can no longer talk to it so we set it to null to keep it from trying and crashing.

That is all we need on the throwing / sending side let’s take a look at the receiving side the MainActivity.java.

import android.app.*;
import android.os.*;
import android.view.*;
import android.widget.*;

public class MainActivity extends Activity implements FragmentLst.OnSongSelectedListener, FragmentArt.OnSwipePerformedListener
{

	@Override
	public void onSwipePerformed(String swipeDirection)
	{
		// TODO: Implement this method
		if(swipeDirection.contains("top")){
			Toast.makeText(this, "You Swiped Up!", Toast.LENGTH_LONG).show();

		} else if(swipeDirection.contains("right")){
			Toast.makeText(this, "You Swiped Right!", Toast.LENGTH_LONG).show();
			

		} else if(swipeDirection.contains("left")){
			Toast.makeText(this, "You Swiped Left!", Toast.LENGTH_LONG).show();
			
			
		} else if(swipeDirection.contains("bottom")){
			Toast.makeText(this, "Mix Canceled", Toast.LENGTH_LONG).show();
			
		}
	}
	

	@Override
	public void onSongSelected(String daSong)
	{
		Toast.makeText(MainActivity.this,daSong,Toast.LENGTH_LONG).show();
		// TODO: Implement this method
	}
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
	{
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

Not too bad, I have another example of an interface we will go over after this one to help make it stick.

public class MainActivity extends Activity implements FragmentLst.OnSongSelectedListener, FragmentArt.OnSwipePerformedListener
{

MainActivity extends Activity and implements FragmentArt.OnSwipePerformedListener. You can extend only one class, which inherit’s everything, but you can implement multiple interfaces, like we do in this instance.

When you configure the interface the way we did, once you implement the interface you will get a error by the opening curly bracket of your class, and if you point at it you will get the error message defined in the thrown exception from the FragmentArt class saying you need to implement unimplemented methods. Once you manually type out the method or let the system type it for you this error will go away, pretty cool, makes you feel like a professional programmer.

Next you @Override the onSongSelected() method, because you are defining a method that is originally defined in the interface that we are implementing.

Then you set up the method just like the one that sent the data so you can catch the data and do what you want with it.

Here we have a bunch of nested if statements checking the contents of the String sent from FragmentArt through the interface.

If we swiped up, the String “top” is sent through the interface and we toast that “You swiped up”. If we swiped right, the String “right” is sent through the interface and we toast “You swiped right!”.

And that’s how you use an interface, let’s do one more just to make it stick.

We are going to send data from the FragmentLst class to MainActivity class. So we will define the interface in the FragmentLst class, use the onAttach() method to get a link (Context) to the MainActivity class, create a Object of the interface type in FragmentLst class and have it point to the MainActivity class, create a method in both classes that accept the same kind of data to pass the data in between, and we will implement the interface in the MainActivity class.

Let’s take a look at the FragmentLst class.

import android.app.*;
import android.os.*;
import android.text.format.*;
import android.view.*;
import android.widget.*;
import java.io.*;
import java.util.*;

public class FragmentLst extends ListFragment
{
	
	private MusicAdapter myListAdapter;
	List<Music> musicList = new ArrayList<Music>();
	File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);

	BufferedReader myBufferReader;
	String[] rawMusicInfo;
	
	OnSongSelectedListener songSelectedListener;
	
	public interface OnSongSelectedListener {
		public void onSongSelected(String daSong);
	}

@Override
	public void onActivityCreated(Bundle savedInstanceState)
	{
	String buff;
		// TODO: Implement this method
		super.onActivityCreated(savedInstanceState);
		
		myListAdapter = new MusicAdapter(getActivity().getApplicationContext(),musicList);
		try{
			myBufferReader = new BufferedReader(new InputStreamReader(new FileInputStream(new File(path,"ohyeah3.txt"))));
		} catch (FileNotFoundException e) {
                     e.printStackTrace();
}
		
		StringBuilder holdThis = new StringBuilder();
		if(myBufferReader != null){
			try{
				while((buff = myBufferReader.readLine()) != null){
					buff = buff.replace(System.getProperty("line.separator"),"");
					holdThis.append(buff);
				}
			} catch(IOException e){
                            e.printStackTrace();
}
		}
		
		rawMusicInfo = holdThis.toString().split(",");
		
		for(int i = 0;i < rawMusicInfo.length;i=i+5){
			Music entry = new Music();
                        entry.album = rawMusicInfo[i];
			entry.name = rawMusicInfo[i+1];
			entry.song = rawMusicInfo[i+2];
			entry.coverArt = rawMusicInfo[i+3];
			entry.time = rawMusicInfo[i+4];
			musicList.add(entry);
		}
		
		setListAdapter(myListAdapter);
	}

	@Override
	public void onListItemClick(ListView l, View v, int position, long id) {
		// TODO Auto-generated method stub
		super.onListItemClick(l, v, position, id);
		//Music music =  myListAdapter.getItem(position);
		Music item = musicList.get(position);
		 songSelectedListener.onSongSelected(item.name);
    }
	
	@Override
	public void onAttach(Activity activity) {
		super.onAttach(activity);
		try{
			songSelectedListener = (OnSongSelectedListener) activity;
		} catch (ClassCastException e) {
			throw new ClassCastException( activity.toString()
										 + " must implement OnTutSelectedListener");
		}
	}

}

Again when the system goes to the main.xml file it sees a path to FragmentLst, it attaches to it and passes MainActivity Context to the onAttach() method seen above.

We pass the Context on to the default onAttach() method so anything that needs to be done that we didn’t do will get taken care of.

We use a try and catch in case we mess something up.

We take a local instance of the interface Object type and point it at the same Object type in the MainActivity class. Giving us a pointer to the method in the MainActivity so we can pass data to it.

The catch section gives us some extra functionality by throwing any ClassCastException’s on to the MainActivity which then reminds us that we need to implement the method defined in the interface defined in the FragmentLst class.

OnSongSelectedListener songSelectedListener;
	
	public interface OnSongSelectedListener {
		public void onSongSelected(String daSong);
	}

We define a local instance of the interface Object type so we can point it to the interface method in MainAcivity, which we just did.

We define the interface Object type and what the method that passes the data must be called and what type of data it will pass.

We take the local instance of the interface Object type that points to MainActivity class and put data in it’s methods parameter(s) that instantly get’s sent to the method of the same name in MainActivity which we can use as we wish.

public class MainActivity extends Activity implements FragmentLst.OnSongSelectedListener, FragmentArt.OnSwipePerformedListener
{

Back to the MainActivity we implement another interface, because we can implement as many as we want.

We must implement the method defined in FragmentLst.

@Override
public void onSongSelected(String daSong){}

Inside this method we can do whatever we want with the passed data. In this case we display it to the screen (Toast it).

Toast.makeText(MainActivity.this,daSong,Toast.LENGTH_LONG).show();

Gestures

There are 2 ways you can listen for gestures, gestures on a Activity or gestures on a View. We are going to listen for gestures on a view, a ImageView to be exact.

To listen for a gesture on a View we use a OnTouchListener

iv.setOnTouchListener(new OnSwipeTouchListener());

Activity’s and View’s have a onTouchEvent() callback method that captures all data while you are touching the screen, where your finger touched down, how long it stayed down, how fast it moved, how far it moved, if there was more than one finger down at a time, when your finger was raised, where your finger was raised. This is fine for creating our own special gestures, but it is major overkill for what we need, so we will also use a GestureDetector. A GestureDetector knows how to translate more common gestures like LongPress, DoubleTap, SingleTap, Fling, Scroll. It detects all these things by default, but for this app we don’t even need that many gestures so we are also going to use a SimpleOnGestureListener. By default all gestures are turned on for a GestureDetector, and for a SimpleOnGestureListener all gestures are turned off. The way we turn on the gestures we want when using a GestureListener is by overriding them and making them do what we want.

	public class OnSwipeTouchListener implements OnTouchListener
	{

		private final GestureDetector gestureDetector 
			= new GestureDetector(new GestureListener());

		public boolean onTouch(final View view, final MotionEvent motionEvent) {
			return gestureDetector.onTouchEvent(motionEvent);
		}
		
		
		

		private final class GestureListener extends SimpleOnGestureListener {

			private static final int SWIPE_THRESHOLD = 100;
			private static final int SWIPE_VELOCITY_THRESHOLD = 100;
			
			//Hidden onTouchEvent(MotionEvent event);

			@Override
			public boolean onDown(MotionEvent e) {
				return true;
			}

			@Override
			public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
				boolean result = false;
				try {
					float diffY = e2.getY() - e1.getY();
					float diffX = e2.getX() - e1.getX();
					if (Math.abs(diffX) > Math.abs(diffY)) {
						if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
							if (diffX > 0) {
								//SwipeRight
								swipePerformedListener.onSwipePerformed("right");
							} else {
								//SwipeLeft
								swipePerformedListener.onSwipePerformed("left");
							}
						}
					} else {
						if (Math.abs(diffY) > SWIPE_THRESHOLD && Math.abs(velocityY) > SWIPE_VELOCITY_THRESHOLD) {
							if (diffY > 0) {
								//SwipeBottom
								swipePerformedListener.onSwipePerformed("bottom");
							} else {
								//SwipeTop
								swipePerformedListener.onSwipePerformed("top");
							}
						}
					}
				} catch (Exception exception) {
					exception.printStackTrace();
				}
				return result;
			}
		}
		
	}

Let’s walk through our code:

First we set a custom OnTouchListener. In the custom OnTouchListener we use the onTouch() method to catch all info of the screen touch, then we send all that info to the GestureDetector that we also define and initialize here. Under normal circumstances this would narrow down the gesture detection to just the most common gestures, but we initialize the GestureDetector with a SimpleOnGestureListener that turns everything off by default. Then we enabled onDown and onFling. The first time you see the onDown() method it kind of makes you scratch your head because onFling has a onSwipeBottom() which you would think would do the same thing, but it doesn’t. onDown() is used to detect when you place your finger on the screen, if you don’t override this method it won’t detect anything. If you just override it, it will only detect when you touch the screen, you have to return true for it to continue listening after you touch the screen. So it is a very important method() to have set up correctly.

Next we have the onFling() method, it extracts the data needed to detect which direction your finger moved, and how fast it got there. For our implementation it is more important to know which way your finger moved than how fast it got there, but the how fast it got there helps keep the app from being too touchy.

onFling() takes where your finger stopped and subtracts where it started, it does this for both the X (horizontal) value and the Y (vertical) values and the one with the most change tells it if you were moving up and down, or left and right, then it makes sure you moved far enough and fast enough to qualify for a swipe (to keep the app from being too touchy). If the result was positive then you moved right or down and if the result is negative then you moved left or up.

Depending on which way you moved it uses the swipePerformedListener.onSwipePerformed() method to send the appropriate message to the MainActivity through the interface.

We will be lazy and just capture any exception, if one happens.

The last thing to talk about is the onListItemClick() method in the FragmentLst class. I was going to say that it didn’t have anything to do with gestures, but now I think about it that might not be true. SingleTap? Hmmmm food for thought.

Anyway, onListItemClick knows what cell you clicked on by position and you can use that position integer to get the Music Object for that position, and then use the interface to pass the song title to the MainActivity.

@Override
	public void onListItemClick(ListView l, View v, int position, long id) {
		// TODO Auto-generated method stub
		super.onListItemClick(l, v, position, id);
		Music item = musicList.get(position);
		 songSelectedListener.onSongSelected(item.name);
    }

ListView – Part 2/ Custom ListAdapter

Part 2 is going to be the rest of the ListView since I need to REPEAT THE CODE I thought I would start a new post.

We’ve seen this before:

blogfinal1/src/com/example/blogfinal1/FragmentLst.java

import android.app.*;
import android.os.*;
import java.io.*;
import java.util.*;

public class FragmentLst extends ListFragment
{
	
	private MusicAdapter myListAdapter;
	List<Music> musicList = new ArrayList<Music>();
	File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);

	BufferedReader myBufferReader;
	String[] rawMusicInfo;

	@Override
	public void onActivityCreated(Bundle savedInstanceState)
	{
	String buff;
		// TODO: Implement this method
		super.onActivityCreated(savedInstanceState);
		
		myListAdapter = new MusicAdapter(getActivity().getApplicationContext(),musicList);
		try{
			myBufferReader = new BufferedReader(new InputStreamReader(new FileInputStream(new File(path,"ohyeah3.txt"))));
		} catch (FileNotFoundException e) {
                     e.printStackTrace();
}
		
		StringBuilder holdThis = new StringBuilder();
		if(myBufferReader != null){
			try{
				while((buff = myBufferReader.readLine()) != null){
					buff = buff.replace(System.getProperty("line.separator"),"");
					holdThis.append(buff);
				}
			} catch(IOException e){
                            e.printStackTrace();
}
		}
		
		rawMusicInfo = holdThis.toString().split(",");
		
		for(int i = 0;i < rawMusicInfo.length;i=i+5){
			Music entry = new Music();
                        entry.album = rawMusicInfo[i];
			entry.name = rawMusicInfo[i+1];
			entry.song = rawMusicInfo[i+2];
			entry.coverArt = rawMusicInfo[i+3];
			entry.time = rawMusicInfo[i+4];
			musicList.add(entry);
		}
		
		setListAdapter(myListAdapter);
	}


}

We left off at line 10, WOW.

A List is a subclass of Collection which you can use to create a grouping of things that are obtainable by a index number beginning with 0.

A ListArray is a List backed by an Array. (Sorry that’s all I got at the moment, Google documentation, when I can provide more insight I will).

Line 10: So we have List, backed by an Array, of Music objects.

Line 11: We create a File object called path that contains a path to the Public accessible directory called Download (Android considers files and folders to be the same thing, you have to perform special logic to differentiate the two).

Line 12: Next is a BufferedReader. BufferedReader is a special Reader that can read ahead of what you may be specifically asking for and saves it in memory to avoid reading from “disk” as much, making reads less “expensive” on the device.

Line 13: A String Array, to use as a buffer of sorts. For each line we read
in from our .txt file we will have 5 characteristics that we will use to
create a Music object.

So now we have our tools laid out that we are going to use, we can start
working to create our list by reading in the data from a file and
placing the characteristics (Strings) into a Music object, that can be used to
pull information from and populate each View, “cell”, in our list.

Line 16: Says that we are overriding the default method onActivityCreated(Bundle) and creating our own. This is how you can tell if this method is provided by the class or one that someone created themselves. If there is no @Override the method() should be defined farther down in the code or in the class it is prefixed by.

Line 17: onActivityCreated(Bundle) runs after the MainActivity has completed the onCreate() method.

Line 19: Creates a String variable called buff that we will use to read in each line from the .txt file which will contain 5 comma separated Strings (song characteristics) that we will use to create each Music object.

Line 20: A comment added by default when the ListFragment class creates the onActivityCreated(Bundle) that I was too lazy to delete and you will see when you start creating yours from scratch anyway.

Line 21: Since we are taking over the onActivityCreated(Bundle) method, but not implementing all the methods included in the default version we have to pass along the data we did capture / hijack to the the default version of the method so it can finish doing with it what it needs.

A better example of this will come later when we add functionality to pause the music when the phone rings. We capture the data sent when the phone rings and perform actions that we want, but if we did not pass on the data saying the phone was ringing, just imagine what that would do for the users experience with your app. It would essentially break the phone.

Line 23: Here we are creating an Object, our custom ListAdapter, so it is actually taking up memory space at this time. To create the object we call our constructor that we talked about in the previous post and pass it the values we defined that we needed, a Context and a List of Music objects (even though they do not exist yet). This context is something we haven’t seen before. Since we are creating the FragmentLst class from the main.xml file we can’t pass in Context to the class so we have to go get it (getActivity().getApplicationContext()) getActivity() finds the MainActivity and .getApplicationContext() gets it’s Context. So we end up with MainActivity.this without passing it in.

Line 24: The try command – you use this when you are going to do something that the system has absolutely no control over. In this case making sure a file is there to be read. The system has to take it on faith that the file will be there. It’s really just something you need to use so you can add Line 25 which will help you debug the problem which has a better than average chance of happening if you are writing code for the first time.

Line 25: Is actually a bunch of commands all crammed on one line, if you want to see what the look like separated go %%%%%to be added%%%%%. What this line does is it grabs the physical file ohyeah3.txt located in the Download directory on the “sdcard” (which may be real or simulated depending on the device) and puts it in memory as a File Object (taking up memory). It then uses a InputStreamReader of type FileInputStream to read the file into a BufferedReader.

Line 26: Here is why we really use the try command. If the file is not there instead of the app just crashing and you having to guess where in your 1000 lines of code did something go wrong it will “catch” the exception (the reason why the system crashed) and you can then print it out (e.printStackTrace();) or Log a message to help debug the issue. There are several types of exceptions you can catch but the one that you will most likely run into in this instance is FileNotFoundException you also have to supply a variable (you don’t have to define it anywhere) to store the information in this case it is e. If you want to catch ALL exceptions you can
} catch (Exception e) {}.

Line 27: e.printStackTrace(); is what runs if there is not a file found, and this will give you a memory dump which will contain several lines of gibberish that will hopefully hint at what the problem is and the problem will most likely be at the very beginning of the dump.

Line 28: End of the try statement.

Line 30: Next is a StringBuilder which is a “convenience” class that helps you string String’s together by appending the text in chunks as you make them. new StringBuilder(); is a Constructor with no special data passed in. Pointing this out to help you recognize a Constructor.

Line 31: If the BufferReader we created actually read in data from a file it will not be equal to null and we can and want to do something with that data.

Line 32: What we are about to do is again prone to error and Android wants us to be prepared for errors so it makes us use a try statement.

A lot of text editors (especially Apple which we will be getting our list from eventually) add hidden return characters to the end of their lines, we need to make sure they are removed or else they will cause the app to crash when we try to put the data in a Music object. So we read each line, we check the line for any special characters that represent a return character (line.separator) if we find one we replace it with “” which is nothing, so we essentially erase it. And then we add the line to our holdThis StringBuilder. We repeat this until we have no more data to read.

Let’s look at the code again and walk through it again, but how the code presents it.

			try{
				while((line = myBufferReader.readLine()) != null){
					buff = buff.replace(System.getProperty("line.separator"),"");
					holdThis.append(buff);
				}
			} catch(IOException e){}
		}

while((buff = myBufferReader.readLine()) != null){

This reads each line of the buffer that contains all the data from the file, it reads the line until it comes to a hidden return command. While it has data to read, the variable “buff” will contain data and not equal null so it will continue to the next line of code.

buff = buff.replace(System.getProperty(“line.separator”),””);

Which takes the variable buff on the right side of the = sign and uses a method called .replace that takes 2 parameters. A parameter which shows the method what to replace:

System.getProperty(“line.separator”)

This is a method supplied by the System class that get’s all character strings that represent a return command (there could be several different ones).

And the second parameter:

""

is what you want to replace it with.

Once it has done this with the single line, it saves the results in the same variable (all previous data is overwritten / removed) buff on the left side of the = sign.

Then it adds it to the end (.append(buff)) of the holdThis StringBuilder object.

If there is a issue reading data into the BufferReader it will THROW an IOException which we can CATCH

} catch(IOException e){
e.printStackTrace();
} 

and print out the memory dump to search for the issue.

We do this for every line until there is no more data to read. At that time buff will equal null and we will fall out of the while statement.

We now have a StringBuilder Object with all our data in it the way we need it….almost.

My guess is that a StringBuilder Object is again a Mutable one and the system doesn’t really like that so we have to use a method in the StringBuilder class to convert it to a string.

holdThis.toString()

**** This method is actually in every class because it is in the Object class. There is a class called Object that every class is based off of. Like our custom ListAdapter class was based on the BaseAdapter class, all classes are based off the Object class, which means they inherit all of it’s methods, so every class has access to the .toString() method.

We also have our song characteristics separated by comma’s, and we want to break our String apart at the comma’s and place these sections in our String array (String[]) called rawMusicInfo (.split(“,”);).

rawMusicInfo = holdThis.toString().split(“,”);

done.

Now we have all our data separated into little “boxes” that we can locate by index number 0,1,2,3….

Now we want to create our Music Objects.

for(int i = 0;i < rawMusicInfo.length;i=i+5){
			Music entry = new Music();
            entry.album = rawMusicInfo[i];
			entry.name = rawMusicInfo[i+1];
			entry.song = rawMusicInfo[i+2];
			entry.coverArt = rawMusicInfo[i+3];
			entry.time = rawMusicInfo[i+4];
			musicList.add(entry);
		}

This will loop us through the String array rawMusicInfo until we reach the end of the array which we find by using rawMusicInfo.length.

To do this we create a integer variable (i), set it to 0 (for the first “box” / value in our array) This is a counter that will perform the for loop (for(){ }) as long as (i) is less than the last “box” / value in the array. Each time through the loop it will increment (i). I did a little trick here, normally you increment by 1 (you can also decrement by a value if it makes the logic easier to understand). Since we are going to be using 5 values at a time I increment by 5 to get to the end a little quicker with less looping.

So each time through the loop I first create a new Music Object:

Music entry = new Music();

Then I take that Object and populate it’s characteristics with the values we get from our string array.

Then I add the Music Object to a List of Music Objects.

Here is a walk through:

i = 0, which is less than the total number of array fields, so I create a Music Object called entry. Then I grab the first field (rawMusicInfo[i] remember i = 0) and assign it to the characteristic that holds album name for the Music Object entry (entry.album)

entry.album = rawMusicInfo[i];

We are still in the first loop, so i = 0, I don’t want the value for the album name, I need the value for song title, which is the 2nd value in the array, so I add 1 to the index number of the array to get that and assign it to the Music Object characteristic that holds song titles.

entry.name = rawMusicInfo[i+1];

Then we are still in the first loop and I add 2 to the index and get the value from the array and assign it to the Music Object characteristic that holds song db numbers.

entry.song = rawMusicInfo[i+2];

I keep doing this until I have defined all of the first Music Object’s characteristics, then I add the Music Object to the List of Music Objects ( musicList ).

Now we are ready to start the next loop, since we increment by 5 we are sitting at the 6th value / index / box in the array (it’s actually the 5th because we started at 0), good thing, because that is the next album name which we assign the the first characteristic of the next Music Object which we created when we started the 2nd loop, and when we are done making that Music Object we jump to the 11th value (10th index #) and so on until i > or = to rawMusicInfo.length then we drop out of the for loop.

We then call:

setListAdapter(myListAdapter);

which is a method provided by the ListFragment class which tells the app that when it creates the list use myListAdapter to do so. So it creates it, like we went over earlier.

		myListAdapter = new MusicAdapter(getActivity().getApplicationContext(),musicList);

We pass it the Context for MainActivity.this and the List of Music Objects we just created musicList.

And there you have it in just under 5000 words how to make a ListFragment with contents!

I am going to make you go back 2 posts this time and get the necessary code to make this work, you will need:

blogfinal1/src/com/example/blogfinal1/FragmentArt.java
blogfinal1/src/com/example/blogfinal1/FragmentTime.java

blogfinal1/res/layout/main.xml
blogfinal1/res/layout/time_view.xml

blogfinal1/res/layout-land/main.xml

and the Manifest file changes.

** New Code

blogfinal1/res/layout/img_row_layout.xml

blogfinal1/src/com/example/blogfinal1/Music.java
blogfinal1/src/com/example/blogfinal1/MusicAdapter.java
blogfinal1/src/com/example/blogfinal1/FragmentLst.java

The FragmentLst.java file is the only one you will be overwriting and you can just copy and paste over the old file.

Remember to change the path in both main.xml files if the name changed and make sure you have your package line at the very top with the correct name in it.

From this point you can copy any of the code (that isn’t a duplicate or example) to create the program except FragmentLst from the first post it is now out of date.

Oh the data, you can make a text file and copy this data into it or make your own if you wish.

Call it ohyeah3.txt or change the code accordingly.

Joe Satriani,If,4160282723214640264,Joe Satriani.jpg,00:04:49,
Joe Satriani,Home,4160282723214640269,Joe Satriani.jpg,00:03:25,
Joe Satriani,Luminous Flesh Giants,4160282723214640266,Joe Satriani.jpg,00:05:55,
Joe Satriani,Down Down Down,4160282723214640265,Joe Satriani.jpg,00:06:13,
Joe Satriani,Look My Way,4160282723214640268,Joe Satriani.jpg,00:04:01,
Joe Satriani,Killer Bee Bop,4160282723214640271,Joe Satriani.jpg,00:03:48,
Joe Satriani,S.M.F,4160282723214640267,Joe Satriani.jpg,00:06:42,
Joe Satriani,Sittin' 'Round,4160282723214640274,Joe Satriani.jpg,00:03:38,
Joe Satriani,Slow Down Blues,4160282723214640272,Joe Satriani.jpg,00:07:22,
Joe Satriani,(You're) My World,4160282723214640273,Joe Satriani.jpg,00:03:56,
Joe Satriani,Moroccan Sunset,4160282723214640270,Joe Satriani.jpg,00:04:21,
Joe Satriani,Cool #9,4160282723214640263,Joe Satriani.jpg,00:06:00,
The Extremist,Rubina's Blue Sky Happiness,4160282723214640247,The Extremist.jpg,00:06:09,
The Extremist,Friends,4160282723214640243,The Extremist.jpg,00:03:29,
The Extremist,The Extremist,4160282723214640244,The Extremist.jpg,00:03:43,
The Extremist,Summer Song,4160282723214640248,The Extremist.jpg,00:04:58,
The Extremist,War,4160282723214640245,The Extremist.jpg,00:05:45,
The Extremist,Motorcycle Driver,4160282723214640251,The Extremist.jpg,00:04:56,
The Extremist,Why,4160282723214640250,The Extremist.jpg,00:04:45,
The Extremist,Cryin',4160282723214640246,The Extremist.jpg,00:05:42,
The Extremist,Tears In The Rain,4160282723214640249,The Extremist.jpg,00:01:21,
The Extremist,New Blues,4160282723214640252,The Extremist.jpg,00:06:56,
Flying In a Blue Dream,I Believe / Interview,4160282723214640255,Flying In a Blue Dream.jpg,00:05:52,
Flying In a Blue Dream,The Forgotten Pt. 2,4160282723214640257,Flying In a Blue Dream.jpg,00:05:07,
Flying In a Blue Dream,Into the Light,4160282723214640259,Flying In a Blue Dream.jpg,00:02:29,
Flying In a Blue Dream,The Mystical Potato Head Groove Thing,4160282723214640254,Flying In a Blue Dream.jpg,00:05:09,
Flying In a Blue Dream,The Forgotten Pt. 1,4160282723214640256,Flying In a Blue Dream.jpg,00:01:12,
Flying In a Blue Dream,The Bells of Lal Pt. Two,4160282723214640258,Flying In a Blue Dream.jpg,00:04:06,
Flying In a Blue Dream,Flying in a Blue Dream,4160282723214640253,Flying In a Blue Dream.jpg,00:05:23,
Flying In a Blue Dream,Strange,6292029424235847124,Flying In a Blue Dream.jpg,00:05:02,
Flying In a Blue Dream,I Believe / Interview,6292029424235847125,Flying In a Blue Dream.jpg,00:05:55,
Flying In a Blue Dream,One Big Rush,6292029424235847126,Flying In a Blue Dream.jpg,00:03:26,
Flying In a Blue Dream,The Phone Call,6292029424235847127,Flying In a Blue Dream.jpg,00:03:01,
Flying In a Blue Dream,Day At the Beach (New Rays from an Ancient Sun),7060199914264384165,Flying In a Blue Dream.jpg,00:02:04,
Flying In a Blue Dream,Back to Shalla-Bal,7060199914264384166,Flying In a Blue Dream.jpg,00:03:14,
Flying In a Blue Dream,Ride,7060199914264384167,Flying In a Blue Dream.jpg,00:04:56,
Flying In a Blue Dream,The Forgotten Pt. One,7060199914264384168,Flying In a Blue Dream.jpg,00:01:12,
Flying In a Blue Dream,The Forgotten Pt. Two,7060199914264384169,Flying In a Blue Dream.jpg,00:05:07,
Flying In a Blue Dream,The Mystical Potato Head Groove Thing,8317032066007941762,Flying In a Blue Dream.jpg,00:05:10,
Flying In a Blue Dream,Can't Slow Down,8317032066007941763,Flying In a Blue Dream.jpg,00:04:50,
Flying In a Blue Dream,Headless,8317032066007941764,Flying In a Blue Dream.jpg,00:01:29,
Flying In a Blue Dream,The Bells of Lal Pt. One,8584424278683016416,Flying In a Blue Dream.jpg,00:01:19,
Flying In a Blue Dream,The Bells of Lal Pt. Two,8584424278683016417,Flying In a Blue Dream.jpg,00:04:07,
Flying In a Blue Dream,Into the Light,8584424278683016418,Flying In a Blue Dream.jpg,00:02:30,
Flying In a Blue Dream,The Feeling / Interview,1148073617125400889,Flying In a Blue Dream.jpg,00:00:50,
Flying In a Blue Dream,Big Bad Moon / Interview,1148073617125400875,Flying In a Blue Dream.jpg,00:05:15,
Flying In a Blue Dream,Flying In a Blue Dream,1148073617125400888,Flying In a Blue Dream.jpg,00:05:23,
Not Of This Earth,Not Of This Earth,4160282723214640312,Not Of This Earth.jpg,00:04:00,
Not Of This Earth,The Snake,4160282723214640313,Not Of This Earth.jpg,00:04:41,
Not Of This Earth,Driving At Night,4160282723214640318,Not Of This Earth.jpg,00:03:31,
Not Of This Earth,The Headless Horseman,4160282723214640321,Not Of This Earth.jpg,00:01:53,
Not Of This Earth,Hordes Of Locusts,4160282723214640319,Not Of This Earth.jpg,00:04:56,
Not Of This Earth,New Day,4160282723214640320,Not Of This Earth.jpg,00:03:46,
Not Of This Earth,Rubina,4160282723214640314,Not Of This Earth.jpg,00:05:53,
Not Of This Earth,Memories,4160282723214640315,Not Of This Earth.jpg,00:04:04,
Not Of This Earth,The Enigmatic,4160282723214640317,Not Of This Earth.jpg,00:03:26,
Not Of This Earth,Brother John,4160282723214640316,Not Of This Earth.jpg,00:02:08

Next up Gestures and interfaces….

ListView – Part 1/ Custom ListAdapter / LayoutInflater / attachToRoot

In my previous post we started making a mp3 player that uses Fragments.

This part about creating a ListFragment is going to be a 2 parter, I am going to start out telling you about the ListView then we are going to get side-tracked on the custom ListAdapter for awhile. Then on the second part I will show you the ListView again and we will finish it out.

There were 3 Fragments:

a ImageView for album art.
a regular Fragment for time remaining and song title
a ListFragment for songs to play

Now we are going to read in some data to populate the ListFragment.

This isn’t a project for beginners so I’m going to skip right to creating an Object for each song.

blogfinal1/src/com/example/blogfinal1/Music.java

public class Music {
	String name;
	String album;
	String song;
	String coverArt;
	String time;	
}

That’s it! but it makes things SO much easier.

Now here is the code for the new ListFragment:

blogfinal1/src/com/example/blogfinal1/FragmentLst.java

import android.app.*;
import android.os.*;
import java.io.*;
import java.util.*;

public class FragmentLst extends ListFragment
{
	
	private MusicAdapter myListAdapter;
	List<Music> musicList = new ArrayList<Music>();
	File path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);

	BufferedReader myBufferReader;
	String[] rawMusicInfo;

	@Override
	public void onActivityCreated(Bundle savedInstanceState)
	{
	String buff;
		// TODO: Implement this method
		super.onActivityCreated(savedInstanceState);
		
		myListAdapter = new MusicAdapter(getActivity().getApplicationContext(),musicList);
		try{
			myBufferReader = new BufferedReader(new InputStreamReader(new FileInputStream(new File(path,"ohyeah3.txt"))));
		} catch (FileNotFoundException e) {
                     e.printStackTrace();
}
		
		StringBuilder holdThis = new StringBuilder();
		if(myBufferReader != null){
			try{
				while((buff = myBufferReader.readLine()) != null){
					buff = buff.replace(System.getProperty("line.separator"),"");
					holdThis.append(buff);
				}
			} catch(IOException e){
                            e.printStackTrace();
}
		}
		
		rawMusicInfo = holdThis.toString().split(",");
		
		for(int i = 0;i < rawMusicInfo.length;i=i+5){
			Music entry = new Music();
                        entry.album = rawMusicInfo[i];
			entry.name = rawMusicInfo[i+1];
			entry.song = rawMusicInfo[i+2];
			entry.coverArt = rawMusicInfo[i+3];
			entry.time = rawMusicInfo[i+4];
			musicList.add(entry);
		}
		
		setListAdapter(myListAdapter);
	}


}

Since we are going to discuss the custom list adapter I might as well give you that code too.

blogfinal1/src/com/example/blogfinal1/MusicAdapter.java

import android.content.*;
import android.view.*;
import android.widget.*;
import java.util.*;

public class MusicAdapter extends BaseAdapter
{
	
	private List<Music> musicList;
	private Context context;
	
	public MusicAdapter(Context context,List<Music> passedList){
		this.context = context;
		this.musicList = passedList;
	}

	@Override
	public int getCount()
	{
		// TODO: Implement this method
		return musicList.size();
	}

	@Override
	public Object getItem(int p1)
	{
		// TODO: Implement this method
		return musicList.get(p1);
	}

	@Override
	public long getItemId(int p1)
	{
		// TODO: Implement this method
		return 0;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent)
	{
		// TODO: Implement this method
		if(convertView == null){
			LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			convertView = inflater.inflate(R.layout.img_row_layout,parent,false);
		}
		TextView myName = (TextView)convertView.findViewById(R.id.name);
		TextView myAlbum = (TextView)convertView.findViewById(R.id.dist);

		Music item = musicList.get(position);
		myName.setText(item.name);
		myAlbum.setText(item.album);

		return convertView;
	}
}

Line 09 in the FragmentLst class we grab some memory so we can make a list adapter.

A list adapter creates each line in the list in the list view, iOS refers to them as “cells” which is a little easier to grasp, because each cell can have more than one line and this is the main reason we create our own for this project. Google does have default list adapters that give you one or two lines of text in each cell. The main reason I use a custom list adapter is because we are using the Music class which is easier to use with a custom list adapter, and since we are going to the trouble to make our own list adapter we might as well throw in a album cover too.

Since we are talking about the list adapter let’s jump to the MusicAdapter class and walk through it.

First on line 06 you will notice that this class extends the BaseAdapter class this is the class that is used to make list adapters and is used by Google when it creates it’s stock list adapter. We are going to make a copy (extend) the BaseAdapter class and change (@Override) what we don’t like about the stock implementation.

But first we need to create some variables:

Line 09 we create a variable for a List of type Music.

A List is like an array, but again I’m going to jump into iOS lingo, they are Mutable this means they are dynamic and can grow as needed, arrays are static, when you create them they are set to a certain size and remain that size.

Although a List seems like the only way to go, other operations don’t like Lists because they ARE dynamic so most of the time once you have the List the way you want it you will have to convert it to an array to use the data in it.

Line 10 defines a Context variable.

line 12 starts the Constructor.

When you call this Constructor you pass in context which points to the application resources that MainActivity has access to, and a list of music objects which we will use to create our custom layout for each list item in the list view.

The Constructor takes these values and assigns them to global variables in the class. When the values are passed in they are only “visible”, usable, in the Constructor(or method(),on a side note, when you pass values there) so they must be assigned to other variables if you want to use them outside of the constructor(or method()).If you can’t or don’t want to think of a new variable name you can use the same name and add (this.) to the front of the variable to point to the global variable. (this) is also a context that points to the local class and by putting it in font of the variable you point to the “global” class variable. (this) is kind of a lazy way to show context and doesn’t always work, because (this) could also mean MainActivity. The safest way to show context for this class is MusicAdapter.this.musicList or if you mean MainActivity context use MainActivity.this.

in this instance I used this in front of musicList, but I didn’t have to since it is only found as a class variable, it is used other places, but only defined in one location.

Next we see some methods that begin with @Override this tells the system and you that these are default methods found in the class that you extended that you are going to take over. They essentially wipe out the original one and you define what it should do instead.

When our program tries to use our custom list adapter it will call these methods by name and they will do what we want.


******* Duplicate Code
@Override
	public int getCount()
	{
		// TODO: Implement this method
		return musicList.size();
	}
******* Duplicate Code

Returns the number of Music Objects in the passed in list. This will be used to tell the list view how many individual “cells” need to be created.


******* Duplicate Code
@Override
	public Object getItem(int p1)
	{
		// TODO: Implement this method
		return musicList.get(p1);
	}
******* Duplicate Code

Very important, the Object you want


******* Duplicate Code
	@Override
	public long getItemId(int p1)
	{
		// TODO: Implement this method
		return 0;
	}
******* Duplicate Code

Not really used, but usually part of the group.

Ok, the good stuff below

Here is where we make the “cell” look the way we want.


******* Duplicate Code
	@Override
	public View getView(int position, View convertView, ViewGroup parent)
	{
		// TODO: Implement this method
		if(convertView == null){
			LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
			convertView = inflater.inflate(R.layout.img_row_layout,parent,false);
		}
		TextView myName = (TextView)convertView.findViewById(R.id.name);
		TextView myAlbum = (TextView)convertView.findViewById(R.id.dist);

		Music item = musicList.get(position);
		myName.setText(item.name);
		myAlbum.setText(item.album);

		return convertView;
	}
******* Duplicate Code

convertView, the view we are converting(reusing) or creating (creating in this instance), will equal null because it is being created for the first time, and this will continue until the list view is full of “cells” once the cells start to fall off the screen they will be converted(reused), and when they are converted they don’t need to be inflated again.

The only place we use the context in this class. We use the context so we can inflate the layout using resources from the MainActivity, which allows us to actually display the cell to the screen, otherwise you would not be able to see it.

Line 07 We create a LayoutInflater which “blows up” the View to fit the screen. It also uses Casting to tell Android what type of Object it is returning (inflater = (LayoutInflater)).

LayoutInflater

Context is a class that contains a method getSystemService(), that returns a Object. Context also has a static (obtainable from other classes without instantiating the Context class) final (constant value) variable LAYOUT_INFLATER_SERVICE = “layout_inflater” that returns a LayoutInflater Object. And so when this returns a LayoutInflater Object the code knows it is getting a Object, but it doesn’t know what kind and that is why you have to cast the (LayoutInflater) Object type when assigning it to the LayoutInflater inflater variable.

Now we have the tool to create a View in the MainActivity we can take a .xml file and create a View (convertView in this example).

convertView = inflater.inflate(R.layout.img_row_layout,parent,false);

The view we want to create (inflate) is R.layout.img_row_layout (which is in the blogfinal1/res/layout directory)

attachToRoot

After the layout file comes (ViewGroup parent) – ViewGroup is the canvas/screen, it is what you put your View in, if you want to or it can just float there. So we are telling it where to put the view, and the 3rd option (attachToRoot) is whether or not you want to attach it to it’s parent, inflate() method is used to create many different Views, there may come a time when you need to set this to true to allow your view to pass on things such as gestures to the parent so that they can be handled, but this is not one of those times, because when creating a list view this is done automatically and trying to do it again (twice) by setting it to true will cause a error. By providing the parent in the previous parameter, you give it what it needs to automatically add the View to the ViewGroup parent. Fragments also attach to the parent automatically during the onCreateView() method.

Now we have a custom View, “cell” just like we defined in R.layout.img_row_layout (which we haven’t seen yet) The cell was designed to have 2 lines of text, different sizes.

Later on we will add a ImageView (album cover) to make it even more custom.

Lines 47 and 48 we get a couple pointers to locations in the cell where the text should go

Line 49 in the MusicAdapter, we create a Music Object called “item” and put a copy of the Music Object in the musicList(a Mutable Array) found at position (int position).

Line 51 and 52 we set the Text of the lines in the custom cell to the characteristics of the Music Object we selected from the musicList and saved in Music Object called “item”.

item.name
item.album

item is the Object and .name is the song name and .album is the album name.

then return the view now that it is the way we want it, so it can be displayed.

We do this all the way through our List musicList, we will also inflate a new View like we covered earlier until the screen fills up and then we will begin converting cells to avoid creating tons of Objects which take up memory.

Now lets look at the layout of the View or cell:

blogfinal1/res/layout/img_row_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >

    <ImageView
        android:id="@+id/img"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="left"
        android:paddingLeft="0dip"
        android:src="@drawable/ic_launcher" />

    <TextView
        android:id="@+id/name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignTop="@+id/img"
        android:layout_toRightOf="@+id/img"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/dist"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/name"
        android:layout_gravity="center"
        android:layout_marginTop="2dip"
        android:layout_toRightOf="@id/img"
        android:gravity="right"
        android:textSize="8dp"
        android:textStyle="italic" />

</RelativeLayout>

We have a ImageView, here we are cheating instead of setting up a variable and assigning a image to a variable and then passing it in to the View we hard code the image file path into the View. This will make all the pictures static, but we will fix that later.

Then our two TextViews configured just how we want to see them.

This is also a RelativeLayout so each subview is relative to each other (usually starting at the top left corner) so it’s kind of wordy about its location and not easy to adjust without a GUI.

….on to the next…..

Android Fragments

This is going to be one big app consisting of multiple concepts that I explain along the way, the final product will send commands over wifi to a iPhone or iPod Touch to play music. As of right now if you don’t have:

a Mac computer of some sort, which allows you to run Xcode, and you also need a Apple Developer License which you can get for free or they are $99 a year.

This will not work completely on just Android it will not play music(updated), but it will give you important knowledge about key Android concepts that you will use for many different things.

I will eventually add some basic code to play music on an android device, I will also show you how to create a remote of sorts using bluetooth to send commands to another android device so it will play music, the remote will accept voice commands and tell the remote device what to play, the remote will vibrate when 30 seconds are left so you can look for a new song….

Ok, let’s see how the blogging thing works…..

So the app starts

it runs the MainActivity’s onCreate() method

import android.app.*;
import android.os.*;
import android.view.*;
import android.widget.*;

public class MainActivity extends Activity
{
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
	{
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

Sorry about that, had to do a little cabbage patch when I saw the code listing works.

which runs setContentView(R.layout.main);

******* You will need to change the path in this file if you use a different name ********

blogfinal1/res/layout/main.xml

<?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="fill_parent"
	android:weightSum="100"
    android:orientation="vertical">


	<fragment
		android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="com.example.blogfinal1.FragmentArt"
		android:id="@+id/frag_a"
		android:layout_weight="20"/>

	<fragment
		android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="com.example.blogfinal1.FragmentTime"
		android:id="@+id/frag_b"
		android:layout_weight="48"/>

	<fragment
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="com.example.blogfinal1.FragmentLst"
		android:id="@+id/frag_c"
		android:layout_weight="32"/>


</LinearLayout>

This is what really makes Fragments different than anything else.

Normally R.layout.main is what you see on your screen when you run the app, but Fragments break up the R.layout.main single view, into subview’s which in this example there are 3 of them:

android:name=”com.example.blogfinal1.FragmentArt”
android:name=”com.example.blogfinal1.FragmentTime”
android:name=”com.example.blogfinal1.FragmentLst”

These are paths (blogfinal1/src/com/example/blogfinal1) to Fragments which create views when the program is run.

The Fragment files are:

FragmentArt.java
FragmentTime.java
FragmentLst.java

Normally you create the R.layout.main file and essentially hard code the application layout.

With fragments you give the R.layout.main the ability to build the views on app creation.

R.layout.main calls each of the Fragment classes when it is called by setContentView(R.layout.main); method.

android:name=”com.example.blogfinal1.FragmentArt”
android:name=”com.example.blogfinal1.FragmentTime”
android:name=”com.example.blogfinal1.FragmentLst”

When R.layout.main calls these classes they return the views which they create and they are then drawn to the screen by R.layout.main.

Other important layout code:

android:id=”@+id/frag_b”

This is what you will use to create a link to pass data to the fragments or to call methods in other fragments. (but not in this section of the tutorial)

******Even though we are not using these in this tutorial
******you need this or a tag file or app will crash

Each fragment requires a unique identifier that the system can use to restore the fragment if the activity is restarted (and which you can use to capture the fragment to perform transactions, such as remove it). There are three ways to provide an ID for a fragment:

android:id= like we use in this tutorial
android:tag= which you can also use for fragments that are not displayed to the screen
android:id= defined in the LinearLayout or RelativeLayout

Other important parts of the R.layout.main file are:

<Fragment       /> 

it segregates the characteristics for each fragment section.

android:layout_weight=”20″

allows us to adjust the layout proportions of each fragment to fit our liking.

and if you your are going to use layout_weight in the sub views you have to set a total weight in the main layout:

android:weightSum=”100″

and make sure you use the:

android:orientation=”vertical”

we are going to make a separate view for when the phone is in landscape mode and if you don’t have this setting set it will grab the horizontal view even though the phone is in portrait mode.

Note:
layout_weight – this is how you divide up the screen space in LinearLayouts, it’s probably one of the trickiest values you will ever have to deal with, it uses a reverse percentage, if you want it to take up 80 % of the screen you set it to 20???? There are ways to adjust the total percentage number (weightSum=) but it is best just to shoot for a total of 100%.

Like I said this is very tricky, if you set it wrong you’re fragment won’t show on the screen, so when you are starting out it’s best just to make everything equal but still total 100 % and then start adjusting by 10. Remove 10 from one and add it to the other, or if there is 3 fragments like this one for instance remove 10 from android:name=”com.example.blogfinal1.FragmentArt” to make it bigger and ADD 5 to android:name=”com.example.blogfinal1.FragmentTime” and ADD 5 to android:name=”com.example.blogfinal1.FragmentLst”. Take it slow with this sometimes you add 5 and the fragment will disappear on you.

Whew! That’s just the beginning of setting up the initial view!

Let’s continue on…

So in the R.layout.main xml view file we have a path that points to the

blogfinal1/src/com/example/blogfinal1

directory. And in that directory we need 3 files besides the MainActivity.java file

FragmentArt.java
FragmentTime.java
FragmentLst.java

These files are where the fragment views actually get made, the views are built and then returned to the R.layout.main view. There is actually 2 different types of fragment files you can have and we use both.

FragmentLst.java is a ListFragment
FragmentArt.java, FragmentTime.java are regular Fragment(s).

A ListFragment creates a list layout magically for you.
A Fragment you must manually create (inflate) the view with code.

Here is the list fragment:

blogfinal1/src/com/example/blogfinal1/FragmentLst.java

import android.app.*;
import android.os.*;

public class FragmentLst extends ListFragment
{

	@Override
	public void onActivityCreated(Bundle savedInstanceState)
	{
		// TODO: Implement this method
		super.onActivityCreated(savedInstanceState);
	}
}

This will create a empty list view that will actually compile (with all the other necessary code) it also displays a progress wheel, magically.

Ok not magically, it knows you want a list view so it automatically inflates one when called.

It does this by extending the ListFragment class.

Also notice that it overrides the onActivityCreated() method not the onCreate() method. This means you can do something AFTER it runs the onCreate() method which creates the list view.

Note:
Magic make obsessive people ANGRY!

Here is a regular fragment:

blogfinal1/src/com/example/blogfinal1/FragmentTime.java

import android.app.*;
import android.os.*;
import android.view.*;
import android.widget.*;

public class FragmentTime extends Fragment
{
	private View mStatusView;

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState){
		mStatusView = inflater.inflate(R.layout.time_view,container,false);
		
		return mStatusView;
	}

}

This class extends the Fragment class which makes you create (inflate) your own xml layout.
Let’s walk through this file.

You need a View object to make your view out of:
private View mStatusView;

When it’s time to actually create the view:

public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState){

Now we need to make (inflate) the view and save a pointer to it.

mStatusView = inflater.inflate(R.layout.time_view,container,false);

This will display the R.layout.time_view:

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

    <TextView 
        android:id="@+id/timeView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
		android:text="Time: 00:00"/>
	<TextView 
        android:id="@+id/songView"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"/>

</LinearLayout>

So we now have the FragmentLst ListFragment on the screen and the FragmentTime time_view on the screen now we need to create the FragmentArt view on the screen. It is the same type of Fragment as FragmentTime, but it looks a little different.

blogfinal1/src/com/example/blogfinal1/FragmentArt.java

import android.app.*;
import android.net.*;
import android.os.*;
import android.view.*;
import android.widget.*;

public class FragmentArt extends Fragment
{

	private ImageView iv = null;

	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
		iv = new ImageView(getActivity());
		iv.setImageURI(Uri.parse("file:/sdcard/CoverArt/1984.jpg"));
 		
		return iv;
	}
}

Since the fragment only consists of a single ImageView there is no need to inflate anything we can just reserve a ImageView object:

private ImageView iv = null;

Use more magic to create the ImageView with context to MainActivity without having to pass in the context from MainActivity (by using the getActivity() method):

iv = new ImageView(getActivity());

Assign an image to the ImageView:
iv.setImageURI(Uri.parse(“file:/sdcard/CoverArt/1984.jpg”));

And pass back the ImageView to the R.layout.main xml file:

return iv;

The changes you have to make to your Manifest File (blogfinal1/AndroidManifest.xml) are:

(Fragments require a minimum of Android version 11)
and
You need permissions to read external storage to get the album cover picture

blogfinal1/AndroidManifest.xml

<uses-sdk 
        android:minSdkVersion="11" 
        android:targetSdkVersion="21" />

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

Finally create a blogfinal1/res/layout-land folder and then create a main.xml file and put this code in it:

******* You will need to change the path in this file if you use a different name ********

blogfinal1/res/layout-land/main.xml

<?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="fill_parent"
	android:weightSum="100">


	<fragment
		android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="com.example.blogfinal1.FragmentTime"
		android:id="@+id/frag_b"
		android:layout_weight="48"/>

	<fragment
                android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="com.example.blogfinal1.FragmentLst"
		android:id="@+id/frag_c"
		android:layout_weight="32"/>

        <fragment
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:name="com.example.blogfinal1.FragmentArt"
		android:id="@+id/frag_a"
		android:layout_weight="20"/>


</LinearLayout>

I removed the vertical setting and changed the layout order a little so you can tell that when you put the phone in landscape position the view is actually different.

…and that’s it, kind of crazy, but not at all like the example I had to use to figure it out, there was really only one example to be found on the internet and of course it did…well was supposed to do 5 other things, but they changed the code half way through the tutorial and it plain just didn’t work. But this one will go ahead and compile it and see, the only thing you will need is a .jpg to use for a album cover.

If you don’t have one I recommend www.albumartexchange.com they have just about everything.

For review the folder/file structure you need is:

blogfinal1/src/com/example/blogfinal1/FragmentArt.java
blogfinal1/src/com/example/blogfinal1/FragmentTime.java
blogfinal1/src/com/example/blogfinal1/FragmentLst.java

blogfinal1/res/layout/main.xml
blogfinal1/res/layout/time_view.xml

blogfinal1/res/layout-land/main.xml

*** If you use a different application name than mine make sure

1) That when you create your .java files/classes that they have your package name at the top
package com.example.blogfinal1;

I left it out to make copying and pasting code easier, but if you are trying to do something this advanced you should already know this.

2) The main.xml file points to the Fragment files, this is a app specific path so it needs to be changed to match your app path/name if it is different than mine.

If you run the app and it instantly crashes this is one of the things you should check first.

In my next post we will read in some data into a file so we can populate the list.

MP3 Player – Fragments / Interfaces / NSD Manager / Threads / AsyncTask

This series is going to create a MP3 player, but it will not play actual music on the device. I am also somewhat of an audiophile. I have spent way too much money to try and get the best sound I can in my car, and I wanted that all from the same device. Alas, iPhone had the best sound (IMO) but I COULD NOT STAND the screen size. I also do computer support and trying to text someone on a iPhone is the worst while your trying to fix a computer system when it is down. So after the iPhone 5 came out I was pretty sure that they were going to do what they wanted, not what I wanted and I had also tried many options for my SIII and Note 3 and none of them sounded as good as the iPhone, again in my opinion. So the only option I could see was to make an app that connected a Android phone to a iPhone so I could pick the songs on the Android phone and play them on the iPhone. I thought I’ll take a industry standard like, bluetooth and make it happen, nope, Apple couldn’t leave that alone either, they made it better by making it not work with anybody else. So after digging I found Bonjour or Network Service Discovery. Bonjour is Apple’s implementation of Network Service Discovery, I think Apple actually took the basic concept and ran with it to make it better. Bonjour is what they use to discover printers on the network so you don’t have to know IP Addresses on the network, you can just “discover” them and then pick the one you want and, voila you have a printer. So once I had a very basic app that would send a string of text from Android to iOS I knew I was set.

NSD / Bonjour:
So this app will be a mp3 player that connects to a iPhone or iPod Touch over Bonjour/NSD.

Fragments:
It will use Fragments for 3 different sections, Cover Art at the top, a middle section with a countdown timer / Song title / button to choose to search by track name or album name, and a list of songs to choose from.

Gestures:
It will also use Gesture’s on the cover art to initiate music mix, pause, and search.

Search:
I will show you how to make an app that enables search with a suggestion list, but the actual app will use search with filtering. Suggestions provide a drop down that gets in the way of the cover art, and if you try to hide them you lose your search.

Filtering:
Filtering changes the contents of the list view at the bottom allowing you to still see the album cover for the currently playing song while leaving the list for you to pick other songs later that match the same search criteria.

Menu Options:
It will contain menu options that allow you to pick other play lists.

Content Provider:
When using suggestions it uses a Content Provider to provide those suggestions from a SQLite Database. A Content Provider is a front end that allows you to share database contents with other apps, by default SQLite databases are only usable by the app that creates them. I will give you a separate example of this, that you will be able to use with this player, but I will then rip it out to use filtering which I think is way better.

Threads:
This app uses Threads, AsyncTask to be more exact, to transfer the data from Android to iOS, this allows the app to “listen” for data without causing the User Interface to time out and crash. It also reconnects automatically when connectivity is lost. There are other ways to do this, a Runnable, is actually what I used for the first version of this app, because that is what Google teaches you in the NSDChat app, but you have to come up with a way to reconnect yourself when connectivity is lost, which I did, and it’s kind of crazy looking back at what I did, but if we get bored I might show you how I did it, it does use a Runnable in a way which let’s you schedule it to run at a later time, kind of like a sleep command. You can not tell an Android app to wait for something to happen, if it takes more than 5 seconds for something to happen on the main thread (where the user interface is running) it sends an ANR (Application Not Responding) to the screen which will ask if the user wants to close your app, which looks bad.

PhoneStateListener:
It contains functionality to listen for incoming calls and if one is detected it will pause music playback.

Interface / Call Back Listener / Delegation
It uses an interface to listen for when a song has been picked in the list fragment and then the song information is passed through this interface to the MainActivity who then sends it to the iPhone / iPod Touch.

Timers:
It uses timers to display how much longer until the song complete’s. We also use it to know when it is time to pick another song when using random or continuous mode.

Long Click Listener:
It uses a long click listener to begin continuous mode.

Object Creation:
We create a Music object which contains all the song characteristics needed to display album name, song title, Uri number (the database representation of the song), album cover, and time.

File Creation:
We take a comma delimited file created by the iPhone or iPod Touch and read it into the app to create the Music Object.

Data Parsing:
The data pulled from the iPone / iPod Touch contains many special characters that need to be removed before we use them. So we make an app that parses the data and removes stuff we don’t want, mainly comma’s in song titles.

Apple Stuff:

I also show you how to create the iOS “Server app”.

And an app that pulls the song meta data of the iPhone / iPod Touch and saves it as a comma delimited file.

It’s quite a bit, but it sure is awesome when you are done, or it was before Apple FINALLY released the iPhone 6 plus (I got one) it’s alright, but as a overall device the Note 3 still rules, and I like the screen better on the Note 3.

How this is going to work

Well as for the overall layout for this blog, I pretty much can guarantee that it’s going to change. One continuous stream of posts will probably work while I’m doing this first project but as I create new projects it will get kind of hard to follow, so if you don’t like what you see now I can pretty much guarantee it will change for the better, just give me time.

So for this first project I am going to create a mp3 player. What I will do is post my code at the top then walk through the code to better explain what it is doing. I’m not going to post ALL my code at once, we will cover a certain topic and make sure that the app works with that topic and then move on. Now I have run into blogs that teach this way and I did not like them at all, you are searching for a concept that you need to learn and they show you how to do this, but it’s like the third or fourth thing in a series and you either have to go back and start at the beginning and follow through the whole series or take their code and cut out what you need and figure out how it works. Ugh. I hope I never have to do that again! But I’m sure I will. Google likes to teach a similar way, teach you 12 concepts with 1 app and it is no fun. What I will do is have the code for the series posted at the top, I will do a walk through of what the code is doing and I will also have a bare bones example that contains minimal extra code. Next post I will tell you what concepts I will cover.

Word to the Noobs

If you happen to stumble upon this blog somehow while I am setting this up, Today is Sunday, January 25th you may want to give me at least a month to get this set up, I do plan to make everything I share super easy to learn, but I am going to start out with a couple advanced topics at first to show off, then I will start back with the basics like ListView’s, Switch Statements, AlarmManager… etc…. So give me some time and I think you will be quite satisfied.

If you want to be ready for my next project which will teach lists, databases, widgets, scheduling, tts (Text to Speech) you will need a weather underground api license, it is free, and we will use it to download weather info, which is nice because it gives you dynamic data, that you can use unlike making up Social Security Numbers and people’s names and addresses….BOOORING! Here is the link.

To get a api license go here. http://www.wunderground.com/weather/api/

We will create apps like:

Say The Temp Lite
Say Today’s Forecast Lite

I have others so check for More apps from same developer.

I Am also going to add Status to the beginning of my posts that will let you know if they are ready for you to copy the code, have been checked for complete wording, under construction, etc.. See my Project Status Page for a list of statuses.