Mobile Programming - Week 1 Introduction PDF
Document Details
Uploaded by BeautifulIrrational
Ege Üniversitesi
Doç. Dr. Özgün Yılmaz
Tags
Summary
This document introduces mobile programming concepts, focusing on mobile computing and Android development. It outlines platform comparisons and provides steps for project installations and layout configurations using Android Studio. The document also explains the event-driven nature of Android applications and basic UI interactions.
Full Transcript
Week 1 Introduction Mobile Programming Ege Üniversitesi Bilgisayar Mühendisliği Bölümü Mobile Computing Nowadays, it is possible to access data and information almost anywhere and anytime. People spend all hours of the day connected to a cellular network such as 3G, 4G...
Week 1 Introduction Mobile Programming Ege Üniversitesi Bilgisayar Mühendisliği Bölümü Mobile Computing Nowadays, it is possible to access data and information almost anywhere and anytime. People spend all hours of the day connected to a cellular network such as 3G, 4G, etc. with their smart cell phones or tablet computers. All these technological developments have made mobile computing possible and revealed the importance of mobile applications. Doç. Dr. Özgün Yılmaz 2 Mobile Computing It is now easier for users to quickly access the content they want to look at on the internet from their phone and tablet instead of turning on the computer next to them. Smartphones have sufficient computing power. In addition, these devices come with sensors such as accelerometer, GPS sensor, etc. High speed internet has become widespread with 3G, 4G, 5G etc. technologies. Doç. Dr. Özgün Yılmaz 3 Mobile Computing In the light of all these developments, mobile applications and mobile programming are gaining importance. Location awareness concept Desktop -> Web -> Mobile (Native, Hybrid, Web) Doç. Dr. Özgün Yılmaz 4 Desktop – Mobile – Tablet Comparison Doç. Dr. Özgün Yılmaz 5 Masaüstü – Mobil Karşılaştırması Doç. Dr. Özgün Yılmaz 6 Desktop – Mobile Comparison Android: %71,52 iOS:%27.83 Doç. Dr. Özgün Yılmaz 7 Mobile Device Laptops Smartphones Tablet computers E-Readers (e.g. Kindle) Mobile gaming devices (e.g. PSP, Nintendo 3DS) Wearable computers (e.g. Apple Watch) Doç. Dr. Özgün Yılmaz 8 Platforms Android (Google) – Native: Java -> Dalvik VM -> Android Runtime (ART) – Kotlin – Generating revenue through advertisements iOS (Apple) – Native: Objective-C – Swift – Available for MacOS only + $99 per year – Hackintosh Doç. Dr. Özgün Yılmaz 9 Platforms iOS to Android monetization ratio was 6:1 Now the monetization ratio is 2:1 Cross platform development: – Xamarin, React Native, Flutter Doç. Dr. Özgün Yılmaz 10 Android Installation First install Java JDK. Then install Android Studio IDE. Android SDK is in the package. The emulator is in the package. Create a virtual cell phone with AVD Android Device Manager from Tools->Android->AVD Manager menu. Doç. Dr. Özgün Yılmaz 11 Doç. Dr. Özgün Yılmaz 12 Doç. Dr. Özgün Yılmaz 13 Doç. Dr. Özgün Yılmaz 14 Your First Android Application An activity is an instance of Activity, a class in the Android SDK. An activity is responsible for managing user interaction with a screen of information. You write subclasses of Activity to implement the functionality that your app requires. A simple application may need only one subclass; a complex application can have many. GeoQuiz is a simple app, so it will have a single Activity subclass named QuizActivity. QuizActivity will manage the user interface, or UI. Doç. Dr. Özgün Yılmaz 15 Layout A layout defines a set of UI objects and their positions on the screen. A layout is made up of definitions written in XML. Each definition is used to create an object that appears on screen, like a button or some text. GeoQuiz will include a layout file named activity_quiz.xml. The XML in this file will define the UI. Doç. Dr. Özgün Yılmaz 16 Doç. Dr. Özgün Yılmaz 17 GeoQuiz App Doç. Dr. Özgün Yılmaz 18 Layout The default activity layout defines two widgets: a ConstraintLayout and a TextView. Widgets are the building blocks you use to compose a UI. A widget can show text or graphics, interact with the user, or arrange other widgets on the screen. Buttons, text input controls, and checkboxes are all types of widgets. The Android SDK includes many widgets that you can configure to get the appearance and behavior you want. Every widget is an instance of the View class or one of its subclasses (such as TextView or Button). Doç. Dr. Özgün Yılmaz 19 Layout For details about ConstraintLayout read: https://developer.android.com/training/constraint- layout Add – a vertical LinearLayout – a TextView – a horizontal LinearLayout – two Buttons Doç. Dr. Özgün Yılmaz 20 Doç. Dr. Özgün Yılmaz 21 Doç. Dr. Özgün Yılmaz 22 The root element of this layout’s view hierarchy is a LinearLayout. – As the root element, the LinearLayout must specify the Android resource XML namespace at http://schemas.android.com/apk/res/android. LinearLayout inherits from a subclass of View named ViewGroup. – A ViewGroup is a widget that contains and arranges other widgets. You use a LinearLayout when you want widgets arranged in a single column or row. Other ViewGroup subclasses are FrameLayout, TableLayout, and RelativeLayout, ConstraintLayout. When a widget is contained by a ViewGroup, that widget is said to be a child of the ViewGroup. The root LinearLayout has two children: a TextView and another LinearLayout. The child LinearLayout has two Button children of its own. Doç. Dr. Özgün Yılmaz 23 layout_width and layout_height android:layout_width and android:layout_height The android:layout_width and android:layout_height attributes are required for almost every type of widget. They are typically set to either match_parent or wrap_content: – match_parent view will be as big as its parent – wrap_content view will be as big as its contents require The TextView is slightly larger than the text it contains due to its android:padding="24dp" attribute. This attribute tells the widget to add the specified amount of space to its contents when determining its size. You are using it to get a little breathing room between the question and the buttons. (Wondering about the dp units? These are density-independent pixels, which you will learn about in Chapter 9.) Doç. Dr. Özgün Yılmaz 24 android:orientation The android:orientation attribute on the two LinearLayout widgets determines whether their children will appear vertically or horizontally. The root LinearLayout is vertical; its child LinearLayout is horizontal. The order in which children are defined determines the order in which they appear on screen. In a vertical LinearLayout, the first child defined will appear topmost. In a horizontal LinearLayout, the first child defined will be leftmost. Doç. Dr. Özgün Yılmaz 25 android:text Notice that the values of these attributes are not literal strings. They are references to string resources. A string resource is a string that lives in a separate XML file called a strings file. You can give a widget a hardcoded string, like android:text="True", but it is usually not a good idea. Placing strings into a separate file and then referencing them is better because it makes localization easy. Doç. Dr. Özgün Yılmaz 26 Now, whenever you refer to @string/false_button in any XML file in the GeoQuiz project, you will get the literal string “False” at runtime. Although the default strings file is named strings.xml, you can name a strings file anything you want. You can also have multiple strings files in a project. As long as the file is located in res/values/, has a resources root element, and contains child string elements, your strings will be found and used. Doç. Dr. Özgün Yılmaz 27 Doç. Dr. Özgün Yılmaz 28 AppCompatActivity is a subclass of Android’s Activity class that provides compatibility support for older versions of Android. You will learn much more about AppCompatActivity in Chapter 13. This file has one Activity method: onCreate(Bundle). The onCreate(Bundle) method is called when an instance of the activity subclass is created. When an activity is created, it needs a UI to manage. To get the activity its UI, you call the following Activity method: – public void setContentView(int layoutResID) This method inflates a layout and puts it on screen. When a layout is inflated, each widget in the layout file is instantiated as defined by its attributes. You specify which layout to inflate by passing in the layout’s resource ID. Doç. Dr. Özgün Yılmaz 29 Resources and Resource IDs A layout is a resource. A resource is a piece of your application that is not code – things like image files, audio files, and XML files. Resources for your project live in a subdirectory of the app/res directory. In the project tool window, you can see that activity_quiz.xml lives in res/layout/. Your strings file, which contains string resources, lives in res/values/. To access a resource in code, you use its resource ID. The resource ID for your layout is R.layout.activity_quiz. Doç. Dr. Özgün Yılmaz 30 app/build/generated/source/r/debug Doç. Dr. Özgün Yılmaz 31 Android generated a resource ID for the entire layout and for each string, but it did not generate IDs for the individual widgets in activity_quiz.xml. Not every widget needs a resource ID. In this chapter, you will only interact with the two buttons in code, so only they need resource IDs. Before generating the resource IDs, switch back to the Android project view. To generate a resource ID for a widget, you include an android:id attribute in the widget’s definition. In activity_quiz.xml, add an android:id attribute to each button. Notice that there is a + sign in the values for android:id but not in the values for android:text. This is because you are creating the IDs and only referencing the strings. Doç. Dr. Özgün Yılmaz 32 Doç. Dr. Özgün Yılmaz 33 Doç. Dr. Özgün Yılmaz 34 Setting listeners Android applications are typically event driven. Unlike command-line programs or scripts, eventdriven applications start and then wait for an event, such as the user pressing a button. (Events can also be initiated by the OS or another application, but user-initiated events are the most obvious.) When your application is waiting for a specific event, we say that it is “listening for” that event. The object that you create to respond to an event is called a listener, and the listener implements a listenerinterface for that event. The Android SDK comes with listener interfaces for various events, so you do not have to write your own. In this case, the event you want to listen for is a button being pressed (or “clicked”), so your listener will implement the View.OnClickListener interface. Doç. Dr. Özgün Yılmaz 35 Doç. Dr. Özgün Yılmaz 36 Using anonymous inner classes Doç. Dr. Özgün Yılmaz 37 Making Toasts Doç. Dr. Özgün Yılmaz 38 Week 2 Mobile Programming Ege Üniversitesi Bilgisayar Mühendisliği Bölümü Modifications to GeoQuiz project add a class named Question to the GeoQuiz project. An instance of this class will encapsulate a single true-false question. Then, create an array of Question objects for QuizActivity to manage. public class Question { private int mTextResId; //The mTextResId variable will hold the resource ID (always an int) of a string resource //for the question. private boolean mAnswerTrue; public Question(int textResId, boolean answerTrue) { mTextResId = textResId; mAnswerTrue = answerTrue; } //Getter and setter } Doç. Dr. Özgün Yılmaz 2 Model View Controller (MVC) Doç. Dr. Özgün Yılmaz 3 Model A model object holds the application’s data and “business logic.” Model classes are typically designed to model the things your app is concerned with, such as a user, a product in a store, a photo on a server, a television show – or a true- false question. Model objects have no knowledge of the UI; their sole purpose is holding and managing data. In Android applications, model classes are generally custom classes you create. All of the model objects in your application compose its model layer. GeoQuiz’s model layer consists of the Question class. Doç. Dr. Özgün Yılmaz 4 View View objects know how to draw themselves on the screen and how to respond to user input, like touches. A simple rule of thumb is that if you can see it on screen, then it is a view. Android provides a wealth of configurable view classes. You can also create custom view classes. An application’s view objects make up its view layer. GeoQuiz’s view layer consists of the widgets that are inflated from activity_quiz.xml. Doç. Dr. Özgün Yılmaz 5 Controller Controller objects tie the view and model objects together. They contain “application logic.” Controllers are designed to respond to various events triggered by view objects and to manage the flow of data to and from model objects and the view layer. In Android, a controller is typically a subclass of Activity, Fragment, or Service. (You will learn about fragments in Chapter 7 and services in Chapter 28.) GeoQuiz’s controller layer, at present, consists solely of QuizActivity. Doç. Dr. Özgün Yılmaz 6 MVC flow with user input Doç. Dr. Özgün Yılmaz 7 Benefits of MVC Separating code into classes helps you design and understand the application as a whole; you can think in terms of classes instead of individual variables and methods. Similarly, separating classes into model, view, and controller layers helps you design and understand an application; you can think in terms of layers instead of individual classes. Although GeoQuiz is not a complicated app, you can still see the benefits of keeping layers separate. In a moment, you are going to update GeoQuiz’s view layer to include a NEXT button. When you do that, you will not need to remember a single thing about the Question class you just created. Doç. Dr. Özgün Yılmaz 8 Benefits of MVC MVC also makes classes easier to reuse. A class with restricted responsibilities is more reusable than one with its fingers in every pie. For instance, your model class, Question, knows nothing about the widgets used to display a true- false question. This makes it easy to use Question throughout your app for different purposes. For example, if you wanted to display a list of all the questions at once, you could use the same object that you use here to display just one question at a time. Doç. Dr. Özgün Yılmaz 9 Add Next Button Update GeoQuiz’s view layer to include a NEXT button. Remove the android:text attribute from the TextView. You no longer want a hardcoded question to be part of its definition. Give the TextView an android:id attribute. This widget will need a resource ID so that you can set its text in QuizActivity’s code. Add the new Button widget as a child of the root LinearLayout. Doç. Dr. Özgün Yılmaz 10 Different screen densities The suffixes on these directory names refer to the screen pixel density of a device: – mdpi medium-density screens (~160dpi) – hdpi high-density screens (~240dpi) – xhdpi extra-high-density screens (~320dpi) – xxhdpi extra-extra-high-density screens (~480dpi) – (There are a few other density categories that are omitted from the solutions, including ldpi and xxxhdpi.) Doç. Dr. Özgün Yılmaz 11 Add icon Doç. Dr. Özgün Yılmaz 12 Drawable Resource In an XML resource, you refer to another resource by its resource type and name. A reference to a string resource begins with @string/. A reference to a drawable resource begins with @drawable/. Doç. Dr. Özgün Yılmaz 13 Rotation GeoQuiz does, however, have a bug. While the app is running, press the NEXT button to show another question. Then rotate the device. If you are running on the emulator, click the rotate left or rotate right button in the floating toolbar to rotate (Figure 2.13). After you rotate, you will see the first question again. How did this happen, and how can you fix it? Doç. Dr. Özgün Yılmaz 14 The Activity Lifecycle Every instance of Activity has a lifecycle. During this lifecycle, an activity transitions between four states: resumed, paused, stopped, and nonexistent. For each transition, there is an Activity method that notifies the activity of the change in its state. Doç. Dr. Özgün Yılmaz 15 Doç. Dr. Özgün Yılmaz 16 Activity States Doç. Dr. Özgün Yılmaz 17 Activity States The resumed state represents the activity the user is currently interacting with. Only one activity across all the apps on the device can be in the resumed state at any given time. Subclasses of Activity can take advantage of the methods named in Figure 3.1 to get work done at critical transitions in the activity’s lifecycle. These methods are often called lifecycle callbacks. You are already acquainted with one of these lifecycle callback methods – onCreate(Bundle). The OS calls this method after the activity instance is created but before it is put on screen. Doç. Dr. Özgün Yılmaz 18 Activity States Typically, an activity overrides onCreate(Bundle) to prepare the specifics of its UI: – inflating widgets and putting them on screen (in the call to (setContentView(int)) – getting references to inflated widgets – setting listeners on widgets to handle user interaction – connecting to external model data It is important to understand that you never call onCreate(Bundle) or any of the other Activity lifecycle methods yourself. You simply override the callbacks in your activity subclass. Then Android calls the lifecycle callbacks at the appropriate time (in relation to what the user is doing and what is happening across the rest of the system) to notify the activity that its state is changing. Doç. Dr. Özgün Yılmaz 19 Lab Çalışması Challenge: Add a Listener to the TextView Challenge: Add a Previous Button Challenge: From Button to ImageButton – To accomplish this challenge, these two widgets must become ImageButtons instead of regular Buttons. – ImageButton is a widget that inherits from ImageView. Button, on the other hand, inherits from TextView. – Figure 2.16 shows their different inheritance hierarchies. Doç. Dr. Özgün Yılmaz 20 ImageButton After you have changed these buttons to ImageButtons, Android Studio will warn you about a missing android:contentDescription attribute. This attribute supports accessibility for users with vision impairments. You set the value to a string, which is read aloud when users have the appropriate settings applied. Add an android:contentDescription attribute to each ImageButton to complete the challenge. Doç. Dr. Özgün Yılmaz 21 Logging the Activity Lifecycle Override lifecycle methods to eavesdrop on QuizActivity’s lifecycle. Each implementation will simply log a message informing you that the method has been called. See how QuizActivity’s state changes at runtime in relation to what the user is doing. Doç. Dr. Özgün Yılmaz 22 Making log messages In Android, the android.util.Log class sends log messages to a shared system-level log. Log has several methods for logging messages. Here is the one that you will use most often in this book: public static int d(String tag, String msg) The d stands for “debug” and refers to the level of the log message. (There is more about the Log levels in the final section of this chapter.) The first parameter identifies the source of the message, and the second is the contents of the message. The first string is typically a TAG constant with the class name as its value. This makes it easy to determine the source of a particular message. Doç. Dr. Özgün Yılmaz 23 Making log messages Doç. Dr. Özgün Yılmaz 24 @Override protected void onCreate(Bundle savedInstanceState) {... } @Override public void onStart() { super.onStart(); Log.d(TAG, "onStart() called"); } @Override public void onResume() { super.onResume(); Log.d(TAG, "onResume() called"); } @Override public void onPause() { super.onPause(); Log.d(TAG, "onPause() called"); } @Override public void onStop() { super.onStop(); Log.d(TAG, "onStop() called"); } @Override public void onDestroy() { super.onDestroy(); Log.d(TAG, "onDestroy() called"); Doç. Dr. Özgün Yılmaz 25 Using Logcat To access the log while the application is running, you can use Logcat, a log viewer included in the Android SDK tools. When you run GeoQuiz, you should see Logcat appear at the bottom of Android Studio, as shown in Figure 3.2. If Logcat is not visible, select the Android Monitor tool window near the bottom of the screen and ensure that the logcat tab is selected. Run GeoQuiz and messages will start materializing in Logcat. By default, log statements that are generated with your app’s package name are shown. You will see your own messages along with some system output. To make your messages easier to find, you can filter the output using the TAG constant. In Logcat, click the dropdown in the top right of the Logcat pane that reads Show only selected application. This is the filter dropdown, which is currently set to show messages from only your app. Selecting No Filters will show log messages generated from all over the system. Doç. Dr. Özgün Yılmaz 26 Using Logcat Three lifecycle methods were called after GeoQuiz was launched and the initial instance of QuizActivity was created: onCreate(Bundle), onStart(), and onResume() (Figure 3.4). Your QuizActivity instance is now in the resumed state (in memory, visible, and active in the foreground). Doç. Dr. Özgün Yılmaz 27 Pressing the Back button Now let’s have some fun. Press the Back button on the device and then check Logcat. Your activity received calls to onPause(), onStop(), and onDestroy() (Figure 3.5). Your QuizActivity instance is now in the nonexistent state (not in memory and thus not visible – and certainly not active in the foreground). Doç. Dr. Özgün Yılmaz 28 Pressing the Back button When you pressed the Back button, you told Android, “I’m done with this activity, and I won’t need it anymore.” Android then destroyed your activity’s view and removed all traces of the activity from memory. This is Android’s way of being frugal with your device’s limited resources. Launch GeoQuiz again by clicking the GeoQuiz app icon. Android creates a new instance of QuizActivity from scratch and calls onCreate(), onStart(), and onResume() to move QuizActivity from nonexistent to resumed. Now press the Home button. The home screen displays and QuizActivity moves completely out of view. What state is QuizActivity in now? Check Logcat for a hint. Your activity received calls to onPause() and onStop(), but not onDestroy() (Figure 3.6). Doç. Dr. Özgün Yılmaz 29 Pressing the Home button Pressing the Home button means the user is telling Android, “I’m going to go look at something else, but I might come back. I’m not really done with this screen yet.” Android pauses and ultimately stops your activity. This means, after pressing Home, your instance of QuizActivity hangs out in the stopped state (in memory, not visible, and not active in the foreground). Android does this so it can quickly and easily restart QuizActivity where you left off when you come back to GeoQuiz later. (This is not the whole story about going Home. Stopped activities can be destroyed at the discretion of the OS. See the section called The Activity Lifecycle, Revisited for the rest of the story.) Doç. Dr. Özgün Yılmaz 30 Overview screen Go back to GeoQuiz by selecting the GeoQuiz task card from the overview screen. To do this, press the Recents button next to the Home button (Figure 3.7). (On devices without a Recents button, long-press the Home button.) Each card in the overview screen represents an app the user has interacted with in the past (Figure 3.8). (The overview screen is often called the “Recents screen” or “task manager” by users. We defer to the developer documentation, which calls it the “overview screen.”) Doç. Dr. Özgün Yılmaz 31 Click on the GeoQuiz task card in the overview screen. QuizActivity will fill the screen. A quick look at Logcat shows that your activity got calls to onStart() and onResume(). Note that onCreate() was not called. This is because QuizActivity was in the stopped state after the user pressed the Home button. Because the activity instance was still in memory, it did not need to be created. Instead, the activity only had to be started (moved to the paused/visible state) and then resumed (moved to the resumed/foreground state). Doç. Dr. Özgün Yılmaz 32 It is also possible for an activity to hang out in the paused state (fully or partially visible, but not in the foreground). The partially visible paused scenario can occur when a new activity with either a transparent background or a smaller-than-screen size is launched on top of your activity. The fully visible scenario occurs in multi-window mode (only available on Android 6.0 Nougat and higher) when the user interacts with a window that does not contain your activity, and yet your activity remains fully visible in the other window. As you continue through the book, you will override the different activity lifecycle methods to do real things for your application. When you do, you will learn more about the uses of each method. Doç. Dr. Özgün Yılmaz 33 Rotation and the Activity Lifecycle Let’s get back to the bug you found at the end of Chapter 2. Run GeoQuiz, press the NEXT button to reveal the second question, and then rotate the device. After rotating, GeoQuiz will display the first question again. Check Logcat to see what has happened. Your output should look like Figure 3.9. Doç. Dr. Özgün Yılmaz 34 When you rotated the device, the instance of QuizActivity that you were looking at was destroyed, and a new one was created. Rotate the device again to witness another round of destruction and rebirth. This is the source of your bug. Each time you rotate the device, the current QuizActivity instance is completely destroyed. The value that was stored in mCurrentIndex in that instance is wiped from memory. This means that when you rotate, GeoQuiz forgets which question you were looking at. As rotation finishes, Android creates a new instance of QuizActivity from scratch. mCurrentIndex is initialized to 0 in onCreate(Bundle), and the user starts over at the first question. Doç. Dr. Özgün Yılmaz 35 Device configurations and alternative resources Rotating the device changes the device configuration. The device configuration is a set of characteristics that describe the current state of an individual device. The characteristics that make up the configuration include screen orientation, screen density, screen size, keyboard type, dock mode, language, and more. Typically, applications provide alternative resources to match device configurations. You saw an example of this when you added multiple arrow icons to your project for different screen densities. Screen density is a fixed component of the device configuration; it cannot change at runtime. On the other hand, some components, like screen orientation, can change at runtime. (There are other configuration changes that can occur at runtime, such as keyboard availability, language, and multiwindow mode.) Doç. Dr. Özgün Yılmaz 36 When a runtime configuration change occurs, there may be resources that are a better match for the new configuration. So, Android destroys the activity, looks for resources that are the best fit for the new configuration, and then rebuilds a new instance of the activity with those resources. To see this in action, let’s create an alternative resource for Android to find and use when the device’s screen orientation changes to landscape. Doç. Dr. Özgün Yılmaz 37 Creating a landscape layout In the project tool window, right-click the new Layout resource file and select New → Android resource file directory. You should see a window similar to Figure 3.10 that lists the resource types and qualifiers for those types. Select layout in the Resource type dropdown. Leave the Source set option set to main. Doç. Dr. Özgün Yılmaz 38 Finally, ensure that Landscape is selected in the Screen orientation dropdown, as shown in Figure 3.11. Verify that the Directory name now indicates that your directory is called layout-land. While this window looks fancy, its purpose is just to set the name of your directory. Click OK and Android Studio will create the res/layout-land/ folder. Doç. Dr. Özgün Yılmaz 39 The -land suffix is another example of a configuration qualifier. Configuration qualifiers on res subdirectories are how Android identifies which resources best match the current device configuration. You can find the list of configuration qualifiers that Android recognizes and the pieces of the device configuration that they refer to at developer.android.com/guide/topics/resources/providingresource s.html. When the device is in landscape orientation, Android will find and use resources in the res/ layout-land directory. Otherwise, it will stick with the default in res/layout/. However, at the moment there are no resources in the res/layout- land directory. Let’s fix that. Doç. Dr. Özgün Yılmaz 40 Copy the activity_quiz.xml file from res/layout/ to res/layout-land/. (If you do not see res/layout-land/ in the project tool window, select Project from the dropdown to switch from the Android view. Just be sure to switch back to the Android view when you are done. You can also copy and paste the file outside of Android Studio using your favorite file explorer or terminal app.) You now have a landscape layout and a default layout. Keep the filename the same. The two layout files must have the same filename so that they can be referenced with the same resource ID. Doç. Dr. Özgün Yılmaz 41 Doç. Dr. Özgün Yılmaz 42 A FrameLayout will replace the top LinearLayout. FrameLayout is the simplest ViewGroup and does not arrange its children in any particular manner. In this layout, child views will be arranged according to their android:layout_gravity attributes. This means that the TextView, LinearLayout, and Button children of the FrameLayout need android:layout_gravity attributes. The Button children of the LinearLayout will stay exactly the same. Doç. Dr. Özgün Yılmaz 43 Saving Data Across Rotation Android does a great job of providing alternative resources at the right time. However, destroying and re-creating activities on rotation can cause headaches, such as GeoQuiz’s bug of reverting back to the first question when the device is rotated. To fix this bug, the post-rotation QuizActivity instance needs to know the old value of mCurrentIndex. You need a way to save this data across a runtime configuration change, like rotation. One way to do this is to override the Activity method: – protected void onSaveInstanceState(Bundle outState) This method is called before onStop(), except when the user presses the Back button. (Remember, pressing Back tells Android the user is done with the activity, so Android wipes the activity from memory completely and does not make any attempt to save data to re-create it.) Doç. Dr. Özgün Yılmaz 44 The default implementation of onSaveInstanceState(Bundle) directs all of the activity’s views to save their state as data in the Bundle object. A Bundle is a structure that maps string keys to values of certain limited types. You have seen this Bundle before. It is passed into onCreate(Bundle): @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);... } When you override onCreate(Bundle), you call onCreate(Bundle) on the activity’s superclass and pass in the bundle you just received. In the superclass implementation, the saved state of the views is retrieved and used to re-create the activity’s view hierarchy. Doç. Dr. Özgün Yılmaz 45 Overriding onSaveInstanceState(Bundle) You can override onSaveInstanceState(Bundle) to save additional data to the bundle and then read that data back in onCreate(Bundle). This is how you are going to save the value of mCurrentIndex across rotation. First, in QuizActivity.java, add a constant that will be the key for the key-value pair that will be stored in the bundle. Next, override onSaveInstanceState(Bundle) to write the value of mCurrentIndex to the bundle with the constant as its key. @Override public void onSaveInstanceState(Bundle savedInstanceState) { super.onSaveInstanceState(savedInstanceState); Log.i(TAG, "onSaveInstanceState"); savedInstanceState.putInt(KEY_INDEX, mCurrentIndex); } Doç. Dr. Özgün Yılmaz 46 Checking bundle in onCreate(Bundle) public class QuizActivity extends AppCompatActivity {... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.d(TAG, "onCreate(Bundle) called"); setContentView(R.layout.activity_quiz); if (savedInstanceState != null) { mCurrentIndex = savedInstanceState.getInt(KEY_INDEX, 0); }... }... } Doç. Dr. Özgün Yılmaz 47 Note that the types that you can save to and restore from a Bundle are primitive types and classes that implement the Serializable or Parcelable interfaces. It is usually a bad practice to put objects of custom types into a Bundle, however, because the data might be stale when you get it back out. It is a better choice to use some other kind of storage for the data and put a primitive identifier into the Bundle instead. Doç. Dr. Özgün Yılmaz 48 The Activity Lifecycle, Revisited Overriding onSaveInstanceState(Bundle) is not just for handling rotation or other runtime configuration changes. An activity can also be destroyed by the OS if the user navigates away for a while and Android needs to reclaim memory (e.g., if the user presses Home and then goes and watches a video or plays a game). Practically speaking, the OS will not reclaim a visible (paused or resumed) activity. Activities are not marked as “killable” until onStop() is called and finishes executing. Stopped activities are fair game to be killed, though. Still, not to worry. If an activity is stopped, that means onSaveInstanceState(Bundle) was called. So resolving the data-loss-across-rotation bug also addresses the situation where the OS destroys your nonvisible activity to free up memory. How does the data you stash in onSaveInstanceState(Bundle) survive the activity’s death? When onSaveInstanceState(Bundle) is called, the data is saved to the Bundle object. That Bundle object is then stuffed into your activity’s activity record by the OS. Doç. Dr. Özgün Yılmaz 49 Doç. Dr. Özgün Yılmaz 50 When your activity is stashed, an Activity object does not exist, but the activity record object lives on in the OS. The OS can reanimate the activity using the activity record when it needs to. Note that your activity can pass into the stashed state without onDestroy() being called. You can rely on onStop() and onSaveInstanceState(Bundle) being called (unless something has gone horribly wrong on the device). Typically, you override onSaveInstanceState(Bundle) to stash small, transient state data that belongs to the current activity in your Bundle. Override onStop() to save any permanent data, such as things the user is editing, because your activity may be killed at any time after this method returns. So when does the activity record get snuffed? When the user presses the Back button, your activity really gets destroyed, once and for all. At that point, your activity record is discarded. Activity records are also discarded on reboot. Doç. Dr. Özgün Yılmaz 51 For the More Curious: Current State of Activity Cleanup As of this writing, activities themselves are not individually destroyed in low- memory situations. Instead, Android clears an entire app process from memory, taking any of the app’s in-memory activities with it. (Each application gets its own process. You will learn more about Android application processes in the section called For the More Curious: Processes vs Tasks in Chapter 24.) Processes containing foreground (resumed) and/or visible (paused) activities get higher priority than other processes. When the OS needs to free up resources, it will select the lower priority processes first. Practically speaking, a process containing a visible activity will not reclaimed by the OS. If a foreground process does get reclaimed, that means something is horribly wrong with the device (and your app being killed is probably the least of the user’s concerns). Doç. Dr. Özgün Yılmaz 52 If you are overriding onSaveInstanceState(Bundle), you should test that your state is being saved and restored as expected. Rotation is easy to test. And, luckily, so is the low-memory situation. Find and click on the Settings icon within the list of applications on the device. When the Settings screen appears, click Developer options (you will need to scroll down until you see the option you are looking for). On the Developer options screen you will see many possible settings. Turn on the setting labeled Don’t keep activities. Doç. Dr. Özgün Yılmaz 53 Lab Çalışması Challenge: Preventing Repeat Answers – Once a user provides an answer for a particular question, disable the buttons for that question to prevent multiple answers being entered. Challenge: Graded Quiz – After the user provides answers for all of the quiz questions, display a Toast with a percentage score for the quiz. – Toast yerine sabit metinde gösterilirse daha iyi olur. Challenge: Reset Düğmesi Doç. Dr. Özgün Yılmaz 54 Hafta 3 481 Mobil Programlama Ege Üniversitesi Bilgisayar Mühendisliği Bölümü Debugging Exceptions and Stack Traces: Expand the Android Monitor tool window so that you can see what has happened. If you scroll up and down in Logcat, you should eventually find an expanse of red, as shown in Figure 4.2. This is a standard AndroidRuntime exception report. If you do not see much in Logcat and cannot find the exception, you may need to select the No Filters option in the filter dropdown. On the other hand, if you see too much in Logcat, you can adjust the Log Level to Error, which will show only the most severe log messages. You can also search for the text “FATAL EXCEPTION,” which will bring you straight to the exception that caused the app to crash. Doç. Dr. Özgün Yılmaz 2 Doç. Dr. Özgün Yılmaz 3 Setting breakpoints To engage the debugger and trigger your breakpoint, you need to debug your app instead of running it. To debug your app, click the debug button (represented by a bug), which is next to the run button. You can also navigate to Run → Debug 'app' in the menu bar. Your device will report that it is waiting for the debugger to attach, and then it will proceed normally. Doç. Dr. Özgün Yılmaz 4 Doç. Dr. Özgün Yılmaz 5 Doç. Dr. Özgün Yılmaz 6 Issues with the R class You are familiar with build errors that occur when you reference resources before adding them or delete resources that other files refer to. Usually, resaving the files once the resource is added or the references are removed will cause Android Studio to rebuild without any fuss. Sometimes, however, these build errors will persist or appear seemingly out of nowhere. If this happens to you, here are some things you can try: – Recheck the validity of the XML in your resource files – Clean your project – Sync your project with Gradle – Run Android Lint Doç. Dr. Özgün Yılmaz 7 Second Activity Create a new activity and a new layout for it. Start an activity from another activity. Starting an activity means asking the OS to create an activity instance and call its onCreate(Bundle) method. Pass data between the parent (starting) activity and the child (started) activity. Doç. Dr. Özgün Yılmaz 8 Listing 5.1 Adding strings (strings.xml)... Incorrect! Are you sure you want to do this? Show Answer Cheat! Cheating is wrong. Choose New → Activity → Empty Activity Set Activity Name to CheatActivity Doç. Dr. Özgün Yılmaz 9 Doç. Dr. Özgün Yılmaz 10 Doç. Dr. Özgün Yılmaz 11 Starting an Activity The simplest way one activity can start another is with the startActivity method: public void startActivity(Intent intent) You might guess that startActivity(Intent) is a static method that you call on the Activity subclass that you want to start. But it is not. When an activity calls startActivity(Intent), this call is sent to the OS. In particular, it is sent to a part of the OS called the ActivityManager. The ActivityManager then creates the Activity instance and calls its onCreate(Bundle) method, as shown in Figure 5.7. How does the ActivityManager know which Activity to start? That information is in the Intent parameter. Doç. Dr. Özgün Yılmaz 12 Doç. Dr. Özgün Yılmaz 13 Communicating with intents An intent is an object that a component can use to communicate with the OS. The only components you have seen so far are activities, but there are also services, broadcast receivers, and content providers. Intents are multipurpose communication tools, and the Intent class provides different constructors depending on what you are using the intent to do. In this case, you are using an intent to tell the ActivityManager which activity to start, so you will use this constructor: public Intent(Context packageContext, Class cls) The Class argument specifies the activity class that the ActivityManager should start. The Context argument tells the ActivityManager which application package the activity class can be found in (Figure 5.8). Doç. Dr. Özgün Yılmaz 14 Doç. Dr. Özgün Yılmaz 15 Within mCheatButton’s listener, create an Intent that includes the CheatActivity class. Then pass the intent into startActivity(Intent) (Listing 5.7). Doç. Dr. Özgün Yılmaz 16 Before starting the activity, the ActivityManager checks the package’s manifest for a declaration with the same name as the specified Class. If it finds a declaration, it starts the activity, and all is well. If it does not, you get a nasty ActivityNotFoundException, which will crash your app. This is why all of your activities must be declared in the manifest. Run GeoQuiz. Press the CHEAT! button, and an instance of your new activity will appear on screen. Now press the Back button. This will destroy the CheatActivity and return you to the QuizActivity. Doç. Dr. Özgün Yılmaz 17 Explicit and implicit intents When you create an Intent with a Context and a Class object, you are creating an explicit intent. You use explicit intents to start activities within your application. It may seem strange that two activities within your application must communicate via the ActivityManager, which is outside of your application. However, this pattern makes it easy for an activity in one application to work with an activity in another application. When an activity in your application wants to start an activity in another application, you create an implicit intent. You can find more about implicit intents in Chapter 15. Doç. Dr. Özgün Yılmaz 18 Passing Data Between Activities Now that you have a QuizActivity and a CheatActivity, you can think about passing data between them. Figure 5.9 shows what data you will pass between the two activities. The QuizActivity will inform the CheatActivity of the answer to the current question when the CheatActivity is started. When the user presses the Back button to return to the QuizActivity, the CheatActivity will be destroyed. In its last gasp, it will send data to the QuizActivity about whether the user cheated. You will start with passing data from QuizActivity to CheatActivity. Doç. Dr. Özgün Yılmaz 19 Using intent extras To inform the CheatActivity of the answer to the current question, you will pass it the value of mQuestionBank[mCurrentIndex].isAnswerTrue() You will send this value as an extra on the Intent that is passed into startActivity(Intent). Extras are arbitrary data that the calling activity can include with an intent. You can think of them like constructor arguments, even though you cannot use a custom constructor with an activity subclass. (Android creates activity instances and is responsible for their lifecycle.) The OS forwards the intent to the recipient activity, which can then access the extras and retrieve the data, as shown in Figure 5.10. Doç. Dr. Özgün Yılmaz 20 An extra is structured as a key-value pair, like the one you used to save out the value of mCurrentIndex in QuizActivity.onSaveInstanceState(Bundle). To add an extra to an intent, you use Intent.putExtra(…). In particular, you will be calling: public Intent putExtra(String name, boolean value) Intent.putExtra(…) comes in many flavors, but it always has two arguments. The first argument is always a String key, and the second argument is the value, whose type will vary. It returns the Intent itself, so you can chain multiple calls if you need to. In CheatActivity.java, add a key for the extra. Doç. Dr. Özgün Yılmaz 21 An activity may be started from several different places, so you should define keys for extras on the activities that retrieve and use them. Using your package name as a qualifier for your extra, as shown in Listing 5.8, prevents name collisions with extras from other apps. Now you could return to QuizActivity and put the extra on the intent, but there is a better approach. There is no reason for QuizActivity, or any other code in your app, to know the implementation details of what CheatActivity expects as extras on its Intent. Instead, you can encapsulate that work into a newIntent(…) method. Doç. Dr. Özgün Yılmaz 22 This static method allows you to create an Intent properly configured with the extras CheatActivity will need. The answerIsTrue argument, a boolean, is put into the intent with a private name using the EXTRA_ANSWER_IS_TRUE constant. You will extract this value momentarily. Using a newIntent(…) method like this for your activity subclasses will make it easy for other code to properly configure their launching intents. Doç. Dr. Özgün Yılmaz 23 You only need one extra, but you can put multiple extras on an Intent if you need to. If you do, add more arguments to your newIntent(…) method to stay consistent with the pattern. To retrieve the value from the extra, you will use: public boolean getBooleanExtra(String name, boolean defaultValue) The first argument is the name of the extra. The second argument of getBooleanExtra(…) is a default answer if the key is not found. Doç. Dr. Özgün Yılmaz 24 Doç. Dr. Özgün Yılmaz 25 Getting a result back from a child activity At this point, the user can cheat with impunity. Let’s fix that by having the CheatActivity tell the QuizActivity whether the user chose to view the answer. When you want to hear back from the child activity, you call the following Activity method: public void startActivityForResult(Intent intent, int requestCode) The first parameter is the same intent as before. The second parameter is the request code. The request code is a user-defined integer that is sent to the child activity and then received back by the parent. It is used when an activity starts more than one type of child activity and needs to know who is reporting back. QuizActivity will only ever start one type of child activity, but using a constant for the request code is a best practice that will set you up well for future changes. In QuizActivity, modify mCheatButton’s listener to call startActivityForResult(Intent, int). Doç. Dr. Özgün Yılmaz 26 Setting a result There are two methods you can call in the child activity to send data back to the parent: – public final void setResult(int resultCode) – public final void setResult(int resultCode, Intent data) Typically, the result code is one of two predefined constants: Activity.RESULT_OK or Activity.RESULT_CANCELED. Setting result codes is useful when the parent needs to take different action depending on how the child activity finished. For example, if a child activity had an OK button and a Cancel button, the child activity would set a different result code depending on which button was pressed. Then the parent activity would take a different action depending on the result code. Calling setResult(…) is not required of the child activity. If you do not need to distinguish between results or receive arbitrary data on an intent, then you can let the OS send a default result code. A result code is always returned to the parent if the child activity was started with startActivityForResult(…). If setResult(…) is not called, then when the user presses the Back button, the parent will receive Activity.RESULT_CANCELED. Doç. Dr. Özgün Yılmaz 27 Sending back an intent In this implementation, you are interested in passing some specific data back to QuizActivity. So you are going to create an Intent, put an extra on it, and then call Activity.setResult(int, Intent) to get that data into QuizActivity’s hands. In CheatActivity, add a constant for the extra’s key and a private method that does this work. Then call this method in the SHOW ANSWER button’s listener. Doç. Dr. Özgün Yılmaz 28 Doç. Dr. Özgün Yılmaz 29 When the user presses the SHOW ANSWER button, the CheatActivity packages up the result code and the intent in the call to setResult(int, Intent). Then, when the user presses the Back button to return to the QuizActivity, the ActivityManager calls the following method on the parent activity: protected void onActivityResult(int requestCode, int resultCode, Intent data) The parameters are the original request code from QuizActivity and the result code and intent passed into setResult(int, Intent). Figure 5.11 shows this sequence of interactions. Doç. Dr. Özgün Yılmaz 30 Doç. Dr. Özgün Yılmaz 31 The final step is to override onActivityResult(int, int, Intent) in QuizActivity to handle the result. However, because the contents of the result Intent are also an implementation detail of CheatActivity, add another method to help decode the extra into something QuizActivity can use. Doç. Dr. Özgün Yılmaz 32 Handling a result In QuizActivity.java, add a new member variable to hold the value that CheatActivity is passing back. Then override onActivityResult(…) to retrieve it, checking the request code and result code to be sure they are what you expect. This, again, is a best practice to make future maintenance easier. Doç. Dr. Özgün Yılmaz 33 Finally, modify the checkAnswer(boolean) method in QuizActivity to check whether the user cheated and to respond appropriately. Doç. Dr. Özgün Yılmaz 34 How Android Sees Your Activities Let’s look at what is going on OS-wise as you move between activities. First, when you click on the GeoQuiz app in the launcher, the OS does not start the application; it starts an activity in the application. More specifically, it starts the application’s launcher activity. For GeoQuiz, QuizActivity is the launcher activity. When the New Project wizard created the GeoQuiz application and QuizActivity, it made QuizActivity the launcher activity by default. Launcher activity status is specified in the manifest by the intent-filter element in QuizActivity’s declaration (Listing 5.18). Doç. Dr. Özgün Yılmaz 35 Doç. Dr. Özgün Yılmaz 36 After the instance of QuizActivity is on screen, the user can press the CHEAT! button. When this happens, an instance of CheatActivity is started – on top of the QuizActivity. These activities exist in a stack (Figure 5.12). Pressing the Back button in CheatActivity pops this instance off the stack, and the QuizActivity resumes its position at the top, as shown in Figure 5.12. Doç. Dr. Özgün Yılmaz 37 A call to Activity.finish() in CheatActivity would also pop the CheatActivity off the stack. If you run GeoQuiz and press Back from the QuizActivity, the QuizActivity will be popped off the stack and you will return to the last screen you were viewing before running GeoQuiz (Figure 5.13). If you started GeoQuiz from the launcher application, pressing the Back button from QuizActivity will return you to the launcher (Figure 5.14). Doç. Dr. Özgün Yılmaz 38 What you are seeing here is that the ActivityManager maintains a back stack and that this back stack is not just for your application’s activities. Activities for all applications share the back stack, which is one reason the ActivityManager is involved in starting your activities and lives with the OS and not your application. The stack represents the use of the OS and device as a whole rather than the use of a single application. Doç. Dr. Özgün Yılmaz 39 Challenge: Closing Loopholes for Cheaters GeoQuiz has a few major loopholes. Here are the loopholes in order, from easiest to hardest to close: – Users can rotate CheatActivity after they cheat to clear out the cheating result. – Once they get back from CheatActivity, users can rotate QuizActivity to clear out mIsCheater. – Users can press NEXT until the question they cheated on comes back around. Doç. Dr. Özgün Yılmaz 40 Android SDK Versions and Compatibility Doç. Dr. Özgün Yılmaz 41 Android devices running older versions are not immediately upgraded or replaced when a newer version is available. Why do so many devices still run older versions of Android? Most of it has to do with heavy competition among Android device manufacturers and US carriers. Carriers want features and phones that no other network has. Device manufacturers feel this pressure, too – all of their phones are based on the same OS, but they want to stand out from the competition. The combination of pressures from the market and the carriers means that there is a bewildering array of devices with proprietary, one-off modifications of Android. A device with a proprietary version of Android is not able to run a new version of Android released by Google. Instead, it must wait for a compatible proprietary upgrade. That upgrade might not be available until months after Google releases its version, if it is ever available at all. Manufacturers often choose to spend resources on newer devices rather than keeping older ones up to date. Doç. Dr. Özgün Yılmaz 42 Compatibility and Android Programming The delay in upgrades combined with regular new releases makes compatibility an important issue in Android programming. To reach a broad market, Android developers must create apps that perform well on devices running Android Version Oreo (8) through 15, and any more recent versions of Android, as well as on different device form factors. Targeting different sizes of devices is easier than you might think. Phone screens are a variety of sizes, but the Android layout system does a good job at adapting. Doç. Dr. Özgün Yılmaz 43 Doç. Dr. Özgün Yılmaz 44 In addition to the minimum supported version, you can also set the target version and the build version. All of these properties are set in the build.gradle file in your app module. The build version lives exclusively in this file. The minimum SDK version and target SDK version are set in the build.gradle file, but are used to overwrite or set values in your AndroidManifest.xml. Open the build.gradle file that exists in your app module. Notice the values for compileSdkVersion, minSdkVersion, and targetSdkVersion. Doç. Dr. Özgün Yılmaz 45 Minimum SDK version The minSdkVersion value is a hard floor below which the OS should refuse to install the app. By setting this version to API level 19 (KitKat), you give Android permission to install GeoQuiz on devices running KitKat or higher. Android will refuse to install GeoQuiz on a device running, say, Jelly Bean. Doç. Dr. Özgün Yılmaz 46 Target SDK version The targetSdkVersion value tells Android which API level your app is designed to run on. Most often this will be the latest Android release. When would you lower the target SDK? New SDK releases can change how your app appears on a device or even how the OS behaves behind the scenes. If you have already designed an app, you should confirm that it works as expected on new releases. Check the documentation at developer.android.com/reference/android/os/Build.VERSION_CODES.html to see where problems might arise. Then you can modify your app to work with the new behavior or lower the target SDK. Not increasing the target SDK when a new version of Android is released ensures that your app will still run with the appearance and behavior of the targeted version on which it worked well. This option exists for compatibility with newer versions of Android, as changes in subsequent releases are ignored until the targetSdkVersion is increased. Doç. Dr. Özgün Yılmaz 47 Compile SDK version The last SDK setting is labeled compileSdkVersion in Listing 6.1. This setting is not used to update the AndroidManifest.xml file. Whereas the minimum and target SDK versions are placed in the manifest when you build your app to advertise those values to the OS, the compile SDK version is private information between you and the compiler. Android’s features are exposed through the classes and methods in the SDK. The compile SDK version, or build target, specifies which version to use when building your own code. When Android Studio is looking to find the classes and methods you refer to in your imports, the build target determines which SDK version it checks against. The best choice for a build target is the latest API level (currently 33, T). However, you can change the build target of an existing application if you need to. For instance, you might want to update the build target when a new version of Android is released so that you can make use of the new methods and classes it introduces. Doç. Dr. Özgün Yılmaz 48 Adding code from later APIs safely The difference between GeoQuiz’s minimum SDK version and build SDK version leaves you with a compatibility gap to manage. For example, what happens if you call code from an SDK version that is later than the minimum SDK of KitKat (API level 19)? When your app is installed and run on a KitKat device, it will crash. This used to be a testing nightmare. However, thanks to improvements in Android Lint, potential problems caused by calling newer code on older devices can be caught at compile time. If you use code from a higher version than your minimum SDK, Android Lint will report build errors. Doç. Dr. Özgün Yılmaz 49 Doç. Dr. Özgün Yılmaz 50 The Build.VERSION.SDK_INT constant is the device’s version of Android. You then compare that version with the constant that stands for the Lollipop release. (Version codes are listed at http://developer.android.com/reference/android/os/Build.VERSION_CO DES.html.) Now your circular reveal code will only be called when the app is running on a device with API level 21 or higher. You have made your code safe for API level 19, and Android Lint should now be content. Run GeoQuiz on a Lollipop or higher device, cheat on a question, and check out your new animation. You can also run GeoQuiz on a KitKat device (virtual or otherwise). It will not have the circular animation, but you can confirm that the app still runs safely. Doç. Dr. Özgün Yılmaz 51 Using the Android Developer Documentation It is a good idea to get comfortable using the developer documentation right away. There is far too much in the Android SDKs to keep in your head, and, with new versions appearing regularly, you will need to learn what is new and how to use it. The Android developer documentation is an excellent and voluminous source of information. The main page of the documentation is developer.android.com. It is split into three parts: Design, Develop, and Distribute. It is all worth perusing when you get a chance. The Design section of the documentation includes patterns and principles for the UI design of your apps. The Develop section contains documentation and training. The Distribute section shows you how to prepare and publish your apps on Google Play or through open distribution. Doç. Dr. Özgün Yılmaz 52 Training Beginning and advanced developer training modules, including downloadable sample code API Guides Topic-based descriptions of app components, features, and best practices Reference Searchable, linked documentation of every class, method, interface, attribute constant, etc. in the SDK Samples Sample code demonstrating some examples of how to use the APIs Android Studio Information about the Android Studio IDE Android NDK Descriptions and links about the Native Development Kit, which allows you to write code in C and C++ Google Services Information about Google’s proprietary APIs, including Google Maps and Google Cloud Messaging Doç. Dr. Özgün Yılmaz 53 Doç. Dr. Özgün Yılmaz 54 Hafta 4 Mobile Programming Ege Üniversitesi Bilgisayar Mühendisliği Bölümü UI Fragments and the Fragment Manager In this chapter, you will start building an application named CriminalIntent. CriminalIntent records the details of “office crimes” – things like leaving dirty dishes in the breakroom sink or walking away from an empty shared printer after documents have printed. With CriminalIntent, you can make a record of a crime including a title, a date, and a photo. You can also identify a suspect from your contacts and lodge a complaint via email, Twitter, Facebook, or another app. After documenting and reporting a crime, you can proceed with your work free of resentment and ready to focus on the business at hand. It will have a list-detail interface: The main screen will display a list of recorded crimes, and users will be able to add new crimes or select an existing crime to view and edit its details (Figure 7.1). Doç. Dr. Özgün Yılmaz 2 Doç. Dr. Özgün Yılmaz 3 The Need for UI Flexibility You might imagine that a list-detail application consists of two activities: one managing the list and the other managing the detail view. Clicking a crime in the list would start an instance of the detail activity. Pressing the Back button would destroy the detail activity and return you to the list, where you could select another crime. That would work, but what if you wanted more sophisticated presentation and navigation between screens? – Imagine that your user is running CriminalIntent on a tablet. Tablets and some larger phones have screens large enough to show the list and detail at the same time – at least in landscape orientation (Figure 7.2). – Imagine the user is viewing a crime on a phone and wants to see the next crime in the list. It would be better if the user could swipe to see the next crime without having to return to the list. Doç. Dr. Özgün Yılmaz 4 Doç. Dr. Özgün Yılmaz 5 Each swipe should update the detail view with information for the next crime. What these scenarios have in common is UI flexibility: the ability to compose and recompose an activity’s view at runtime depending on what the user or the device requires. Activities were not built to provide this flexibility. An activity’s views may change at runtime, but the code to control those views must live inside the activity. As a result, activities are tightly coupled to the particular screen being used. Doç. Dr. Özgün Yılmaz 6 Introducing Fragments You can get around the latter of the Android law by moving the app’s UI management from the activity to one or more fragments. A fragment is a controller object that an activity can deputize to perform tasks. Most commonly, the task is managing a UI. The UI can be an entire screen or just one part of the screen. A fragment managing a UI is known as a UI fragment. A UI fragment has a view of its own that is inflated from a layout file. The fragment’s view contains the interesting UI elements that the user wants to see and interact with. The activity’s view contains a spot where the fragment’s view will be inserted. In fact, while in this chapter the activity will host a single fragment, an activity can have several spots for the views of several fragments. Doç. Dr. Özgün Yılmaz 7 You can use the fragment(s) associated with the activity to compose and recompose the screen as your app and users require. The activity’s view technically stays the same throughout its lifetime, and no laws of Android are violated. Let’s see how this would work in a list-detail application to display the list and detail together. You would compose the activity’s view from a list fragment and a detail fragment. The detail view would show the details of the selected list item. Selecting another item should display a new detail view. This is easy with fragments; the activity will replace the detail fragment with another detail fragment (Figure 7.3). No activities need to die for this major view change to happen. Doç. Dr. Özgün Yılmaz 8 Doç. Dr. Özgün Yılmaz 9 Using UI fragments separates the UI of your app into building blocks, which is useful for more than just list-detail applications. Working with individual blocks, it is easy to build tab interfaces, tack on animated sidebars, and more. Achieving this UI flexibility comes at a cost: more complexity, more moving parts, and more code. Doç. Dr. Özgün Yılmaz 10 CriminalIntent Doç. Dr. Özgün Yılmaz 11 Doç. Dr. Özgün Yılmaz 12 Doç. Dr. Özgün Yılmaz 13 Two types of fragments Fragments were introduced in API level 11 along with the first Android tablets and the sudden need for UI flexibility. You must choose which implementation of fragments that you want use: native fragments or support fragments. The native implementation of fragments is built into the device that the user runs your app on. If you support many different versions of Android, each of those Android versions could have a slightly different implementation of fragments (for example, a bug could be fixed in one version and not the versions prior to it). The support implementation of fragments is built into a library that you include in your application. This means that each device you run your app on will depend on the same implementation of fragments no matter the Android version. In CriminalIntent, you will use the support implementation of fragments. Detailed reasoning for this decision is laid out at the end of the chapter in the section called For the More Curious: Why Support Fragments Are Superior. Doç. Dr. Özgün Yılmaz 14 Adding dependencies in Android Studio You will use the implementation of fragments that comes with the AppCompat library. The AppCompat library is one of Google’s many compatibility libraries that you will use throughout this book. You will learn much more about the AppCompat library in Chapter 13. To use the AppCompat library, it must be included in your list of dependencies. Your project comes with two build.gradle files, one for the project as a whole and one for your app module. Open the build.gradle file located in your app module. Doç. Dr. Özgün Yılmaz 15 Doç. Dr. Özgün Yılmaz 16 In the current dependencies section of your build.gradle file, you should see something similar to Listing 7.1 that specifies that the project depends on all of the.jar files in its libs directory. You will also see dependencies for other libraries that are automatically included when projects are created with Android Studio, most likely including the AppCompat library. Gradle allows for the specification of dependencies that you have not copied into your project. When your app is compiled, Gradle will find, download, and include the dependencies for you. All you have to do is specify an exact string incantation and Gradle will do the rest. If you do not have the AppCompat library listed in your dependencies, Android Studio has a tool to help you add the library and come up with this string incantation. Navigate to the project structure for your Project (File → Project Structure...). Doç. Dr. Özgün Yılmaz 17 The fragment lifecycle The fragment lifecycle is similar to the activity lifecycle: It has stopped, paused, and resumed states, and it has methods you can override to get things done at critical points – many of which correspond to activity lifecycle methods. The correspondence is important. Because a fragment works on behalf of an activity, its state should reflect the activity’s state. Thus, it needs corresponding lifecycle methods to handle the activity’s work. One critical difference between the fragment lifecycle and the activity lifecycle is that fragment lifecycle methods are called by the hosting activity, not the OS. The OS knows nothing about the fragments that an activity is using to manage things. Fragments are the activity’s internal business. You will see more of the fragment lifecycle methods as you continue building CriminalIntent. Doç. Dr. Özgün Yılmaz 18 Two approaches to hosting You have two options when it comes to hosting a UI fragment in an activity: – add the fragment to the activity’s layout – add the fragment in the activity’s code The first approach is known as using a layout fragment. It is straightforward but inflexible. If you add the fragment to the activity’s layout, you hardwire the fragment and its view to the activity’s view and cannot swap out that fragment during the activity’s lifetime. Doç. Dr. Özgün Yılmaz 19 The second approach, adding the fragment to the activity’s code, is more complex – but it is the only way to have control at runtime over your fragments. You determine when the fragment is added to the activity and what happens to it after that. You can remove the fragment, replace it with another, and then add the first fragment back again. Thus, to achieve real UI flexibility you must add your fragment in code. This is the approach you will use for CrimeActivity’s hosting of a CrimeFragment. The code details will come later in the chapter. First, you are going to define CrimeActivity’s layout. Doç. Dr. Özgün Yılmaz 20 There are a couple of things to notice in this implementation. First, Fragment.onCreate(Bundle) is a public method, whereas Activity.onCreate(Bundle) is protected. Fragment.onCreate(Bundle) and other Fragment lifecycle methods must be public, because they will be called by whatever activity is hosting the fragment. Second, similar to an activity, a fragment has a bundle to which it saves and retrieves its state. You can override Fragment.onSaveInstanceState(Bundle) for your own purposes just as you can override Activity.onSaveInstanceState(Bundle). Doç. Dr. Özgün Yılmaz 21 Also, note what does not happen in Fragment.onCreate(Bundle): You do not inflate the fragment’s view. You configure the fragment instance in Fragment.onCreate(Bundle), but you create and configure the fragment’s view in another fragment lifecycle method: – public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) This method is where you inflate the layout for the fragment’s view and return the inflated View to the hosting activity. The LayoutInflater and ViewGroup parameters are necessary to inflate the layout. The Bundle will contain data that this method can use to re-create the view from a saved state. In CrimeFragment.java, add an implementation of onCreateView(…) that inflates fragment_crime.xml. You can use the same trick from Figure 7.16 to fill out the method declaration. Doç. Dr. Özgün Yılmaz 22 Doç. Dr. Özgün Yılmaz 23 Within onCreateView(…), you explicitly inflate the fragment’s view by calling LayoutInflater.inflate(…) and passing in the layout resource ID. The second parameter is your view’s parent, which is usually needed to configure the widgets properly. The third parameter tells the layout inflater whether to add the inflated view to the view’s parent. You pass in false because you will add the view in the activity’s code. Doç. Dr. Özgün Yılmaz 24 Adding a UI Fragment to the FragmentManager When the Fragment class was introduced in Honeycomb, the Activity class was changed to include a piece called the FragmentManager. The FragmentManager is responsible for managing your fragments and adding their views to the activity’s view hierarchy (Figure 7.18). The FragmentManager handles two things: a list of fragments and a back stack of fragment transactions (which you will learn about shortly). Doç. Dr. Özgün Yılmaz 25 For CriminalIntent, you will only be concerned with the FragmentManager’s list of fragments. To add a fragment to an activity in code, you make explicit calls to the activity’s FragmentManager. The first step is to get the FragmentManager itself. Do so in onCreate(Bundle) in CrimeActivity.java. Doç. Dr. Özgün Yılmaz 26 Fragment transactions This code creates and commits a fragment transaction. Fragment transactions are used to add, remove, attach, detach, or replace fragments in the fragment list. They are the heart of how you use fragments to compose and recompose screens at runtime. The FragmentManager maintains a back stack of fragment transactions that you can navigate. Doç. Dr. Özgün Yılmaz 27 Fragment transactions The FragmentManager.beginTransaction() method creates and returns an instance of FragmentTransaction. The FragmentTransaction class uses a fluent interface – methods that configure FragmentTransaction return a FragmentTransaction instead of void, which allows you to chain them together. So the code highlighted above says, “Create a new fragment transaction, include one add operation in it, and then commit it.” The add(…) method is the meat of the transaction. It has two parameters: a container view ID and the newly created CrimeFragment. The container view ID should look familiar. It is the resource ID of the FrameLayout that you defined in activity_crime.xml. Doç. Dr. Özgün Yılmaz 28 It may seem odd that the FragmentManager identifies the CrimeFragment using the resource ID of a FrameLayout. But identifying a UI fragment by the resource ID of its container view is built into how the FragmentManager operates. If you are adding multiple fragments to an activity, you would typically create separate containers with separate IDs for each of those fragments. Doç. Dr. Özgün Yılmaz 29 First, you ask the FragmentManager for the fragment with a container view ID of R.id.fragment_container. If this fragment is already in the list, the FragmentManager will return it. Why would a fragment already be in the list? – The call to CrimeActivity.onCreate(Bundle) could be in response to CrimeActivity being re-created after being destroyed on rotation or to reclaim memory. When an activity is destroyed, its FragmentManager saves out its list of fragments. When the activity is re-created, the new FragmentManager retrieves the list and re-creates the listed fragments to make everything as it was before. On the other hand, if there is no fragment with the given container view ID, then fragment will be null. In this case, you create a new CrimeFragment and a new fragment transaction that adds the fragment to the list. Doç. Dr. Özgün Yılmaz 30 The FragmentManager of an activity is responsible for calling the lifecycle methods of the fragments in its list. The onAttach(Context), onCreate(Bundle), and onCreateView(…) methods are called when you add the fragment to the FragmentManager. The onActivityCreated(Bundle) method is called after the hosting activity’s onCreate(Bundle) method has executed. You are adding the CrimeFragment in CrimeActivity.onCreate(Bundle), so this method will be called after the fragment has been added. Doç. Dr. Özgün Yılmaz 31 What happens if you add a fragment while the activity is already resumed? In that case, the FragmentManager immediately walks the fragment through whatever steps are necessary to get it caught up to the activity’s state. For example, as a fragment is added to an activity that is already resumed, that fragment gets calls to onAttach(Context), onCreate(Bundle), onCreateView(…), onActivityCreated(Bundle), onStart(), and then onResume(). Once the fragment’s state is caught up to the activity’s state, the hosting activity’s FragmentManager will call further lifecycle methods around the same time it receives the corresponding calls from the OS to keep the fragment’s state aligned with that of the activity. Doç. Dr. Özgün Yılmaz 32 Application Architecture with Fragments Designing your app with fragments the right way is supremely important. Many developers, after first learning about fragments, try to use them for every reusable component in their application. This is the wrong way to use fragments. Fragments are intended to encapsulate major components in a reusable way. A major component in this case would be on the level of an entire screen of your application. If you have a significant number of fragments on screen at once, your code will be littered with fragment transactions and unclear responsibility. A better architectural solution for reuse with smaller components is to extract them into a custom view (a class that subclasses View or one of its subclasses). Use fragments responsibly. A good rule of thumb is to have no more than two or three fragments on the screen at a time (Figure 7.21). Doç. Dr. Özgün Yılmaz 33 Doç. Dr. Özgün Yılmaz 34 CriminalIntent CriminalIntent’s model layer currently consists of a single instance of Crime. In this chapter, you will update CriminalIntent to work with a list of crimes. The list will display each Crime’s title and date, as shown in Figure 8.1. Doç. Dr. Özgün Yılmaz 35 Doç. Dr. Özgün Yılmaz 36 In the model layer, you have a new object, CrimeLab, that will be a centralized data stash for Crime objects. Displaying a list of crimes requires a new activity and a new fragment in CriminalIntent’s controller layer: CrimeListActivity and CrimeListFragment. (Where are CrimeActivity and CrimeFragment in Figure 8.2? They are part of the detail view, so we are not showing them here. In Chapter 10, you will connect the list and the detail parts of CriminalIntent.) In Figure 8.2, you can also see the view objects associated with CrimeListActivity and CrimeListFragment. The activity’s view will consist of a fragment-containing FrameLayout. The fragment’s view will consist of a RecyclerView. You will learn more about the RecyclerView class later in the chapter. Doç. Dr. Özgün Yılmaz 37 Updating CriminalIntent’s Model Layer The first step is to upgrade CriminalIntent’s model layer from a single Crime object to a List of Crime objects. Doç. Dr. Özgün Yılmaz 38 Singletons and centralized data storage You are going to store the List of crimes in a singleton. A singleton is a class that allows only one instance of itself to be created. A singleton exists as long as the application stays in memory, so storing the list in a singleton will keep the crime data available throughout any lifecycle changes in your activities and fragments. Be careful with singleton classes, as they will be destroyed when Android removes your application from memory. The CrimeLab singleton is not a solution for long-term storage of data, but it does allow the app to have one owner of the crime data and provides a way to easily pass that data between controller classes. Doç. Dr. Özgün Yılmaz 39 Doç. Dr. Özgün Yılmaz 40 Let’s give CrimeLab some Crime objects to store. In CrimeLab’s constructor, create an empty List of Crimes. Also, add two methods: a getCrimes() method that returns the List and a getCrime(UUID) that returns the Crime with the given ID. Doç. Dr. Özgün Yılmaz 41 List is an interface that supports an ordered list of objects of a given type. It defines methods for retrieving, adding, and deleting elements. A commonly used implementation of List is ArrayList, which uses a regular Java array to store the list elements. Because mCrimes holds an ArrayList – and ArrayList is also a List – both ArrayList and List are valid types for mCrimes. In situations like this, we recommend using the interface type for the variable declaration: List. That way, if you ever need to use a different kind of List implementation – like LinkedList, for example – you can do so easily. Doç. Dr. Özgün Yılmaz 42 The mCrimes instantiation line uses diamond notation, , which was introduced in Java 7. This shorthand notation tells the compiler to infer the type of items the List will contain based on the generic argument passed in the variable declaration. Here, the compiler will infer that the ArrayList contains Crimes because the variable declaration private List mCrimes; specifies Crime for the generic argument. (The more verbose equivalent, which developers were required to use prior to Java 7, is mCrimes = new ArrayList();.) Eventually, the List will contain user-created Crimes that can be saved and reloaded. For now, populate the List with 100 boring Crime objects. Doç. Dr. Özgün Yılmaz 43 Doç. Dr. Özgün Yılmaz 44 An Abstract Activity for Hosting a Fragment In a moment, you will create the CrimeListActivity class that will host a CrimeListFragment. First, you are going to set up a view for CrimeListActivity. Doç. Dr. Özgün Yılmaz 45 A generic fragment-hosting layout For CrimeListActivity, you can simply reuse the layout defined in activity_crime.xml (which is copied in Listing 8.4). This layout provides a FrameLayout as a container view for a fragment, which is then named in the activity’s code. Listing 8.4 activity_crime.xml is already generic Because activity_crime.xml does not name a particular fragment, you can use it for any activity hosting a single fragment. Rename it activity_fragment.xml to reflect its larger scope. In the project tool window, right-click res/layout/activity_crime.xml. (Be sure to right- click activity_crime.xml and not fragment_crime.xml.) From the context menu, select Refactor → Rename.... Rename this layout activity_fragment.xml and click Refactor. Doç. Dr. Özgün Yılmaz 46 Doç. Dr. Özgün Yılmaz 47 An abstract Activity class To create the CrimeListActivity class, you could reuse CrimeActivity’s code. Look back at the code you wrote for CrimeActivity (which is copied in Listing 8.6). It is simple and almost generic. In fact, the only nongeneric code is the instantiation of the CrimeFragment before it is added to the FragmentManager. Doç. Dr. Özgün Yılmaz 48 Nearly every activity you will create in this book will require the same code. To avoid typing it again and again, you are going to stash it in an abstract class. Right-click on the com.bignerdranch.android.criminalintent package, select New → Java Class, and name the new class SingleFragmentActivity. Make this class a subclass of AppCompatActivity and make it an abstract class. Your generated file should look like this: Doç. Dr. Özgün Yılmaz 49 In this code, you set the activity’s view to be inflated from activity_fragment.xml. Then you look for the fragment in the FragmentManager in that container, creating and adding it if it does not exist. The only difference between the code in Listing 8.8 and the code in CrimeActivity is an abstract method named createFragment() that you use to instantiate the fragment. Subclasses of SingleFragmentActivity will implement this method to return an instance of the fragment that the activity is hosting. Doç. Dr. Özgün Yılmaz 50 Using an abstract class Try it out with CrimeActivity. Change CrimeActivity’s superclass to SingleFragmentActivity, remove the implementation of onCreate(Bundle), and implement the createFragment() method as shown in Listing 8.9. Doç. Dr. Özgün Yılmaz 51 Creating the new controllers Now, you will create the two new controller classes: CrimeListActivity and CrimeListFragment. Right-click on the com.bignerdranch.android.criminalintent package, select New → Java Class, and name the class CrimeListActivity. Modify the new CrimeListActivity class to also subclass SingleFragmentActivity and implement the createFragment() method. If you have other methods in your CrimeListActivity, such as onCreate, remove them. Let SingleFragmentActivity do its job and keep CrimeListActivity simple. Create CrimeListFragment class Right-click on the com.bignerdranch.android.criminalintent package again, select New → Java Class, and name the class CrimeListFragment. For now, CrimeListFragment will be an empty shell of a fragment. You will work with this fragment later in the chapter. Now your activity code is nice and tidy. And SingleFragmentActivity will save you a lot of typing and time as you proceed through the book. Doç. Dr. Özgün Yılmaz 52 Declaring CrimeListActivity Now that you have created CrimeListActivity, you must declare it in the manifest. In addition, you want the list of crimes to be the first screen that the user sees when CriminalIntent is launched, so CrimeListActivity should be the launcher activity. In the manifest, declare CrimeListActivity and move the launcher intent filter from CrimeActivity’s declaration to CrimeListActivity’s declaration. Doç. Dr. Özgün Yılmaz 53 Doç. Dr. Özgün Yılmaz 54 RecyclerView, Adapter, and ViewHolder Now, you want CrimeListFragment to display a list of crimes to the user. To do this, you will use a RecyclerView. RecyclerView is a subclass of ViewGroup. It displays a list of child View objects, one for each item in your list of items. Depending on the complexity of what you need to display, these child Views can be complex or very simple. For your first implementation, each item in the list will display the title and date of a Crime. The View object on each row will be a LinearLayout containing two TextViews, as shown in Figure 8.4. Doç. Dr. Özgün Yılmaz 55 Doç. Dr. Özgün Yılmaz 56 Later you will be able to run CriminalIntent and swipe to scroll through 100 Views to see all of your Crimes. Does that mean that you have 100 View objects in memory? Thanks to your RecyclerView, no. Creating a View for every item in the list all at once could easily become unworkable. As you can imagine, a list can have far more than 100 items, and your list items can be much more involved than your simple implementation here. Also, a Crime only needs a View when it is onscreen, so there is no need to have 100 Views ready and waiting. It would make far more sense to create view objects only as you need them. RecyclerView does just that. Instead of creating 100 Views, it creates 12 – enough to fill the screen. When a view is scrolled off the screen, RecyclerView reuses it rather than throwing it away. In short, it lives up to its name: It recycles views over and over. Doç. Dr. Özgün Yılmaz 57 ViewHolders and Adapters The RecyclerView’s only responsibilities are recycling TextViews and positioning them onscreen. To get the TextViews in the f