CHAPTER FOUR: Multiple Activities, Passing Data between Activities, Transitions, Persistent Data

Chapter opener image: © Fon_nongkran/Shutterstock

Introduction

Most apps involve the use of several screens. In this chapter, we learn how to code several activities, how to go from one to another and back, how to share data between activities, how to set up transitions between them, and how to save the state of an app and retrieve it whenever the user starts the app again (i.e., how to make the data persistent). We build a mortgage calculator app as a vehicle to learn all these concepts.

We explore two layout managers: a TableLayout for the first screen and a Relative-Layout for the second screen. In the second screen, we explore more GUI components: radio buttons, which are used to display mutually exclusive choices.

4.1 Model: The Mortgage Class

The Model for this app is the Mortgage class, which encapsulates a mortgage. A typical mortgage has three parameters: the mortgage amount, the interest rate, and the number of years for the mortgage. For simplicity, we assume that the interest rate parameter is the annual rate, and is compounded monthly; we also assume that the monthly payment is constant. TABLE 4.1 shows the flow of money. M is the mortgage amount that we receive at time 0 from the bank. P is the monthly payment that we pay every month to the bank, starting at month 1, and ending at month n*12, where n is the number of years.

If r is annual interest rate, then mR = r/12 is the monthly interest rate. The present value of all the monthly payments discounted using the monthly interest rate of mR is equal to M, the mortgage amount. Thus, we have the following equation between M and P, mR, and n.

Let’s set a = 1/(1 + mR); thus, we have:

TABLE 4.1 Monthly cash flow for a mortgage, M, of n years with monthly payment P

Month 0 1 2 3 4 . . . . . . n*12 — 1 n*12
Mortgage M                
Payment   P P P P . . .   P P
  • M = P((–1 + a + 1 – a n*12 + 1)/(1 – a)) = P(a – a n*12 + 1)/(1 – a)

    • M = P a(1 – a n*12)/(1 – a)) = P(1 – an*12)a/(1 – a)

      • 1 – a = 1 – 1 /(1 + mR) = mR(1 + mR)

Therefore, a/(1 – a) = 1/mR

Thus, M = P(1 – a n*12)/mR; our formula for the monthly payment is:

P = mR * M/(1 – a n*12) where a = 1/(1+mR).

EXAMPLE 4.1 shows the Mortgage class, including its three instance variables, amount, years, and rate (lines 9–11), constructor (lines 13–17), mutators (lines 19–32), and accessors (lines 34–36, 42–44, and 46–48). The monthlyPayment method is at lines 50–54. We have included a DecimalFormat constant MONEY (lines 6–7) so that we can format the mortgage amount, the monthly payment, and the total payment with a dollar sign and two digits after the decimal point. The methods getFormattedAmount (lines 38–40), formattedMonthlyPayment (lines 56-58), and formattedTotalPayment (lines 64–66) return string representations of amount, the monthly payment, and the total payment respectively. The DecimalFormat class, part of the java.text package is imported at line 3. We choose not to provide a formatting method for the interest rate because we want to show its exact value in the app.

EXAMPLE 4.1 The Mortgage class

4.2 Using a TableLayout for the Front Screen GUI: Mortgage Calculator App, Version 0

We use the empty activity template for this app. The View part of the Model-View-Controller for this app consists of two screens. The first screen displays the characteristics of a mortgage: the amount, the number of years, the interest rate, the monthly payment, and the total payments over the course of the mortgage. The user does not interact with this screen, it is read-only. We want to display all this information as a table of six rows and two columns. For each of the first five rows, the first column is a label describing the data displayed in the second column. The last row is a button that will enable the user to go to another screen to update the data for the mortgage amount, number of years, and interest rate. FIGURE 4.1 shows a preview of Version 0 of the app inside the environment.

We use a TableLayout to manage the first screen of the app. A TableLayout arranges its children in rows and columns. It typically contains TableRow elements, and each of those defines a row. The row with the most columns determines the number of columns for the layout. A cell is defined as the rectangular area of a row spanning over one or more columns. We can also use a View or one of its subclasses instead of a TableRow, in which case the View will span the entire row. A TableLayout has very few XML attributes of its own, other than those inherited from its ancestor classes, such as LinearLayout, ViewGroup, and View. FIGURE 4.2 shows the inheritance hierarchy.

FIGURE 4.1 Preview of the Mortgage Calculator app, Version 0

FIGURE 4.2 Inheritance hierarchy for TableLayout and TableRow

We want to use a standard margin of 16 pixels for both our TableLayout and our RelativeLayout. Thus, we modify the dimens.xml file and create a dimen element named activity_margin with value 16dp as shown in EXAMPLE 4.2.

EXAMPLE 4.2 The dimens.xml file

EXAMPLE 4.3 shows the activity_main.xml file, using a TableLayout to define the GUI with six TableRow elements. At line 7, we specify a margin around the TableLayout of 16 pixels, using the dimen element activity_margin previously defined in dimens.xml. The top three rows are defined at lines 10–19, 21–30, and 32–41 and show the mortgage parameters. Each row contains two TextView elements; the first one is a descriptive label and the second one shows the corresponding data. For the first TextView element of each new TableRow element, we specify a padding of 10 dip (lines 15, 26, 37) to space out the GUI component in the new row from the element above and from the left edge of the screen. We assign text to all these TextView elements using Strings (at lines 14, 18, 25, 29, 36, 40) that are defined in the strings.xml file (EXAMPLE 4.4). We use default values $100000, 30 years, and 3.5% in strings.xml for the mortgage parameters; the corresponding monthly payment is $449.04 and the total payment over 30 years is $161654.66. Since we intend to later update by code the text inside the TextView elements in the second column, we give each of them an id (lines 17, 28, and 39) so that we can retrieve them using the findViewById method. The findViewById method returns a reference to the GUI component whose id is the argument of the method.

EXAMPLE 4.3 The activity_main.xml file

At lines 43–46, we insert a View element that is 5 pixels high and red. This defines a thin red rectangle that spans the width of the screen, showing as a red line. This enables us to separate the top area of the screen displaying the mortgage parameters from the bottom area of the screen showing the calculated data (i.e., monthly payment and total payment). These are shown in the next two rows. The XML code for these two rows is similar to the code for the first three rows, using the same padding, some Strings from strings.xml, and ids.

