Chapter 3: Java - Part 24
Okay, it's been a while since the last time we stopped to compared code with each other. Here is what I have for MainActivity. You'll notice copious comments, as well as some very minor reordering and restructuring here and there to make the code more readable.

package glaukon.tutorials.tipcalc;

// Import statements let us use code that's not in our project's package.

import java.util.ArrayList;


import android.os.Bundle;

import android.view.Menu;

import android.view.View;

import android.view.View.OnClickListener;

import android.view.View.OnFocusChangeListener;

import android.widget.EditText;

import android.widget.ImageButton;

import android.widget.SeekBar;

import android.widget.SeekBar.OnSeekBarChangeListener;

import android.widget.TableLayout;

import android.widget.TableRow;

import android.widget.TextView;

// MainActivity inherits from the Activity class, but also from the OnClickListener, OnFocusChangeListener, and OnSeekBarChangeListener interfaces.

public class MainActivity extends Activity implements OnClickListener, OnFocusChangeListener, OnSeekBarChangeListener {

// Variable declarations go here.

// totalTip is calculated as tipPercentValue * grandTotal.

private double totalTip;

private double tipPercentValue;

// tax is an absolute number, and taxPercentValue is calculated as tax / grandTotal.

private double tax;

private double taxPercentValue;

// grandTotal does not include tip or tax, but is simply the sum of all the Diners' totals.

private double grandTotal;

// Next, we have the UI elements we made in the layout XML file.

private TableLayout mainTable;

private ImageButton addDinerButton;

// firstCustomer, amount1of1, addButton1, textSplit1, and textSplit1Dollar are all attributes of the first Diner.

private EditText firstCustomer;

private EditText amount1of1;

private ImageButton addButton1;

private TextView textSplit1;

private TextView textSplit1Dollar;

private EditText tipPercent;

private TextView tipDollar;

private SeekBar tipSlider;

private EditText taxDollar;

private TextView textGTotal;

// dinerList will be used to organize all Diners in one outer-object.

private ArrayList<Diner> dinerList;

// Replace the onCreate method we inherited from Activity with our own code using @Override.


protected void onCreate(Bundle savedInstanceState) {

// First, run Activity's version of onCreate.


// Inflate the UI using activity_mail.xml in the layout folder.


// Set all doubles to 0, except tipPercent, which defaults to 15%.

// Note that we use 0.0 instead of 0. Doesn't really matter, just reminds us they're doubles, not ints.

totalTip = 0.0;

tipPercentValue = 0.15;

tax = 0.0;

taxPercentValue = 0.0;

grandTotal = 0.0;

// Retrieve all the Views we need from our UI using findViewById.

// Then assign them to variables we declared earlier.

mainTable = (TableLayout) findViewById(;

addDinerButton = (ImageButton) findViewById(;

firstCustomer = (EditText) findViewById(;

amount1of1 = (EditText) findViewById(;

addButton1 = (ImageButton) findViewById(;

textSplit1 = (TextView) findViewById(;

textSplit1Dollar = (TextView) findViewById(;

tipDollar = (TextView) findViewById(;

tipPercent = (EditText) findViewById(;

tipSlider = (SeekBar) findViewById(;

taxDollar = (EditText) findViewById(;

textGTotal = (TextView) findViewById(;

// Make a new ArrayList called dinerList, then make the first Diner using the Views from our UI.

// Finally, add that Diner to dinerList.

dinerList = new ArrayList<Diner>();

Diner diner = new Diner(firstCustomer, amount1of1, addButton1, textSplit1, textSplit1Dollar);


// Set up listeners.









// Replace the onCreateOptiosnMenu method we inherited from Activity with our own code using @Override.


public boolean onCreateOptionsMenu(Menu menu) {

// Inflate the options menu using activity_main.xml in the menu folder.

getMenuInflater().inflate(, menu);

return true;


// Implement the onClick method of OnClickListener.


public void onClick(View v) {

// If it's addDinerButton that was clicked, do this.

if (v == addDinerButton) {

// Create a 2 TableRows, 2 EditTexts, 1 ImageButton, and 2 TextViews.

// Set their attributes based on inflated Views from the UI.

TableRow row1 = new TableRow(this);

EditText et1 = new EditText(this);








EditText et2 = new EditText(this);








// Make sure the order EditText gets selected automatically.


ImageButton ib = new ImageButton(this);


// Remember to set the listener.


// Add Views to rows, then add rows to mainTable.

// Use a for loop to get the right row numbers to insert rows at.

// Make sure to account for every order of every Diner.




int rowIndex = 1;

for (int i = 0; i < dinerList.size(); i++) {

rowIndex += dinerList.get(i).orderList.size();


mainTable.addView(row1, rowIndex);

TableRow row2 = new TableRow(this);

TextView tv1 = new TextView(this);



TextView tv2 = new TextView(this);





// Add 11 static rows to rowIndex, as well as one row for each Diner in the bill splitting portion of app.

mainTable.addView(row2, rowIndex + 11 + dinerList.size());

// Make a new Diner and add it to dinerList.

Diner diner = new Diner(et1, et2, ib, tv1, tv2);


// If it's not addDinerButton, do this.

} else {

// Use a for loop to run though all of dinerList.

// If any one of their ibAddOrder ImageButtons were clicked, do this.

for (int i = 0; i < dinerList.size(); i++) {

if (v == dinerList.get(i).ibAddOrder) {

// Create a row, then 2 EditTexts.

// The first is a dummy one used to push the second one into the second column.

// Again, grab attributes from Views inflated by XML.

TableRow row3 = new TableRow(this);

EditText emptyEditText = new EditText(this);


EditText newOrder = new EditText(this);











// For loop again to get the right row number to insert the row at.

int rowIndex2 = 1;

for (int j = 0; j <= i; j++) {

rowIndex2 += dinerList.get(j).orderList.size();


mainTable.addView(row3, rowIndex2);

// Add the order to the correct Diner's orderList.






// Implement the onFocusChange method of OnFocusChangeListener.


public void onFocusChange(View v, boolean hasFocus) {

// If tipPercent lost focus, do this.

if (v == tipPercent && hasFocus == false) {

// Get the tip from tipPercent, making sure to adjust it so that Double.parseDouble can handle it.

tipPercentValue = Double.parseDouble(((EditText) v).getText().toString().replace("%", ""));

// If the user set the tip higher than 50, bring it back down to 50.

if (tipPercentValue > 50) {

tipPercentValue = 50;


// Use setProgress to trigger the code in onProgressChanged.

// Note we have to cast to int because tipPercentValue is a double.

tipSlider.setProgress((int) tipPercentValue);

// If the user didn't change the tip, then setProgress won't actually move tipSlider.

// Thus, onProgressChange won't get triggered, and tipPercent won't get divided by 100.

// To make sure tipPercent is a percentage, and not a whole number, we check if it's greater than 0.5.

// If it is, we need to change it back to a percentage here, since onProgressChanged was never triggered.

if (tipPercentValue > .5) {

tipPercentValue = tipPercentValue / 100;


// We don't need to do anything else in this block, since onProgressChanged should get triggered.

// The rest of the stuff that needs to happen will happen in onProgressChanged.

// And if it was not triggered, then that means nothing changed and nothing needs to happen anyway.

// If tipPercent didn't trigger onFocusChange, check if taxDollar lost focus. If so, do this.

} else if (v == taxDollar && hasFocus == false) {

// Set tax to what's inside taxDollar. Then set taxDollar to tax.

// We need to set taxDollar to back to tax for the sake of formatting.

tax = Double.parseDouble(((EditText) v).getText().toString().replace("$", "").replace(",", ""));

((EditText) v).setText("$" + String.format("%,.2f", tax));

// Then we run calcGrandTotal to update everything.


// Finally, if it wasn't tipPercent or taxDollar, do this stuff.

} else {

// Use a for loop to run though all of dinerList.

for (int i = 0; i < dinerList.size(); i++) {

// If any one of their etName EditTexts lost focus, do this.

if (v == dinerList.get(i).etName && hasFocus == false) {

// Set the tvName TextView using the Diner's setName method.


} else {

// Otherwise, run through the orderList for each Diner.

for (int j = 0; j < dinerList.get(i).orderList.size(); j++) {

// If any EditTexts in the orderList lost focus, do this.

if (v == dinerList.get(i).orderList.get(j) && hasFocus == false) {

// Run the updateTotal method to calculate the Diner's new total.

// Then use calcGrandTotal to update everthing that needs updating.

dinerList.get(i).updateTotal((EditText) v);








// Implement the onProgressChanged method of OnSeekBarChangeListener.


public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {

// Check to make sure the correct seekBar was changed (technically not necessary since we only have one).

if (seekBar == tipSlider) {

// Set tipPercentValue to the progress of tipSlider, then set the tipPercent EditText accordingly.

// Make sure tipPercentValue goes back to being a percent value, not a whole number, at the end.

tipPercentValue = progress;

tipPercent.setText(String.format("%.0f", tipPercentValue) + "%");

tipPercentValue = tipPercentValue / 100;

// Finally, update everything else with calcGrandTotal.





public void onStartTrackingTouch(SeekBar seekBar) {

// Not needed.



public void onStopTrackingTouch(SeekBar seekBar) {

// Not needed.


// This is the final method that gets run every time something changes.

// It does all the major updating that the user sees.

public void calcGrandTotal() {

// Before calculating grandTotal, remember to reset it back to 0 first.

grandTotal = 0.0;

// Go through dinerList and add each Diner's total to grandTotal.

for (int i = 0; i < dinerList.size(); i++) {

grandTotal += dinerList.get(i).total;


// Calculate totalTip, then set tipDollar accordingly.

totalTip = grandTotal * tipPercentValue;

tipDollar.setText("$" + String.format("%,.2f", totalTip));

// Then calculate taxPercentValue.

taxPercentValue = tax / grandTotal;

// Use tipPercentValue and taxPercentValue to call every Diner's setTotalText method.

for (int i = 0; i < dinerList.size(); i++) {

dinerList.get(i).setTotalText(tipPercentValue, taxPercentValue);


// To finish, set textGTotal to grandTotal + totalTip + tax.

textGTotal.setText("$" + String.format("%,.2f", grandTotal + totalTip + tax));



And here's my Diner class:

package glaukon.tutorials.tipcalc;

// Import statements let us use code that's not in our project's package.

import java.util.ArrayList;

import android.widget.EditText;

import android.widget.ImageButton;

import android.widget.TextView;

public class Diner {

// Declare the variables we'll need.

// total does not include tip or tax.

public double total;

public ArrayList<EditText> orderList;

public EditText etName;

public EditText etFirstOrder;

public ImageButton ibAddOrder;

public TextView tvName;

public TextView tvSplitBill;

// This is the constructor. It runs when you instantiate a Diner.

// It brings together all the Views that are associated with a new Diner object.

public Diner(EditText et1, EditText et2, ImageButton ib, TextView tv1, TextView tv2) {

// total starts off as 0, and is used to keep track of the sum of a Diner's orders.

total = 0.0;

// We also need an ArrayList to keep track of each Diner's orders.

orderList = new ArrayList<EditText>();

etName = et1;

etFirstOrder = et2;

ibAddOrder = ib;

tvName = tv1;

tvSplitBill = tv2;

// Make sure to add the first order to orderList.



// This method sets the Text property of tvName to whatever the user changed the Text property of etName to.

public void setName() {

// We need to use toString because getText does not return a string.



// This method adds a new order to a Diner's orderList.

public void newOrder(EditText newOrder) {



// This method updates the Diner's total based its orderList.

// It is run when the user changes focus from an EditText in orderList.

public void updateTotal(EditText toBeUpdated) {

// First, reset total

total = 0.0;

// Then, format the EditText that was just changed

toBeUpdated.setText("$" + String.format("%,.2f", editTextToDouble(toBeUpdated)));

// Finally, go through each order and add it to total. Use editTextToDouble to help.

for (int i = 0; i < orderList.size(); i++) {

total += editTextToDouble(orderList.get(i));



// We used to have an overloaded updateTotal method here, but we no longer need it.

// public void updateTotal() {

// updateTotal(etFirstOrder);

// }

// This method converts what's inside an EditText to a double, and returns it.

public double editTextToDouble(EditText et) {

double db = 0.0;

db = Double.parseDouble(et.getText().toString().replace("$", "").replace(",", ""));

return db;


// This method uses the tip and tax MainActivity gets it to set the proper value for tvSplitBill.

public void setTotalText(double tip, double tax) {

tvSplitBill.setText("$" + String.format("%,.2f", total * (1 + tip) + total * tax));