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.
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.
TableLayout
for the Front Screen GUI: Mortgage Calculator App, Version 0We 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.
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.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.
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.
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.
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
.
RelativeLayout
for the Second Screen GUIThe 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. |
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.
FIGURE 4.3 shows what the second screen looks like inside the environment.
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:
▸ 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 |
---|
|
TABLE 4.4 Methods of the Activity
class
Activity Class Methods |
---|
|
|
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.
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.
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.
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. |
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.
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.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.
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.
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.
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.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.
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.
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.
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).
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.
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.
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.
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.
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.
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
The direct superclass of LinearLayout and RelativeLayout is
View
ViewGroup
Layout
Object
TableLayout and TableRow are direct subclasses of
LinearLayout
ViewGroup
RelativeLayout
View
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
In what package is the Intent class?
java.intent
android.widget
android.activity
android.content
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
What method of the Activity class is automatically called when an activity is about to restart?
onCreate
onDestroy
onRestart
onGo
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)
What method of the Activity class is automatically called when an activity becomes invisible to the user?
onResume
onStop
onPause
onInvisible
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
In what package do we find the Animation class?
android.animation
android.view
android.view.animation
android.animation.view
What is not a subclass of the Animation class?
ScaleAnimation
RotateAnimation
AlphaAnimation
MoveAnimation
What class do we use to play several animations together?
AnimationSequence
SequenceAnimation
SeveralAnimation
AnimationSet
What static method of the class PreferenceManager do we use to get SharedPreferences?
getPreferences
sharedPreferences
getDefaultPreferences
getDefaultSharedPreferences
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>
This code draws a blue line that is 2 pixels thick.
<!-- blue line; your code goes here -->
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” />
Inside the AndroidManifest.xml file, add an activity element of the type MyActivity
class
<!-- Your code goes here -->
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 }
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
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>
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>
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
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 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.
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.
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.
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.
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.
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.
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.
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.
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 the
will 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.
3.145.57.254