The last row shows a button. Since there is only one element in that row, we center the row at line 73 using the android:gravity attribute with value center. We specify 50 dip for the padding above the row (line 74) to better separate the button from the row above it. At line 77, we specify modifyData as the method that will be called when the user clicks on the button.

EXAMPLE 4.4 The strings.xml file

We want the font size for all elements to be larger than the default font size, thus, we specify a text size of 22sp at line 3 of the file styles.xml (EXAMPLE 4.5). This font size may work well for some Android devices and not as well for others, but we do not worry about this issue in this app—we cover that topic later in the book. The AppTheme style, at line 2, is the default style specified in the AndroidManifest.xml file as the theme for the app.

EXAMPLE 4.5 The styles.xml file

In order to keep this app simple, we only allow the app to run in vertical orientation. Thus, inside the AndroidManifest.xml file, we add an android:screenOrientation attribute to the activity element and set its value to portrait.

4.3 Using a RelativeLayout for the Second Screen GUI

The second component of the View part of the app is the second screen. It enables the user to change the three mortgage parameters: the amount, the number of years, and the interest rate. We use this opportunity to explore radio buttons, which we use to select the number of years for the mortgage.

When we first create a project, it automatically creates an Activity class and the XML file (whose default names are MainActivity and activity_main.xml) for its GUI. Since we are adding a second screen to the app, we add another XML file, activity_data.xml, to define its GUI, and another Activity class, DataActivity, to control it. Be sure to create the activity_data.xml file in the res/layout directory, and do not use uppercase letters in the file name. If we need to separate nouns in our file names, we use an underscore character.

For the GUI of this second screen, we use the RelativeLayout class, a subclass of View-Group, as shown in Figure 4.2. It enables us to position components relative to other components.

The RelativeLayout class has a public static inner class named LayoutParams (we refer to it using RelativeLayout.LayoutParams) that contains many XML attributes that specifically relate to arranging elements relative to each other. These attributes enable us to position a View relative to another View on the screen. In order to reference another View, we can give that View an id and use that id to reference it. We can also position a View with respect to its parent View, in which case we do not need to reference the parent View with its id. Some of these attributes are listed in TABLE 4.2. The first eight attributes listed (android:layout_alignLeft, to android:layout_toRightOf) expect their value to be the id of a View.

For example, at line 15 of EXAMPLE 4.6, we use the code:

android:layout_toRightOf=”@+id/label_years”

in order to specify that the radio buttons group is positioned to the right of the View whose id is label_years. That View, a TextView, is defined at lines 8–12, and its id is specified at line 9.

At line 18, we use the code

android:layout_alignLeft=”@+id/data_rate”

to further define the position of the radio buttons group. This specifies that it is left-aligned with the View whose id is data_rate. That View, an EditText, is defined at lines 67–76, and its id is defined at line 68.

Thus, when we use a RelativeLayout, we typically assign ids to a lot of View elements for the purpose of referring to them when we define the position of other View elements relative to them.

We can use the last four attributes listed in Table 4.2 to position a View relative to its parent View. The attributes android:layout_alignParentLeft and android:layout_align-ParentRight relate to vertical alignment, while android:layout_alignParentBottom and android:layout_alignParentTop relate to horizontal alignment.

TABLE 4.2 Useful XML attributes of RelativeLayout.LayoutParams

XML attribute Description
android:layout_alignLeft View’s left edge matches the value of the view’s left edge.
android:layout_alignRight View’s right edge matches the value of the view’s right edge.
android:layout_alignBottom View’s bottom edge matches the value of the view’s bottom edge.
android:layout_alignTop View’s top edge matches the value of the view’s top edge.
android:layout_above View goes above the value view.
android:layout_below View goes below the value view.
android:layout_toLeftOf View goes left of the value view.
android:layout_toRightOf View goes right of the value view.
android:layout_alignParentLeft If true, view’s left edge matches its parent’s left edge.
android:layout_alignParentRight If true, view’s right edge matches its parent’s right edge.
android:layout_alignParentBottom If true, view’s bottom edge matches its parent’s bottom edge.
android:layout_alignParentTop If true, view’s top edge matches its parent’s top edge.

EXAMPLE 4.6 The activity_data.xml file

At line 54, we use the code

android:layout_alignParentRight=”true”

to specify that the EditText should be right aligned with its parent, the RelativeLayout, which is a View, and in this case encompasses the whole screen; so that means that the EditText’s right edge should be vertically aligned with the right edge of the screen.

At line 81, we center the button element horizontally. At line 82, we specify that it should be positioned below the View whose id is data_rate and that it should be 50dp below it (line 83). We also specify that the method goBack will execute when the user clicks on the button (line 84).

We specify both EditText’s android:inputType attribute to be numberDecimal (lines 57 and 76). In this way, the user can only enter digits and a maximum of one. (dot) character.

EXAMPLE 4.7 shows the updated strings.xml, with some String variables added at lines 16–21. The strings are used in the activity_data.xml file at lines 25, 30, 36, 56, 75, and 85 (Example 4.6).

At this point, when we run the app, we can only see the first screen, because we have no way to go to the second screen, yet. However, we can temporarily modify the statement in MainActivity.java that sets the resource to be used for the first screen in MainActivity.java and show the second screen instead. We show that at lines 11–12 of EXAMPLE 4.8.

EXAMPLE 4.7 The updated strings.xml file

EXAMPLE 4.8 Edits in the MainActivity class, replacing activity_main with activity_data

FIGURE 4.3 shows what the second screen looks like inside the environment.

4.4 Connecting Two Activities: Mortgage Calculator App, Version 1

We have now defined and coded the Model and the View part of the app, for which we have two XML files defining two Views. Next, we code the Controller part of the app. We want to be able to navigate back and forth between the two Views that we have created. For this, we need to complete the following steps:

FIGURE 4.3 Preview of the second screen of our Mortgage Calculator app, Version 0

  • ▸ Add some code in the first Activity class so that we can go to the second View via some user interaction, in this case when the user clicks on the Modify Data button.

  • ▸ Add a new class, DataActivity, which extends Activity, to manage the second View. Include the code so that we can dismiss that activity and go back to the first View when the user clicks on the Done button.

  • ▸ Add another activity element (for the second activity) in the AndroidManifest.xml file.

