Sei sulla pagina 1di 31

Android

Application Programming
Challenge: Tic-Tac-Toe App

Introduction

The goal of this assignment is to create a simple tic-tac-toe game for Android
as illustrated on the right. You will use buttons to represent the game board
and a text control to display the status of the game at the bottom of the
screen. The buttons will have no text on them when the game starts, but
when the user clicks on a button, it will display a green X. The computer will
them move and turn the appropriate button text to a red O. The text control
will indicate whose turn it is and when the game is over.

Creating the Android Project



Start Android Studio, select File à New à Project… When the new Project
dialog appears, select the Android folder, select Android Project, and click
Next. When the New Android Project dialog appears, enter the
following values:

• Project name: AndroidTicTacToe-Tutorial2
• Application Name: Android Tic-Tac-Toe
• Package Name: co.edu.unal.tictactoe
• Activity Name: AndroidTicTacToeActivity

Click the Finish button, and your project will be created.

App Design

Rather than starting from scratch, a fully functional tic-tac-toe game
that runs in a console window has been provided for you at:
http://cs.harding.edu/fmccown/android/TicTacToeConsole.java

Run this program and take a look at the source code to familiarize yourself with how it works. It uses a char
array to represent the game board with X representing the human and O representing the computer. The
program implements a simple AI for the computer: it will win the game if possible, block the human from
winning, or make a random move. The game has all the logic to switch between the two players and to check
for a winner.

Our goal is to convert this console game into an Android game. We have two basic choices when porting this
game to Android: we could merge the game logic with the user interface (UI) code, or we could keep the two
separated as much as possible. The second approach is preferred since it allows us to make changes to the UI,
like changing the layout of the board, without having to modify code that just deals with game logic. Also if we
decide to port our game to another platform like the BlackBerry or iPhone, having our UI and game logic cleanly
separated will minimize the amount of code needing to be modified and make the port much easier to perform.

Frank McCown at Harding University 1


Our goal then is to place all the UI logic in AndroidTicTacToeActivity.java and all the game logic in
TicTacToeGame.java.

1. Add TicTacToeGame.java to your Android project by clicking on File à New à Class the package should be
set to edu.harding.tictactoe. Give it the name TicTacToeGame and click Finish. You will now need to copy
and paste the tic-tac-toe code from the console game into TicTacToeGame.java. You should also change the
constructor name from TicTacToeConsole to TicTacToeGame.

2. There are some modifications we’ll need to make to the TicTacToeGame class in order to expose the game
logic to the AndroidTicTacToeActivity. First, it’s no longer necessary that we place the numbers 1-9 into
the board character array to represent free positions, so let’s create a constant called OPEN_SPOT that uses
a space character to represent a free location on the game board. We’ll continue to use X and O to
represent the human and computer players.

// Characters used to represent the human, computer, and open spots
public static final char HUMAN_PLAYER = 'X';
public static final char COMPUTER_PLAYER = 'O';
public static final char OPEN_SPOT = ' ';


3. You should remove all the code in the TicTacToeGame constructor that is no longer needed for playing in
the console. The only line you need to leave in the constructor is the random number generater
initialization. You should also remove the getUserMove() and main() functions which are no longer
needed.

4. You will need to create several public methods that can be used to manipulate the game board and
determine if there is a winner. Below is a listing of the public functions you will need to craft the existing
code into. It is left to you to implement these methods. All other methods in the TicTacToeGame class
should be private since we do not want to expose them outside the class.

/** Clear the board of all X's and O's by setting all spots to OPEN_SPOT. */
public void clearBoard()

/** Set the given player at the given location on the game board.
* The location must be available, or the board will not be changed.
*
* @param player - The HUMAN_PLAYER or COMPUTER_PLAYER
* @param location - The location (0-8) to place the move
*/
public void setMove(char player, int location)

/** Return the best move for the computer to make. You must call setMove()
* to actually make the computer move to that location.
* @return The best move for the computer to make (0-8).
*/
public int getComputerMove()

/**
* Check for a winner and return a status value indicating who has won.
* @return Return 0 if no winner or tie yet, 1 if it's a tie, 2 if X won,
* or 3 if O won.
*/
public int checkForWinner()


5. For the game logic to be accessible to the AndroidTicTacToeActivity, create a class-level variable for the
TicTacToeGame class in AndroidTicTacToeActivity.java:

Frank McCown at Harding University 2



