Writing a simple Android app: 3. Adding the functionality

STEM Speed Ramp

The STEM Speed Ramp

Storing the settings

Of all the data captured by the app, the only data I want to persist is the settings data. The rest of it must be entered each time by the students. I decided to use ‘internal storage’ to do this because the data is private to the application and using a database for it seems like overkill.

I decided to persist the data using a simple serialisable settings object. To get things going I just created a new class by right clicking my code namespace in the Package Explorer and created a new class called SettingsData. I added java.io.Serializable to the list of implemented interfaces and clicked Finish. I added the default serialVersionUID by hovering over the orange warning marker and selecting it from the presented options.

I then added private member variables for each of the various fields required, hovered over each of them with my mouse and used the ‘quick fixes’ box to add getters and setters (or accessors and mutators if you’re posh).

The final class is defined as follows.

package com.siemens.stem;

import java.io.Serializable;

/**
 * The serialisable settings for the ramp configurator.
 * @author Jon Masters
 *
 */
public final class SettingsData implements Serializable {

    /**
     * Version information for the class.
     */
    private static final long serialVersionUID = 1L;

    /**
     * The host name or IP address of ramp.
     */
    private String host;
    
    /**
     * The port for the ramp.
     */
    private int port;
    
    /**
     * The time to display the speed after each run.
     */
    private float speedDisplayOnTime;
    
    /**
     * The pause time between simulations.
     */
    private float simulationPauseTime;
    
    /**
     * The sped limit for the ramp.
     */
    private int speedLimitThreshold;

    /**
     * Gets the host name / ip
     * @return the host / ip
     */
    public String getHost() {
        return host;
    }

    /**
     * sets the host name / ip
     * @param host
     */
    public void setHost(String host) {
        this.host = host;
    }

    /**
     * Get the ramp port.
     * @return the tcp port for the ramp
     */
    public int getPort() {
        return port;
    }

    /**
     * Sets the ramp tcp port.
     * @param port
     */
    public void setPort(int port) {
        this.port = port;
    }

    /**
     * Gets the speed display on time.
     * @return the time the speed is displayed for.
     */
    public float getSpeedDisplayOnTime() {
        return speedDisplayOnTime;
    }

    /**
     * Sets the speed display on time.
     * @param speedDisplayOnTime
     */
    public void setSpeedDisplayOnTime(float speedDisplayOnTime) {
        this.speedDisplayOnTime = speedDisplayOnTime;
    }

    /**
     * Gets the simulation pause time.
     * @return the simulation pause time.
     */
    public float getSimulationPauseTime() {
        return simulationPauseTime;
    }

    /**
     * sets the simulation pause time.
     * @param simulationPauseTime
     */
    public void setSimulationPauseTime(float simulationPauseTime) {
        this.simulationPauseTime = simulationPauseTime;
    }

    /**
     * Gets the speed limit for the ramp.
     * @return the speed limit.
     */
    public int getSpeedLimitThreshold() {
        return speedLimitThreshold;
    }

    /**
     * sets the ramp speed limit.
     * @param speedLimitThreshold
     */
    public void setSpeedLimitThreshold(int speedLimitThreshold) {
        this.speedLimitThreshold = speedLimitThreshold;
    }
}

Once that was done I wrote some additional methods to populate the controls from the settings object, the settings object from the controls and load and save the settings object to a file private to the app. I also added a member variable to the MainActivity class and refactored it a little to make the onCreate method a little easier to read. I then updated the onCreate method to load the settings from the private file and also to register an event handler for the ‘send’ button in the settings tab that saves the settings to the private file. The resulting MainActivity class was as follows.

package com.siemens.stem;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.StreamCorruptedException;
import java.util.Arrays;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.TabHost;
import android.widget.TabHost.TabSpec;
import android.widget.EditText;

/**
 * Defines the class that implements the tab controls that are used
 * to enter the data for the ramp.
 * @author Jon Masters
 * 
 */
public class MainActivity extends Activity {

    /**
     * defines the filename for storing the settings.
     */
    static private String SETTINGS_FILE = "settings";
    
    /**
     * Holds the settings for the app.
     */
    private SettingsData settings;
    