The Intent and Activity classes provide the functionality to start a new activity and go back to a previous activity. The Intent class encapsulates the concept of an operation to be performed. It is typically used to launch a new activity, and can also be used to launch a service.

TABLE 4.3 shows one of the Intent constructors. It accepts two parameters: context, a Context parameter, and cls, a Class parameter. Cls represents the type of class that this Intent intends to execute. In this case, it is an argument whose type should be Class. Context represents the context of this application package. The Activity class is a subclass of Context, thus, an Activity object “is a” Context object. Typically, if we are already executing an activity from the current application package, the current Activity object, represented by the keyword this, is therefore a Context object as well, and is used as the first argument of this constructor.

TABLE 4.3 Constructor of the Intent class

Intent Constructor
  1. Intent( Context context, Class <?> cls )

    1. Constructs an Intent that is intended to execute a class modeled by type ? (probably some Activity class) that is in the same application package as context

TABLE 4.4 Methods of the Activity class

Activity Class Methods
  1. public void startActivity( Intent intent )

    1. Launches a new activity using intent as the Intent to start it.

  1. public void finish( )

    1. Closes this activity and pops it off the stack; the screen for the prior activity is shown.

TABLE 4.4 shows the startActivity and finish methods of the Activity class. StartActivity is typically called by the current Activity object reference to execute its Intent argument.

EXAMPLE 4.9 shows the MainActivity class, including the modifyData method at lines 16–19, which tells the app to go to a DataActivity. In order to do that, we do two things:

  • ▸ Create an Intent to go to a DataActivity

  • ▸ Execute that Intent and start that DataActivity

The Intent class belongs to the android.content package. We import it at line 3. At line 17, we instantiate myIntent, passing this and DataActivity.class (whose type is Class) to the Intent constructor. At line 18, we call startActivity with myIntent, and thus start a new activity of type DataActivity.

To create a second activity, we create a new class, named DataActivity, which extends AppCompatActivity. EXAMPLE 4.10 shows the DataActivity class. It has a similar onCreate method to the one in the MainActivity class, using the resource activity_data instead of activity_main at line 11. The method goBack, called when the user clicks on the Done button, is coded at lines 14–16. In this version, it just calls the finish method, which dismisses this activity, returning the app to the View associated with the main activity. At this point, the DataActivity class only enables the user to display its associated View and to go back to the first View by clicking on the Done button.

EXAMPLE 4.9 The MainActivity class of the Mortgage Calculator app, Version 1

EXAMPLE 4.10 The DataActivity class of the Mortgage Calculator app, Version 1

When we add an activity to an app, we need to add a corresponding activity element to the AndroidManifest.xml file. EXAMPLE 4.11 shows the updated file. We specify the second activity element at lines 20–23.

EXAMPLE 4.11 The AndroidManifest.xml file

The activity tag has many possible attributes. An important one is android:name: it specifies the name of the corresponding Activity class. The syntax is:

android:name=”ActivityClassName”

The value must be specified (there is no default value) and can be a fully qualified class name such as com.jblearning.mortgagev1.MainActivity. If the value starts with a. (dot) as at lines 11 and 21 (.MainActivity and.DataActivity), then the value is appended to the package name listed as the package attribute value of the manifest element (lines 2–3).

We can now run the app and go back and forth between the first and second Views (Figures 4.1 and 4.3). We can also edit the mortgage amount, interest rate, and number of years in the second screen. However, the values in the first View are unchanged at this point. This will change in Version 2.

4.5 The Life Cycle of an Activity

An activity goes through a life cycle, and methods are called automatically as an activity is started, paused, stopped, or closed. TABLE 4.5 lists these methods.

To illustrate which methods are called and when as the user runs the app, we include all the methods of Table 4.5 in our MainActivity and DataActivity classes. Each method calls its super method and outputs something to Logcat. EXAMPLE 4.12 and EXAMPLE 4.13 show the two classes. Note that if we do not call the super methods, the app will crash.

For convenience, we add a constant in each class (lines 10 and 9, respectively) that we use in each of the Log statements. These two constants have the same value, MainActivity, the name of the filter. We could have added a second filter for the DataActivity class, however, in this case, we want to check the order of execution of the activity life cycle methods from both activities. So it is convenient to click on only one filter and see all the outputs at once and in the correct sequence.

TABLE 4.5 Life cycle methods of the Activity class

Method Description
onCreate(Bundle) Called when the activity is created; the Bundle argument stores the activity’s previously frozen state, if there is one.
onStart() Called after onCreate, when the activity becomes visible.
onResume() Called after onStart, when the user starts interacting with the activity.
onPause() Called when android starts or resumes another activity.
onStop() Called when the activity becomes invisible to the user.
onRestart() Called when the activity is about to restart.
onDestroy() Called when the activity has ended or is being destroyed by the system because the system is running out of memory and needs to free some memory.

EXAMPLE 4.12 The MainActivity class with its life cycle methods

EXAMPLE 4.13 The DataActivity class with its life cycle methods

An activity remains in memory until it is destroyed, at which time the onDestroy method is called. Activities are organized on a stack—whenever a new activity is started, it goes to the top of the stack. When an activity is destroyed, it is popped off the stack.

TABLE 4.6 shows the state of the output and the activity stack as the user starts the app and interacts with the app on the device.

When the app starts, the onCreate, onStart, and onResume methods of MainActivity, the starting activity, are called in that order. When the user touches the Modify Data button, the onPause method of MainActivity is called, then the onCreate, onStart, and onResume methods of the DataActivity class are called, and then the onStop method of MainActivity is called. The onDestroy method of the MainActivity class is not called, because the instance of MainActivity is still in memory and at the bottom of the activity stack. The instance of Data-Activity is now at the top of the stack. When the user touches the Done button, the onPause method of DataActivity is called, then the onRestart, onStart, and onResume methods of MainActivity are called, and then the onStop and onDestroy methods of DataActivity are called. The call to onDestroy shows that the DataActivity instance, previously at the top of the stack, is popped off the stack and is no longer in memory. The call to the onRestart method of MainActivity shows that the MainActivity instance, now at the top of the stack (the only one on the stack at this point), is restarted. Note that the onCreate method is not called because the MainActivity instance was created before and is still in memory.