public class AndroidTicTacToeActivity extends Activity {

// Represents the internal state of the game


private TicTacToeGame mGame;


We’ll instantiate this object in a later step in the onCreate() method. The Activity can now use mGame to
start a new game, set moves, and check for a winner. Note that Android programming convensions use a
lowercase “m” at the beginning of all member variables to distinguish themselves from local parameters and
variables; we’ll use the same naming conventions throughout the tutorials.

Creating a Game Board



We will represent the tic-tac-toe game board using buttons or ButtonView widgets. We will follow best-
practices and create our screen layout using XML rather than hard-code the creation of the buttons in our Java
code.

1. From your project, navigate to the res/layout folder and double-click on main.xml. The file will likely load
into Graphical Layout mode which gives us a preview of how the View will look in the emulator. Click the
main.xml tab at the bottom of the window to change to the XML view of the file.

2. There are several different ways we could lay out the buttons on the screen. We are going to combine the
LinearLayout which displays child view elements horizontally or vertically, and the TableLayout which
displays elements in rows and columns.


The image below is our goal where each button is numbered 1 through 9.



The XML below places buttons 1 through 3 into a TableRow and a TextView below the grid. The TextView
will be used later to indicate whose turn it is. It is left to you to define buttons 4 through 9 which should be
placed in their own TableRows.

Frank McCown at Harding University 3



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center_horizontal" >

<TableLayout
android:id="@+id/play_grid" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_marginTop="5dp" >

<TableRow android:gravity="center_horizontal">
<Button android:id="@+id/one" android:layout_width="100dp"
android:layout_height="100dp" android:text="1"
android:textSize="70dp" />

<Button android:id="@+id/two" android:layout_width="100dp"


android:layout_height="100dp" android:text="2"
android:textSize="70dp" />

<Button android:id="@+id/three" android:layout_width="100dp"


android:layout_height="100dp" android:text="3"
android:textSize="70dp" />
</TableRow>
</TableLayout>

<TextView android:id="@+id/information" android:layout_width="fill_parent"


android:layout_height="wrap_content" android:text="info"
android:gravity="center_horizontal" android:layout_marginTop="20dp"
android:textSize="20dp" />

</LinearLayout>

Notice that each Button has several attributes:

• id – a unique ID which will be used in our Java code to access the widget. The @ tells the XML parser to
expand the rest of the ID string and identify it as an ID resource. The + means this is a new resource
name that must be added to the app’s resources in the R.java file.

• layout_width – the width of the widget in density-independent pixels (dp). These allow the buttons to
scale appropriately for Android devices with different resolutions. The TextView uses a layout_width
value of fill_parent which means the widget should fill the entire parent. Another acceptable value
would be wrap_content which makes the widget just big enough to hold the content.

• layout_height – the height of the widget in pixels or fill_parent or wrap_content.

• text – the text that should be displayed in the button.

• textSize – the font size of the text. This can be defined with pixels (px), inches (in), millimeters (mm),
points (pt), density-independent pixels (dp), or scale-independent pixels (sp – like dp but scaled by the
user's font size preference). Using dp is usually best practice.

The TextView uses these additional properties:

• gravity – specifies how the text should be placed within its container. The center_horizontal value
centers the text horizontally.

• layout_marginTop – how much of a margin should be left on the top side of the widget.

Frank McCown at Harding University 4




Once you have typed all the XML into main.xml, click the
Graphical Layout tab to display the view in a WYSIWYG
editor. In the Graphical Layout editor, you can click on a
widget and see its attribute values in the Properties tab. In
the figure on the right, you can see the Id of button 1 is set to
“@+id/one”. You can edit any of the values in this list, and
they will be automatically updated in main.xml.

The Graphical Layout editor is useful for dropping widgets
onto your View and sizing them. However, many Android
programs prefer to edit the XML directly.




Accessing the Widgets

We now need to connect the buttons and text widgets defined in main.xml with our Activity.

1. In AndroidTicTacToeActivity.java, declare a Button array and TextView member variables:

// Buttons making up the board
private Button mBoardButtons[];

// Various text displayed


private TextView mInfoTextView;


2. At this point, you’ll likely encounter an error in Eclipse indicating that the Button and TextView classes
“cannot be resolved to a type.” When you encounter these types of syntax errors, press Ctrl-Shift-O to
make Eclipse fix all your import statements. Pressing Ctrl-Shift-O at this point will add the following import
statements and remove the error messages:

import android.widget.Button;
import android.widget.TextView;


3. In the onCreate() method, instantiate the array and use the findViewById() method to attach each
button in main.xml with a slot in the array. Note that the ID’s are found in R.java and match precisely the
name assigned to each button in main.xml. You’ll also need to instatiate mGame so our Activity will be able to
access the tic-tac-toe game logic.

Frank McCown at Harding University 5


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

mBoardButtons = new Button[TicTacToeGame.BOARD_SIZE];


mBoardButtons[0] = (Button) findViewById(R.id.one);
mBoardButtons[1] = (Button) findViewById(R.id.two);
mBoardButtons[2] = (Button) findViewById(R.id.three);
mBoardButtons[3] = (Button) findViewById(R.id.four);
mBoardButtons[4] = (Button) findViewById(R.id.five);
mBoardButtons[5] = (Button) findViewById(R.id.six);
mBoardButtons[6] = (Button) findViewById(R.id.seven);
mBoardButtons[7] = (Button) findViewById(R.id.eight);
mBoardButtons[8] = (Button) findViewById(R.id.nine);

mInfoTextView = (TextView) findViewById(R.id.information);

mGame = new TicTacToeGame();


}


Adding Game Logic



Let’s now add some logic to start a new game.

1. First, to start a new game, we’ll need to clear the game board of any X’s and O’s from the previous game by
calling mGame.clearBoard(). This only clears the internal representation of the game board, not the game
board we are seeing on the screen.

// Set up the game board.
private void startNewGame() {

mGame.clearBoard();


2. To clear the visible game board, we need to loop through all the buttons representing the board and set
their text to an empty string. We also need to enable each button (we will disable them later when a move
is placed), and we to create an event listener for each button by setting each button’s OnClickListener to
a new ButtonClickListener, a class which we will define shortly. We pass to ButtonClickListener’s
constructor the button’s number (0-8) which will be used to identify which button was actually clicked on.
Add the code below to the startNewGame() function.

// Reset all buttons
for (int i = 0; i < mBoardButtons.length; i++) {
mBoardButtons[i].setText("");
mBoardButtons[i].setEnabled(true);
mBoardButtons[i].setOnClickListener(new ButtonClickListener(i));
}


3. Finally, we will indicate that the human is to go first. Note that the “You go first.” text should normally not
be hard-coded into the Java source code for a number of reasons, but fix this later. Add these lines to the
bottom of startNewGame().

Frank McCown at Harding University 6



// Human goes first
mInfoTextView.setText("You go first.");

} // End of startNewGame


4. You need to call startNewGame() when the App first loads, so add this call on the last line of onCreate():

@Override
public void onCreate(Bundle savedInstanceState) {
...

startNewGame();
}


5. Now let’s add the ButtonClickListener that was used earlier when resetting the buttons. It needs to
implement the android.view.View.OnClickListener interface which means it needs to implement an
onClick() method. We also need to create a constructor that takes an integer parameter, the button
number, since we need to know which button was clicked on. Without this information, we won’t be able to
easily determine which button was selected and where the X should be placed.

The onClick() method is called when the user clicks on a button. We should only allow the user to click on
enabled buttons which represent valid locations the user may place an X. We will write a setMove()
function shortly which will place the X or O at the given location and disable the button. After displaying the
human’s move, we need to see if the human has won or tied by calling checkForWinner(). If this function
returns back 0, then the game isn’t over, so the computer must now make its move. Once it moves, we
must again check to see if the game is over and update the information text appropriately.

// Handles clicks on the game board buttons
private class ButtonClickListener implements View.OnClickListener {
int location;

public ButtonClickListener(int location) {


this.location = location;
}

public void onClick(View view) {


if (mBoardButtons[location].isEnabled()) {
setMove(TicTacToeGame.HUMAN_PLAYER, location);

// If no winner yet, let the computer make a move


int winner = mGame.checkForWinner();
if (winner == 0) {
mInfoTextView.setText("It's Android's turn.");
int move = mGame.getComputerMove();
setMove(TicTacToeGame.COMPUTER_PLAYER, move);
winner = mGame.checkForWinner();
}

if (winner == 0)
mInfoTextView.setText("It's your turn.");
else if (winner == 1)
mInfoTextView.setText("It's a tie!");
else if (winner == 2)
mInfoTextView.setText("You won!");
else
mInfoTextView.setText("Android won!");
}
}
}

Frank McCown at Harding University 7



6. Below is the setMove() function which is called from the ButtonClickListener. Place this function in
your AndroidTicTacToeActivity class. It updates the board model, disables the button, sets the text of
the button to X or O, and makes the X green and the O red.

private void setMove(char player, int location) {

mGame.setMove(player, location);
mBoardButtons[location].setEnabled(false);
mBoardButtons[location].setText(String.valueOf(player));
if (player == TicTacToeGame.HUMAN_PLAYER)
mBoardButtons[location].setTextColor(Color.rgb(0, 200, 0));
else
mBoardButtons[location].setTextColor(Color.rgb(200, 0, 0));
}


7. Now run your app and play a game. You should see the a message at the bottom of the board telling you
when it’s your turn, but the computer moves so quickly that you will likely never see the message indicating
it’s the computer’s turn. When the game is over, you won’t be able to play another game unless you restart
the app. This is a problem we’ll fix in the next section.

8. Another problem is that when the game is over, the human can continue making moves! You will need to fix
this problem by introducing a class-level boolean mGameOver variable to AndroidTicTacToeActivity.
When the game starts, mGameOver should be set to false. When the user is clicking on a button, the
variable should be checked so a move isn’t made when the game is over. And mGameOver needs to be set to
true when the game has come to an end. It’s left to you to make the appopriate modifications.

Adding a Menu

In order to start a new game, let’s add an option menu so that when the user clicks on the device’s Menu
button, the New Game option appears as pictured below.



1. In AndroidTicTacToeActivity, override the onCreateOptionsMenu() method which is called only once,
when the Activity’s option menu is shown the first time. The code below creates a single menu option
labelled New Game. The function must return true for the menu to be displayed.

Frank McCown at Harding University 8



@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add("New Game");
return true;
}



2. Next, override the onOptionsItemSelected() method which will be called when the user clicks on the
New Game menu item. Since there’s only one menu item that can be selected, we will call
startNewGame()and return true, indicating that we processed the option here. In a later tutorial we will
learn how to process different menu selections.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
startNewGame();
return true;
}


3. Run the Tic-tac-toe app again, and play a complete game. You should now be able to click the emulator’s
Menu option which will display the New Game option which, when clicked, clears the board and starts a
new game.

String Messages

Several times we have hard-coded UI string messages into our Java source code. For example, when the user
wins a game, we have hard-coded the response “You win!” This is a problem for a couple of reasons. First, we
may want to change the contents of this message at a later date, and it would be helpful if we did not have to
dig through our source code and recompile just to change this message. Secondly, it we wanted our game to
use messages in other languages (localization), hard-coding other languages in the source code is problematic.

We should instead follow best-practices and place all our UI messages in strings.xml, a file that is located in the
project’s res/values directory.

1. Locate strings.xml and double-click on it to view its contents in Eclipse.

2. Add the following strings to be used in your application:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Tic-Tac-Toe</string>
<string name="first_human">You go first.</string>
<string name="turn_human">Your turn.</string>
<string name="turn_computer">Android\'s turn.</string>
<string name="result_tie">It\'s a tie.</string>
<string name="result_human_wins">You won!</string>
<string name="result_computer_wins">Android won!</string>
</resources>


A public, static, final identifier will be created for each of these messages in R.java. Note that a single
apostrophe (“It\’s”) in your string messages must be escaped by placing a backslash in front of it.

Frank McCown at Harding University 9



3. Now locate where you have hard-coded specific messages in your Java code and modify it to use the
messages from strings.xml. For example, to display the “You won!”:

mInfoTextView.setText(R.string.result_human_wins);



Run your app again and verify that the messages are being displayed properly. Although localization is not
covered in this tutorial, it is rather straight-forward to support it by creating strings.xml files for other locals.

Extra Challenge

As currently implemented, the human always goes first. Make the game fairer by alternating who gets to go
first. Also keep track of how many games the user has won, the computer has won, and ties. You should display
this information using TextView controls under the game status TextView. Use the RelativeLayout class to
position the TextView controls next to each other
like the example on the right:

References

Jason Houle’s Tic Tac Toe for Android tutorial was helpful in producing this tutorial.