    /**
     * Called by the framework to build the options menu.
     */
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    /**
     * Called by the framework when the activity enters the created state. This is where
     * all the initialization needs to happen.
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // set up the tab control
        buildTabs();
        
        // load the data for the settings if it is available
        loadSettingsData(); 
        
        // add the settings data to the settings tab
        updateSettingsControls();
        
        // set up the button handlers
        Button settingsSendButton = (Button)findViewById(R.id.buttonSettingsSend);
        settingsSendButton.setOnClickListener(new View.OnClickListener() {
            
            @Override
            public void onClick(View v) {
                
                // save the settings
                MainActivity.this.readSettingsControls();
                MainActivity.this.saveSettingsData();
            }
        });
    }
    
    /**
     * Updates the controls in the activity tabs to reflect the settings data.
     */
    private void updateSettingsControls() {
        
        // we want to leave the controls empty if the settings are at the 
        // default values
        if (this.settings.getHost() != null) {
            EditText host = (EditText)findViewById(R.id.editTextSettingsHost);
            host.setText(this.settings.getHost());
        }
    
        if (this.settings.getPort() != 0) {
            EditText port = (EditText)findViewById(R.id.editTextSettingsPort);
            port.setText(String.valueOf(this.settings.getPort()));
        }
        
        if (this.settings.getSpeedDisplayOnTime() != 0.0f) {
            EditText sdot = (EditText)findViewById(R.id.editTextSpeedDisplayOnTime);
            sdot.setText(String.valueOf(this.settings.getSpeedDisplayOnTime()));
        }
        
        if (this.settings.getSimulationPauseTime() != 0.0f) {
            EditText spt = (EditText)findViewById(R.id.editTextSimulationPauseTime);
            spt.setText(String.valueOf(this.settings.getSimulationPauseTime()));
        }
    
        if (this.settings.getSpeedLimitThreshold() != 0.0f) {
            EditText slt = (EditText)findViewById(R.id.editTextSpeedLimitThreshold);
            slt.setText(String.valueOf(this.settings.getSpeedLimitThreshold()));
        }
    }

    /**
     * Reads the settings from the controls into the settings object.
     */
    private void readSettingsControls() {
        
        EditText host = (EditText)findViewById(R.id.editTextSettingsHost);
        String hostText = host.getText() == null || host.getText().length() == 0 ?
                null : host.getText().toString();
        this.settings.setHost(hostText);
    
        EditText port = (EditText)findViewById(R.id.editTextSettingsPort);
        String portText = port.getText() == null || port.getText().length() == 0 ?
                "0" : port.getText().toString();
        this.settings.setPort(Integer.parseInt(portText));
        
        EditText sdot = (EditText)findViewById(R.id.editTextSpeedDisplayOnTime);
        String sdotText = sdot.getText() == null || sdot.getText().length() == 0 ?
                "0.0" : sdot.getText().toString();
        this.settings.setSpeedDisplayOnTime(Float.parseFloat(sdotText));
        
        EditText spt = (EditText)findViewById(R.id.editTextSimulationPauseTime);
        String sptText = spt.getText() == null || spt.getText().length() == 0 ?
                "0.0" : spt.getText().toString();
        this.settings.setSimulationPauseTime(Float.parseFloat(sptText));

        EditText slt = (EditText)findViewById(R.id.editTextSpeedLimitThreshold);
        String sltText = slt.getText() == null || slt.getText().length() == 0 ?
                "0.0" : slt.getText().toString();
        this.settings.setSpeedLimitThreshold(Integer.parseInt(sltText));
    }