At that point, if the user just waits and stops interacting with the app, the app goes to the background and is no longer visible; the onPause and onStop methods of MainActivity, the current activity, are called. Then, when the user touches the Power button and swipes the screen, the onRestart, onStart, and onResume methods of MainActivity are called as the current activity of the app comes to the foreground.

TABLE 4.6 Output and state of Activity stack as the user interacts with the app

Action Output Activity Stack
User starts app Inside MainActivity:onCreate
Inside MainActivity:onStart
Inside MainActivity:onResume
Main activity
User touches the Modify Data button Inside MainActivity:onPause
Inside DataActivity:onCreate
Inside DataActivity:onStart
Inside DataActivity:onResume
Inside MainActivity:onStop
Data activityMain activity
User touches the Done button Inside DataActivity:onPause
Inside MainActivity:onRestart
Inside MainActivity:onStart
Inside MainActivity:onResume
Inside DataActivity:onStop
Inside DataActivity:onDestroy
Main activity
User waits a while, app goes to the background, is no longer visible Inside MainActivity:onPause
Inside MainActivity:onStop
Main activity
User touches the device’s Power button, then swipes the screen Inside MainActivity:onRestart
Inside MainActivity:onStart
Inside MainActivity:onResume
Main activity
User hits the device’s Home Key button MainActivity:onPause
MainActivity:onStop
Main activity
User touches the app icon Inside MainActivity:onRestart
Inside MainActivity:onStart
Inside MainActivity:onResume
Main activity
User touches the device’s Back Key button Inside MainActivity:onPause
Inside MainActivity:onStop
Inside MainActivity:onDestroy
 

Then, if the user touches the Home button, onPause and onStop are called. When the user touches the app icon on the screen, the app restarts, thus, onRestart, onStart, and onResume are called again.

Finally, if the user touches the Back Key button, onPause, onStop, and onDestroy are called and we exit the current activity. The activity stack is now empty and we exit the app.

4.6 Sharing Data between Activities: Mortgage Calculator App, Version 2

In Version 2, we add functionality to the Controller in order to have a fully functional app. For this, we need to be able to pass the values input by the user in the second View to the activity managing the first View so that we can compute the monthly and total payments and display them. When we go back to the second View to edit the values again, we need to retrieve and show the most recent values, not the default values.

There are several ways that we can pass data from one activity to another, including:

  • ▸ Pass data using the putExtra methods of the Intent class. Data must be either primitive data types or Strings.

  • ▸ Declare a public static instance of a class of the Model (in this app, the Mortgage class) in one Activity class. That makes that instance globally accessible by any other Activity class.

  • ▸ Rewrite the Mortgage class as a “singleton” class so that all the Activity classes can access and share the same object.

  • ▸ Write data to a file and read it from that file.

  • ▸ Write data to a SQLite database and read it from it.

In this app, we want to share a Mortgage object between the two screens, rather than sharing primitive data types or Strings. Thus, we will not use the putExtra methods of Intent. Later in the book, we show how to use the putExtra methods. Writing data to a file or a SQLite database is an overkill if we only want to pass data between two activities.

A singleton class is a class from which only one object can be instantiated. We can declare several object references of that class, but after instantiation, they will all point to the same object in memory. Thus, activities can share that same object, reading data from it and writing data to it. We could recode the Mortgage class so that it is a singleton class, but if we write other apps, we may want to be able to instantiate more than one Mortgage object. Thus, we decide not to implement the Mortgage class as a singleton.

We implement the second strategy, the most simple for this app. We declare a public static variable of type Mortgage in the MainActivity class and we access it from the DataActivity class. In MainActivity, we have the following declaration:

public static Mortgage mortgage;

We can access it inside the DataActivity class using the expression:

MainActivity.mortgage

In this way, the same Mortgage object can be referenced from both Activity classes. This is what we want for this app, only one Mortgage object rather than two identical Mortgage objects.

EXAMPLE 4.14 shows the updated MainActivity class. We declare the Mortgage variable mortgage as public and static at line 10. It is instantiated at line 14 inside the onCreate method.

The onStart method (lines 18–21) is called automatically when we start the app, when we come back from the data activity, or when we bring back the main activity to the foreground after it went to the background. We want the data to be updated every time those events happen, so we call the updateView method at line 20. The updateView method, coded at lines 23–34, updates the five TextView elements with current mortgage data. We retrieve each TextView element using the findViewById method and typecast the returned View to a TextView. Then we call methods from the Mortgage class with the mortgage object in order to set the text of each TextView element with current mortgage data. For example, at line 31, we set the text inside the TextView displaying the monthly payment. We call the formattedMonthlyPayment method of the Mortgage class with the mortgage object in order to retrieve the monthly payment value. We then call the setText method with monthlyTV and pass that value.

EXAMPLE 4.14 The MainActivity class, Mortgage Calculator app, Version 2

EXAMPLE 4.15 shows the updated DataActivity class. It adds two functionalities:

  • ▸ It updates the mortgage parameters displayed in the View controlled by this Activity.

  • ▸ It updates the mortgage object of the MainActivity class when the user leaves this activity.

The updateView method (lines 16–30) updates the various elements of the View controlled by this Activity based on the values of the three instance variables of the static variable mortgage of the MainActivity class. It first gets a reference to the mortgage object of the Main-Activity class at line 17. It then updates the states of the radio buttons at lines 18–24 based on the value of the years instance variable of mortgage. If that value is 10 (line 18), we turn on the 10 years radio button (line 20). If that value is 15 (line 21), we turn on the 15 years radio button (line 23).Otherwise, we do nothing, because the 30 years radio button’s state is specified as on in the activity_data.xml file. Since the three radio buttons are defined in the activity_data file as part of a RadioGroup element, they are mutually exclusive—turning one on automatically turns the others off. We use the findViewById method to retrieve the radio buttons. The EditText element displaying the mortgage amount is updated at line 27, and the EditText element displaying the interest rate is updated at line 29.

EXAMPLE 4.15 The DataActivity class of the Mortgage Calculator app, Version 2