Except as otherwise noted, the content of this document is
licensed under the Creative Commons Attribution 3.0 License
http://creativecommons.org/licenses/by/3.0

Frank McCown at Harding University 10


Android Application Programming
Challenge: Menus and Dialog Boxes

Introduction

In the previous tutorial, you created a simple tic-tac-toe game for the Android.
The goal of this tutorial is to improve your game by creating a more
sofisticated menu that uses a few dialog boxes.In this tutorial you will create
an options menu (as shown on the right) with three options:

1. New Game – to start a new game


2. Difficulty – to set the AI difficulty level to Easy, Harder, or Expert
3. Quit – to quit the app

You should familiarize yourself with the official Menu Design Guidelines when
designing your own menus. Android also supports context menus, floating
lists of menu items that appear when holding your finger down on the screen
(a long press),but they will not be covered in this tutorial.

Changes to the Game Logic

We are going to create a menu option in this tutorial to change the difficulty level of the game. Right now the
game only has one difficulty level which we’ll call “Expert”.You will need to modify the TicTacToeGame class so
the difficulty level can be set. When set to the “Easy” level, the computer will always just make random moves.
When set to the “Harder” level, the computer will make a winning move if possible, otherwise it will make a
random move.

1. Open TicTacToeGame.java, and add an enumeration for the difficulty level and a variable for keeping track
of the current difficulty level setting:

publicclassTicTacToeGame {

// The computer's difficulty levels


publicenumDifficultyLevel {Easy, Harder, Expert};

// Current difficulty level


privateDifficultyLevelmDifficultyLevel = DifficultyLevel.Expert;

2. Create getters and setters for the difficulty level:

publicDifficultyLevelgetDifficultyLevel() {
returnmDifficultyLevel;
}

publicvoidsetDifficultyLevel(DifficultyLeveldifficultyLevel) {
mDifficultyLevel = difficultyLevel;
}

Frank McCown at Harding University 1


3. Finally, modify the getComputerMove()method to call the appropriate function depending on the difficulty
level. It’s left to you to implement getRandomMove(), getWinningMove(), and getBlockingMove() based
on the pre-existing code. Note that the functions might need to temporarily modify the board array, but
they should leave the array in the same state it was in before the functions were called.

publicintgetComputerMove() {

int move = -1;

if (mDifficultyLevel == DifficultyLevel.Easy)
move = getRandomMove();
elseif (mDifficultyLevel == DifficultyLevel.Harder) {
move = getWinningMove();
if (move == -1)
move = getRandomMove();
}
elseif (mDifficultyLevel == DifficultyLevel.Expert) {

// Try to win, but if that's not possible, block.


// If that's not possible, move anywhere.
move = getWinningMove();
if (move == -1)
move = getBlockingMove();
if (move == -1)
move = getRandomMove();
}

return move;
}

Create the Menu in XML

Now we will add a menu to the Activity that will be used to start a new game, set the TicTacToeGame’s difficulty
level, and quit. Android can display up to six menu options at once, and a More option makes an expanded menu
available. We’ll only have three options in our menu. Instead of creating the menu options inside of your code
like we did in the previous assignment, it’s best-practice to create your menu via XML instead so changes can be
made to the menu without modifying any code.

The first thing you need to do is create the menu in XML:


1. Right-click on the res directory and select New  Folder
2. Name the folder menu.
3. Right-click on the new menu directory and select New  File
4. Name the file options_menu.xml.
5. Enter the following XML into the new file:

<menuxmlns:android="http://schemas.android.com/apk/res/android">
<itemandroid:id="@+id/new_game"
android:title="New Game"
android:icon="@drawable/new_game"/>
<itemandroid:id="@+id/ai_difficulty"
android:title="Difficulty"
android:icon="@drawable/difficulty_level"/>
<itemandroid:id="@+id/quit"
android:title="Quit"
android:icon="@drawable/quit_game"/>
</menu>

Frank McCown at Harding University 2


This creates three menu options with text and images. You will add the menu images to your project in the
next step.

Add Menu Items Images

Now you’ll need to add the images that are used in the menu items to your project:

1. Create new_game.png, difficulty_level.png, and quit_game.png images using your favorite image editor.
Feel free to use images you find on the web (AndroidIcons.com has some free icons) orfrom the SDK (e.g.,
C:\ANDROID_SDK\platforms\android-2.1\data\res\drawable-mdpi). Make sure the images are not taller
than 42 pixels, or the images will obstruct the text in the menu items.If you want to create icons that
conform strictly to the Android platform, take a look at the official Android Icon Design Guidelineswhich give
very specific instructions for creating menu item icons, including instructions for creating three different
sizes of images for the various screen sizes Android may be running on.

2. Your project will have three folders under res called drawable-hdpi, drawable-ldpi, and drawable-mdpi.
These folders are for holding images that are best for different resolutions. We will not concern ourselves
with this issue right now. So create a folder called res/drawable, just like you did earlier when you created
res/menu. The images in this folder will be used regardless of the Android device’s screen resolution.

3. Drag the image files you created in step 1 on top of the drawable folder in Eclipse. This will require you to
have the file explorer window open in front of Eclipse drag and drop the files. Another way to add your
images to the project is to copy the images into the drawable folder using the file explorer and then select
FileRefresh in Eclipse.

4. You should now see your png images in the drawable folder. If you used different file names than were
specified in step 1, you should correct your options_menu.xml file so it uses the same names as your files.

Display the Menu and Respond to Menu Selections

Now you need to add code to your program to create the menu and respond to menu items selections:

1. Modify your code to load the options menu by modifying your onCreateOptionsMenu()function. Note how
R.menu.options_menu equates to the ID in options_menu.xml you created earlier.

@Override
publicbooleanonCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);

MenuInflaterinflater = getMenuInflater();
inflater.inflate(R.menu.options_menu, menu);
returntrue;
}

Next you need to modify the onOptionsItemSelected()functionwhich is called when a menu option is
selected. You can tell which menu item was selected by examining the menu item’s ID which was given in
the options_menu.xml file. (showDialog is deprecated).

Frank McCown at Harding University 3


@Override
publicbooleanonOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
caseR.id.new_game:
startNewGame();
returntrue;
caseR.id.ai_difficulty:
showDialog(DIALOG_DIFFICULTY_ID);
returntrue;
caseR.id.quit:
showDialog(DIALOG_QUIT_ID);
returntrue;
}
returnfalse;
}

The menu you just added will display dialog boxes that correspond to some constants which we’ll define
next.

2. Create two constants which will be used to identify the two dialog boxes:

staticfinalintDIALOG_DIFFICULTY_ID = 0;
staticfinalintDIALOG_QUIT_ID = 1;

3. You will next need to override the


Activity’sonCreateDialog()method which is called the first time
showDialog() is called on a dialog box. Its id parameter indicates
which dialog box should be created.

We will make use of the AlertDialogclass which is useful for creating


simple dialog boxes with 1-3 buttons.

In the code below, there are two places marked “TODO” where the
code is incomplete. You willneed to set the selectedvariable so the
setSingleChoiceItems() function will properly show radio buttons labeled Easy, Harder, and Expert as
shown on the right. The currently selected difficulty level should initially be selected.

When one of the radio buttons is selected, the dialog will close, and the itemparameter of onClick will
indicate which radio button was selected. You will need to write the code to set the game’s internal
difficulty level. You will also need to add the appropriate string constants to strings.xml.

Note that each call to the builder object returns the reference to the builder object so method-chaining
becomes possible.

Frank McCown at Harding University 4


@Override
protected Dialog onCreateDialog(int id) {
Dialog dialog = null;
AlertDialog.Builder builder = newAlertDialog.Builder(this);

switch(id) {
caseDIALOG_DIFFICULTY_ID:

builder.setTitle(R.string.difficulty_choose);

finalCharSequence[] levels = {
getResources().getString(R.string.difficulty_easy),
getResources().getString(R.string.difficulty_harder),
getResources().getString(R.string.difficulty_expert)};

// TODO: Set selected, an integer (0 to n-1), for the Difficulty dialog.


// selected is the radio button that should be selected.

builder.setSingleChoiceItems(levels, selected,
newDialogInterface.OnClickListener() {
publicvoidonClick(DialogInterface dialog, int item) {
dialog.dismiss(); // Close dialog

// TODO: Set the diff level of mGamebased on which item was selected.

// Display the selected difficulty level


Toast.makeText(getApplicationContext(), levels[item],
Toast.LENGTH_SHORT).show();
}
});
dialog = builder.create();

break;
}

return dialog;
}

The code above displays a Toast message (see Android.widget.Toast) that indicates which difficulty level
was selected from the dialog box. Toast messages appear for just a few seconds and are helpful for
presenting short, brief information to the user.

4. Next add to the same switchstatement the code to display a Yes/No


confirmation dialog when the user clicks on Quit. You’ll need to add
the appropriate strings to strings.xml.When the user clicks on Yes, the
onClick()method executes and calls the Activity’s finish()method
which causes the activity to terminate.

Frank McCown at Harding University 5


switch(id) {
...
caseDIALOG_QUIT_ID:
// Create the quit confirmation dialog

builder.setMessage(R.string.quit_question)
.setCancelable(false)
.setPositiveButton(R.string.yes, newDialogInterface.OnClickListener() {
publicvoidonClick(DialogInterface dialog, int id) {
AndroidTicTacToeActivity.this.finish();
}
})
.setNegativeButton(R.string.no, null);
dialog = builder.create();

break;
}

Note that most Android apps do not have a Quit option since clicking on the device’s Home button
essentially does the same thing.

Run your app and verify that the menu options work as expected.

Extra Challenge

There are two extra challenges:

1. Create your own custom icon for your application. Name it


icon.png, and place it in your res/drawable folder. Open your
AndroidManifest.xml file, and alter android:icon to point to your
new icon.png:

<application
android:icon="@drawable/icon"
android:label="@string/app_name" >

Now run your app on the emulator. Press the Home key and press the App Drawer button on the screen to list
all the apps installed. This is where you will see the icon you have created for your app. Mine is shown above.

2. Create a menu option that displays an About dialog box,similar the one
shown on the right, which identifies you as the programmer.

You can create your own custom dialog box by first creating a
res/layout/about_dialog.xmlfile with the dialog box components (just like
you did in main.xml). You’ll need to reference an image you create from the
res/drawablefolder in your XML file. In the onCreateDialog()method, use a
LayoutInflaterto expand about_dialog.xmlinto a View, attach the Viewto
an AlertDialog.Builder,and create the dialog. The code that does all this is
shown below.

Frank McCown at Harding University 6


AlertDialog.Builder builder = new AlertDialog.Builder(this);
Context context = getApplicationContext();
LayoutInflaterinflater = (LayoutInflater) context.getSystemService(LAYOUT_INFLATER_SERVICE);
View layout = inflater.inflate(R.layout.about_dialog,null);
builder.setView(layout);
builder.setPositiveButton("OK", null);
Dialog dialog = builder.create();

Except as otherwise noted, the content of this document is licensed under the Creative Commons Attribution 3.0 License
http://creativecommons.org/licenses/by/3.0

Frank McCown at Harding University 7


Android​ ​Application​ ​Programming
Challenge:​ ​Changing​ ​the​ ​Orientation​ ​and​ ​Saving​ ​State

Introduction

Your​ ​tic-tac-toe​ ​game​ ​only​ ​works​ ​in​ ​portrait​ ​mode.​ ​ ​If​ ​you​ ​were​ ​to​ ​flip​ ​the​ ​emulator​ ​into​ ​landscape​ ​mode​ ​by
pressing​ ​Ctrl-F11,​ ​you​ ​would​ ​see​ ​the​ ​board​ ​is​ ​cleared​ ​of​ ​all​ ​the​ ​images,​ ​and​ ​the​ ​text​ ​beneath​ ​the​ ​board​ ​is​ ​not
visible.​ ​ ​While​ ​we​ ​could​ ​force​ ​the​ ​user​ ​to​ ​only​ ​use​ ​our​ ​app​ ​in​ ​portrait​ ​mode,​ ​we​ ​should​ ​make​ ​our​ ​app​ ​more
flexible​ ​by​ ​allowing​ ​the​ ​user​ ​to​ ​play​ ​with​ ​it​ ​in​ ​either​ ​mode.

The​ ​goal​ ​of​ ​this​ ​tutorial​ ​is​ ​to​ ​show​ ​you​ ​how​ ​to​ ​develop​ ​an​ ​application​ ​that​ ​can​ ​work​ ​in​ ​portrait​ ​and​ ​landscape
mode.​ ​ ​You​ ​will​ ​also​ ​learn​ ​how​ ​to​ ​make​ ​the​ ​application’s​ ​data​ ​persist​ ​when​ ​the​ ​orientation​ ​is​ ​changed​ ​and​ ​how
to​ ​make​ ​data​ ​persist​ ​between​ ​application​ ​invocations.

Changing​ ​the​ ​Orientation

When​ ​you​ ​press​ ​Ctrl-F11,​ ​your​ ​emulator​ ​will​ ​switch​ ​from​ ​portrait​ ​mode​ ​to​ ​landscape​ ​mode.​ ​ ​Pressing​ ​Crtl-F11
again​ ​switches​ ​it​ ​back.​ ​ ​Your​ ​game​ ​was​ ​built​ ​for​ ​portrait​ ​mode,​ ​so​ ​it​ ​does​ ​not​ ​behave​ ​properly​ ​when​ ​switched​ ​to
landscape​ ​mode.

You​ ​can​ ​force​ ​Android​ ​to​ ​only​ ​show​ ​your​ ​app​ ​in​ ​portrait​ ​mode:​ ​just​ ​add​ ​ ​android:screenOrientation=
"portrait"​​ ​(or​ " ​ landscape"​​ ​if​ ​you​ ​wish)​ ​to​ ​the​ <​ activity>​​ ​element​ ​in​ ​the​ ​app’s​ ​AndroidManifext.xml​ ​file.
However,​ ​best​ ​practice​ ​is​ ​to​ ​allow​ ​the​ ​user​ ​to​ ​interact​ ​with​ ​the​ ​app​ ​in​ ​either​ ​orientation.

Our​ ​goal​ ​is​ ​to​ ​create​ ​a​ ​landscaped​ ​orientation​ ​that​ ​looks​ ​like
the​ ​screenshot​ ​on​ ​the​ ​right.

1. To​ ​create​ ​more​ ​screen​ ​real​ ​estate,​ ​remove​ ​the​ ​title​ ​bar
from​ ​the​ ​application​ ​window​ ​by​ ​modifying​ ​the
application​​ ​element​ ​in​ A ​ ndroidManifest.xml​:

2. Create​ ​a​ ​res/layout-land​​ ​directory.

3. Copy​ ​the​ ​res/layout/main.xml​​ ​file​ ​into​ ​the​ ​layout-land​ ​directory.​ ​ ​This​ ​is​ ​the​ ​file​ ​that​ ​Android​ ​will​ ​apply
to​ ​the​ ​Activity’s​ ​View​ ​when​ ​the​ ​emulator​ ​is​ ​put​ ​in​ ​landscape​ ​mode.

4. Open​ ​the​ ​layout-land/main.xml​​ ​file​ ​and​ ​change​ ​the​ ​size​ ​of​ ​the​ ​BoardView​​ ​to​ ​270x270​ ​dp​ ​to​ ​make​ ​it​ ​take
up​ ​less​ ​space.​ ​ ​Also​ ​align​ ​it​ ​on​ ​the​ ​left​ ​side​ ​of​ ​the​ ​screen.​ ​ ​To​ ​do​ ​this,​ ​you​ ​can​ ​change​ ​the​ L​ inearLayout​’s
orientation​​ ​property​ ​to​ h ​ orizontal​​ ​or​ ​replace​ ​the​ L​ inearLayout​​ ​entirely​ ​with​ ​a​ R​ elativeLayout​.​ ​ ​You
will​ ​also​ ​need​ ​to​ ​change​ ​the​ ​layout​ ​of​ ​the​ T​ extView​​ ​controls​ ​so​ ​they​ ​are​ ​displayed​ ​to​ ​the​ ​right​ ​of​ ​the​ ​game
board.​ ​ ​ ​ ​It​ ​is​ ​left​ ​to​ ​you​ ​to​ ​make​ ​these​ ​edits.​ ​ ​To​ ​test​ ​your​ ​layout​ ​without​ ​running​ ​your​ ​game​ ​in​ ​the
emulator,​ ​you​ ​can​ ​switch​ ​to​ ​Graphical​ ​Layout​ ​mode​ ​and​ ​set​ ​the​ ​orientation​ ​to​ ​Landscape​ ​as​ ​shown​ ​below.

​ ​Frank​ ​McCown​ ​at​ ​Harding​ ​University1


​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​

5. After​ ​you​ ​have​ ​the​ ​BoardView​​ ​and​ ​TextView​s​ ​aligned​ ​properly,​ ​run​ ​your​ ​program​ ​and​ ​press​ ​Ctrl-F11​ ​to
change​ ​its​ ​orientation.​ ​ ​Your​ ​app​ ​should​ ​switch​ ​orientations​ ​and​ ​display​ ​all​ ​the​ ​screen​ ​elements​ ​correctly.

Try​ ​making​ ​a​ ​few​ ​moves,​ ​and​ ​before​ ​the​ ​game​ ​is​ ​over,​ ​press​ ​Ctrl-F11.​ ​ ​What​ ​happens?​ ​ ​When​ ​the​ ​orientation
is​ ​changed​ ​in​ ​the​ ​middle​ ​of​ ​the​ ​game,​ ​the​ ​board​ ​is​ ​cleared​ ​and​ ​your​ ​game​ ​starts​ ​over​ ​from​ ​the​ ​beginning.​ ​ ​If
you​ ​play​ ​a​ ​few​ ​games​ ​and​ ​then​ ​change​ ​the​ ​orientation,​ ​you’ll​ ​notice​ ​that​ ​the​ ​scores​ ​are​ ​also​ ​reset​ ​to​ ​zero.
You​ ​can​ ​imagine​ ​how​ ​frustrated​ ​a​ ​user​ ​would​ ​get​ ​if​ ​they​ ​were​ ​about​ ​to​ ​win​ ​a​ ​game,​ ​they​ ​shifted​ ​their​ ​phone
a​ ​little​ ​which​ ​caused​ ​the​ ​orientation​ ​to​ ​change,​ ​and​ ​the​ ​game​ ​reset!​ ​ ​Ideally​ ​we’d​ ​like​ ​our​ ​game​ ​to​ ​maintain
its​ ​state​ ​when​ ​changing​ ​orientation,​ ​so​ ​we’ll​ ​fix​ ​this​ ​problem​ ​next.

Saving​ ​Instance​ ​State

Before​ ​discussing​ ​how​ ​state​ ​information​ ​is​ ​stored,​ ​it​ ​is


helpful​ ​to​ ​get​ ​a​ ​firm​ ​grasp​ ​on​ ​the​ ​life​ ​cycle​ ​of​ ​an​ ​Activity.
1
The​ ​Activity​ ​life​ ​cycle​ ​is​ ​illustrated​ ​in​ ​the​ ​diagram ​ ​below.
Each​ ​rectangle​ ​represents​ ​the​ ​Activity’s​ ​methods​ ​which​ ​are
called​ ​in​ ​response​ ​to​ ​different​ ​events.​ ​ ​For​ ​example,​ ​after
starting​ ​an​ ​Activity,​ ​the​ o​ nCreate()​​ ​method​ ​is​ ​called​ ​first
followed​ ​by​ o​ nStart()​​ ​and​ o​ nResume()​.​ ​ ​The​ ​application
then​ ​runs​ ​until​ ​another​ ​Activity​ ​comes​ ​in​ ​front​ ​of​ ​the
currently​ ​running​ ​Activity,​ ​causing​ ​the​ o​ nPause()​​ ​method
to​ ​trigger.​ ​ ​If​ ​the​ ​Activity​ ​is​ ​no​ ​longer​ ​visible,​ o​ nStop()
triggers,​ ​and​ ​if​ ​Android​ ​decides​ ​another​ ​process​ ​needs​ ​the
memory,​ ​the​ ​Activity​ ​is​ ​killed.​ ​ ​However,​ ​if​ ​the​ ​Activity​ ​is
navigated​ ​to​ ​before​ ​being​ ​killed,​ o​ nRestart()​​ ​is​ ​triggered
followed​ ​by​ o​ nStart()​​ ​and​ o​ nResume()​.

1
​ ​Figure​ ​is​ ​from​ ​Application​ ​Fundamentals​ ​at​ ​http://developer.android.com/guide/topics/fundamentals.html

​ ​Frank​ ​McCown​ ​at​ ​Harding​ ​University2


Changing​ ​the​ ​orientation​ ​of​ ​an​ ​Android​ ​device​ ​will​ ​cause​ ​your​ ​Activity​ ​to​ ​terminate​ ​and​ ​restart,​ ​so​ ​all​ ​the​ ​app’s
variables​ ​are​ ​reset,​ ​and​ ​the​ ​game​ ​is​ ​started​ ​anew.​ ​ ​This​ ​is​ ​just​ ​like​ ​what​ ​happens​ ​when​ ​when​ ​your​ ​Activity​ ​is​ ​no
longer​ ​in​ ​the​ ​foreground,​ ​its​ ​memory​ ​is​ ​reclaimed,​ ​and​ ​you​ ​navigate​ ​back​ ​to​ ​the​ ​app.

Fortunately,​ ​Android​ ​provides​ ​a​ ​mechanism​ ​to​ ​remember​ ​whatever​ ​pieces​ ​of​ ​information​ ​we​ ​would​ ​like​ ​when
the​ ​Activity​ ​is​ ​started​ ​again​ ​after​ ​changing​ ​the​ ​device’s​ ​orientation.​ ​ ​We​ ​first​ ​need​ ​to​ ​decide​ ​what​ ​information​ ​is
necessary​ ​to​ ​start​ ​our​ ​game​ ​back​ ​in​ ​the​ ​same​ ​state.​ ​ ​We’ll​ ​bundle​ ​the​ ​information​ ​together​ ​before​ ​the​ ​Activity​ ​is
killed,​ ​and​ ​Android​ ​will​ ​pass​ ​it​ ​back​ ​to​ ​us​ ​in​ ​the​ o​ nCreate()​​ ​method.​ ​ ​We’ll​ ​extract​ ​the​ ​bundled​ ​information​ ​and
put​ ​it​ ​back​ ​into​ ​the​ ​necessary​ ​variables​ ​to​ ​pick​ ​up​ ​right​ ​where​ ​we​ ​left​ ​off.

1. To​ ​save​ ​the​ ​state​ ​of​ ​the​ ​game,​ ​you​ ​need​ ​to​ ​override​ ​the​ ​onSaveInstanceState()​​ ​method​ ​for​ ​the
AndroidTicTacToe​​ ​class​ ​(the​ ​Activity).​ ​ ​This​ ​method​ ​is​ ​called​ ​by​ ​Android​ ​before​ ​it​ ​pauses​ ​the​ ​application
(before​ o​ nPause()​​ ​is​ ​triggered),​ ​and​ ​it​ ​passes​ ​the​ ​method​ ​a​ B​ undle​​ ​object​ ​which​ ​can​ ​be​ ​used​ ​to​ ​save
key/value​ ​pairs​ ​that​ ​will​ ​be​ ​given​ ​back​ ​to​ ​the​ ​application​ ​(via​ ​another​ ​call​ ​to​ o​ nCreate()​)​ ​even​ ​if​ ​the​ ​Activity
is​ ​dropped​ ​from​ ​memory.

For​ ​the​ ​above​ ​code​ ​to​ ​work,​ ​you​ ​will​ ​need​ ​to​ ​create​ ​a​ ​getBoardState()​​ ​function​ ​for​ ​the​ ​TicTacToeGame
class​ ​which​ ​returns​ ​a​ ​character​ ​array​ ​representing​ ​the​ ​state​ ​of​ ​the​ ​board.​ ​ ​ I​ t​ ​is​ ​left​ ​for​ ​you​ ​to​ ​implement
this​ ​method.

2. When​ ​onCreate()​​ ​is​ ​called​ ​the​ ​first​ ​time,​ ​its​ ​parameter​ ​savedInstanceState​​ ​will​ ​be​ ​null,​​ ​and​ ​the​ ​game
should​ ​be​ ​started​ ​like​ ​usual.​ ​ ​But​ ​after​ ​changing​ ​the​ ​orientation,​ o​ nCreate​’​s​ ​savedInstanceState​​ ​will
contain​ ​the​ ​key/value​ ​pairs​ ​placed​ ​in​ o​ nSaveInstanceState​‘s​ o​ utState​​ ​bundle.​ ​ ​You​ ​will​ ​need​ ​to​ ​extract
the​ ​values​ ​from​ ​the​ B​ undle​​ ​and​ ​place​ ​them​ ​back​ ​into​ ​their​ ​associated​ ​variables​ ​like​ ​so:

​ ​Frank​ ​McCown​ ​at​ ​Harding​ ​University3


For​ ​the​ ​above​ ​code​ ​to​ ​work,​ ​you​ ​will​ ​need​ ​to​ ​create​ ​a​ ​setBoardState()​​ ​function​ ​for​ ​the​ ​TicTacToeGame
class​ ​that​ ​allows​ ​the​ ​board​ ​state​ ​to​ ​be​ ​set​ ​to​ ​the​ ​values​ ​in​ ​the​ ​given​ ​char​ ​array.​ ​ ​You​ ​will​ ​likely​ ​find​ ​the
array’s​ c​ lone()​​ ​method​ ​to​ ​be​ ​helpful.​ ​ ​This​ ​is​ ​left​ ​to​ ​you​ ​to​ ​complete.

3. The​ ​code​ ​above​ ​also​ ​calls​ ​a​ ​displayScores()​​ ​function​ ​to​ ​show​ ​the​ ​scores.​ ​ ​Declare​ ​it​ ​like​ ​so:

4. Note​ ​that​ ​there​ ​is​ ​also​ ​an​ ​onRestoreInstanceState()​​ ​method​ ​that​ ​can​ ​be​ ​overridden​ ​for​ ​the​ ​Activity​ ​to
restore​ ​the​ ​application’s​ ​state.​ ​ ​So​ ​instead​ ​of​ ​placing​ ​your​ ​restore​ ​code​ ​in​ ​the​ ​else​ ​statement​ ​in​ ​step​ ​2,​ ​you
could​ ​do​ ​this:

5. Run​ ​your​ ​application​ ​and​ ​make​ ​some​ ​moves.​ ​ ​When​ ​you​ ​press​ ​Ctrl-F11,​ ​the​ ​board​ ​should​ ​remain​ ​in​ ​the​ ​same
condition​ ​it​ ​was​ ​before.​ ​ ​However,​ ​you’ll​ ​probably​ ​notice​ ​that​ ​the​ ​computer​ ​makes​ ​an​ ​additional​ ​move​ ​when
it’s​ ​your​ ​turn!​ ​ ​This​ ​is​ ​because​ ​the​ ​variable​ ​that​ ​ensures​ ​it’s​ ​the​ ​correct​ ​person’s​ ​turn​ ​is​ ​not​ ​being​ ​saved.​ ​ ​It’s
left​ ​to​ ​you​ ​to​ ​fix​ ​this​ ​bug.

​ ​Frank​ ​McCown​ ​at​ ​Harding​ ​University4


Saving​ ​Persistent​ ​Information

If​ ​you​ ​were​ ​to​ ​click​ ​the​ ​Back​ ​button​ ​on​ ​the​ ​emulator​ ​and​ ​restart​ ​the​ ​application,​ ​either​ ​by​ ​clicking​ ​on​ ​the​ ​app’s
icon​ ​or​ ​using​ ​Eclipse​ ​to​ ​restart,​ ​your​ ​application’s​ ​state​ ​is​ ​not​ ​saved;​ ​your​ ​game​ ​board​ ​will​ ​be​ ​cleared​ ​and​ ​your
game​ ​scores​ ​(Human,​ ​Tie,​ ​Android)​ ​will​ ​be​ ​reset​ ​to​ ​0.​ ​ ​That’s​ ​because​ ​Android​ ​only​ ​saves​ ​state​ ​information​ ​(by
calling​ ​ o​ nSaveInstanceState()​)​ ​if​ ​Android​ ​is​ ​responsible​ ​for​ ​killing​ ​the​ ​Activity.​ ​ ​If​ ​you​ ​kill​ ​it​ ​by​ ​clicking​ ​the
Back​ ​button,​ ​for​ ​example,​ ​then​ o​ nSaveInstanceState()​​ ​is​ ​not​ ​called.

In​ ​order​ ​for​ ​information​ ​to​ ​persist​ ​between​ ​application​ ​restarts,​ ​there​ ​are​ ​several​ ​mechanisms​ ​to​ ​choose​ ​from.
You​ ​could​ ​store​ ​data​ ​to​ ​a​ ​file​ ​and​ ​read​ ​it​ ​back​ ​in,​ ​or​ ​you​ ​could​ ​save​ ​data​ ​to​ ​a​ ​SQLite​ ​database.​ ​If​ ​you​ ​only​ ​need​ ​to
store​ ​simple​ ​key/value​ ​pairs​ ​like​ ​we​ ​have​ ​been​ ​doing,​ ​the​ S​ haredPreferences​​ ​is​ ​the​ ​ideal​ ​class​ ​to​ ​use.

Follow​ ​the​ ​instructions​ ​below​ ​to​ ​use​ ​the​ ​SharedPreferences​​ ​class​ ​to​ ​make​ ​the​ ​game​ ​scores​ ​persist​ ​between
application​ ​restarts:

1. Declare​ ​a​ ​SharedPreferences​​ ​data​ ​member​ ​for​ ​the​ A​ ndroidTicTacToe​​ ​class:

2. In​ ​the​ ​onCreate()​​ ​method,​ ​use​ ​Activity.getSharedPreferences()​​ ​to​ ​initialize​ ​mPrefs​.​ ​ ​The​ ​first
argument​ ​is​ ​the​ ​name​ ​of​ ​the​ ​preference​ ​file,​ ​and​ ​the​ ​second​ ​argument​ ​is​ ​typically​ ​MODE_PRIVATE​​ ​or​ ​0.

3. When​ ​your​ ​application​ ​is​ ​being​ ​terminated,​ ​the​ ​Activity’s​ ​onStop()​​ ​method​ ​will​ ​be​ ​called​ ​(refer​ ​back​ ​to​ ​the
Activity​ ​life​ ​cycle​ ​figure​ ​shown​ ​earlier).​ ​ ​This​ ​is​ ​your​ ​chance​ ​to​ ​save​ ​any​ ​persistent​ ​information.​ ​ ​Use​ ​the
SharedPreferences​​ ​object​ ​to​ ​save​ ​the​ ​scores​ ​like​ ​so:

Note​ ​that​ ​the​ ​SharedPreferences.Editor​​ ​has​ ​methods​ ​for​ ​storing​ ​any​ ​primitive​ ​type.​ ​ ​Calling​ ​commit()
replaces​ ​any​ ​of​ ​the​ ​data​ ​that​ ​was​ ​previously​ ​stored​ ​in​ ​SharedPreferences​​ ​with​ ​the​ ​new​ ​data.

4. Now​ ​we​ ​need​ ​to​ ​restore​ ​the​ ​game​ ​scores​ ​from​ ​mPrefs​​ ​when​ ​the​​ ​onCreate()​​ ​method​ ​is​ ​being​ ​called:

​ ​Frank​ ​McCown​ ​at​ ​Harding​ ​University5


In​ ​the​ ​code​ ​above,​ ​getInt()​​ ​is​ ​supplied​ ​0​ ​for​ ​the​ ​second​ ​argument​ ​which​ ​will​ ​be​ ​the​ ​value​ ​returned​ ​if​ ​the
preferences​ ​have​ ​not​ ​been​ ​previously​ ​saved​ ​(like​ ​the​ ​first​ ​time​ ​you​ ​run​ ​your​ ​modified​ ​application).

5. Your​ ​app​ ​will​ ​now​ ​save​ ​the​ ​scores​ ​indefinitely.​ ​ ​However,​ ​the​ ​user​ ​may​ ​not​ ​want​ ​to​ ​be​ ​reminded​ ​that
Android​ ​beat​ ​him​ ​20​ ​times​ ​at​ ​tic-tac-toe.​ ​ ​So​ ​let’s​ ​give​ ​the​ ​user​ ​the​ ​ability​ ​to​ ​reset​ ​the​ ​scores.​ ​ ​Add​ ​a​ ​new
menu​ ​item​ ​to​ r​ es/menu/options_menu.xml​​ ​labeled​ ​“Reset​ ​Scores”,​ ​and​ ​add​ ​an​ ​image​ ​for​ ​the​ ​menu​ ​item.
To​ ​keep​ ​the​ ​menu​ ​from​ ​getting​ ​cluttered,​ ​remove​ ​the​ ​Quit​ ​option​ ​which​ ​really​ ​isn’t​ ​necessary​ ​anyway.

6. When​ ​the​ ​Reset​ ​Scores​ ​menu​ ​item​ ​is​ ​selected,​ ​set​ ​the​ m​ HumanWins​,​ ​mComputerWins​,​ ​and​ ​mTies​​ ​variables​ ​to
0​ ​and​ ​re-display​ ​the​ ​scores.

7. Since​ ​we​ ​are​ ​saving​ ​the​ ​values​ ​of​ ​mHumanWins​,​ ​mComputerWins​,​ ​and​ ​mTies​​ ​in​ ​the​ ​preferences,​ ​you​ ​can​ ​safely
remove​ ​all​ ​the​ ​code​ ​you​ ​previously​ ​entered​ ​that​ ​saved​ ​their​ ​values​ ​with​ ​savedInstanceState​.

Now​ ​run​ ​your​ ​application​ ​and​ ​play​ ​a​ ​few​ ​games.​ ​ ​Take​ ​a​ ​look​ ​at​ ​the​ ​scores.​ ​ ​Click​ ​the​ ​Back​ ​button​ ​on​ ​the
emulator​ ​and​ ​restart​ ​your​ ​game​ ​by​ ​clicking​ ​on​ ​the​ ​TicTacToe​ ​icon​ ​or​ ​starting​ ​it​ ​from​ ​Eclipse.​ ​ ​The​ ​scores​ ​should
be​ ​the​ ​same​ ​as​ ​they​ ​were​ ​before​ ​you​ ​started​ ​a​ ​new​ ​game.​ ​ ​Now​ ​test​ ​your​ ​Reset​ ​Scores​ ​menu​ ​item.​ ​ ​The​ ​scores
should​ ​immediately​ ​go​ ​back​ ​to​ ​0.

You​ ​can​ ​see​ ​the​ ​values​ ​that​ ​were​ ​stored​ ​in​ ​your​ ​shared​ ​preferences​ ​by​ ​going​ ​back​ ​to​ ​Eclipse​ ​and​ ​opening​ ​the
DDMS​ ​perspective​.​ ​ ​This​ ​is​ ​done​ ​by​ ​clicking​ ​the​ ​DDMS​ ​button​ ​in​ ​the​ ​upper-right​ ​corner​ ​of​ ​Eclipse.​ ​ ​If​ ​you​ ​don’t
see​ ​this​ ​button,​ ​select​ ​Window​ ​>​ ​Open​ ​Perspective​ ​>​ ​Other…​​ ​from​ ​Eclipse’s​ ​menu​ ​and​ ​choose​ ​DDMS​​ ​from​ ​the
dialog​ ​box.​ ​ ​The​ ​DDMS​ ​persepective​ ​will​ ​now​ ​be​ ​open,​ ​and​ ​you​ ​will​ ​see​ ​a​ ​DDMS​ ​button​ ​in​ ​the​ ​upper-right​ ​corner
of​ ​Eclipse.

​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​

The​ ​figure​ ​above​ ​shows​ ​the​ ​File​ ​Explorer​​ ​which​ ​was​ ​used​ ​to​ ​navigate​ ​to
data/data/edu.harding.tictactoe/shared_prefs​.​ ​ ​Here​ ​you​ ​will​ ​find​ ​the​ ​ttt_prefs.xml​​ ​file​ ​that​ ​was
created​ ​by​ ​your​ ​app.​ ​ ​If​ ​you​ ​would​ ​like​ ​to​ ​see​ ​the​ ​contents​ ​of​ ​the​ ​file,​ ​select​ ​the​ ​file​ ​and​ ​press​ ​the​ ​disk​ ​icon​ ​which

​ ​Frank​ ​McCown​ ​at​ ​Harding​ ​University6


is​ ​circled​ ​in​ ​the​ ​figure​ ​above.​ ​ ​You​ ​can​ ​then​ ​save​ ​it​ ​using​ ​the​ ​save​ ​file​ ​dialog​ ​box​ ​that​ ​appears.​ ​ ​The​ ​contents​ ​of
the​ ​file​ ​will​ ​look​ ​something​ ​like​ ​this:

Extra​ ​Challenge

There​ ​are​ ​two​ ​extra​ ​challenges:

1. The​ ​game’s​ ​difficulty​ ​level​ ​is​ ​not​ ​being​ ​saved,​ ​so​ ​every​ ​time​ ​the​ ​game​ ​starts,​ ​the​ ​difficulty​ ​level​ ​is​ ​reset​ ​to
Expert.​ ​ ​Fix​ ​this​ ​by​ ​saving​ ​the​ ​game​ ​difficulty​ ​level​ ​to​ ​SharedPreferences​.​ ​ ​This​ ​may​ ​be​ ​somewhat
challenging​ ​because​ ​the​ ​game’s​ ​difficulty​ ​level​ ​is​ ​using​ ​an​ ​enumerated​ ​type,​ ​and​ ​the
SharedPreferences.Editor​​ ​doesn’t​ ​have​ ​a​ ​method​ ​to​ ​save​ ​the​ ​value​ ​of​ ​an​ ​enumerated​ ​type.​ ​ ​You​ ​should
save​ ​an​ ​integer​ ​which​ ​corresponds​ ​to​ ​the​ ​enumerated​ ​value​ ​and​ ​convert​ ​that​ ​int​ ​back​ ​into​ ​an​ ​enumerated
type​ ​when​ ​you​ ​retrieve​ ​the​ ​preference.

2. If​ ​you​ ​press​ ​Ctrl-F11​ ​immediately​ ​after​ ​making​ ​a​ ​move​ ​and​ ​before​ ​the​ ​computer​ ​has​ ​moved,​ ​the​ ​application
will​ ​likely​ ​crash.​ ​ ​You’ll​ ​find​ ​the​ ​LogCat​ ​helpful​ ​for​ ​determining​ ​what​ ​is​ ​causing​ ​the​ ​crash.​ ​ ​A​ ​simple​ ​try-catch
block​ ​around​ ​the​ ​line​ ​of​ ​code​ ​causing​ ​the​ ​exception​ ​will​ ​stop​ ​the​ ​app​ ​from​ ​crashing.​ ​ ​However,​ ​the​ ​game​ ​will
still​ ​not​ ​work​ ​correctly​ ​when​ ​changing​ ​the​ ​orientation​ ​immediately​ ​before​ ​the​ ​computer​ ​moves;​ ​the​ ​game
will​ ​indicate​ ​it’s​ ​still​ ​Android’s​ ​turn,​ ​but​ ​the​ ​computer​ ​will​ ​never​ ​make​ ​a​ ​move.​ ​ ​This​ ​problem​ ​has​ ​to​ ​do​ ​with
the​ H​ andler​​ ​executing​ ​code​ ​on​ ​old​ ​instances​ ​of​ ​an​ A​ ctivity​​ ​which​ ​has​ ​been​ ​terminated.​ ​ ​You​ ​need​ ​to​ ​fix
this​ ​problem​ ​by​ ​making​ ​the​ ​computer​ ​make​ ​a​ ​move​ ​after​ ​the​ ​new​ ​Activity​​ ​has​ ​restarted​ ​if​ ​it’s​ ​the
computer’s​ ​turn​ ​to​ ​move.​ ​ ​This​ ​requires​ ​adding​ ​a​ ​few​ ​lines​ ​of​ ​code​ ​to​ ​the​ o​ nCreate()​​ ​method.

Except​ ​as​ ​otherwise​ ​noted,​ ​the​ ​content​ ​of​ ​this​ ​document​ ​is​ ​licensed​ ​under​ ​the​ ​Creative​ ​Commons​ ​Attribution​ ​3.0​ ​License
http://creativecommons.org/licenses/by/3.0

​ ​Frank​ ​McCown​ ​at​ ​Harding​ ​University7


Android Application Programming
Challenge:App Settings

Introduction

Most Android applications have a Settings menu option which allows


users to set the application’s settings/preferences. This is so common
that a specialized Activity class was developed just for changing
application settings (android.preference.PreferenceActivity ).In
this tutorial you will learn how to create applications settings using the
PreferenceActivity, and you will learn how to start another activity
using an Intent.

Create the Preference XML and PreferenceActivity

The PreferenceActivity simplifies the creation of a settings screen for your application. It uses an XML file
(preferences.xml) to list the various application settings. The preferences are saved using the
SharedPreferencesclass, just as they were in the previous tutorial. In some cases, very little to no code needs
to be written to change application settings.

The first thing you need to do is create an XML file that lists the three application settings for our tic-tac-toe
game:

1. Create a res/xml directory.

2. Create a res/xml/preferences.xml file. We’ll be creating three preferences:

1) a CheckBoxPreference for turning the sound on and off,

Frank McCown at Harding University 1


2) an EditTextPreferencefor setting the text that is displayed when the
user wins, and

3) a ListPreference for setting the AI’s difficulty level.

These three are not the only types of preferences, but they are probably
the most popular types of preferences.

3. Type the XML below into preferences.xml:

<?xmlversion="1.0"encoding="utf-8"?>
<PreferenceScreenxmlns:android="http://schemas.android.com/apk/res/android">

<CheckBoxPreference
android:key="sound"
android:title="Sound"
android:defaultValue="true"
android:summary="Turn the sound on or off"/>

<EditTextPreference
android:key="victory_message"
android:summary=""
android:defaultValue="@string/result_human_wins"
android:title="Victory message"/>

<ListPreference
android:key="difficulty_level"
android:title="Difficulty level"
android:summary=""
android:defaultValue="@string/difficulty_expert"
android:entries="@array/list_difficulty_level"
android:entryValues="@array/list_difficulty_level"/>

</PreferenceScreen>

The PreferenceScreen can have any number of preferences inside it. If there are a large number of
preferences which can be organized by type, it’s a good idea to organize these into PreferenceCategory’s.
Since we only have three preferences, a PreferenceCategory is not necessary.

Note that some of the default values for the preferences are coming strings that we defined earlier in the
strings.xml file (result_human_wins and difficulty_expert). The ListPreference will get its values
from an arrays.xml file we’ll create in the next step.

4. Create a res/values/arrays.xml file with the following XML:

Frank McCown at Harding University 2


<?xmlversion="1.0"encoding="utf-8"?>
<resources>

<string-arrayname="list_difficulty_level">
<item>@string/difficulty_easy</item>
<item>@string/difficulty_harder</item>
<item>@string/difficulty_expert</item>
</string-array>

</resources>

This file is creating a list that will be displayed when selecting the difficulty level, and it uses strings from the
strings.xml file rather than hard-coding text that may need to be changed sometime in the future.

5. Now add a Java class to your project calledSettings which extends the PreferenceActivity class. This
class shouldspecify in the onCreate()methodthat thepreferences.xmlfile will be used to display the list of
preferences:

publicclass Settings extends PreferenceActivity {

@Override
protectedvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
}

Now we need to display the settings which we’ll do in the next step.

Display the Settings

Since we are using the Preferences to set the difficulty level, we don’t need to do this using the application
menu anymore. We’ll replace the Difficulty menu button with one that displays the settings.

1. In res/menu/options_menu.xml, replace the Difficulty menu with a Settings menu option. You will also
need to add an image to res/drawable for representing the settings option.

2. In AndroidTicTacToe.java, remove the final int DIALOG_DIFFICULTY_ID and the code for creating the
difficulty level dialog box in onCreateDialog().

3. Replace the code for handing the Difficulty menu option in onOptionsItemSelected() with code that will
launch the Settings activity. It will use an Intent to start the activity:

case R.id.settings:
startActivityForResult(new Intent(this, Settings.class), 0);
returntrue;

The startActivityForResult() function indicates that we would like to be notified when the Settings
activity is exited (when the user presses the Android device’s Back button). This is important since we’ll
need to change some class-level variables if the sound is turned on or off or if the difficulty level is changed.

Frank McCown at Harding University 3


There’s also a startActivity() function which could be used if we don’t need to be notified when the
activity is closed.

4. Now create the onActivityResult()method which will be called when the Settings activity is exited. The
resultCode will be set to RESULT_CANCELED if the user clicks the Back button on their device.

@Override
protectedvoid onActivityResult(int requestCode, int resultCode, Intent data) {

if (requestCode == RESULT_CANCELED) {
// Apply potentially new settings

mSoundOn = mPrefs.getBoolean("sound", true);

String difficultyLevel = mPrefs.getString("difficulty_level",


getResources().getString(R.string.difficulty_harder));

if (difficultyLevel.equals(getResources().getString(R.string.difficulty_easy)))
mGame.setDifficultyLevel(TicTacToeGame.DifficultyLevel.Easy);
elseif (difficultyLevel.equals(getResources().getString(R.string.difficulty_harder)))
mGame.setDifficultyLevel(TicTacToeGame.DifficultyLevel.Harder);
else
mGame.setDifficultyLevel(TicTacToeGame.DifficultyLevel.Expert);
}
}
}

The mSoundOn variable from above is not yet defined.It is left to you to declare this class-level boolean
variable and use it to determine if the mHumanMediaPlayer or mComputerMediaPlayer should be played.

5. In order for the Intent to work, you must also modify the app’s AndroidManifest.xml file, adding the
following inside the <application> tags which indicate the Settings activity should be included as part of
this app:

<!-- Allow the Settings activity to be launched -->


<activityandroid:name=".Settings" android:label="Settings"></activity>

6. Thesound settings and the difficulty level need to be setin AndroidTicTacToe’sonCreate() method like
so:

// Restore the scores from the persistent preference data source


mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
mSoundOn = mPrefs.getBoolean("sound", true);
String difficultyLevel = mPrefs.getString("difficulty_level",
getResources().getString(R.string.difficulty_harder));
if (difficultyLevel.equals(getResources().getString(R.string.difficulty_easy)))
mGame.setDifficultyLevel(TicTacToeGame.DifficultyLevel.Easy);
elseif (difficultyLevel.equals(getResources().getString(R.string.difficulty_harder)))
mGame.setDifficultyLevel(TicTacToeGame.DifficultyLevel.Harder);
else
mGame.setDifficultyLevel(TicTacToeGame.DifficultyLevel.Expert);

Note that the code segment above initializes the mPrefs variable differently than was done in the previous
tutorial. This is so the PreferenceActivity and the AndroidTicTacToe activity can both be using the
same settings.

Frank McCown at Harding University 4


7. Finally, change the code in the endGame() function to display the victory message preference:

elseif (winner == 2) {
mHumanWins++;
mHumanScoreTextView.setText(Integer.toString(mHumanWins));
String defaultMessage = getResources().getString(R.string.result_human_wins);
mInfoTextView.setText(mPrefs.getString("victory_message", defaultMessage));
}

Run your app andlaunch the Settings screen by selecting the Menu and pressing Settings. Verify that you can
now turn the sound on and off, set the victory message, and set the difficulty level. We’ve gained a lot of
functionality with little code.

You can view the settings using the File Explorer in the DDMS perspective in Eclipse. Look under the
data/data/edu.harding.tictactoe/shared_prefs directory, and you will see a file called
edu.harding.tictactoe_preferences.xml.

Displaying Setting Information

There is one thing that would make the settings a little more user friendly… displaying the actual victory
message and difficulty level inside the settings menu. That way the user doesn’t have to actually select the
setting just to see what it is set to. In other words, we want to change the settings on the left to look like the
settings on the right:

1. In the Settings.java file, add the following code to the onCreate() method:

Frank McCown at Harding University 5


final SharedPreferences prefs =
PreferenceManager.getDefaultSharedPreferences(getBaseContext());

final ListPreference difficultyLevelPref = (ListPreference)findPreference("difficulty_level");


String difficulty = prefs.getString("difficulty_level",
getResources().getString(R.string.difficulty_expert));
difficultyLevelPref.setSummary((CharSequence) difficulty);

difficultyLevelPref.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
@Override
publicboolean onPreferenceChange(Preference preference, Object newValue) {
difficultyLevelPref.setSummary((CharSequence) newValue);

// Since we are handling the pref, we must save it


SharedPreferences.Editor ed = prefs.edit();
ed.putString("difficulty_level", newValue.toString());
ed.commit();
returntrue;
}
});

The code above first sets the difficulty level’s setting’s summary to the currently selected difficulty level.
Then it creates an OnPreferenceChangeListener which will execute when the difficulty level is changed.
This is necessary so we can change the summary when a different level is selected, but it also makes us
manually have to save the preference.

2. You will need to write similar code to retrieve the victory message, display it as the summary text, and add
an OnPreferenceChangeListener to save the message. The code below will get you started:

finalEditTextPreferencevictoryMessagePref = (EditTextPreference)
findPreference("victory_message");
String victoryMessage = prefs.getString("victory_message",
getResources().getString(R.string.result_human_wins));

Run the app again and verify that the settings now display the current victory message and difficulty level.

Extra Challenge

Create a Board color preference that will allow the user to change the board color. An ideal method is to display
the ColorPickerDialog that is provided in the Android API demos:
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/graphics/ColorPic
kerDialog.html

As shown in the figure below, the user clicks an area around the circle to pick a color and then must click the
circle in the middle to dismiss the dialog. When they return to the game, the board color should match the
selected color.

Frank McCown at Harding University 6


The Settings class will need to instantiate the ColorPickerDialog using the current color setting. The
constructor takes a OnColorChangedListenerwhich is used to notify the caller when the color is selected. You
will need to save the selected color in the same manner in which you saved other preferences. The color should
be applied to the BoardView’s board color. You will need to create getters and setters for the board color in
the BoardView to accomplish this task.

Except as otherwise noted, the content of this document is licensed under the Creative Commons Attribution 3.0 License
http://creativecommons.org/licenses/by/3.0

Frank McCown at Harding University 7

Potrebbero piacerti anche