Android 7: Assignment 2, Part 1, One Button Recursion and Debugging
Introduction This set of overheads begins with a simple app where the sendMessage() method is small, but contains some significant code It is a lead-in to debugging As soon as code gets more complicated you have to be able to debug Android Studio has built-in debugging features, but sometimes the simple ways are the best…
7.1 The OneButtonRecursion app 7.2 Unhelpful Error Messages 7.3 Logging Output
7.1 The OneButtonRecursion app
Screen shots for the one button recursion app are shown on the following overhead Functionally all that happens when you click the button is counting down to 0 recursively It happens so fast there’s nothing visible on the screen except the result
The OneButtonRecursion app will be presented in the order in which it was developed: 1. activity_main.xml, the layout 2. strings.xml, the resources 3. A look at R.java, the resources as made available by the system 4. MainActivity.java, the code for the app
As you might guess, part of what makes the app interesting is the fact that it includes recursion It does something so simple that the recursion, although not necessary, is easy to understand Recursion is covered because it will be used in the implementation of Wari in the assignment The recursion also introduces the possibility of runtime errors
This example also has some things in common with the earlier assignments It includes syntax for converting between strings and numbers, which can also lead to errors if not done correctly These errors are runtime errors, so they lead to the topic of debugging in Android
activity_main.xml for OneButtonRecursion The app uses a RelativeLayout It contains a button and a text view The XML code for the layout is given on the following overheads for reference
<RelativeLayout xmlns:android="http://schemas. android xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context=".MainActivity" >
<Button android:id="@+id/button1" style="?android:attr/buttonStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:text="@string/button1Contents" android:onClick="sendMessage" />
<TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignBaseline="@+id/button1" android:layout_alignBottom="@+id/button1" android:layout_toRightOf="@+id/button1" android:text="@string/textView1Contents" /> </RelativeLayout>
There were some items in the XML that haven’t come up before Where did they come from? I used the graphical tools to drag and drop components into the layout The system generated the corresponding XML If necessary, you can then edit the system generated XML to tailor the layout to the specific app you’re trying to create
strings.xml for OneButtonRecursion The strings.xml file for the app is shown on the following overhead There are no surprises The button and the text view have string resources associated with them
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">One Button Recursion</string> <string name="action_settings">Settings</string> <string name="hello_world">Hello world!</string> <string name="button1Contents">Button1</string> <string name="textView1Contents">4</string> </resources>
R.Java for OneButtonRecursion As you recall, the R.java file is generated for you It’s important because it’s the place where you can access resources when writing the Java code for MainActivity The relevant parts of R.java are shown on the following overhead
public static final class id { … public static final int button1=0x7f080000; public static final int textView1=0x7f080001; } public static final class string { public static final int button1Contents=0x7f050003; public static final int textView1Contents=0x7f050004;
MainActivity.java for OneButtonRecursion, Discussion The logic of OneButtonRecursion is quite simple The TextView is initially set to a value of 4 When you click the button, you call sendMessage()
sendMessage() checks to see what the current value is If the value is >0, then a call is made to a method named playNextCup() and the value is passed in
playNextCup() decrements the value It updates the text area to this new value If the value has not yet reached 0, it calls itself recursively The end result of this sequence of actions is that the value in the text view goes 4, 3, 2, 1, 0—too fast for the eye to see One click of the button causes the text view to go from 4 to 0, recursively
The logic is simple The code is considerably more complex than it would need to be to accomplish this simple task It repeatedly gets a handle on the text view and passes this around—which is needless, considering there is only one text area It also includes if statements to check “which button was clicked” when there is only one button
The code is written in this way because it is a preview of what will have to be done to implement Wari It is clumsy here, but it’s easier to first get an idea of what’s involved in a program that does something simple with only one text view and one button
In Wari, an app with >1 text view and button, it becomes apparent that it is necessary to repeatedly get references and pass them around and check which button was clicked
MainActivity.java for OneButtonRecursion, Code At the top of the main activity the Log class is imported and the TAG string definition is given These will be useful for logging and debugging
import android.util.Log; public class MainActivity extends Activity { private static final String TAG = "OneButtonRecursion"; …
sendMessage() for OneButtonRecursion The code for sendMessage() is shown on the following overhead Note that the calls to findViewById() and playNextCup() are floating in space They are calls on the MainActivity itself
public void sendMessage(View view) { Button clickedButton = (Button) view; if(clickedButton == findViewById(R.id.button1)) { TextView cup = (TextView) findViewById(R.id.textView1); int handFull = Integer.parseInt(cup.getText().toString()); if(handFull > 0) cup.setText(handFull + ""); playNextCup((TextView) findViewById(R.id.textView1), handFull); } else
playNextCup() for OneButtonRecursion playNextCup() is the recursive method It contains the uses of Log and TAG (because when you’re doing the recursion it’s a likely place to make a mistake…) Using them for debugging will be discussed shortly
public static void playNextCup(TextView cupIn, int handFullIn) { handFullIn--; cupIn.setText(handFullIn + ""); if(handFullIn != 0) playNextCup(cupIn, handFullIn); Log.i(TAG, "In recursion " + handFullIn); } else
7.2 Unhelpful Error Messages
When writing the code even for this simple app, I made mistakes (go figure) It quickly became apparent that I would need to be able to debug in the Android environment I’m not referring now to the debugging tools in Android Studio—they’re beyond the scope of this course I’m referring to simply figuring out what was going on
Compiler errors are shown in the bottom window of the development environment The screen shot on the following overhead illustrates: How a bad line of code is highlighted in the editor And how the error messages are shown at the bottom
I also got two good (bad) examples with OneButtonRecursion of what can go more seriously wrong and the quandary you’re left in First of all, notice that if you right click on the mouse with the pointer near the left hand margin of the editor, you have the option of showing line numbers in the source This can be very helpful It’s illustrated on the following overheads
As usual, like with many systems, the error messages might not be very helpful Being able to look the error message up on the Web can very helpful in trying to figure out what it means Do not be afraid to do this I find that stackoverflow.com is the likeliest place to find correct answers (after potentially sorting through things that don’t apply)
Example 1 Here is a snippet of code that’s in error: int handFull = Integer.parseInt(cup.getText().toString()); … cup.setText(handFull); The next overhead shows what happens when you try to run it This is not very helpful
This is the line of code that’s the problem It assumes that handFull gets converted to a String automatically cup.setText(handFull); This is not the case It is a potentially confusing error because there is a version of setText() which accepts an integer parameter, where the integer is the id for a resource
The value contained in handFull doesn’t agree with any defined resource id value, so the system has a runtime problem (Imagine the fun you’d have if, by accident, handFull did contain a valid resource id, and you got inexplicable results instead of a runtime error)
In any case, once you know what’s wrong, the solution is simple This is the corrected code: int handFull = Integer.parseInt(cup.getText().toString()); … cup.setText(handFull + “”);
Identifying the line of that contains an error can be accomplished by inserting print statements into code This will be illustrated after considering another error
Example 2 The same two lines of code contain a call that can also generate another unhelpful runtime error int handFull = Integer.parseInt(cup.getText().toString()); … cup.setText(handFull + “”); Consider the call to parseInt() parseInt() will fail if you apply it to a string that can’t be parsed as an integer
If you write the code as shown and the text view you’re getting the string from doesn’t contain an integer You will are likely to get error messages at build time Whether you can sort them out or not, you will definitely get the “Unfortunately, OneButtonRecursion has stopped …OK…” message at run time
7.3 Logging Output
Remember that the playNext() method contained calls to the static method i in the Log class which causes the code to generate runtime output This is shown again on the following overhead
public static void playNextCup(TextView cupIn, int handFullIn) { handFullIn--; cupIn.setText(handFullIn + ""); if(handFullIn != 0) { playNextCup(cupIn, handFullIn); Log.i(TAG, "In recursion " + handFullIn); } else { } }
The screen shot on the following overhead shows the logcat screen open at the bottom of the development environment when OneButtonRecursion has been run The highlighted lines are the output of the program, showing the succession of values generated by the recursion and put out by the call to Log.i()
In order to do simple debugging, you need to be able to print output strings from your code, like calling System.out.println() in Java code The Android system will write to LogCat The programmer can also send non-graphical output there
These are the lines of code in the application related to logging import android.util.Log; public class MainActivity extends Activity { private static final String TAG = "OneButtonRecursion"; … Log.i(TAG, "In recursion " + handFullIn);
In order to write to LogCat you need to import the Log class Log contains static methods, like “i”, which stands for “information” (as opposed to println() for System.out) These log methods take two strings as parameters, a tag, and the actual output
It’s simplest to just define a TAG so that every line from a given app is clearly identified in the LogCat Then at strategic places put calls to Log.i() to see what’s happening Android Studio has fancier debugging tools, but I’ve always done it by hand in this way
An Example On the following overhead is a block of code that gave me a runtime error As noted earlier, the runtime error messages are not always as helpful as they might be You’re not told which line of code caused the runtime error I couldn’t tell what exactly was wrong—it was the highlighted parseInt() call
TextView capturedCup = (TextView) activityIn. findViewById(R. id TextView capturedCup = (TextView) activityIn.findViewById(R.id.textView16); int capturedCount = Integer.parseInt(capturedCup.getText().toString()); capturedCount += seedCount; capturedCup.setText(capturedCount + "");
The code on the following overhead shows how I picked the block apart and was able to identify exactly which line and which call was causing the problem
/* Debugging code */ /* Log.i(TAG, "1"); TextView capturedCup = (TextView) activityIn.findViewById(R.id.textView16); Log.i(TAG, "2, capturedCup id: " + capturedCup); CharSequence tempText = capturedCup.getText(); Log.i(TAG, "3"); String tempString = tempText.toString(); Log.i(TAG, "4, tempString contents of capturedCup: " + tempString); int capturedCount = Integer.parseInt(tempString); Log.i(TAG, "5"); capturedCount += seedCount; Log.i(TAG, "6"); capturedCup.setText("x"); //(capturedCount + ""); Log.i(TAG, "7"); */
Summary and Mission The assignment is to get this to work with two changes: 1. The relative positions of the button and the text view have to be different from what’s given in the example 2. You have to use any number except 4 as the starting value shown on the screen and which the recursion works on You will turn this in at the same time as the rest of Assignment 2
The End