The goBack method (lines 57–60), executes when the user clicks on the Done button. Before the user leaves this activity (line 59) and returns to the main activity, we want to update the state of the mortgage object based on the values the user inputs. We call the method updateMortgageObject at line 58. As in the updateView method, the first thing we do inside the updateMortgageObject method (lines 32–55) is get a reference to the mortgage object. Then, we update the values of its instance variables amount, years, and rate. At lines 34–41, we update years based on the current state of the three radio buttons. We call the method isChecked, inherited by RadioButton from CompoundButton, to check if a radio button is on or off. At line 42, we get a reference to the EditText element displaying the mortgage amount and retrieve its text value and assign it to the String variable amountString at line 43. We do the same for the interest rate value and assign the value retrieved to the String variable rateString at line 45. Because the amount and rate instance variable of the mortgage object are floats, we need to convert the two Strings to floats. In the activity_data.xml file, we specified numberDecimal for the android:inputType attribute associated with the two EditTexts; therefore, we are guaranteed to get Strings that look like floats. However, we still take the extra precaution of using try and catch blocks when converting the two Strings to floats at lines 46–54. We use default values for amount and rate in the catch block.

FIGURE 4.4 and FIGURE 4.5 show the two screens after the user has updated the mortgage parameters on the second screen.

FIGURE 4.4 The Mortgage Calculator app running inside the emulator, Version 2 (second screen)

FIGURE 4.5 The Mortgage Calculator app running inside the emulator, Version 2 (first screen after coming back from the second screen)

4.7 Transitions between Activities: Mortgage Calculator App, Version 3

We want to improve Version 2 by adding animated transitions between the two screens.

A transition is typically an animation special effect when going from one screen to another: for example, we can fade out of the current screen into the new one, or fade in to present the new screen, or bring a screen with a sliding motion from left to right (or right to left). Two types of animations can be used: tween animation and frame animation. A tween animation is defined with its starting and ending points, and intermediary frames are automatically generated. A frame animation is defined by using a sequence of various images from the beginning to the end of the animation.

In Version 3, we make a tween animation that slides from left to right to go from the first to the second screen, and a combination of fade in and scaling transitions to come back from the second screen to the first one. Like a layout, a String, or a style, a transition can be defined as a resource defined in an XML file. It can also be defined programmatically.

When looking for resources, the Android framework looks inside the res directory. We create a directory named anim in the res directory, and add two XML files, slide_from_left.xml and fade_in_and_scale.xml in it. FIGURE 4.6 shows the directory structure. R represents the res directory, and we access these two resources using the expressions R.anim.fade_in_and_scale and R.anim.slide_from_left. The Android framework automatically creates fade_in_and_scale and slide_from_left as public static int constants in the anim class, itself a public static inner class of the R class.

The abstract class Animation is the root class for animation classes. It defines some XML attributes that we can use to define an animation using an XML file. It also defines some methods we can use to define the animation by code. It has five direct subclasses: AnimationSet, Alpha-Animation, RotateAnimation, ScaleAnimation, and TranslateAnimation. TABLE 4.7 shows these five classes and their corresponding XML elements. An AnimationSet can be used to define a group of animations to be run concurrently. We can also run several animations sequentially by using several AnimationSets in sequence.

An XML animation file must have a single root element such as <alpha>, <rotate>, <translate>, <scale>, or <set>. We can use the element to nest other elements inside it and define several animations that run concurrently.

TABLE 4.8 shows some selected XML attributes and their meaning for the XML elements in Table 4.7. The android:duration and the android:interpolator attributes are common to all animations. The android:interpolator attribute specifies a resource that defines the smoothness of the animation, in particular its acceleration or deceleration. The default is linear speed, or no acceleration.

FIGURE 4.6 The directory structure showing the transition XML files

TABLE 4.7 Selected animation XML elements and their corresponding classes

XML Element Class Description
set AnimationSet Defines a set of (concurrent) animations
alpha AlphaAnimation A fade in or fade out animation
rotate RotateAnimation A rotating animation around a fixed point
scale ScaleAnimation A scaling animation from a fixed point
translate TranslateAnimation A sliding (horizontal or vertical) animation

When assigning values to these attributes, we can use either absolute values or relative values. A relative value can be relative to the element itself using the syntax value%, for example, 30%, or can be relative to its parent using the syntax value%p, for example, 50%p.

TABLE 4.8 Selected XML attributes of the various Animation classes

XML Element XML Attribute Description
  android:duration Amount of time in milliseconds the animation should run
  android:interpolator An interpolator to apply to the animation
alpha android:fromAlpha Starting opacity value between 0.0 and 1.0
alpha android:toAlpha Ending opacity value between 0.0 and 1.0
rotate android:fromDegrees Starting angle of the rotation
rotate android:toDegrees Ending angle of the rotation
rotate android:pivotX X-coordinate of the fixed point of the rotation
rotate android:pivotY Y-coordinate of the fixed point of the rotation
scale android:fromXScale Starting X scaling value between 0.0 and 1.0
scale android:toXScale Ending X scaling value between 0.0 and 1.0
scale android:fromYScale Starting X scaling value of between 0.0 and 1.0
scale android:toYScale Ending Y scaling value between 0.0 and 1.0
scale android:pivotX X-coordinate of fixed point when scaling takes place
scale android:pivotY Y-coordinate of fixed point when scaling takes place
translate android:fromXDelta X-coordinate of the starting point of the translation
translate android:toXDelta X-coordinate of the ending point of the translation
translate android:fromYDelta Y-coordinate of the starting point of the translation
translate android:toYDelta Y-coordinate of the ending point of the translation

EXAMPLE 4.16 shows the sliding from the left side of the screen transition. For a horizontal sliding transition, we define the starting x-coordinate using the attribute android:fromXDelta and the ending x-coordinate using the attribute android:toXDelta, which should be set to 0. The android:fromXDelta value should be negative if the screen comes in left to right (and positive if the screen comes in right to left). The two values are defined at lines 5 and 6. The time of the transition is defined using the attribute android:duration; its value is in milliseconds. Line 7 defines a transition lasting 4 seconds.

EXAMPLE 4.16 The slide_from_left.xml file

EXAMPLE 4.17 shows a fade in and scaling transitions that run concurrently. They are both defined inside a set element.

For the fade animation (lines 4–7), we use an alpha element and we define the starting opacity using the android:fromAlpha attribute and the ending opacity using the android:toAlpha attribute. For a full fade in, the starting opacity is 0 and the ending opacity is 1. They are defined at lines 5 and 6. Line 7 defines a transition lasting 3 seconds.

For the scaling animation (lines 9–16), we use a scale element and we define the starting and ending x and y scaling values using the android:fromXScale, android:toXScale, android:-fromYScale, and android:toYScale attributes. Usually, we want to finish with scale 1. Thus, the values of android:toXScale and android:toYScale are both 1.0 (lines 11 and 13). For a full scaling animation, we specify 0.0 for android:fromXScale and android:fromYScale (lines 10 and 12). We define the pivot point to be the center of the scaling animation. The android:pivotX and android:pivotY attributes specify the x- and y-coordinates of that pivot point. If we want to define the scaling animation as starting on the top left corner and expanding toward the bottom right corner, we set these two values to 0.0. If we want to define the scaling animation to start at the center of the screen and expanding outward, we use a relative value and set these two values to 50% (lines 14 and 15). Since we run both animations concurrently, we specify the same duration, 3 seconds (line 16), as we specified for the fade in animation.

EXAMPLE 4.17 The fade_in_and_scale.xml file

TABLE 4.9 The overridePendingTransition method of the Activity class

Method Description
void overridePendingTransition (int enterAnimResource, int exitAnimResource) The enterAnimResource and exitAnimResource parameters, both resource ids, specify the animations to enter a new activity and exit the current activity, respectively. A value 0 specifies no animation.

The overridePendingTransition method, inherited from the Activity class, shown in TABLE 4.9, allows us to specify one or two transitions when switching from one activity to another. It should be called immediately after calling startActivity (to start a new activity) or finish (to go back to the previous activity).

In the MainActivity class, the method modifyData (EXAMPLE 4.18) includes the code to go to the second screen. We call overridePendingTransition at line 39 and specify the slide_from_left resource as the animation to use to transition to the second screen. The value 0 for the second argument specifies that no animation is used to transition from the first screen.

EXAMPLE 4.18 The modifyData method in the MainActivity class

In the DataActivity class, the method goBack (EXAMPLE 4.19) includes the code to go back to the first screen. We call overridePendingTransition at line 60 and specify the fade_in_and_scale resource to use to transition to the first screen and no transition from the current screen.

EXAMPLE 4.20 shows part of the R.java, which is automatically generated. Inside the project, it is located in the app/build/generated/source/r/debug/com/jblearning/mortgagev3 directory.

EXAMPLE 4.19 The goBack method in the DataActivity class

EXAMPLE 4.20 Selected contents of the R.java file

This file should not be modified. Among other things, it includes public static classes containing constants for the transitions, the ids, the layouts, the strings, etc.

If we run the app, we can see the sliding to the left transition going to the second screen, and the fade in and scaling transition coming back to the first screen (shown in FIGURE 4.7).

FIGURE 4.7 The Mortgage Calculator app in the middle of the fade_in_and_scale transition, Version 3

4.8 Handling Persistent Data: Mortgage Calculator App, Version 4

In Version 4 of the app, we want to make the data chosen by the user persistent. When the user uses the app for the first time, we show the default values for the three mortgage parameters, the mortgage amount, the interest rate, and the number of years. But when the user uses the app again, we want to show the values that were used the last time the user used the app.

In order to implement that functionality, we write to a file on the device the mortgage parameters every time they are changed. When we start the app the first time, the file does not exist and we use the default parameters for the mortgage. When we run the app afterward, we read the mortgage parameters from the file. Although we could use the openFileOutput and openFileInput methods of the ContextWrapper class to open a file for writing and reading, it is easier to use the user preferences system in order to store and retrieve persistent data. Preferences for an app are organized as a set of key/value pairs, like a hashtable. In this app, since we have three values for a mortgage, we have three key/value pairs.

TABLE 4.10 Selected methods of the SharedPreferences.Editor interface

Method Description
SharedPreferences. Editor putInt(String key, int value) Associates value with key in this SharedPreferences.Editor. These key/value pairs should be committed by calling either the commit or apply method. Returns this SharedPreferences.Editor so that method calls can be chained.
SharedPreferences. Editor putFloat(String key, float value) Associates value with key in this SharedPreferences.Editor. These key/value pairs should be committed by calling either the commit or apply method. Returns this SharedPreferences.Editor so that method calls can be chained.
boolean commit() Commits the preferences changes made by this SharedPreferences.Editor (using putDataType method calls) to the corresponding SharedPreferences object.

The SharedPreferences interface includes the functionality to write to and read from the user preferences. Its static inner interface, Editor, enables us to store user preferences. TABLE 4.10 shows some of its methods. The putDataType methods have this general method header:

public SharedPreferences.Editor putDataType( String key, DataType
value )

It associates value with key in this SharedPreferences.Editor. The data type can be either a primitive data type or a String. In order to actually write to the user preferences, we need to call the commit or apply method. Assuming we have a SharedPreferences.Editor reference named editor, in order to associate the value 10 with the key rating, we write:

// editor is a SharedPreferences.Editor
editor.putInt( ”rating”, 10 );

To retrieve data previously written to the user preferences, we use the getDataType methods of the SharedPreferences interface. TABLE 4.11 shows some of them. The getDataType methods have this general method header:

public DataType getDataType( String key, DataType defaultValue )

The return value is the value that was previously associated with key when user references were written to. If the key does not exist, defaultValue is returned. Assuming we have a Shared-Preferences reference named pref, in order to retrieve the value that was previously associated with the key rating and written to the preferences, we write:

// pref is a SharedPreferences
int storedRating = pref.getInt( ”rating”, 1 );

TABLE 4.11 Selected methods of the SharedPreferences interface

Method Description
int getInt(String key, int defaultValue) Returns the int value associated with key in this SharedPreferences object. Returns defaultValue if the key is not found.
float getFloat(String key, float defaultValue) Returns the float value associated with key in this SharedPreferences object. Returns defaultValue if the key is not found.

TABLE 4.12 The getDefaultSharedPreferences method of the PreferenceManager class

Method Description
static SharedPreferences getDefaultSharedPreferences(Context context) Returns the SharedPreferences for context.

We can use the getDefaultSharedPreferences static method of the PreferenceManager class, shown in TABLE 4.12, in order to get a SharedPreferences reference. Since the Activity class inherits from Context and our MainActivity and DataActivity classes inherit from Activity, we can pass the keyword this as the argument of this method. Thus, inside an Activity class, in order to get a SharedReferences inside our two classes, we can write:

SharedPreferences pref =
  ReferenceManager.getDefaultSharedPreferences( this );

The View components of our app are still the same. Most of the changes take place in the Model. We modify the Mortgage class, so that it includes a method to write mortgage data to the user preferences system and a constructor to read data from it. In both the MainActivity and DataActivity classes, which make up the Controller parts of the app, we use these methods to either load or write the mortgage parameters from and to the user preferences system.

EXAMPLE 4.21 shows the updated parts of the Mortgage class. The SharedPreferences interface and the PreferenceManager class are imported at lines 5–6. Lines 11–13 define three String constants that hold the preferences key names for amount, years, and rate.

The method setPreferences is coded at lines 84–93. We include a Context parameter so we can pass it to the getDefaultSharedPreferences method. When we call the setPreferences method from the DataActivity class using the Mortgage object reference mortgage, we will pass this. The Context class is imported at line 4.

At lines 86–87, we call the getDefaultSharedPreferences in order to obtain a SharedPreferences reference. At line 88, we call the edit method and get a SharedPreferences.Editor reference. With it, we write our mortgage data at lines 89–91 using the three keys defined at lines 11–13. At line 92, we call commit to actually write to the preferences.

EXAMPLE 4.21 The Mortgage class, Mortgage Calculator app, Version 4

We add an overloaded constructor at lines 26–33. We read mortgage data from the preferences at lines 30–32 and call the mutators in order to assign the three values read to the amount, years, and rate instance variables. If a key is not found, we specify an appropriate default value.

There is only one line of code to change in the MainActivity class: the statement that instantiates mortgage. Instead of using the default constructor, we use the overloaded constructor of the Mortgage class (line 14 of EXAMPLE 4.22). The argument this represents the current MainActivity, therefore an Activity, and therefore a Context object reference.

EXAMPLE 4.22 The onCreate method in the MainActivity class, Mortgage Calculator app, Version 4

There is also only one line of code to add to the DataActivity class: a statement that writes the data in mortgage to the user preferences for this app. We do this toward the end of the updateMortgage-Object method by calling the setPreferences method with mortgage, once again passing this as its argument (line 51 of EXAMPLE 4.23). The updateMortgageObject method is called right after the user has updated the mortgage parameters on the second screen and before going back to the first screen.

EXAMPLE 4.23 The updateMortgageObject method in the DataActivity class, Mortgage Calculator app, Version 4

As we can see from the previous example, it is simple to implement persistent data for this app. Other than one line of code in each Activity class (the two Controllers for this app), we coded two methods, one writing to a file, and the other one reading from a file, in our Model, the Mortgage class. The two Views remain unchanged. Simple updates and improvements are one of the benefits of the Model-View-Controller architecture.

When an app writes to the user preferences, it writes to the device file system. Generally, when we release an app to Google Play that requires interaction with a device, we may need to include a uses-permission element in the AndroidManifest.xml file so that the app operates correctly. Furthermore, before somebody downloads the app, he or she is informed that the app writes to the device’s file system. The syntax for such an element is:

<uses-permission android:name=”permissionName” />

The android:name attribute is the name of the permission: its value relates to the fact that the app wants to use a function or service of the device, for example, its camera, its list of contacts, or its ability to read or send SMS messages. There are many values that can be assigned to this attribute, for example android.permission.CAMERA, android.permission.READ_CONTACTS, android.permission.FLASHLIGHT, or in this app’s case android.permission. WRITE_EXTERNAL_STORAGE.

For this app, since we are writing on the device file system, we need to include the following inside the manifest element in AndroidManifest.xml (note that this is not necessary when we run the app in the emulator):

<uses-permission android:name=”android.permission.WRITE_EXTERNAL_
STORAGE” />

When we run the app the second time, the data that we entered on the second screen the first time we ran the app is now shown on the first screen. The app is pulling data from the preferences that were written into the first time we ran the app.

Chapter Summary

  • The Android framework provides layouts to help us organize a View.

  • Layouts are subclasses of ViewGroup.

  • A TableLayout arranges its children in rows and columns.

  • A RelativeLayout positions components relative to other components.

  • We can call the startActivity method of the Activity class, passing an Intent argument, to start a new Activity for that Intent.

  • Activities are managed on a stack—the most recently started Activity is on top of the stack.

  • We can call the finish method to close an Activity. This pops it off the stack and the app returns to the previous Activity.

  • An Activity goes through a life cycle, and methods are called automatically as an activity is started, paused, stopped, or closed.

  • There are many ways to either pass data or share data between two activities, which include: using the putExtra methods of the Intent class, using a singleton class for the Model, or using a global variable representing the Model.

  • One way for activities to share data is to declare a public static instance of a class of the Model in one Activity class. In this way, it is global and can be accessed by any other Activity class.

  • A transition is an animation special effect going from one screen to another.

  • The android framework provides classes for fading, scaling, translating, and rotating animations. Animations can be coded in XML files and placed in the anim directory, which should be placed in the res directory.

  • The SharedPreferences interface provides the functionality to write and read preferences to the file system.

  • The getDefaultSharedPreferences static method of the PreferenceManager class returns a SharedPreferences reference.

  • If an app writes to the file system, we need to include a uses-permission element in the AndroidManifest.xml file.

Exercises, Problems, and Projects

Multiple-Choice Exercises

  1. The TableLayout class can be used to organize various GUI components

    • As a table of rows and columns

    • As a table of multiple rows with only one column each

    • As a table of only one row and multiple columns

    • As a table of only one row and one column

  2. The direct superclass of LinearLayout and RelativeLayout is

    • View

    • ViewGroup

    • Layout

    • Object

  3. TableLayout and TableRow are direct subclasses of

    • LinearLayout

    • ViewGroup

    • RelativeLayout

    • View

  4. The RelativeLayout class is a good choice to organize various GUI components

    • To give the components absolute x- and y-coordinates

    • So that we position components relative to other components

    • As a grid of multiple rows and columns

    • It is never a good choice

  5. In what package is the Intent class?

    • java.intent

    • android.widget

    • android.activity

    • android.content

  6. After you have created an Intent for a new activity, what method of the Activity class do you call with that Intent parameter in order to start a new activity?

    • startActivity

    • newActivity

    • startIntent

    • newIntent

  7. What method of the Activity class is automatically called when an activity is about to restart?

    • onCreate

    • onDestroy

    • onRestart

    • onGo

  8. What methods of the Activity class (and in what order) are automatically called when an activity is first created?

    • onCreate

    • onCreate, onStart, and onResume (in that order)

    • onCreate and onResume

    • onStart, onCreate, and onResume (in that order)

  9. What method of the Activity class is automatically called when an activity becomes invisible to the user?

    • onResume

    • onStop

    • onPause

    • onInvisible

  10. Two activities can share the same data

    • No, it is not possible

    • Yes, but it is only possible by writing to and reading from the same file

    • Yes, but it is only possible by writing to and reading from a SQLite database

    • Yes, for example by each accessing a public static instance variable from another class

  11. In what package do we find the Animation class?

    • android.animation

    • android.view

    • android.view.animation

    • android.animation.view

  12. What is not a subclass of the Animation class?

    • ScaleAnimation

    • RotateAnimation

    • AlphaAnimation

    • MoveAnimation

  13. What class do we use to play several animations together?

    • AnimationSequence

    • SequenceAnimation

    • SeveralAnimation

    • AnimationSet

  14. What static method of the class PreferenceManager do we use to get SharedPreferences?

    • getPreferences

    • sharedPreferences

    • getDefaultPreferences

    • getDefaultSharedPreferences

Fill in the Code

  1. Inside a TableLayout element, this code adds a row that contains an EditText and a TextView whose ids are game and player.

    <TableRow
        android:layout_width=”wrap_content”
        android:layout_height=”wrap_content” >
        <!--Your code goes here -->
         
    </TableRow>
    
  2. This code draws a blue line that is 2 pixels thick.

    <!-- blue line; your code goes here -->
    
  3. Inside a TableRow of a RelativeLayout element, this code adds an EditText whose id is age and is positioned to the right of a View whose id is name

    <EditText
        android:layout_width=”wrap_content”
       android:layout_height=”wrap_content”
       <!-- Your code goes here -->
          
       android:inputType=”numberDecimal” />
    
  4. Inside the AndroidManifest.xml file, add an activity element of the type MyActivity class

    <!-- Your code goes here -->
    
  5. Inside an activity, when the user clicks on a button, the method goToSecondActivity executes. Write the code to start a new activity from the SecondActivity class.

    public void goToSecondActivity( View v ) {
    // Your code goes here
    
    }
    
  6. When the user comes back to this activity from another activity, we want the method modifyThisActivity to execute. Override the appropriate method and make the call to the modifyThisActivity method inside it.

    public void modifyThisActivity( ) {
       // this method is already coded
    }
    // Your code goes here
    
  7. This XML file defines a resource for a full scaling transition starting on the top left corner and expanding toward the bottom right corner and lasting 2 seconds.

    <?xml version=”1.0” encoding=”utf-8”?>
    <set xmlns:android=”http://schemas.android.com/apk/res/android”>
      <scale
      android:fromXScale=”0.0”
      android:fromYScale=”0.0”
      <!--Your code goes here -->
    
    </set>
    
  8. This XML file defines a resource for a transition rotating 180 degrees clockwise around the top left corner, finishing in the normal position and lasting five seconds.

    <?xml version=”1.0” encoding=”utf-8”?>
    <set xmlns:android=”http://schemas.android.com/apk/res/android”>
      <rotate
      <!--Your code goes here -->
    
    </set>
    
  9. This code writes the values 45 and ”Hello” to user preferences using the keys number and hi.

    SharedPreferences preferences = PreferenceManager.
    getDefaultSharedPreferences( );
    SharedPreferences.Editor editor = preferences.edit( );
    // Your code goes here
    
  10. This code reads the integer value associated with the key grade and the String value associated with the key course from the user preferences and assigns them to two variables. If the keys do not exist, the default values 80 and CS3 should be assigned to the two variables.

    SharedPreferences preferences = PreferenceManager.
    getDefaultSharedPreferences();
    SharedPreferences.Editor editor = preferences.edit();
    // Your code goes here
    

Write an app

  1. Write an app using two activities: one activity plays TicTacToe, and the other activity asks the user to choose who plays first (X or O) and the colors for the Xs and Os. Include a Model. Include transitions between the two activities.

  2. Write an app using two activities: one activity asks the user to give the answer to a simple math problem—addition, subtraction, or multiplication—the other activity asks the user to choose the arithmetic operation. Include a Model. Include transitions between the two activities.

  3. Write an app using two activities: one activity performs a unit conversion from Celsius to Fahrenheit or Fahrenheit to Celsius, and the other activity asks the user to choose which way to make that conversion. Include a Model. Include transitions between the two activities.

  4. Write an app using two activities: one activity performs a unit conversion from miles to kilometers or kilometers to miles, and the other activity asks the user to choose which way to make that conversion. Include a Model. Include transitions between the two activities.

  5. Write an app using two activities: one activity performs the translation from English to another language of the sentence Hello World, and the other activity asks the user to choose one of five languages for the translation. Include a Model. Include transitions between the two activities.

  6. Write an app using two activities: one activity performs a unit conversion from pounds to kilograms or kilograms to pounds, and the other activity asks the user to choose which way to make that conversion. Include a Model. Include transitions between the two activities. Make the user choice persistent so that next time the user runs the app, his or her previous choice is the default.

  7. Write an app using two activities: one activity performs a currency conversion from dollars to another currency, and the other activity asks the user to choose which currency to use among five currencies. Include a Model. Include transitions between the two activities. Make the user choice persistent so that next time the user runs the app, his or her previous choice is the default.

  8. Write an app using two activities: one activity calculates the monthly payment for a car lease, and the other activity asks the user for the car lease parameters—duration in months, down payment, lease rate, and car value at the end of the lease. Include a Model. Include transitions between the two activities. Make the user choice persistent so that next time the user runs the app, his or her previous choices are the default values when the app starts.

  9. Write an app using two activities: one activity encrypts with a fixed shift (using a Caesar cipher) a text that the user types in a text field, and the other activity asks the user to define the shift, an integer between 1 and 25 (if the shift value is 3, then the word thewill be encrypted into wkh. If the word is zoo, the encrypted word is crr). Assume that only lowercase letters from a to z will be used. Include a Model. Include transitions between the two activities. Make the user choice persistent so that next time the user runs the app, his or her previous shift value is the default value when the app starts.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.217.168