    /**
     * Load the settings from a private file.
     */
    private void loadSettingsData() {
        
        // check to see if the settings exist
        if (Arrays.asList(fileList()).contains(SETTINGS_FILE)) {

            try {

                // yes it does, try to load it
                ObjectInputStream ois = new ObjectInputStream(
                        openFileInput(SETTINGS_FILE));

                this.settings = (SettingsData)ois.readObject();
                
            } catch (StreamCorruptedException e) {
                e.printStackTrace();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        
        // if there were any problems or the file didn't exist
        // use an new empty object
        if (this.settings == null) {
            // create an empty one to hold the data
            this.settings = new SettingsData();
        }
    }

    /**
     * Save the settings data to a private file.
     */
    private void saveSettingsData() {
    
        try {
            
            ObjectOutputStream oos = new ObjectOutputStream(openFileOutput(SETTINGS_FILE, 0));
            oos.writeObject(this.settings);
            
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }       
    }

    /**
     * Set up the tab control with the correct pages.
     */
    private void buildTabs() {
        // get a reference to the tab host
        TabHost tabHost = (TabHost)findViewById(R.id.tabhost);
        
        // set it up
        tabHost.setup();
        
        // create the easy tab
        TabSpec spec1 = tabHost.newTabSpec("Easy");
        spec1.setContent(R.id.easyTab);
        spec1.setIndicator(getString(R.string.tab_caption_easy));

        // create the hard tab
        TabSpec spec2 = tabHost.newTabSpec("Hard");
        spec2.setContent(R.id.hardTab);
        spec2.setIndicator(getString(R.string.tab_caption_hard));
        
        // create the settings tab
        TabSpec spec3 = tabHost.newTabSpec("Settings");
        spec3.setContent(R.id.settingsTab);
        spec3.setIndicator(getString(R.string.tab_caption_settings));
        
        // add them to the tab host
        tabHost.addTab(spec1);
        tabHost.addTab(spec2);
        tabHost.addTab(spec3);
    }
}

Having done all that I ran up the app to test it and everything behaved as expected. It starts up for the first time with no saved settings file and so the code creates a default, empty settings object and loads the controls appropriately i.e. with no values in the edit boxes.

If the user hits the send button, the values from the edit boxes are transferred to the settings object and this is persisted as a private file to the app. When the app is next reloaded, the previous settings are transferred to the settings controls in the form.

Transmitting Data to the ramp

For the next step, I chose to implement the core of the code that sends the data to the ramp. As I didn’t have the ramp handy I started by implementing a very simple ramp simulator. I did this by creating a new Java application and adding a class with a main() method. The class simply creates a socket listener on a port passed in on the command line and when a TCP connection is made, responds to text commands by repeating the received line and sending back the ramp prompt as defined by the interface specification.

The complete listing of the simulator is as follows.

package com.siemens.rampsim;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class Application {

    static String APP_OUT = "\nRamp Simulator\n==============\n";
    static String APP_PARAMS_OUT = "Listening on port %d\n\n";
    static String USAGE_TEXT = "USAGE:\njava com.siemens.rampsim.Application [PORT]";
    static String CONN_ACCEPTED = "Connection accepted from client %s";
    static String CLIENT_DISCONNECT = "\n\nClient %s has disconnected.";
    static String RAMP_PROMPT = "ramp>";
    
    /**
     * @param args
     */
    public static void main(String[] args) {

        System.out.println(APP_OUT);
        
        if (args.length != 1) {
            System.out.println(USAGE_TEXT);
            return;
        }
    
        int port = Integer.parseInt(args[0]);
        
        try {
            
            ServerSocket rampSocket = new ServerSocket(port);
            
            while(true) {
                
                System.out.println(String.format(APP_PARAMS_OUT, port));
                Socket sock = rampSocket.accept();
                                
                InetSocketAddress addr = (InetSocketAddress)sock.getRemoteSocketAddress();
                String remoteHost = addr.getHostName();
                
                System.out.println(String.format(CONN_ACCEPTED, remoteHost));
                BufferedReader br = new BufferedReader(new InputStreamReader(sock.getInputStream()));
                DataOutputStream dos = new DataOutputStream(sock.getOutputStream());
                
                while(true) {
                    
                    String receivedData = br.readLine();

                    // check that we still have a connection
                    if (receivedData != null) {
                        dos.writeBytes(receivedData);
                        dos.writeBytes("\n");
                        System.out.println(receivedData);
                        
                        dos.writeBytes(RAMP_PROMPT);
                        System.out.print(RAMP_PROMPT);
                    }
                    else {
                        break;
                    }
                }
                
                System.out.println(String.format(CLIENT_DISCONNECT, remoteHost));
            }
            
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

The code above provides a very simple way of testing the app and is in no way supposed to represent best practice for implementing a socket server but it does the job I needed it to do. Running up the simulator is simply a case of opening a terminal window, navigating to the application’s bin directory and executing the following command.

java com.siemens.rampsim.Application 10101

The argument 10101 tells the application to listen on that particular port.

The next thing I did was to write a new class to handle the actual transmission of the data.

package com.siemens.stem;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;

/**
 * This is a static class that is used to write the configuration data to the ramp.
 * @author Jon Masters
 *
 */
class RampWriter {

    static String HEADWAY = "HEADWAY=%d\n";
    static String DISTCONV = "DISTCONV=%d\n";
    static String TIMECONV = "TIMECONV=%d\n";
    static String CARSCALE = "CARSCALE=%d\n";
    static String GRAVITY = "GRAVITY=%s\n";
    static String DROPTIME = "DROPTIME=%d\n";
    static String CARACCEL = "CARACCEL=%s\n";
    static String SPEEDDISPLAY = "SPEEDDISPLAY=%s\n";
    static String SPEEDLIMITTHRESH = "SPEEDLIMITTHRESH=%d\n";
    static String SIMPAUSE = "SIMPAUSE=%s\n";
    
    protected RampWriter(){}
    
    /**
     * Writes the settings from the easy tab to the ramp
     * @param host - the ramp ip address or host name
     * @param port - the port the ramp is listening on
     * @param distanceBetweenLoops - the distance between the loops used for speed calculation
     * @param distanceConversionFactor - the distance conversion factor
     * @param timeConversionFactor - the time conversion factor
     * @param carScale - the scale of the model car
     * @param ballDropTime - the time it takes for the ball to drop
     * @throws IOException
     * @throws IllegalArgumentException
     */
    static void writeEasySettings(InetAddress host, int port, int distanceBetweenLoops, 
            int distanceConversionFactor, int timeConversionFactor, int carScale, int ballDropTime) 
            throws IOException, IllegalArgumentException {
        
        Socket client = null;
        PrintWriter out = null;
        BufferedReader in = null;
        
        try {
            
            // open a connection to the ramp
            client = new Socket(host, port);
            out = new PrintWriter(client.getOutputStream());
            in = new BufferedReader(new InputStreamReader(client.getInputStream()));
            
            // start by writing a line feed which should result in the test 'ramp>' back
            // from the ramp
            out.write("\n");    
            out.flush();
            readFromRamp(in);
            
            // write the settings out to the ramp
            out.write(String.format(HEADWAY, distanceBetweenLoops));
            out.flush();
            readFromRamp(in);
            
            out.write(String.format(DISTCONV, distanceConversionFactor));
            out.flush();
            readFromRamp(in);
            
            out.write(String.format(TIMECONV, timeConversionFactor));
            out.flush();
            readFromRamp(in);
            
            out.write(String.format(CARSCALE, carScale));
            out.flush();
            readFromRamp(in);
            
            out.write(String.format(DROPTIME, ballDropTime));
            out.flush();
            readFromRamp(in);        
        }
        finally {
            // make sure we close anything that needs to be closed
            if (in != null) {
                in.close();
            }
            
            if (out != null) {
                out.close();
            }
            
            if (client != null && client.isConnected()) {
                client.close();
            }
        }
    }

    /**
     * Writes the settings for the hard tab to the ramp.
     * @param host - the ramp IP or host name
     * @param port - the port the ramp is listening on
     * @param distanceBetweenLoops - the distance between the loops
     * @param carScale - the scale of the vehicle
     * @param accelerationOfGravity - the gravitational constant
     * @param ballDropTime - the time taken for the ball to drop
     * @param carAcceleration - the acceleration of the car
     * @throws IOException
     * @throws IllegalArgumentException
     */
    static void writeHardSettings(InetAddress host, int port, int distanceBetweenLoops, 
            int carScale, float accelerationOfGravity, int ballDropTime, float carAcceleration) 
            throws IOException, IllegalArgumentException {
        
        Socket client = null;
        PrintWriter out = null;
        BufferedReader in = null;
        
        try {
            
            // open a connection to the ramp
            client = new Socket(host, port);
            out = new PrintWriter(client.getOutputStream());
            in = new BufferedReader(new InputStreamReader(client.getInputStream()));
            
            // start by writing a line feed which should result in the test 'ramp>' back
            // from the ramp
            out.write("\n");    
            out.flush();
            readFromRamp(in);
            
            // write the settings out to the ramp
            out.write(String.format(HEADWAY, distanceBetweenLoops));
            out.flush();
            readFromRamp(in);
            
            out.write(String.format(CARSCALE, carScale));
            out.flush();
            readFromRamp(in);
            
            out.write(String.format(GRAVITY, accelerationOfGravity));
            out.flush();
            readFromRamp(in);
                        
            out.write(String.format(DROPTIME, ballDropTime));
            out.flush();
            readFromRamp(in);        
            
            out.write(String.format(CARACCEL, carAcceleration));
            out.flush();
            readFromRamp(in);

        }
        finally {
            // make sure we close anything that needs to be closed
            if (in != null) {
                in.close();
            }
            
            if (out != null) {
                out.close();
            }
            
            if (client != null && client.isConnected()) {
                client.close();
            }
        }
    }
    
    /**
     * Writes the settings for the ramp to the ramp.
     * @param host - the ramp IP or host name
     * @param port - the port the ramp is listening on
     * @param speedDisplayTime - the length of time the speed should be displayed
     * @param simulationPauseTime - the gap between simulated runs in sim mode.
     * @param speedLimitThreshold - the speed limit for the ramp
     * @throws IOException
     * @throws IllegalArgumentException
     */
    static void writeRampSettings(InetAddress host, int port, float speedDisplayTime, 
            float simulationPauseTime, int speedLimitThreshold) 
            throws IOException, IllegalArgumentException {
        
        Socket client = null;
        PrintWriter out = null;
        BufferedReader in = null;
        
        try {
            
            // open a connection to the ramp
            client = new Socket(host, port);
            out = new PrintWriter(client.getOutputStream());
            in = new BufferedReader(new InputStreamReader(client.getInputStream()));
            
            // start by writing a line feed which should result in the test 'ramp>' back
            // from the ramp
            out.write("\n");    
            out.flush();
            readFromRamp(in);
            
            // write the settings out to the ramp
            out.write(String.format(SPEEDDISPLAY, speedDisplayTime));
            out.flush();
            readFromRamp(in);
            
            out.write(String.format(SPEEDLIMITTHRESH, speedLimitThreshold));
            out.flush();
            readFromRamp(in);
            
            out.write(String.format(SIMPAUSE, simulationPauseTime));
            out.flush();
            readFromRamp(in);                       
        }
        finally {
            // make sure we close anything that needs to be closed
            if (in != null) {
                in.close();
            }
            
            if (out != null) {
                out.close();
            }
            
            if (client != null && client.isConnected()) {
                client.close();
            }
        }
    }   
    
    /**
     * Reads the response to a command from the ramp.
     * @param in - the buffered read around the socket connection inpu stream
     * @throws IOException
     * @throws IllegalArgumentException
     */
    private static void readFromRamp(BufferedReader in) throws IOException,
            IllegalArgumentException {
        
        // keep reading from the socket until we get the prompt
        StringBuffer sb = new StringBuffer();
        int nextChar = 0;
        while(nextChar != -1) {
            nextChar = in.read();
            char c = (char)nextChar;
            sb.append(c);
            if (sb.toString().endsWith("ramp>")) {
                break;
            }
        }
        
        // if we didn't end with ramp> then we're not talking to a ramp
        if (!sb.toString().endsWith("ramp>")) {
            throw new IllegalArgumentException(
                "The ramp did not respond as expected. Check that the host and port settings are correct.");
        }
    }
}

I then updated the setting button handler to send the data to the new class.

settingsSendButton.setOnClickListener(new View.OnClickListener() {
			
	@Override
	public void onClick(View v) {
				
		// save the settings
		MainActivity.this.readSettingsControls();
		MainActivity.this.saveSettingsData();
				
		// get a reference to the settings
		SettingsData rampSettings = MainActivity.this.settings;
				
		// send the config data to the ramp
		try {
			
			RampWriter.writeRampSettings(
				InetAddress.getByName(rampSettings.getHost()), 
				rampSettings.getPort(), 
				rampSettings.getSpeedDisplayOnTime(), 
				rampSettings.getSimulationPauseTime(), 
				rampSettings.getSpeedLimitThreshold());
					
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (UnknownHostException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
});

Having done this, I then added another data class, this one not persisted, that holds data retrieved from the controls when the ‘send’ buttons are pressed on either the ‘easy’ or ‘hard’ tabs. Rather than create a different class for each I created a single class to capture this data. The main reason for doing it this way was that several of the settings are used by both tabs and it just seemed wrong to be holding that data twice. I therefore added a new class called WorksheetData and added in the required fields, getters and setters (it’s at times like these that you really miss languages that allow you to specify members, getters and setters in a single line!)

package com.siemens.stem;

/**
 * This is a model class that holds the worksheet data from the
 * easy and hard tabs.
 * @author Jon Masters
 *
 */
class WorksheetData {

    private int distanceBetweenLoops; 
    
    private int distanceConversionFactor;
    
    private int timeConversionFactor; 
    
    private int carScale;
    
    private int ballDropTime;
    
    private float accelerationOfGravity;
    
    private float carAcceleration;
    
    public int getDistanceBetweenLoops() {
        return distanceBetweenLoops;
    }
    
    public void setDistanceBetweenLoops(int distanceBetweenLoops) {
        this.distanceBetweenLoops = distanceBetweenLoops;
    }

    public int getDistanceConversionFactor() {
        return distanceConversionFactor;
    }

    public void setDistanceConversionFactor(int distanceConversionFactor) {
        this.distanceConversionFactor = distanceConversionFactor;
    }

    public int getTimeConversionFactor() {
        return timeConversionFactor;
    }

    public void setTimeConversionFactor(int timeConversionFactor) {
        this.timeConversionFactor = timeConversionFactor;
    }

    public int getCarScale() {
        return carScale;
    }

    public void setCarScale(int carScale) {
        this.carScale = carScale;
    }

    public int getBallDropTime() {
        return ballDropTime;
    }

    public void setBallDropTime(int ballDropTime) {
        this.ballDropTime = ballDropTime;
    }

    public float getAccelerationOfGravity() {
        return accelerationOfGravity;
    }

    public void setAccelerationOfGravity(float accelerationOfGravity) {
        this.accelerationOfGravity = accelerationOfGravity;
    }

    public float getCarAcceleration() {
        return carAcceleration;
    }

    public void setCarAcceleration(float carAcceleration) {
        this.carAcceleration = carAcceleration;
    }
}

There’s absolutely nothing special about the WorksheetData class, it really is just a holder for data. The next thing I did was to add a WorksheetData member to the MainActivity class and also new methods to populate the WorksheetData class from the various controls. I implemented an ‘Easy’ tab version and a ‘Hard’ tab version.

Inside the onCreate method I added the following additional code to handle the button clicks on the easy and hard tabs.

Button easySendButton = (Button)findViewById(R.id.buttonEasySend);
easySendButton.setOnClickListener(new View.OnClickListener() {
    
    @Override
    public void onClick(View v) {
        
        // read the data from the controls into the data object
        MainActivity.this.readEasyWorksheetDataControls();
        
        // get an easy ref to the data
        WorksheetData wsd = MainActivity.this.worksheetData;
        SettingsData sd = MainActivity.this.settings;
        
        try {
            
            RampWriter.writeEasySettings(
                    InetAddress.getByName(sd.getHost()), 
                    sd.getPort(), 
                    wsd.getDistanceBetweenLoops(), 
                    wsd.getDistanceConversionFactor(), 
                    wsd.getTimeConversionFactor(), 
                    wsd.getCarScale(), 
                    wsd.getBallDropTime());
            
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (UnknownHostException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
});

Button hardSendButton = (Button)findViewById(R.id.buttonHardSend);
hardSendButton.setOnClickListener(new View.OnClickListener() {
    
    @Override
    public void onClick(View v) {
        
        // read the data from the controls into the data object
        MainActivity.this.readHardWorksheetDataControls();
        
        // get an easy ref to the data
        WorksheetData wsd = MainActivity.this.worksheetData;
        SettingsData sd = MainActivity.this.settings;
        
        try {
            
            RampWriter.writeHardSettings(
                    InetAddress.getByName(sd.getHost()), 
                    sd.getPort(), 
                    wsd.getDistanceBetweenLoops(), 
                    wsd.getCarScale(), 
                    wsd.getAccelerationOfGravity(), 
                    wsd.getBallDropTime(), 
                    wsd.getCarAcceleration());
            
        } catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (UnknownHostException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
});

I then added the two missing methods to the MainActivity class.

/**
 * Reads the worksheet data from the controls into the data object.
 */
private void readEasyWorksheetDataControls() {
    
    // make sure the worksheet data member has been created
    if (this.worksheetData == null) {
        this.worksheetData = new WorksheetData();
    }
    
    EditText loopDistCtrl = (EditText)findViewById(R.id.editTextEasyDistanceBetweenLoops);
    String loopDistText = loopDistCtrl.getText() == null || loopDistCtrl.getText().length() == 0 ?
            "0" : loopDistCtrl.getText().toString();
    this.worksheetData.setDistanceBetweenLoops(Integer.parseInt(loopDistText));
    
    EditText distConvCtrl = (EditText)findViewById(R.id.editTextEasyDistanceConversionFactor);
    String distConvText = distConvCtrl.getText() == null || distConvCtrl.getText().length() == 0 ?
            "0" : distConvCtrl.getText().toString();
    this.worksheetData.setDistanceConversionFactor(Integer.parseInt(distConvText));
        
    EditText timeConvCtrl = (EditText)findViewById(R.id.editTextEasyTimeConversion);
    String timeConvText = timeConvCtrl.getText() == null || timeConvCtrl.getText().length() == 0 ?
            "0" : timeConvCtrl.getText().toString();
    this.worksheetData.setTimeConversionFactor(Integer.parseInt(timeConvText));
        
    EditText carScaleCtrl = (EditText)findViewById(R.id.editTextEasyCarScale);
    String carScaleText = carScaleCtrl.getText() == null || carScaleCtrl.getText().length() == 0 ?
            "0" : carScaleCtrl.getText().toString();
    this.worksheetData.setCarScale(Integer.parseInt(carScaleText));

    EditText ballDropCtrl = (EditText)findViewById(R.id.editTextEasyBalldropTime);
    String ballDropText = ballDropCtrl.getText() == null || ballDropCtrl.getText().length() == 0 ?
            "0.0" : ballDropCtrl.getText().toString();
    this.worksheetData.setBallDropTime(Integer.parseInt(ballDropText));
}
    
/**
 * Reads the worksheet data from the controls into the data object.
 */
private void readHardWorksheetDataControls() {
        
    // make sure the worksheet data member has been created
    if (this.worksheetData == null) {
        this.worksheetData = new WorksheetData();
    }
        
    EditText loopDistCtrl = (EditText)findViewById(R.id.editTextHardDistanceBetweenLoops);
    String loopDistText = loopDistCtrl.getText() == null || loopDistCtrl.getText().length() == 0 ?
            "0" : loopDistCtrl.getText().toString();
    this.worksheetData.setDistanceBetweenLoops(Integer.parseInt(loopDistText));

    EditText carScaleCtrl = (EditText)findViewById(R.id.editTextHardCarScale);
    String carScaleText = carScaleCtrl.getText() == null || carScaleCtrl.getText().length() == 0 ?
            "0" : carScaleCtrl.getText().toString();
    this.worksheetData.setCarScale(Integer.parseInt(carScaleText));

    EditText accGravityCtrl = (EditText)findViewById(R.id.editTextHardAccelerationGravity);
    String accGravityText = accGravityCtrl.getText() == null || accGravityCtrl.getText().length() == 0 ?
            "0.0" : accGravityCtrl.getText().toString();
    this.worksheetData.setAccelerationOfGravity(Float.parseFloat(accGravityText));
        
    EditText ballDropCtrl = (EditText)findViewById(R.id.editTextHardBallDropTime);
    String ballDropText = ballDropCtrl.getText() == null || ballDropCtrl.getText().length() == 0 ?
            "0" : ballDropCtrl.getText().toString();
    this.worksheetData.setBallDropTime(Integer.parseInt(ballDropText));

    EditText carAccelCtrl = (EditText)findViewById(R.id.editTextHardCarAcceleration);
    String carAccelText = carAccelCtrl.getText() == null || carAccelCtrl.getText().length() == 0 ?
            "0.0" : carAccelCtrl.getText().toString();
    this.worksheetData.setCarAcceleration(Float.parseFloat(carAccelText));
}

A quick test with the ramp simulator showed that this was working however there were quite a few areas where there could be errors. For example, what happens if the settings haven’t been entered before the student tries to send the worksheet data? What happens if the host or port is entered but entered incorrectly?

A bit of experimentation showed that when the host information has been incorrectly entered showed that it would throw an IOException. When the port is incorrectly entered, an IllegalArgumentException is thrown. The simplest way to get this information to user seemed to be to use the Android Toast mechanism to display an error message so I updated the catch handlers in the button onClick handlers to display a fixed error message defined in the strings.xml and formatted to include the exception message. The code added to each of the catch blocks was as follows.

    MainActivity.this.displayError(
        String.format(MainActivity.this.getString(
            R.string.ramp_connection_error), e.getMessage()));

Testing this with deliberately wrong host or port information results in an error message being displayed as show below.

Connection error display

Connection error display

Now that I had this functionality complete, I decided to get the styling and splash screen in place.

In the next article I’ll discuss how I styled the screens and added the splash screen. In the meantime if you’d like to see the finished code, I’ve uploaded it to GitHub here: https://github.com/jpmasters/stem-ramp-configurator.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: