Head First Android Development A Brain Friendly Guide, 2nd Edition
User Manual:
Open the PDF directly: View PDF
.
Page Count: 930
| Download | |
| Open PDF In Browser | View PDF |
Android Development d n 2n itio Ed Head First A Brain-Friendly Guide Put fragments under the microscope Learn how Constraint Layouts can change your life Avoid embarrassing activities Create out-of-this-world services Find your way with Android's Location Services Fool around in the Design Support Library Dawn Griffiths & David GriffiRobson ths & Eric Freeman Elisabeth Head First Android Development Wouldn’t it be dreamy if there were a book on developing Android apps that was easier to understand than the space shuttle flight manual? I guess it’s just a fantasy… Dawn Griffiths David Griffiths Boston Head First Android Development by Dawn Griffiths and David Griffiths Copyright © 2017 David Griffiths and Dawn Griffiths. All rights reserved. Printed in the United States of America. Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472. O’Reilly Media books may be purchased for educational, business, or sales promotional use. Online editions are also available for most titles (http://oreilly.com/safari). For more information, contact our corporate/institutional sales department: (800) 998-9938 or corporate@oreilly.com. Series Creators: Kathy Sierra, Bert Bates Editor: Dawn Schanafelt Cover Designer: Karen Montgomery Production Editor: Kristen Brown Proofreader: Rachel Monaghan Indexer: Angela Howard Page Viewers: Mum and Dad, Rob and Lorraine Printing History: June 2015: First Edition. August 2017: Second Edition Mum and Dad The O’Reilly logo is a registered trademark of O’Reilly Media, Inc. The Head First series designations, Head First Android Development, and related trade dress are trademarks of O’Reilly Media, Inc. Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and O’Reilly Media, Inc., was aware of a trademark claim, the designations have been printed in caps or initial caps. While every precaution has been taken in the preparation of this book, the publisher and the authors assume no responsibility for errors or omissions, or for damages resulting from the use of the information contained herein. No kittens were harmed in the making of this book, but several pizzas were eaten. ISBN: 978-1-491-97405-6 [M] Rob and Lorraine To our friends and family. Thank you so much for all your love and support. the authors Authors of He ad First Android Development s David Griffith Dawn Griffiths Dawn Griffiths started life as a mathematician at a top UK university, where she was awarded a firstclass honors degree in mathematics. She went on to pursue a career in software development and has over 20 years’ experience working in the IT industry. Before writing Head First Android Development, Dawn wrote three other Head First books (Head First Statistics, Head First 2D Geometry, and Head First C). She also created the video course The Agile Sketchpad with her husband, David, to teach key concepts and techniques in a way that keeps your brain active and engaged. When Dawn’s not working on Head First books or creating videos, you’ll find her honing her Tai Chi skills, reading, running, making bobbin lace, or cooking. She particularly enjoys spending time with her wonderful husband, David. David Griffiths began programming at age 12, when he saw a documentary on the work of Seymour Papert. At age 15, he wrote an implementation of Papert’s computer language LOGO. After studying pure mathematics at university, he began writing code for computers and magazine articles for humans. He’s worked as an Agile coach, a developer, and a garage attendant, but not in that order. He can write code in over 10 languages and prose in just one, and when not writing, coding, or coaching, he spends much of his spare time traveling with his lovely wife—and coauthor—Dawn. Before writing Head First Android Development, David wrote three other Head First books—Head First Rails, Head First Programming, and Head First C—and created The Agile Sketchpad video course with Dawn. You can follow us on Twitter at https://twitter.com/ HeadFirstDroid and visit the book’s website at https:// tinyurl.com/HeadFirstAndroid. iv table of contents Table of Contents (Summary) Intro xxix 1 Getting Started: Diving in 1 2 Building Interactive Apps: Apps that do something 37 3 Multiple Activities and Intents: State your intent 77 4 The Activity Lifecycle: Being an activity 119 5 Views and View Groups Enjoy the view 169 6 Constraint Layouts: Put things in their place 221 7 List views and Adapters: Getting organized 247 289 8 Support Libraries and App Bars: Taking shortcuts 9 Fragments: Make it modular 339 10 Fragments for Larger Interfaces: Different size, different interface 393 11 Dynamic Fragments: Nesting fragments 433 12 Design Support Library: Swipe right 481 13 Recycler Views and Card Views: Get recycling 537 14 Navigation Drawers: Going places 579 15 SQLite Databases: Fire up the database 621 16 Basic cursors: Getting data out 657 17 Cursors and AsyncTasks: Staying in the background 693 18 Started Services: At your service 739 19 Bound Services and Permissions: Bound together 767 i Relative and Grid Layouts: Meet the relatives 817 ii Gradle: The Gradle build tool 833 iii ART: The Android Runtime 841 iv ADB: The Android debug bridge 849 v The Android Emulator: Speeding things up 857 vi Leftovers: The top ten things (we didn’t cover) 861 v table of contents Table of Contents (the real thing) Intro Your brain on Android. Here you are trying to learn something, while here your brain is, doing you a favor by making sure the learning doesn’t stick. Your brain’s thinking, “Better leave room for more important things, like which wild animals to avoid and whether naked snowboarding is a bad idea.” So how do you trick your brain into thinking that your life depends on knowing how to develop Android apps? I wonder how I can trick my brain into remembering this stuff… vi Authors of Head First Android Development iv Who is this book for? xxx We know what you’re thinking xxxi We know what your brain is thinking xxxi Metacognition: thinking about thinking xxxiii Here’s what WE did xxxiv Read me xxxvi The technical review team xxxviii Acknowledgments xxxix Safari® Books Online xl table of contents 1 getting started Diving In Android has taken the world by storm. Everybody wants a smartphone or tablet, and Android devices are hugely popular. In this book, we’ll teach you how to develop your own apps, and we’ll start by getting you to build a basic app and run it on an Android Virtual Device. Along the way, you’ll meet some of the basic components of all Android apps, such as activities and layouts. All you need is a little Java know-how... DK dS An oi dr Welcome to Androidville 2 The Android platform dissected 3 Here’s what we’re going to do 4 Your development environment 5 Install Android Studio 6 Build a basic app 7 How to build the app 8 Activities and layouts from 50,000 feet 12 How to build the app (continued) 13 You’ve just created your first Android app 15 Android Studio creates a complete folder structure for you 16 Useful files in your project 17 Edit code with the Android Studio editors 18 Run the app in the Android emulator 23 Creating an Android Virtual Device 24 Run the app in the emulator 27 You can watch progress in the console 28 What just happened? 30 Refining the app 31 What’s in the layout? 32 activity_main.xml has two elements 33 Update the text displayed in the layout 34 Take the app for a test drive 35 Your Android Toolbox 36Activity Layout vii table of contents 2 building interactive apps Apps That Do Something Most apps need to respond to the user in some way. In this chapter, you’ll see how you can make your apps a bit more interactive. You’ll learn how to get your app to do something in response to the user, and how to get your activity and layout talking to each other like best buddies. Along the way, we’ll take you a bit deeper into how Android actually works by introducing you to R, the hidden gem that glues everything together. Device Layout BeerExpert viii 38 40 We’ve created a default activity and layout 41 A closer look at the design editor 42 Add a button using the design editor 43 activity_find_beer.xml has a new button 44 A closer look at the layout code 45 strings.xml Activity Let’s build a Beer Adviser app Create the project Let’s take the app for a test drive 49 Hardcoding text makes localization hard 50 Create the String resource 51 Use the String resource in your layout 52 The code for activity_find_beer.xml 53 Add values to the spinner 56 Add the string-array to strings.xml 57 Test drive the spinner 58 We need to make the button do something 59 Make the button call a method 60 What activity code looks like 61 Add an onClickFindBeer() method to the activity 62 onClickFindBeer() needs to do something 63 Once you have a View, you can access its methods 64 Update the activity code 65 The first version of the activity 67 What the code does 68 Build the custom Java class 70 What happens when you run the code 74 Test drive your app 75 Your Android Toolbox 76 table of contents 3 multiple activities and intents State Your Intent Most apps need more than one activity. So far we’ve just looked at single-activity apps, which is fine for simple apps. But when things get more complicated, just having the one activity won’t cut it. We’re going to show you how to build apps with multiple activities, and how you can get your apps talking to each other using intents. We’ll also look at how you can use intents to go beyond the boundaries of your app and make activities in other apps on your device perform actions. Things are about to get a whole lot more powerful... Apps can contain more than one activity Intent To: AnotherActivity Hey, user. Which activity do you want to use this time? CreateMessageActivity 78 Here’s the app structure 79 Get started: create the project 79 Update the layout 80 Create the second activity and layout 82 Welcome to the Android manifest file 84 An intent is a type of message 86 What happens when you run the app 88 Pass text to a second activity 90 Update the text view properties 91 putExtra() puts extra info in an intent 92 Update the CreateMessageActivity code 95 Get ReceiveMessageActivity to use the information in the intent 96 What happens when the user clicks the Send Message button 97 We can change the app to send messages to other people 98 How Android apps work 99 Create an intent that specifies an action 101 Change the intent to use an action 102 How Android uses the intent filter 106 What if you ALWAYS want your users to choose an activity? 112 What happens when you call createChooser() 113 Change the code to create a chooser 115 Your Android Toolbox 118 Android User ix table of contents 4 the activity lifecycle Being an Activity Activities form the foundation of every Android app. So far you’ve seen how to create activities, and made one activity start another using an intent. But what’s really going on beneath the hood? In this chapter, we’re going to dig a little deeper into the activity lifecycle. What happens when an activity is created and destroyed? Which methods get called when an activity is made visible and appears in the foreground, and which get called when the activity loses the focus and is hidden? And how do you save and restore your Activity launched activity’s state? Read on to find out. onCreate() onStart() onResume() Activity running onPause() onStop() onDestroy() Activity destroyed x onRestart() How do activities really work? 120 The Stopwatch app 122 Add String resources 123 How the activity code will work 125 Add code for the buttons 126 The runTimer() method 127 The full runTimer() code 129 The full StopwatchActivity code 130 Rotating the screen changes the device configuration 136 The states of an activity 137 The activity lifecycle: from create to destroy 138 The updated StopwatchActivity code 142 What happens when you run the app 143 There’s more to an activity’s life than create and destroy 146 The updated StopwatchActivity code 151 What happens when you run the app 152 What if an app is only partially visible? 154 The activity lifecycle: the foreground lifetime 155 Stop the stopwatch if the activity’s paused 158 Implement the onPause() and onResume() methods 159 The complete StopwatchActivity code 160 What happens when you run the app 163 Your handy guide to the lifecycle methods 167 Your Android Toolbox 168 table of contents 5 views and view groups Enjoy the View You’ve seen how to arrange GUI components using a linear layout, but so far we’ve only scratched the surface. In this chapter we’ll look a little deeper and show you how linear layouts really work. We’ll introduce you to the frame layout, a simple layout used to stack views, and we’ll also take a tour of the main GUI components and how you use them. By the end of the chapter, you’ll see that even though they all look a little different, all layouts and GUI components have more in common than you might think. Your user interface is made up of layouts and GUI components Frame layouts let your views overlap one another. This is useful for displaying text on top of images, for example. The linear layoutViewGroup layout.xml 170 LinearLayout displays views in a single row or column 171 Add a dimension resource file for consistent padding across layouts 174 Use margins to add distance between views 176 Let’s change up a basic linear layout 177 Make a view streeeeetch by adding weight 179 Values you can use with the android:gravity attribute 183 The full linear layout code 186 Frame layouts stack their views 188 Add an image to your project 189 The full code to nest a layout 192 FrameLayout: a summary 193 Playing with views 201 Editable text view 202 Toggle button 204 Switch 205 Checkboxes 206 Radio buttons 208 Spinner 210 Image view 211 Adding images to buttons 213 Scroll views 215 Toasts 216 Your Android Toolbox 220 The editable text field The button View View xi table of contents 6 constraint layouts Put Things in Their Place Let’s face it, you need to know how to create great layouts. If you’re building apps you want people to use, you need to make sure they look exactly the way you want. So far you’ve seen how to use linear and frame layouts, but what if your design is more complex? To deal with this, we’ll introduce you to Android’s new constraint layout, a type of layout you build visually using a blueprint. We’ll show you how constraints let you position and size your views, irrespective of screen size and orientation. Finally, you’ll find out how to save time by making Android Studio infer and add constraints on your behalf. Nested layouts can be inefficient This time, the Infer Constraints button added constraints to our new EditText. xii 222 Introducing the Constraint Layout 223 Make sure your project includes the Constraint Layout Library 224 Add the String resources to strings.xml 225 Use the blueprint tool 226 Position views using constraints 227 Add a vertical constraint 228 Changes to the blueprint are reflected in the XML 229 How to center views 230 Adjust a view’s position by updating its bias 231 How to change a view’s size 232 How to align views 238 Let’s build a real layout 239 First, add the top line of views 240 The Infer Constraints feature guesses which constraints to add 241 Add the next line to the blueprint... 242 Finally, add a view for the message 243 Test drive the app 244 Your Android Toolbox 245 table of contents 7 list views and adapters Getting Organized Want to know how best to structure your Android app? You’ve learned about some of the basic building blocks that are used to create apps, and now it’s time to get organized. In this chapter, we’ll show you how you can take a bunch of ideas and structure them into an awesome app. You’ll learn how lists of data can form the core part of your app design, and how linking them together can create a powerful and easy-to-use app. Along the way, you’ll get your first glimpse of using event listeners and adapters to make your app more dynamic. Display a start screen with a list of options. Display a list of the drinks we sell. Show details of each drink. This is our array. Drink. drinks Every app starts with ideas 248 Use list views to navigate to data 251 The drink detail activity 253 The Starbuzz app structure 254 The Drink class 256 The top-level layout contains an image and a list 258 The full top-level layout code 260 Get list views to respond to clicks with a listener 261 Set the listener to the list view 262 A category activity displays the data for a single category 267 Update activity_drink_category.xml 268 For nonstatic data, use an adapter 269 Connect list views to arrays with an array adapter 270 Add the array adapter to DrinkCategoryActivity 271 App review: where we are 274 How we handled clicks in TopLevelActivity 276 The full DrinkCategoryActivity code 278 Update the views with the data 281 The DrinkActivity code 283 What happens when you run the app 284 Your Android Toolbox 288 We’ll create an array adapter to bind our list view to our array. Array Adapter This is our list view. ListView xiii table of contents 8 support libraries and app bars Taking Shortcuts Everybody likes a shortcut. And in this chapter you’ll see how to add shortcuts to your apps using app bars. We’ll show you how to start activities by adding actions to your app bar, how to share content with other apps using the share action provider, and how to navigate up your app’s hierarchy by implementing the app bar’s Up button. Along the way we’ll introduce you to the powerful Android Support Libraries, which are key to making your apps look fresh on older versions of Android. Great apps have a clear structure 290 Different types of navigation 291 onCreate(Bundle) Add an app bar by applying a theme 293 onStart() Create the Pizza app 295 onRestart() Add the v7 AppCompat Support Library 296 onResume() AndroidManifest.xml can change your app bar’s appearance 299 onPause() How to apply a theme 300 onStop() Define styles in a style resource file 301 onDestroy() Customize the look of your app 303 Define colors in a color resource file 304 The code for activity_main.xml 305 ActionBar vs. Toolbar 306 Include the toolbar in the activity’s layout 312 Add actions to the app bar 315 Activity onSaveInstanceState() FragmentActivity AppCompatActivity YourActivity Change the app bar text by adding a label 318 The code for AndroidManifest.xml 319 Control the action’s appearance 322 The full MainActivity.java code 325 Enable Up navigation 327 onCreate(Bundle) Share content on the app bar 331 yourMethod() Add a share action provider to menu_main.xml 332 Specify the content with an intent 333 The full MainActivity.java code 334 Your Android Toolbox 337 Intent xiv ShareActionProvider ACTION_SEND type: “text/plain” messageText: ”Hi!” AppActivity table of contents 9 fragments Make It Modular You’ve seen how to create apps that work in the same way no matter what device they’re running on. But what if you want your app to look and behave differently depending on whether it’s running on a phone or a tablet? In this case you need fragments, modular code components that can be reused by different activities. We’ll show you how to create basic fragments and list fragments, how to add them to your activities, and how to get your fragments and activities to communicate with one another. Your app needs to look great on ALL devices 340 Your app may need to behave differently too 341 Fragments allow you to reuse code 342 The phone version of the app 343 Create the project and activities 345 Add a button to MainActivity’s layout 346 How to add a fragment to your project 348 The fragment’s onCreateView() method 350 Add a fragment to an activity’s layout 352 Get the fragment and activity to interact 359 The Workout class 360 Hmmm, aelement. I need to know what goes here. activity_detail A list fragment comes complete with its own list view so you don’t need to add it yourself. You just need to provide the list fragment with data. onCreate() MainActivity Pass the workout ID to the fragment 361 Get the activity to set the workout ID 363 The fragment lifecycle 365 Set the view’s values in the fragment’s onStart() method 367 How to create a list fragment 374 The updated WorkoutListFragment code 377 The code for activity_main.xml 381 Connect the list to the detail 384 The code for WorkoutListFragment.java 387 MainActivity needs to implement the interface 388 DetailActivity needs to pass the ID to WorkoutDetailFragment 389 Your Android Toolbox 392 Fragment Manager WorkoutDetail Fragment xv table of contents 10 fragments for larger interfaces Different Size, Different Interface So far we’ve only run our apps on devices with a small screen.But what if your users have tablets? In this chapter you’ll see how to create flexible user interfaces by making your app look and behave differently depending on the device it’s running on. We’ll show you how to control the behavior of your app when you press the Back button by introducing you to the back stack and fragment transactions. Finally, you’ll find out how to save and restore the state of your fragment. The device screen's large, so I'll use the large version of the layout. layout-large Android activity_main.xml I'm committed. Make it so! MainActivity FragmentTransaction xvi The Workout app looks the same on a phone and a tablet 394 Designing for larger interfaces 395 The phone version of the app 396 The tablet version of the app 397 Create a tablet AVD 399 Put screen-specific resources in screen-specific folders 402 The different folder options 403 Tablets use layouts in the layout-large folder 408 What the updated code does 410 We need to change the itemClicked() code 412 You want fragments to work with the Back button 413 Welcome to the back stack 414 Back stack transactions don’t have to be activities 415 Use a frame layout to replace fragments programmatically 416 Use layout differences to tell which layout the device is using 417 The revised MainActivity code 418 Using fragment transactions 419 The updated MainActivity code 423 Rotating the tablet breaks the app 427 Saving an activity’s state (revisited) 428 The updated code for WorkoutDetailFragment.java 430 Your Android Toolbox 432 Tablet table of contents 11 dynamic fragments Nesting Fragments So far you’ve seen how to create and use static fragments.But what if you want your fragments to be more dynamic? Dynamic fragments have a lot in common with dynamic activities, but there are crucial differences you need to be able to deal with. In this chapter you’ll see how to convert dynamic activities into working dynamic fragments. You’ll find out how to use fragment transactions to help maintain your fragment state. Finally, you’ll discover how to nest one fragment inside another, and how the child fragment manager helps you control unruly back stack behavior. Whenever I see android:onClick, I assume it’s all about me. My methods run, not the fragment’s. Adding dynamic fragments 434 The new version of the app 436 Create TempActivity 437 TempActivity needs to extend AppCompatActivity 438 The StopwatchFragment.java code 444 The StopwatchFragment layout 447 Add StopwatchFragment to TempActivity’s layout 449 The onClick attribute calls methods in the activity, not the fragment 452 Activity Attach the OnClickListener to the buttons 457 The StopwatchFragment code 458 Rotating the device resets the stopwatch 462 Use for static fragments... 463 Change activity_temp.xml to use a FrameLayout 464 The full code for TempActivity.java 467 Add the stopwatch to WorkoutDetailFragment 469 The full WorkoutDetailFragment.java code 476 Your Android Toolbox 480 I display workout details, and I also display the stopwatch. The transaction to add StopwatchFragment is nested inside the transaction to add WorkoutDetailFragment. Workout Details Stopwatch xvii table of contents 12 design support library Swipe Right Ever wondered how to develop apps with a rich, slick UI? With the release of the Android Design Support Library, it became much easier to create apps with an intuitive UI. In this chapter, we’ll show you around some of the highlights. You’ll see how to add tabs so that your users can navigate around your app more easily. You’ll discover how to animate your toolbars so that they can collapse or scroll on a whim. You’ll find out how to add floating action buttons for common user actions. Finally, we’ll introduce you to snackbars, a way of displaying short, informative messages to the user that they can interact with. We’ll get the toolbar to scroll when the user scrolls the content in TopFragment. We’ll add this scrollable content to TopFragment. xviii The Bits and Pizzas app revisited 482 The app structure 483 Use a view pager to swipe through fragments 489 Add a view pager to MainActivity’s layout 490 Tell a view pager about its pages using a fragment pager adapter 491 The code for our fragment pager adapter 492 The full code for MainActivity.java 494 Add tab navigation to MainActivity 498 How to add tabs to your layout 499 Link the tab layout to the view pager 501 The full code for MainActivity.java 502 The Design Support Library helps you implement material design 506 Make the toolbar respond to scrolls 508 Add a coordinator layout to MainActivity’s layout 509 How to coordinate scroll behavior 510 Add scrollable content to TopFragment 512 The full code for fragment_top.xml 515 Add a collapsing toolbar to OrderActivity 517 How to create a plain collapsing toolbar 518 How to add an image to a collapsing toolbar 523 The updated code for activity_order.xml 524 FABs and snackbars 526 The updated code for activity_order.xml 528 The full code for OrderActivity.java 533 Your Android Toolbox 535 table of contents 13 recycler views and card views Get Recycling You’ve already seen how the humble list view is a key part of most apps.But compared to some of the material design components we’ve seen, it’s somewhat plain. In this chapter, we’ll introduce you to the recycler view, a more advanced type of list that gives you loads more flexibility and fits in with the material design ethos. You’ll see how to create adapters tailored to your data, and how to completely change the look of your list with just two lines of code. We’ll also show you how to use card views to give your data a 3D material design appearance. ViewHolder CardView Each of our ViewHolders will contain a CardView. We created the layout for this CardView earlier in the chapter. There’s still work to do on the Bits and Pizzas app 538 Recycler views from 10,000 feet 539 Add the pizza data 541 Display the pizza data in a card 542 How to create a card view 543 The full card_captioned_image.xml code 544 Add a recycler view adapter 546 Define the adapter’s view holder 548 Override the onCreateViewHolder() method 549 Add the data to the card views 550 The full code for CaptionedImagesAdapter.java 551 Create the recycler view 553 Add the RecyclerView to PizzaFragment’s layout 554 The full PizzaFragment.java code 555 A recycler view uses a layout manager to arrange its views 556 Specify the layout manager 557 The full PizzaFragment.java code 558 Make the recycler view respond to clicks 566 Create PizzaDetailActivity 567 The code for PizzaDetailActivity.java 569 Get a recycler view to respond to clicks 570 You can listen for view events from the adapter 571 Keep your adapters reusable 572 Add the interface to the adapter 573 Implement the listener in PizzaFragment.java 575 Your Android Toolbox 578 xix table of contents 14 navigation drawers Going Places You’ve already seen how tabs help users navigate your apps. But if you need a large number of them, or want to split them into sections, the navigation drawer is your new BFF. In this chapter, we’ll show you how to create a navigation drawer that slides out from the side of your activity at a single touch. You’ll learn how to give it a header using a navigation view, and provide it with a structured set of menu items to take the user to all the major hubs of your app. Finally, you’ll discover how to set up a navigation view listener so that the drawer responds to the slightest touch and swipe. This is the CatChat app. xx Tab layouts allow easy navigation... 580 We’re going to create a navigation drawer for a new email app 581 Navigation drawers deconstructed 582 Create the CatChat project 584 Create InboxFragment 585 Create DraftsFragment 586 Create SentItemsFragment 587 Create TrashFragment 588 Create a toolbar layout 589 Update the app’s theme 590 Create HelpActivity 591 Create FeedbackActivity 592 Create the navigation drawer’s header 594 The full nav_header.xml code 595 How to group items together 598 Add the support section as a submenu 600 The full menu_nav.xml code 601 How to create a navigation drawer 602 The full code for activity_main.xml 603 Add InboxFragment to MainActivity’s frame layout 604 Add a drawer toggle 607 Respond to the user clicking items in the drawer 608 Implement the onNavigationItemSelected() method 609 Close the drawer when the user presses the Back button 614 The full MainActivity.java code 615 Your Android Toolbox 619 table of contents 15 SQLite databases Fire Up the Database If you’re recording high scores or saving tweets, your app will need to store data. A nd on Android you usually keep your data safe inside a SQLite database. In this chapter, we’ll show you how to create a database, add tables to it, and prepopulate it with data, all with the help of the friendly SQLite helper. You’ll then see how you can cleanly roll out upgrades to your database structure, and how to downgrade it if you need to undo any changes. Your database, sir. Will that be all? onCreate() SQLite helper DRINK Name: “starbuzz” Version: 1 SQLite database Back to Starbuzz 622 Android uses SQLite databases to persist data 623 Android comes with SQLite classes 624 The current Starbuzz app structure 625 Let’s change the app to use a database 626 The SQLite helper manages your database 627 Create the SQLite helper 628 Inside a SQLite database 630 You create tables using Structured Query Language (SQL) 631 Insert data using the insert() method 632 Insert multiple records 633 The StarbuzzDatabaseHelper code 634 What the SQLite helper code does 635 What if you need to make changes to the database? 636 SQLite databases have a version number 637 What happens when you change the version number 638 Upgrade your database with onUpgrade() 640 Downgrade your database with onDowngrade() 641 Let’s upgrade the database 642 Upgrade an existing database 645 Update records with the update() method 646 Apply conditions to multiple columns 647 Change the database structure 649 Delete tables by dropping them 650 The full SQLite helper code 651 Your Android Toolbox 656 xxi table of contents 16 basic cursors Getting Data Out So how do you connect your app to a SQLite database? o far you’ve seen how to create a SQLite database using a SQLite helper. The S next step is to get your activities to access it. In this chapter, we’ll focus on how you read data from a database. You’ll find out how to use cursors to get data from the database. You’ll see how to navigate cursors, and how to get access to their data. Finally, you’ll discover how to use cursor adapters to bind cursors to list views. The story so far... 658 The new Starbuzz app structure 659 What we’ll do to change DrinkActivity to use the Starbuzz database 660 Hey, Cursor, I need more... Cursor? Hey, buddy, are you there? Cursor CursorAdapter If you close the cursor too soon, the cursor adapter won’t be able to get more data from the cursor. xxii The current DrinkActivity code 661 Get a reference to the database 662 Get data from the database with a cursor 663 Return all the records from a table 664 Return records in a particular order 665 Return selected records 666 The DrinkActivity code so far 669 To read a record from a cursor, you first need to navigate to it 670 Navigate cursors 671 Get cursor values 672 The DrinkActivity code 673 What we’ve done so far 675 The current DrinkCategoryActivity code 677 Get a reference to the Starbuzz database... 678 How do we replace the array data in the list view? 679 A simple cursor adapter maps cursor data to views 680 How to use a simple cursor adapter 681 Close the cursor and database 682 The story continues 683 The revised code for DrinkCategoryActivity 688 The DrinkCategoryActivity code (continued) 689 Your Android Toolbox 691 table of contents 17 cursors and asynctasks Staying in the Background In most apps, you’ll need your app to update its data. o far you’ve seen how to create apps that read data from a SQLite database. But S what if you want to update the app’s data? In this chapter you’ll see how to get your app to respond to user input and update values in the database. You’ll also find out how to refresh the data that’s displayed once it’s been updated. Finally, you’ll onPreExecute see how writing efficient multithreaded code with AsyncTasks will keep your app speedy. doInBackground onProgressUpdate onPostExecute We want our Starbuzz app to update database data 694 Add a checkbox to DrinkActivity’s layout 696 Display the value of the FAVORITE column 697 Respond to clicks to update the database 698 The full DrinkActivity.java code 701 Display favorites in TopLevelActivity 705 Refactor TopLevelActivity.java 707 The new TopLevelActivity.java code 710 Change the cursor with changeCursor() 715 What code goes on which thread? 723 AsyncTask performs asynchronous tasks 724 The onPreExecute() method 725 The doInBackground() method 726 The onProgressUpdate() method 727 The onPostExecute() method 728 The AsyncTask class parameters 729 The full UpdateDrinkTask class 730 The full DrinkActivity.java code 732 A summary of the AsyncTask steps 737 Your Android Toolbox 737 Latte Cappuccino Filter ListView CursorAdapter Cursor Database xxiii table of contents started services 18 At Your Service There are some operations you want to keep on running, irrespective of which app has the focus. If you start downloading a file, for instance, you don’t want the download to stop when you switch to another app. In this chapter we’ll introduce you to started services, components that run operations in the background. You’ll see how to create a started service using the IntentService class, and find out how its lifecycle fits in with that of an activity. Along the way, you’ll discover how Service created to log messages, and keep users informed using Android’s built-in notification service. onCreate() onStartCommand() onHandleIntent() Service running onDestroy() Service destroyed Services work in the background 740 We’ll create a STARTED service 741 Use the IntentService class to create a basic started service 742 How to log messages 743 The full DelayedMessageService code 744 You declare services in AndroidManifest.xml 745 Add a button to activity_main.xml 746 You start a service using startService() 747 The states of a started service 750 The started service lifecycle: from create to destroy 751 Your service inherits the lifecycle methods 752 Android has a built-in notification service 755 We’ll use notifications from the AppCompat Support Library 756 First create a notification builder 757 Issue the notification using the built-in notification service 759 The full code for DelayedMessageService.java 760 Your Android Toolbox 765 MainActivity will use this layout. activity_main.xml The activity will pass text to the service. xxiv MainActivity.java Text 1...2..3...4...5...6...7 ...8...9...10... Here’s the text. The service will display the text after 10 seconds. DelayedMessageService.java table of contents 19 bound services and permissions Bound Together Started services are great for background operations, but what if you need a service that’s more interactive? In this chapter you’ll discover how to create a bound service, a type of service your activity can interact with. You’ll see how to bind to the service when you need it, and how to unbind from it when you’re done to save resources. You’ll find out how to use Android’s Location Services to get location updates from your device GPS. Finally, you’ll discover how to use Android’s permission model, including handling runtime permission requests. Are we nearly there yet? OdometerService Bound services are bound to other components 768 Create a new service 770 Implement a binder 771 Add a getDistance() method to the service 772 Update MainActivity’s layout 773 Create a ServiceConnection 775 Use bindService() to bind the service 778 Use unbindService() to unbind from the service 779 Call OdometerService’s getDistance() method 780 The full MainActivity.java code 781 The states of a bound service 787 Add the AppCompat Support Library 790 Add a location listener to OdometerService 792 Here’s the updated OdometerService code 795 Calculate the distance traveled 796 The full OdometerService.java code 798 Get the app to request permission 802 Check the user’s response to the permission request 805 Add notification code to onRequestPermissionsResults() 809 The full code for MainActivity.java 811 Your Android Toolbox 815 It’s been great having you here in Androidville 816 Intent Android Odometer Service onBind() OdometerService xxv table of contents i relative and grid layouts Meet the Relatives There are two more layouts you will often meet in Androidville. In this book we’ve concentrated on using simple linear and frame layouts, and introduced you to Android’s new constraint layout. But there are two more layouts we’d like you to know about: relative layouts and grid layouts. They’ve largely been superseded by the constraint layout, but we have a soft spot for them, and we think they’ll stay around for a few more years. Each of these areas is a cell. ii gradle The Gradle Build Tool Most Android apps are created using a build tool called Gradle. Gradle works behind the scenes to find and download libraries, compile and deploy your code, run tests, clean the grouting, and so on. Most of the time you might not even realize it’s there because Android Studio provides a graphical interface to it. Sometimes, however, it’s helpful to dive into Gradle and hack it manually. In this appendix we’ll introduce you to some of Gradle’s many talents. xxvi table of contents iii art The Android Runtime Ever wonder how Android apps can run on so many kinds of devices? Android apps run in a virtual machine called the Android runtime (ART), not the Oracle Java Virtual Machine (JVM). This means that your apps are quicker to start on small, low-powered devices and run more efficiently. In this appendix, we’ll look at how ART works. classes.dex aapt .apk Resources iv adb The Android Debug Bridge In this book, we’ve focused on using an IDE for all your Android needs. B ut there are times when using a command-line tool can be plain useful, like those times when Android Studio can’t see your Android device but you just know it’s there. In this chapter, we’ll introduce you to the Android Debug Bridge (or adb), a command-line tool you can use to communicate with the emulator or Android devices. adb adbd adb command adb daemon process Device Device xxvii table of contents v the android emulator Speeding Things Up Ever felt like you were spending all your time waiting for the emulator? There’s no doubt that using the Android emulator is useful. It allows you to see how your app will run on devices other than the physical ones you have access to. But at times it can feel a little...sluggish. In this appendix, we’ll explain why the emulator can seem slow. Even better, we’ll give you a few tips we’ve learned for speeding it up. AVD All the Android Virtual Devices run on an emulator called QEMU. vi AVD AVD AVD AVD QEMU Emulator leftovers The Top Ten Things (we didn’t cover) Even after all that, there’s still a little more. There are just a few more things we think you need to know. We wouldn’t feel right about ignoring them, and we really wanted to give you a book you’d be able to lift without extensive training at the local gym. Before you put down the book, read through these tidbits. The battery’s running low, in case anyone’s interested. Android xxviii 1. Distributing your app 862 2. Content providers 863 3. Loaders 864 4. Sync adapters 864 5. Broadcasts 865 6. The WebView class 866 7. Settings 867 8. Animation 868 9. App widgets 869 10. Automated testing 870 the intro how to use this book Intro I can’t believe they put that in an Android book. ning question: In this section, we answer thein bur book on Android?” “So why DID they put that a you are here 4 xxix how to use this book Who is this book for? If you can answer “yes” to all of these: 1 Do you already know how to program in Java? 2 Do you want to master Android app development, create the next big thing in software, make a small fortune, and retire to your own private island? 3 Do you prefer actually doing things and applying the stuff you learn over listening to someone in a lecture rattle on for hours on end? tle OK, maybe that one’s a litta got far-fetched. But you start somewhere, right? this book is for you. Who should probably back away f rom this book? If you can answer “yes” to any of these: 1 Are you looking for a quick introduction or reference book to developing Android apps? 2 Would you rather have your toenails pulled out by 15 screaming monkeys than learn something new? Do you believe an Android book should cover everything, especially all the obscure stuff you’ll never use, and if it bores the reader to tears in the process, then so much the better? this book is not for you. [Note from Marketing: this book is for anyone with a credit card or a PayPal account] xxx intro the intro We know what you’re thinking “How can this be a serious book on developing Android apps?” “What’s with all the graphics?” “Can I actually learn it this way?” We know what your brain is thinking Your bra THIS is imin thinks portant. Your brain craves novelty. It’s always searching, scanning, waiting for something unusual. It was built that way, and it helps you stay alive. So what does your brain do with all the routine, ordinary, normal things you encounter? Everything it can to stop them from interfering with the brain’s real job—recording things that matter. It doesn’t bother saving the boring things; they never make it past the “this is obviously not important” filter. How does your brain know what’s important? Suppose you’re out for a day hike and a tiger jumps in front of you—what happens inside your head and body? Great. Only 900 more dull, dry, boring pages. Neurons fire. Emotions crank up. Chemicals surge. And that’s how your brain knows… This must be important! Don’t forget it! But imagine you’re at home or in a library. It’s a safe, warm, tiger‑free zone. You’re studying. Getting ready for an exam. Or trying to learn some tough 3 technical topic your boss thinks will take a week, ten days at the most. in thinks Your bran’t worth THIS is saving. Just one problem. Your brain’s trying to do you a big favor. It’s trying to make sure that this obviously unimportant content doesn’t clutter up scarce resources. Resources that are better spent storing the really big things. Like tigers. Like the danger of fire. Like how you should never have posted those party photos on your Facebook page. And there’s no simple way to tell your brain, “Hey brain, thank you very much, but no matter how dull this book is, and how little I’m registering on the emotional Richter scale right now, I really do want you to keep this stuff around.” you are here 4 xxxi how to use this book er as a learner. t” read We think of a “Head Firs n make sure you have to get it, the st, Fir ? ng thi me so e to learn on the latest So what does it tak o your head. Based int ts fac ing sh pu t learning It’s not abou ational psychology, you don’t forget it. obiology, and educ ur ne , ce ien sc e itiv on. research in cogn what turns your brain on a page. We know t tex n tha re mo lot takes a ciples: First lear ning prin Some of the Head much ne, and make learning morable than words alo me re mo far more s are ng s age dies). It also makes thi Make it visual. Im recall and transfer stu in ent than on vem pro her im rat 89% hics they relate to, more effective (up to or near the gr ap in th wi s ated to the rd rel wo ms e th as likely to solve proble understandable. Put rs will be up to twice rne lea and e, pag r the the bottom or on ano t. ten con performed up ent studies, students alized style. In rec on rs pe d an l first-person, a na to the reader, using Use a conver satio content spoke directly the if ts tes g casual language. nin Use . ear s instead of lec turing to 40% better on post-l rie sto l Tel e. ton l ma ner-party her than tak ing a for ion to: a stimulating din conversational style rat uld you pay more attent wo ich Wh . sly iou ser Don’t take yourself too e? companion, or a lec tur your neurons, , unless you actively flex eply. In other words de re mo inspired to ink and th s, d, engaged, curiou Get the learner to der has to be motivate rea A d. hea llenges, r cha you d in ns d for that, you nee nothing much happe ate new knowledge. An ner ge and and ns, in sio bra clu the con olve both sides of solve problems, draw s, and activities that inv ion est qu ing vok pro exercises, and thoughtmultiple senses. rn this, but I can’t the “I really want to lea had all ’ve We . ion nt the ordinary, he reader’s atte things that are out of Get—and keep—t brain pays attention to r You e. enc eri esn’t have to be exp do e” ic on gh, technical top stay awake past page ed. Learning a new, tou ect exp un , ing tch -ca interesting, strange, eye ick ly if it’s not. l learn much more qu boring. Your brain wil is largely dependent remember something w that your ability to kno w no We s. you feel something. ion You remember when Touch their emot ut. abo e car you at otions like t. You remember wh dog. We’re talking em on its emotional conten s about a boy and his rie sto ng chi ren ve a puzzle, learn sol rt‑w hea e!” that comes when you No, we’re not talking rul “I of ling fee the technical than , “what the…?” , and ething that “I’m more surprise, curiosity, fun realize you know som or d, har is nks thi e els y something everybod ering doesn’t. ine Eng m fro b Bo u” tho xxxii intro the intro Me tacognition: thinking about thinking If you really want to learn, and you want to learn more quickly and more deeply, pay attention to how you pay attention. Think about how you think. Learn how you learn. Most of us did not take courses on metacognition or learning theory when we were growing up. We were expected to learn, but rarely taught to learn. I wonder how I can trick my brain into remembering this stuff… But we assume that if you’re holding this book, you really want to learn how to develop Android apps. And you probably don’t want to spend a lot of time. If you want to use what you read in this book, you need to remember what you read. And for that, you’ve got to understand it. To get the most from this book, or any book or learning experience, take responsibility for your brain. Your brain on this content. The trick is to get your brain to see the new material you’re learning as Really Important. Crucial to your well‑being. As important as a tiger. Otherwise, you’re in for a constant battle, with your brain doing its best to keep the new content from sticking. So just how DO you get your brain to treat Android development like it was a hungry tiger? There’s the slow, tedious way, or the faster, more effective way. The slow way is about sheer repetition. You obviously know that you are able to learn and remember even the dullest of topics if you keep pounding the same thing into your brain. With enough repetition, your brain says, “This doesn’t feel important to him, but he keeps looking at the same thing over and over and over, so I suppose it must be.” The faster way is to do anything that increases brain activity, especially different types of brain activity. The things on the previous page are a big part of the solution, and they’re all things that have been proven to help your brain work in your favor. For example, studies show that putting words within the pictures they describe (as opposed to somewhere else in the page, like a caption or in the body text) causes your brain to try to makes sense of how the words and picture relate, and this causes more neurons to fire. More neurons firing = more chances for your brain to get that this is something worth paying attention to, and possibly recording. A conversational style helps because people tend to pay more attention when they perceive that they’re in a conversation, since they’re expected to follow along and hold up their end. The amazing thing is, your brain doesn’t necessarily care that the “conversation” is between you and a book! On the other hand, if the writing style is formal and dry, your brain perceives it the same way you experience being lectured to while sitting in a roomful of passive attendees. No need to stay awake. But pictures and conversational style are just the beginning… you are here 4 xxxiii how to use this book Here’s what WE did We used pictures, because your brain is tuned for visuals, not text. As far as your brain’s concerned, a picture really is worth a thousand words. And when text and pictures work together, we embedded the text in the pictures because your brain works more effectively when the text is within the thing it refers to, as opposed to in a caption or buried in the body text somewhere. We used redundancy, saying the same thing in different ways and with different media types, and multiple senses, to increase the chance that the content gets coded into more than one area of your brain. We used concepts and pictures in unexpected ways because your brain is tuned for novelty, and we used pictures and ideas with at least some emotional content, because your brain is tuned to pay attention to the biochemistry of emotions. That which causes you to feel something is more likely to be remembered, even if that feeling is nothing more than a little humor, surprise, or interest. We used a personalized, conversational style, because your brain is tuned to pay more attention when it believes you’re in a conversation than if it thinks you’re passively listening to a presentation. Your brain does this even when you’re reading. We included activities, because your brain is tuned to learn and remember more when you do things than when you read about things. And we made the exercises challenging-yetdoable, because that’s what most people prefer. We used multiple learning styles, because you might prefer step-by-step procedures, while someone else wants to understand the big picture first, and someone else just wants to see an example. But regardless of your own learning preference, everyone benefits from seeing the same content represented in multiple ways. We include content for both sides of your brain, because the more of your brain you engage, the more likely you are to learn and remember, and the longer you can stay focused. Since working one side of the brain often means giving the other side a chance to rest, you can be more productive at learning for a longer period of time. And we included stories and exercises that present more than one point of view, because your brain is tuned to learn more deeply when it’s forced to make evaluations and judgments. We included challenges, with exercises, and by asking questions that don’t always have a straight answer, because your brain is tuned to learn and remember when it has to work at something. Think about it—you can’t get your body in shape just by watching people at the gym. But we did our best to make sure that when you’re working hard, it’s on the right things. That you’re not spending one extra dendrite processing a hard-to-understand example, or parsing difficult, jargon-laden, or overly terse text. We used people. In stories, examples, pictures, and the like, because, well, you’re a person. And your brain pays more attention to people than it does to things. xxxiv intro the intro Here’s what YOU can do to bend your brain into submission So, we did our part. The rest is up to you. These tips are a starting point; listen to your brain and figure out what works for you and what doesn’t. Try new things. Cut this out and sti on your refrigerator.ck it 1 Slow down. The more you understand, the less you have to memorize. Don’t just read. Stop and think. When the book asks you a question, don’t just skip to the answer. Imagine that someone really is asking the question. The more deeply you force your brain to think, the better chance you have of learning and remembering. 2 3 4 6 Listen to your brain. 8 Feel something. Read “There Are No Dumb Questions.” That means all of them. They’re not optional sidebars, they’re part of the core content! Don’t skip them. Make this the last thing you read before bed. Or at least the last challenging thing. Part of the learning (especially the transfer to long-term memory) happens after you put the book down. Your brain needs time on its own, to do more processing. If you put in something new during that processing time, some of what you just learned will be lost. 5 Talk about it. Out loud. Speaking activates a different part of the brain. If you’re trying to understand something, or increase your chance of remembering it later, say it out loud. Better still, try to explain it out loud to someone else. You’ll learn more quickly, and you might uncover ideas you hadn’t known were there when you were reading about it. Your brain works best in a nice bath of fluid. Dehydration (which can happen before you ever feel thirsty) decreases cognitive function. 7 Do the exercises. Write your own notes. We put them in, but if we did them for you, that would be like having someone else do your workouts for you. And don’t just look at the exercises. Use a pencil. There’s plenty of evidence that physical activity while learning can increase the learning. Drink water. Lots of it. 9 Pay attention to whether your brain is getting overloaded. If you find yourself starting to skim the surface or forget what you just read, it’s time for a break. Once you go past a certain point, you won’t learn faster by trying to shove more in, and you might even hurt the process. Your brain needs to know that this matters. Get involved with the stories. Make up your own captions for the photos. Groaning over a bad joke is still better than feeling nothing at all. Write a lot of code! There’s only one way to learn to develop Android apps: write a lot of code. And that’s what you’re going to do throughout this book. Coding is a skill, and the only way to get good at it is to practice. We’re going to give you a lot of practice: every chapter has exercises that pose a problem for you to solve. Don’t just skip over them—a lot of the learning happens when you solve the exercises. We included a solution to each exercise—don’t be afraid to peek at the solution if you get stuck! (It’s easy to get snagged on something small.) But try to solve the problem before you look at the solution. And definitely get it working before you move on to the next part of the book. you are here 4 xxxv how to use this book Re ad me This is a learning experience, not a reference book. We deliberately stripped out everything that might get in the way of learning whatever it is we’re working on at that point in the book. And the first time through, you need to begin at the beginning, because the book makes assumptions about what you’ve already seen and learned. We assume you’re new to Android, but not to Java. We’re going to be building Android apps using a combination of Java and XML. We assume that you’re familiar with the Java prorgamming language. If you’ve never done any Java programming at all, then you might want to read Head First Java before you start on this one. We start off by building an app in the very first chapter. Believe it or not, even if you’ve never developed for Android before, you can jump right in and start building apps. You’ll also learn your way around Android Studio, the official IDE for Android development. The examples are designed for learning. As you work through the book, you’ll build a number of different apps. Some of these are very small so you can focus on a specific part of Android. Other apps are larger so you can see how different components fit together. We won’t complete every part of every app, but feel free to experiment and finish them yourself. It’s all part of the learning experience. The source code for all the apps is here: https://tinyurl.com/HeadFirstAndroid. The activities are NOT optional. The exercises and activities are not add-ons; they’re part of the core content of the book. Some of them are to help with memory, some are for understanding, and some will help you apply what you’ve learned. Don’t skip the exercises. xxxvi intro the intro The redundancy is intentional and important. One distinct difference in a Head First book is that we want you to really get it. And we want you to finish the book remembering what you’ve learned. Most reference books don’t have retention and recall as a goal, but this book is about learning, so you’ll see some of the same concepts come up more than once. The Brain Power exercises don’t have answers. For some of them, there is no right answer, and for others, part of the learning experience of the Brain Power activities is for you to decide if and when your answers are right. In some of the Brain Power exercises, you will find hints to point you in the right direction. you are here 4 xxxvii the review team The technical revie w te am Andy Jacqui Technical reviewers: Andy Parker is currently working as a development manager, but has been a research physicist, teacher, designer, reviewer, and team leader at various points in his career. Through all of his roles, he has never lost his passion for creating top quality, well-designed, and well-engineered software. Nowadays, he spends most of his time managing great Agile teams and passing on his wide range of experience to the next generation of developers. xxxviii intro Jacqui Cope started coding to avoid school netball practice. Since then she has gathered 30 years’ experience working with a variety of financial software systems, from coding in COBOL to test management. Recently she has gained her MSc in Computer Security and has moved into software Quality Assurance in the higher education sector. In her spare time, Jacqui likes to cook, walk in the countryside, and watch Doctor Who from behind the sofa. the intro Acknowledgments Our editor: Heartfelt thanks to our wonderful editor Dawn Schanafelt for picking up the reins on the second edition. She has truly been amazing, and a delight to work with. She made us feel valued and supported every step of the way, and gave us invaluable feedback and insight exactly when we needed it. We’ve appreciated all the many times she told us our sentences had all the right words, but not necessarily in the right order. Dawn Schanafelt Thanks also to Bert Bates for teaching us to throw away the old rulebook and for letting us into his brain. The O’Reilly team: A big thank you goes to Mike Hendrickson for having confidence in us and asking us to write the first edition of the book; Heather Scherer for her behindthe-scenes organization skills and herding; the early release team for making early versions of the book available for download; and the design team for all their extra help. Finally, thanks go to the production team for expertly steering the book through the production process and for working so hard behind the scenes. Family, friends, and colleagues: Writing a Head First book is a rollercoaster of a ride, even when it’s a second edition, and this one’s been no exception. We’ve truly valued the kindness and support of our family and friends along the way. Special thanks go to Ian, Steve, Colin, Angela, Paul B, Chris, Michael, Mum, Dad, Carl, Rob, and Lorraine. The without-whom list: Our technical review team did a great job of keeping us straight, and making sure that what we covered was spot on. We’re also grateful to Ingo Krotzky for his valuable feedback on the early release of this book, and all the people who gave us feedback on the first edition. We think the book’s much, much better as a result. Finally, our thanks to Kathy Sierra and Bert Bates for creating this extraordinary series of books. you are here 4 xxxix o’reilly safari O’Reilly Safari® Safari (formerly Safari Books Online) is a membership-based training and reference platform for enterprise, government, educators, and individuals. Members have access to thousands of books, training videos, Learning Paths, interactive tutorials, and curated playlists from over 250 publishers, including O’Reilly Media, Harvard Business Review, Prentice Hall Professional, AddisonWesley Professional, Microsoft Press, Sams, Que, Peachpit Press, Adobe, Focal Press, Cisco Press, John Wiley & Sons, Syngress, Morgan Kaufmann, IBM Redbooks, Packt, Adobe Press, FT Press, Apress, Manning, New Riders, McGrawHill, Jones & Bartlett, and Course Technology, among others. For more information, please visit http://oreilly.com/safari. xl intro 1 getting started Diving In Android has taken the world by storm. Everybody wants a smartphone or tablet, and Android devices are hugely popular. In this book, we’ll teach you how to develop your own apps, and we’ll start by getting you to build a basic app and run it on an Android Virtual Device. Along the way, you’ll meet some of the basic components of all Android apps, such as activities and layouts. All you need is a little Java know-how... this is a new chapter 1 android overview Welcome to Androidville Android is the world’s most popular mobile platform. At the last count, there were over two billion active Android devices worldwide, and that number is growing rapidly. Android is a comprehensive open source platform based on Linux and championed by Google. It’s a powerful development framework that includes everything you need to build great apps using a mix of Java and XML. What’s more, it enables you to deploy those apps to a wide variety of devices—phones, tablets, and more. So what makes up a typical Android app? Layouts define what each screen looks like A typical Android app is composed of one or more screens. You define what each screen looks like using a layout to define its appearance. Layouts are usually defined in XML, and can include GUI components such as buttons, text fields, and labels. We’re going to build our Android apps using a mixture of Java and XML. We’ll explain things along the way, but you’ll need to have a fair understanding of Java to get the most out of this book. Layouts tell Android what the screens in your app look like. Activities define what the app does Layouts only define the appearance of the app. You define what the app does using one or more activities. An activity is a special Java class that decides which layout to use and tells the app how to respond to the user. As an example, if a layout includes a button, you need to write Java code in the activity to define what the button should do when you press it. Activities define what the app should do. Sometimes extra resources are needed too In addition to activities and layouts, Android apps often need extra resources such as image files and application data. You can add any extra files you need to the app. Android apps are really just a bunch of files in particular directories. When you build your app, all of these files get bundled together, giving you an app you can run on your device. 2 Chapter 1 Resources can include sound and image files. getting started The Android platform dissected Don’t worry if this seems like a lot to take in. The Android platform is made up of a number of different components. It includes core applications such as Contacts, a set of APIs to help you control what your app looks like and how it behaves, and a whole load of supporting files and libraries. Here’s a quick look at how they all fit together: Android comes with a set of core applications such as Contacts, Phone, Calendar, and a browser. When you build apps, you have access to the same APIs used by the core applications. You use these APIs to control what your app looks like and how it behaves. Underneath the application framework lies a set of C and C++ libraries. These libraries get exposed to you through the framework APIs. Underneath everything else lies the Linux kernel. Android relies on the kernel for drivers, and also core services such as security and memory management. We’re just giving you an overview of what’s included in the Android platform. We’ll explain the different components in more detail as and when we need to. Applications Contacts Home Browser Phone ... Application Framework Window Manager Activity Manager Package Manager Telephony Manager View System Content Providers Location Manager Resource Manager Libraries Notification Manager Android Runtime Surface Manager Media Framework SQLite OpenGL | ES FreeType WebKit SGL SSL libc Core Libraries The Android runtime comes with a set of core libraries that implement most of the Java programming language. Each Android app runs in its own process. Linux Kernel Display Driver Camera Driver Flash Memory Driver Binder (IPC) Driver Keypad Driver WiFi Driver Audio Drivers Power Management The great news is that all of the powerful Android libraries are exposed through the APIs in the application framework, and it’s these APIs that you use to create great Android apps. All you need to begin is some Java knowledge and a great idea for an app. you are here 4 3 steps Here’s what we’re going to do So let’s dive in and create a basic Android app. There are just a few things we need to do: 1 Set up a development environment. We need to install Android Studio, which includes all the tools you need to develop Android apps. 2 Build a basic app. We’ll build a simple app using Android Studio that will display some sample text on the screen. 3 Run the app in the Android emulator. We’ll use the built-in emulator to see the app up and running. 4 Change the app. Finally, we’ll make a few tweaks to the app we created in step 2, and run it again. Q: Are all Android apps developed in Java? A: You can develop Android apps in other languages, too. Most developers use Java, so that’s what we’re covering in this book. 4 Chapter 1 Q: How much Java do I need to know for Android app development? A: You really need experience with Java SE (Standard Edition). If you’re feeling rusty, we suggest getting a copy of Head First Java by Kathy Sierra and Bert Bates. Q: Do I need to know about Swing and AWT? A: Android doesn’t use Swing or AWT, so don’t worry if you don’t have Java desktop GUI experience. You are here. Your development environment Java is the most popular language used to develop Android applications. Android devices don’t run .class and .jar files. Instead, to improve speed and battery performance, Android devices use their own optimized formats for compiled code. That means that you can’t use an ordinary Java development environment—you also need special tools to convert your compiled code into an Android format, to deploy them to an Android device, and to let you debug the app once it’s running. getting started Set up environment Build app Run app Change app All of these come as part of the Android SDK. Let’s take a look at what’s included. The Android SDK The Android Software Development Kit contains the libraries and tools you need to develop Android apps. Here are some of the main points: SDK Platform There’s one of these for each version of Android. Documentation So you can access the latest API documentation offline. SDK Tools Tools for debugging and testing, plus other useful utilities. The SDK also features a set of platform dependent tools. Sample apps If you want practical code examples to help you understand how to use some of the APIs, the sample apps might help you. oid dr SDK An Android support Extra APIs that aren’t available in the standard platform. Google Play Billing Allows you to integrate billing services in your app. Android Studio is a special version of IntelliJ IDEA IntelliJ IDEA is one of the most popular IDEs for Java development. Android Studio is a version of IDEA that includes a version of the Android SDK and extra GUI tools to help you with your app development. In addition to providing you with an editor and access to the tools and libraries in the Android SDK, Android Studio gives you templates you can use to help you create new apps and classes, and it makes it easy to do things such as package your apps and run them. you are here 4 5 installation Set up environment Build app Run app Change app Install Android Studio Before we go any further, you need to install Android Studio on your machine. We’re not including the installation instructions in this book as they can get out of date pretty quickly, but you’ll be fine if you follow the online instructions. First, check the Android Studio system requirements here: We’re using Android Studio version 2.3. You’ll need to use this version or above to get the most out of this book. http://developer.android.com/sdk/index.html#Requirements Then follow the Android Studio installation instructions here: https://developer.android.com/sdk/installing/index.html?pkg=studio Once you’ve installed Android Studio, open it and follow the instructions to add the latest SDK tools and Support Libraries. When you’re done, you should see the Android Studio welcome screen. You’re now ready to build your first Android app. This is the Android Studio welcome screen. It includes a set of options for things you can do. 6 Chapter 1 Google sometimes changes their URLs. If these URLs don’t work, search for Android Studio and you should find them. getting started Q: You say we’re going to use Android Studio to build the Android apps. Do I have to? A: Strictly speaking, you don’t have to use Android Studio to build Android apps. All you need is a tool that will let you write and compile Java code, plus a few other tools to convert the compiled code into a form that Android devices can run. Android Studio is the official Android IDE, and the Android team recommends using it. But quite a lot of people use IntelliJ IDEA instead. Q: Can I write Android apps without using an IDE? A: It’s possible, but it’s more work. Most Android apps are now created using a build tool called Gradle. Gradle projects can be created and built using a text editor and a command line. Q: ANT? A build tool? So is gradle like Q: Most apps are built using Gradle? I thought you said most developers use Android Studio. A: Android Studio provides a graphical interface to Gradle, and also to other tools for creating layouts, reading logs, and debugging. You can find out more about Gradle in Appendix II. A: It’s similar, but Gradle is much more powerful than ANT. Gradle can compile and deploy code, just like ANT, but it also uses Maven to download any third-party libraries your code needs. Gradle also uses Groovy as a scripting language, which means you can easily create quite complex builds with Gradle. Build a basic app Now that you’ve set up your development environment, you’re ready to create your first Android app. Here’s what the app will look like: This is the name of the application. This is a very simple app, but that’s all you need for your very first Android app. There’ll be a small piece of sample text right here that Android Studio will put in for us. you are here 4 7 create project You’ve completed this step now, so we’ve checked it off. How to build the app Whenever you create a new app, you need to create a new project for it. Make sure you have Android Studio open, and follow along with us. 1. Create a new project The Android Studio welcome screen gives you a number of options. We want to create a new project, so click on the option for “Start a new Android Studio project.” Click on this option to start a new Android Studio project. Any projects you create will appear here. This is our first project, so this area is currently empty. 8 Chapter 1 Set up environment Build app Run app Change app getting started Set up environment Build app Run app Change app How to build the app (continued) 2. Configure the project You now need to configure the app by telling Android Studio what you want to call it, what company domain to use, and where you would like to store the files. The package name must stay the same for the lifetime of your app. Android Studio uses the company domain and application name to form the name of the package that will be used for your app. As an example, if you give your app a name of “My First App” and use a company domain of “hfad.com”, Android Studio will derive a package name of com.hfad.myfirstapp. The package name is really important in Android, as it’s used by Android devices to uniquely identify your app. Enter an application name of “My First App”, enter a company domain of “hfad.com”, uncheck the option to include C++ support, and accept the default project location. Then click on the Next button. Some versions of Android Studio may have an extra option asking if you want to include Kotlin support. Uncheck this option if it’s there. It’s a unique identifier for your app and used to manage multiple versions of the same app. The application name is shown in the Google Play Store and various other places, too. The wizard forms the package name by combining the application name and the company domain. Use a company domain of hfad.com. Uncheck the option to include C++ support. If prompted, also uncheck the option to include Kotlin support. All of the files for your project will be stored here. you are here 4 9 api level How to build the app (continued) Set up environment Build app Run app Change app 3. Specify the minimum SDK You now need to indicate the minimum SDK of Android your app will use. API levels increase with every new version of Android. Unless you only want your app to run on the very newest devices, you’ll probably want to specify one of the older APIs. Here, we’re choosing a minimum SDK of API level 19, which means it will be able to run on most devices. Also, we’re only going to create a version of our app to run on phones and tablets, so we’ll leave the other options unchecked. When you’ve done this, click on the Next button. The minimum required SDK is the lowest version your app will support. Your app will run on devices with this level API or higher. It won’t run on devices with a lower API. 10 Chapter 1 There’s more about the different API levels on the next page. getting started Android Versions Up Close You’ve probably heard a lot of things about Android that sound tasty, like Jelly Bean, KitKat, Lollipop, and Nougat. So what’s with all the confectionary? Android versions have a version number and a codename. The version number gives the precise version of Android (e.g., 7.0), while the codename is a more generic “friendly” name that may cover several versions of Android (e.g., Nougat). The API level refers to the version of the APIs used by applications. As an example, the equivalent API level for Android version 7.1.1 is 25. Version Codename 1.0 API level 1 1.1 2 1.5 Cupcake 3 1.6 Donut 4 2.0–2.1 Eclair 5–7 2.2.x Froyo 8 2.3–2.3.7 Gingerbread 9–10 3.0 - 3.2 Honeycomb 11–13 4.0–4.0.4 Ice Cream Sandwich 14–15 4.1 - 4.3 Jelly Bean 16–18 4.4 KitKat 19–20 5.0–5.1 Lollipop 21–22 6.0 Marshmallow 23 7.0 Nougat 24 7.1–7.1.2 Nougat 25 Hardly anyone uses these versions anymore. Most devices use one of these APIs. When you develop Android apps, you really need to consider which versions of Android you want your app to be compatible with. If you specify that your app is only compatible with the very latest version of the SDK, you might find that it can’t be run on many devices. You can find out the percentage of devices running particular versions here: https://developer.android.com/ about/dashboards/index.html. you are here 4 11 50,000 feet Set up environment Build app Run app Change app Activities and layouts from 50,000 feet The next thing you’ll be prompted to do is add an activity to your project. Every Android app is a collection of screens, and each screen is composed of an activity and a layout. An activity is a single, defined thing that your user can do. You might have an activity to compose an email, take a photo, or find a contact. Activities are usually associated with one screen, and they’re written in Java. A layout describes the appearance of the screen. Layouts are written as XML files and they tell Android how the different screen elements are arranged. Let’s look in more detail at how activities and layouts work together to create a user interface: 1 The device launches your app and creates an activity object. 2 The activity object specifies a layout. The activity tells Android to display the layout onscreen. 4 The user interacts with the layout that’s displayed on the device. 5 The activity responds to these interactions by running application code. 6 The activity updates the display... 7 ...which the user sees on the device. 2 Device Activity Layout 3 4 UserNow that you know a bit more about what activities and layouts are, let’s go through the last couple of steps in the Create New Project wizard and get it to create an activity and layout. 12 Chapter 1 Activities define actions. 1 3 Layouts define how the user interface is presented. 7 5 Device 6 Activity getting started How to build the app (continued) Set up environment Build app Run app Change app 4. Add an activity The next screen lets you choose among a series of templates you can use to create an activity and layout. We’re going to create an app with an empty activity and layout, so choose the Empty Activity option and click the Next button. There are other types of activity you can choose from, but for this exercise make sure you select the Empty Activity option. you are here 4 13 customize activity How to build the app (continued) Set up environment Build app Run app Change app 5. Customize the activity You will now be asked what you want to call the screen’s activity and layout. Enter an activity name of “MainActivity”, make sure the option to generate a layout file is checked, enter a layout name of “activity_main”, and then uncheck the Backwards Compatibility (AppCompat) option. The activity is a Java class, and the layout is an XML file, so the names we’ve given here will create a Java class file called MainActivity.java and an XML file called activity_main.xml. When you click on the Finish button, Android Studio will build your app. Uncheck the Backwards Compatibility (AppCompat) option. You’ll find out more about this setting later in the book. 14 Chapter 1 Name the activity “MainActivity” and the layout “activity_main”. Also make sure the option to generate the layout is checked. getting started Set up environment Build app Run app Change app You’ve just created your first Android app So what just happened? ¥ The Create New Project wizard created a project for your app, configured to your specifications. You defined which versions of Android the app should be compatible with, and the wizard created all of the files and folders needed for a basic valid app. ¥ The wizard created an activity and layout with template code. The template code includes layout XML and activity Java code, with sample “Hello World!” text in the layout. When you finish creating your project by going through the wizard, Android Studio automatically displays the project for you. Here’s what our project looks like (don’t worry if it looks complicated—we’ll break it down over the next few pages): This is the project in Android Studio. you are here 4 15 folder structure Set up environment Build app Run app Change app Android Studio creates a complete folder structure for you An Android app is really just a bunch of valid files in a particular folder structure, and Android Studio sets all of this up for you when you create a new app. The easiest way of looking at this folder structure is with the explorer in the leftmost column of Android Studio. The explorer contains all of the projects that you currently have open. To expand or collapse folders, just click on the arrows to the left of the folder icons. Click on the arrow here and choose the Project option to see the files and folders that make up your project. The folder structure includes different types of files If you browse through the folder structure, you’ll see that the wizard has created various types of files and folders for you: This is the name of the project. These files and folders are all included in your project. Click on these arrows to expand or collapse the folders. ¥ Java and XML source files These are the activity and layout files for your app. ¥ Android-generated Java files There are some extra Java files you don’t need to touch that Android Studio generates for you automatically. ¥ Resource files These include default image files for icons, styles your app might use, and any common String values your app might want to look up. ¥ Android libraries In the wizard, you specified the minimum SDK version you want your app to be compatible with. Android Studio makes sure your app includes the relevant Android libraries for that version. ¥ Configuration files The configuration files tell Android what’s actually in the app and how it should run. Let’s take a closer look at some of the key files and folders in Androidville. 16 Chapter 1 getting started Set up environment Build app Run app Change app Useful files in your project Android Studio projects use the Gradle build system to compile and deploy apps. Gradle projects have a standard structure. Here are some of the key files and folders you’ll be working with: The app folder is a module in your project. MyFirstApp The root folder is the name of your project. All the files for your project go in here. The build folder contains files that Android Studio creates for you. You don’t usually edit anything in this folder. app build Every Android project needs a file called R.java, which is created for you and which lives in the generated/ source folder. Android uses this file to keep track of resources in the app. generated/source r/debug The src folder contains source code you write and edit. com/hfad/myfirstapp src The java folder contains any Java code you write. Any activities you create live here. You can find app resources in the res folder. For example, the layout subfolder contains layouts, and the values subfolder contains resource files for values such as Strings. You can get other types of resources too. Every Android app must include a file called AndroidManifest.xml at its root. The manifest file contains essential information about the app, such as what components it contains, required libraries, and other declarations. R.java main java com/hfad/myfirstapp MainActivity.java MainActivity.java defines an activity. An activity tells Android how the app should interact with the user. res layout Drink.java activity_drink.xml 3 DrinkCategoryActivity.java We’ve created these activities and their layouts. 1 When the app gets launched, it starts activity TopLevelActivity. The activity displays a list of options for Drinks, Food, and Stores. 2 The user clicks on Drinks in TopLevelActivity. This launches activity DrinkCategoryActivity, which displays a list of drinks. 3 DrinkCategoryActivity gets the values for its list of drinks from the Drink.java class file. The next thing we’ll do is get DrinkCategoryActivity to launch DrinkActivity, passing it details of whichever drink was clicked. 274 Chapter 7activity_main.xml defines a layout. A layout tells Android how your app should look. activity_main.xml values AndroidManifest.xml strings.xml strings.xml is a String resource file. It includes Strings such as the app’s name and any default text values. Other files such as layouts and activities can look up text values from here. you are here 4 17 meet the editors Edit code with the Android Studio editors You view and edit files using the Android Studio editors. Double-click on the file you want to work with, and the file’s contents will appear in the middle of the Android Studio window. Set up environment Build app Run app Change app The code editor Most files get displayed in the code editor, which is just like a text editor, but with extra features such as color coding and code checking. Double-click on the file in the explorer and the file contents appear in the editor panel. The design editor If you’re editing a layout, you have an extra option. Rather than edit the XML (such as that shown on the next page), you can use the design editor, which allows you to drag GUI components onto your layout, and arrange them how you want. The code editor and design editor give different views of the same file, so you can switch back and forth between the two. 18 Chapter 1 You dictate which editor you’re using with these tabs. You can edit layouts using the visual editor by dragging and dropping components. getting started Here’s the code from an example layout file (not the one Android Studio generated for us). We know you’ve not seen layout code before, but just see if you can match each of the descriptions at the bottom of the page to the correct lines of code. We’ve done one to get you started. activity_main.xml Add padding to the screen margins. Include a GUI component for displaying text. Make the GUI component just large enough for its content. Display the String “Hello World!” Make the layout the same width and height as the screen size on the device. you are here 4 19 solution SOLUTION Here’s the code from an example layout file (not the one Android Studio generated for us). We know you’ve not seen layout code before, but just see if you can match each of the descriptions at the bottom of the page to the correct lines of code. We’ve done one to get you started. activity_main.xml Add padding to the screen margins. Include a GUI component for displaying text. Make the GUI component just large enough for its content. 20 Chapter 1 Display the String “Hello World!” Make the layout the same width and height as the screen size on the device. getting started Now let’s see if you can do the same thing for some activity code. This is example code, and not necessarily the code that Android Studio will have generated for you. Match the descriptions below to the correct lines of code. MainActivity.java package com.hfad.myfirstapp; import android.os.Bundle; import android.app.Activity; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } } setContentView(R.layout.activity_main); This is the package name. These are Android classes used in MainActivity. Implement the onCreate() method from the Activity class. This method is called when the activity is first created. MainActivity extends the Specify which layout to use. Android class android.app.Activity. you are here 4 21 another solution SOLUTION Now let’s see if you can do the same thing for some activity code. This is example code, and not necessarily the code that Android Studio will have generated for you. Match the descriptions below to the correct lines of code. MainActivity.java package com.hfad.myfirstapp; import android.os.Bundle; import android.app.Activity; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ) } setContentView(R.layout.activity_main); This is the package name. These are Android classes used in MainActivity. Implement the onCreate() method from the Activity class. This method is called when the activity is first created. MainActivity extends the Specify which layout to use. 22 Chapter 1 Android class android.app.Activity. getting started Run the app in the Android emulator So far you’ve seen what your Android app looks like in Android Studio and got a feel for how it hangs together. But what you really want to do is see it running, right? You have a couple of options when it comes to running your apps. The first option is to run them on a physical device. But what if you don’t have one with you, or you want to see how your app looks on a type of device you don’t have? In that case, you can use the Android emulator that’s built into the Android SDK. The emulator enables you to set up one or more Android virtual devices (AVDs) and then run your app in the emulator as though it’s running on a physical device. So what does the emulator look like? Here’s an AVD running in the Android emulator. It looks just like a phone running on your computer. The emulator recreates the exact hardware environment of an Android device: from its CPU and memory through to the sound chips and the video display. The emulator is built on an existing emulator called QEMU (pronounced “queue em you”), which is similar to other virtual machine applications you may have used, like VirtualBox or VMWare. Set up environment Build app Run app Change app The Android emulator allows you to run your app on an Android virtual device (AVD), which behaves just like a physical Android device. You can set up numerous AVDs, each emulating a different type of device. Once you’ve set up an AVD, you'll be able to see your app running on it. Android Studio launches the emulator for you. The exact appearance and behavior of the AVD depends on how you’ve set up the AVD in the first place. The AVD here is set up to mimic a Nexus 5X, so it will look and behave just like a Nexus 5X on your computer. Let’s set up an AVD so that you can see your app running in the emulator. you are here 4 23 create avd Create an Android Virtual Device There are a few steps you need to go through in order to set up an AVD within Android Studio. We’ll set up a Nexus 5X AVD running API level 25 so that you can see how your app looks and behaves running on this type of device. The steps are pretty much identical no matter what type of virtual device you want to set up. Set up environment Build app Run app Change app Open the Android Virtual Device Manager The AVD Manager allows you to set up new AVDs, and view and edit ones you’ve already created. Open it by selecting Android on the Tools menu and choosing AVD Manager. If you have no AVDs set up already, you’ll be presented with a screen prompting you to create one. Click on the “Create Virtual Device” button. Click on this button to create an AVD. Select the hardware On the next screen, you’ll be prompted to choose a device definition. This is the type of device your AVD will emulate. You can choose a variety of phone, tablet, wear, or TV devices. We’re going to see what our app looks like running on a Nexus 5X phone. Choose Phone from the Category menu and Nexus 5X from the list. Then click the Next button. 24 Chapter 1 When you select a device, its details appear here. getting started Creating an AVD (continued) Set up environment Build app Run app Change app Select a system image Next, you need to select a system image. The system image gives you an installed version of the Android operating system. You can choose the version of Android you want to be on your AVD. You need to choose a system image for an API level that’s compatible with the app you’re building. As an example, if you want your app to work on a minimum of API level 19, choose a system image for at least API level 19. We want our AVD to run API level 25, so choose the system image with a release name of Nougat and a target of Android 7.1.1 (API level 25). Then click on the Next button. If you don’t have this system image installed, you’ll be given the option to download it. We’ll continue setting up the AVD on the next page. you are here 4 25 check configuration Set up environment Build app Run app Change app Creating an AVD (continued) Verify the AVD configuration On the next screen, you’ll be asked to verify the AVD configuration. This screen summarizes the options you chose over the last few screens, and gives you the option of changing them. Accept the options, and click on the Finish button. These are the options you chose over the past few pages. The AVD Manager will create the AVD for you, and when it’s done, display it in the AVD Manager list of devices. You may now close the AVD Manager. Your Nexus 5X AVD has been created. 26 Chapter 1 getting started Set up environment Build app Run app Change app Run the app in the emulator Now that you’ve set up your AVD, let’s run the app on it. To do this, choose the “Run ‘app’” command from the Run menu. When you’re asked to choose a device, select the Nexus 5X AVD you just created. Then click OK. The AVD can take a few minutes to appear, so while we wait, let’s take a look at what happens when you choose Run. Compile, package, deploy, and run The Run command doesn’t just run your app. It also handles all the preliminary tasks that are needed for the app to run: This is the AVD we just created. Libraries 2 1 Java file Resources APK Bytecode Run APK file 3 4 5 Emulator An APK file is an Android application package. It’s basically a JAR or ZIP file for Android applications. Emulator 1 The Java source files get compiled to bytecode. 2 An Android application package, or APK file, gets created. The APK file includes the compiled Java files, along with any libraries and resources needed by your app. 3 Assuming there’s not one already running, the emulator gets launched and then runs the AVD. 4 Once the emulator has been launched and the AVD is active, the APK file is uploaded to the AVD and installed. 5 The AVD starts the main activity associated with the app. Your app gets displayed on the AVD screen, and it’s all ready for you to test out. you are here 4 27 be patient Set up environment Build app Run app Change app You can watch progress in the console It can sometimes take quite a while for the emulator to launch with your AVD—often several minutes. If you like, you can watch what’s happening using the Android Studio console. The console gives you a blow-by-blow account of what the build system is doing, and if it encounters any errors, you’ll see them highlighted in the text. We suggest finding something else to do while waiting for the emulator to start. Like quilting, or cooking a small meal. You can find the console at the bottom of the Android Studio screen (click on the Run option at the bottom of the screen if it doesn’t appear automatically): Here’s the output from our console window when we ran our app: 03/13 10:45:41: Launching app Install the app. $ adb install-multiple -r /Users/dawng/AndroidStudioProjects/MyFirstApp/app/build/intermediates/ split-apk/debug/dep/dependencies.apk /Users/dawng/AndroidStudioProjects/MyFirstApp/app/build/ intermediates/split-apk/debug/slices/slice_1.apk /Users/dawng/AndroidStudioProjects/MyFirstApp/ app/build/intermediates/split-apk/debug/slices/slice_2.apk /Users/dawng/AndroidStudioProjects/ MyFirstApp/app/build/intermediates/split-apk/debug/slices/slice_0.apk /Users/dawng/ AndroidStudioProjects/MyFirstApp/app/build/intermediates/split-apk/debug/slices/slice_3.apk /Users/ dawng/AndroidStudioProjects/MyFirstApp/app/build/intermediates/split-apk/debug/slices/slice_6.apk / Users/dawng/AndroidStudioProjects/MyFirstApp/app/build/intermediates/split-apk/debug/slices/slice_4. apk /Users/dawng/AndroidStudioProjects/MyFirstApp/app/build/intermediates/split-apk/debug/slices/ slice_5.apk /Users/dawng/AndroidStudioProjects/MyFirstApp/app/build/intermediates/split-apk/debug/ slices/slice_7.apk /Users/dawng/AndroidStudioProjects/MyFirstApp/app/build/intermediates/split-apk/ debug/slices/slice_8.apk /Users/dawng/AndroidStudioProjects/MyFirstApp/app/build/intermediates/ split-apk/debug/slices/slice_9.apk /Users/dawng/AndroidStudioProjects/MyFirstApp/app/build/outputs/ apk/app-debug.apk Split APKs installed $ adb shell am startservice com.hfad.myfirstapp/com.android.tools.fd.runtime.InstantRunService $ adb shell am start -n "com.hfad.myfirstapp/com.hfad.myfirstapp.MainActivity" -a android.intent. action.MAIN -c android.intent.category.LAUNCHER Connected to process 2685 on device Nexus_5X_API_25 [emulator-5554] 28 Chapter 1 Android Studio has finished launching the AVD we just set up. The emulator launches our app by starting the main activity for it. This is the activity the wizard created for us. getting started Test drive So let’s look at what actually happens onscreen when you run your app. First, the emulator fires up in a separate window. The emulator takes a while to load the AVD, but then you see what looks like an actual Android device. ...and here’s the AVD home screen. It looks and behaves just like a real Nexus 5X device. The emulator launches... Wait a bit longer, and you’ll see the app you just created. The application name appears at the top of the screen, and the default sample text “Hello World!” is displayed in the middle of the screen. Android Studio created the sample text “Hello World!” without us telling it to. Set up environment Build app Run app Change app This is the name of the app. The wizard created sample text for us. Here’s the app running on the AVD. you are here 4 29 what happened Set up environment Build app Run app Change app What just happened? Let’s break down what happens when you run the app: 1 Android Studio launches the emulator, loads the AVD, and installs the app. 2 When the app gets launched, an activity object is created from MainActivity.java. 3 4 The activity specifies that it uses the layout activity_main.xml. 1 2 3 Device The activity tells Android to display the layout on the screen. The text “Hello World!” gets displayed. 4 Activity Layout In this particular instance, we’re using a virtual device. Q: You mentioned that when you create an APK file, the Java source code gets compiled into bytecode and added to the APK. Presumably you mean it gets compiled into Java bytecode, right? A: It does, but that’s not the end of the story. Things work a little differently on Android. The big difference with Android is that your code doesn’t actually run inside an ordinary Java VM. It runs on the Android runtime (ART) instead, and on older devices it runs in a predecessor to ART called Dalvik. This means that you write your Java source code and compile it into .class files using the Java compiler, and then the .class files get stitched into one or more files in DEX format, which is smaller, more efficient bytecode. ART then runs the DEX code. You can find more details about this in Appendix III. Q: That sounds complicated. Why not just use the normal Java VM? A: ART can convert the DEX bytecode into native code that can run directly on the CPU of the Android device. This makes the app run a lot faster, and use a lot less battery power. Q: A: Q: Is a Java virtual machine really that much overhead? Yes. Because on Android, each app runs inside its own process. If it used ordinary JVMs, it would need a lot more memory. app? A: Do I need to create a new AVD every time I create a new No, once you’ve created the AVD you can use it for any of your apps. You may find it useful to create multiple AVDs in order to test your apps in different situations. As an example, in addition to a phone AVD you might want to create a tablet AVD so you can see how your app looks and behaves on larger devices. 30 Chapter 1 getting started Set up environment Build app Run app Change app Refine the app Over the past several pages, you’ve built a basic Android app and seen it running in the emulator. Next, we’re going to refine the app. At the moment, the app displays the sample text “Hello World!” that the wizard put in as a placeholder. You’re going to change that text to say something else instead. So what do we need to change in order to achieve that? To answer that, let’s take a step back and look at how the app is currently built. Your app currently says “Hello World!” but we're going to change it to something else instead. The app has one activity and one layout When we built the app, we told Android Studio how to configure it, and the wizard did the rest. The wizard created an activity for us, and also a default layout. Our activity specifies what the app does and how it should interact with the user. The activity controls what the app does Android Studio created an activity for us called MainActivity.java. The activity specifies what the app does and how it should respond to the user. MainActivity.java The layout controls the app's appearance MainActivity.java specifies that it uses the layout Android Studio created for us called activity_main.xml. The layout specifies what the app looks like. We want to change the appearance of the app by changing the text that’s displayed. This means that we need to deal with the Android component that controls what the app looks like, so we need to take a closer look at the layout. Our layout specifies what the app looks like. activity_main.xml you are here 4 31 the layout What’s in the layout? We want to change the sample “Hello World!” text that Android Studio created for us, so let’s start with the layout file activity_main.xml. If it isn’t already open in an editor, open it now by finding the file in the app/src/main/res/layout folder in the explorer and double-clicking on it. If you can’t see the folder structure in the explorer, try switching to Project view. Click on this arrow to change how the files and folders are shown. The design editor The design editor As you learned earlier, there are two ways of viewing and editing layout files in Android Studio: through the design editor and through the code editor. When you choose the design option, you can see that the sample text “Hello World!” appears in the layout as you might expect. But what’s in the underlying XML? Let’s see by switching to the code editor. You can see the design editor by choosing “Design” here. The code editor When you choose the code editor option, the content of activity_main.xml is displayed. Let’s take a closer look at it. The code editor To see the code editor, click on “Text” in the bottom tab. 32 Chapter 1 Here’s the sample text. getting started Set up environment Build app Run app Change app activity_main.xml has two elements Below is the code from activity_main.xml that Android Studio generated for us. We’ve left out some of the details you don’t need to think about just yet; we’ll cover them in more detail through the rest of the book. Here’s our code: This is the full path of activity_main.xml. This element determines how components should be displayed, in this case the “Hello World!” text.element. ... > MyFirstApp app/src/main android:layout_width="wrap_content" res android:layout_height="wrap_content" layout android:text="Hello World!" ... /> As you can see, the code contains two elements. The first is anWe’ve left out some of the XML too. activity_main.xml element. This is a type of layout element that tells Android how to display components on the device screen. There are various types of layout element available for you to use, and you’ll find out more about these later in the book. The most important element for now is the second element, the . This element is used to display text to the user, in our case the sample text “Hello World!” The key part of the code within the element is the line starting with android:text. This is a text property describing the text that should be displayed: The element describes the text in the layout. Don’t worry if your layout code looks different from ours. Android Studio may give you slightly different XML depending on which version you’re using. You don’t need to worry about this, because from the next chapter onward you’ll learn how to roll your own layout code, and replace a lot of what Android Studio gives you. This is the text that's being displayed. Let’s change the text to something else. you are here 4 33 update text Set up environment Build app Run app Change app Update the text displayed in the layout The key part of the element is this line: android:text="Hello World!" /> android:text means that this is the text property of the element, so it specifies what text should be displayed in the layout. In this case, the text that’s being displayed is “Hello World!” Display the text... ...“Hello World!” android:text="Hello World!" /> To update the text that’s displayed in the layout, simply change the value of the text property from "Hello World!" to "Sup doge". The new code for the should look like this: ... We’ve left out some of the code, as all we’re doing for now is changing the text that’s displayed. Change the text here from “Hello World!” to “Sup doge”. Once you’ve updated the file, go to the File menu and choose the Save All option to save your change. Q: A: My layout code looks different from yours. Is that OK? Yes, that’s fine. Android Studio may generate slightly different code if you’re using a different version than us, but that doesn’t really matter. From now on you’ll be learning how to create your own layout code, and you’ll replace a lot of what Android Studio gives you. Q: Am I right in thinking we’re hardcoding the text that's displayed? A: Yes, purely so that you can see how to update text in the layout. There’s a better way of displaying text values than hardcoding them in your layouts, but you’ll have to wait for the next chapter to learn what it is. 34 Chapter 1 MyFirstApp app/src/main res layout activity_main.xml Q: The folders in my project explorer pane look different from yours. Why’s that? A: Android Studio lets you choose alternate views for how to display the folder hierarchy, and it defaults to the “Android” view. We prefer the “Project” view, as it reflects the underlying folder structure. You can change your explorer to the “Project” view by clicking on the arrow at the top of the explorer pane, and selecting the “Project” option. We’re using the Project view. Click on this arrow to change the explorer view. getting started Take the app for a test drive Once you’ve edited the file, try running your app in the emulator again by choosing the “Run ‘app’” command from the Run menu. You should see that your app now says “Sup doge” instead of “Hello World!” Set up environment Build app Run app Change app Here’s the updated version of our app. The sample text now says “Sup doge” instead of “Hello World!” You’ve now built and updated your first Android app. you are here 4 35 toolbox CHAPTER 1 Your Android Toolbox You’ve got Chapter 1 under your belt and now you’ve added Android basic concepts to your toolbox. Versions of Android have a version number, API level, and code name. Android Studio is a special version of IntelliJ IDEA that interfaces with the Android Software Development Kit (SDK) and the Gradle build system. A typical Android app is composed of activities, layouts, and resource files. Layouts describe what your app looks like. They’re held in the app/ src/main/res/layout folder. Activities describe what your app does, and how it interacts with the user. The activities you write are held in the app/src/main/java folder. 36 Chapter 1 You can download the full code for the chapter from https://tinyurl.com/ HeadFirstAndroid. AndroidManifest.xml contains information about the app itself. It lives in the app/src/main folder. An AVD is an Android Virtual Device. It runs in the Android emulator and mimics a physical Android device. An APK is an Android application package. It’s like a JAR file for Android apps, and contains your app’s bytecode, libraries, and resources. You install an app on a device by installing the APK. Android apps run in separate processes using the Android runtime (ART). The element is used for displaying text. 2 building interactive apps Apps That Do Something I wonder what happens if I press the button marked “ejector seat”? Most apps need to respond to the user in some way. In this chapter, you’ll see how you can make your apps a bit more interactive. You’ll learn how to get your app to do something in response to the user, and how to get your activity and layout talking to each other like best buddies. Along the way, we’ll take you a bit deeper into how Android actually works by introducing you to R, the hidden gem that glues everything together. this is a new chapter 37 beer adviser Let’s build a Beer Adviser app In Chapter 1, you saw how to create an app using the Android Studio New Project wizard, and how to change the text displayed in the layout. But when you create an Android app, you’re usually going to want the app to do something. In this chapter, we’re going to show you how to create an app that the user can interact with: a Beer Adviser app. In the app, users can select the types of beer they enjoy, click a button, and get back a list of tasty beers to try out. Choose your beer type, click the button... ...and the app comes up with a list of beer suggestions. Here’s how the app will be structured: 1 The layout specifies what the app will look like. It includes three GUI components: • A drop-down list of values called a spinner, which allows the user to choose which type of beer they want. • A button that when pressed will return a selection of beer types. • A text field that displays the types of beer. 2 3 4 This is what the layout looks like. 1 The file strings.xml includes any String resources needed by the layout—for example, the label of the button specified in the layout and the types of beer. The activity specifies how the app should interact with the user. It takes the type of beer the user chooses, and uses this to display a list of beers the user might be interested in. It achieves this with the help of a custom Java class. The custom Java class contains the application logic for the app. It includes a method that takes a type of beer as a parameter, and returns a list of beers of this type. The activity calls the method, passes it the type of beer, and uses the response. 38 Chapter 2 Layout 2 strings.xml 3 Activity 4 Custom Java building interactive apps Here’s what we’re going to do So let’s get to work. There are a few steps you need to go through to build the Beer Adviser app (we’ll tackle these throughout the rest of the chapter): 1 2 3 4 Create a project. You’re creating a brand-new app, so you’ll need to create a new project. Just like before, you’ll need to create an empty activity with a layout. We’ll show you the details of how to do this on the next page. Update the layout. Once you have the app set up, you need to amend the layout so that it includes all the GUI components your app needs. Connect the layout to the activity. The layout only creates the visuals. To add smarts to your app, you need to connect the layout to the Java code in your activity. Layout Activity Write the application logic. You’ll add a Java custom class to the app, and use it to make sure users get the right beer based on their selection. Layout you are here 4 39 create project Create project Update layout Connect activity Write logic Create the project Let’s begin by creating the new app (the steps are similar to those we used in the previous chapter): 1 pen Android Studio and choose “Start a new Android Studio project” from O the welcome screen. This starts the wizard you saw in Chapter 1. 2 hen prompted, enter an application name of “Beer Adviser” and a W company domain of “hfad.com”, making your package name com.hfad. beeradviser. Make sure you uncheck the option to include C++ support. 3 4 e want the app to work on most phones and tablets, so choose a minimum W SDK of API 19, and make sure the option for “Phone and Tablet” is selected. This means that any phone or tablet that runs the app must have API 19 installed on it as a minimum. Most Android devices meet this criterion. If your version of Android Studio has an option to include Kotlin support, uncheck this option too. Choose an empty activity for your default activity. Call the activity “FindBeerActivity” and the accompanying layout “activity_find_beer”. Make sure the option to generate the layout is selected and you uncheck the Backwards Compatibility (AppCompat) option. s, The wizard will take you through these step r “Bee ion icat appl just like before. Call your Adviser,” make sure it uses a minimum SDK yof API 19, and then tell it to create an emptlayout activity called “FindBeerActivity” and a called “activity_find_beer”. 2 3 Make sure you choose the Empty Activity option. 40 Chapter 2 4 Make sure you UNCHECK the Backwards Compatibility (AppCompat) option. building interactive apps We’ve created a default activity and layout When you click on the Finish button, Android Studio creates a new project containing an activity called FindBeerActivity.java and a layout called activity_find_beer.xml. Let’s start by changing the layout file. To do this, switch to the Project view of Android Studio’s explorer, go to the app/src/main/ res/layout folder, and open the file activity_find_beer.xml. Then switch to the text version of the code to open the code editor, and replace the code in activity_find_beer.xml with the following (we’ve bolded all the new code): Click on the Text tab to open the code editor. We’re replacing the code Android Studio generated for us. We’ve just changed the code Android Studio gave us so that it uses a layout . This is used to display GUI components next to each other, either vertically or horizontally. If it’s vertically, they’re displayed in a single column, and if it’s horizontally, they’re displayed in a single row. You’ll find out more about how this works as we go through the chapter. Any changes you make to a layout’s XML are reflected in Android Studio’s design editor, which you can see by clicking on the Design tab. We’ll look at this in more detail on the next page. activity_ find_beer.xml Click on the Design tab to open the design editor. you are here 4 41 design editor A closer look at the design editor The design editor presents you with a more visual way of editing your layout code than editing XML. It features two different views of the layouts design. One shows you how the layout will look on an actual device, and the other shows you a blueprint of its structure: This view of the design gives you an idea of how your layout will look on an actual device. The text view in our layout's XML code appears in both views of the design editor. Create project Update layout Connect activity Write logic If Android Studio doesn’t show you both views of the layout, click on the “Show Design + Blueprint” icon in the design editor’s toolbar. This is the blueprint view, which focuses more on the layout's structure. To the left of the design editor, there’s a palette that contains components you can drag to your layout. We’ll use this next. This list shows you the different categories of component you can add to your layout. You can click on them to filter the components displayed in the palette. You can increase the size of the palette by clicking on this area and dragging it downward. 42 Chapter 2 These are the components You'll find out more about them later in the book. building interactive apps Create project Update layout Connect activity Write logic Add a button using the design editor We’re going to add a button to our layout using the design editor. Find the Button component in the palette, click on it, and then drag it into the design editor so that it’s positioned above the text view. The button appears in the layout’s design: Here’s the Button component. Drag it into the design editor. Put the button above the text. You can add it to either view of the design. Changes in the design editor are reflected in the XML Dragging GUI components to the layout like this is a convenient way of updating the layout. If you switch to the code editor, you’ll see that adding the button via the design editor has added some lines of code to the file: ... There’s a new 2 1 Device We’ve created Drink.java. DrinkActivity.java We haven't created DrinkActivity yet. list views and adapters Pool Puzzle Your goal is to create an activity that binds a Java array of colors to a spinner. Take code snippets from the pool and place them into the blank lines in the activity. You may not use the same snippet more than once, and you won’t need to use all the snippets. ... Remember, we covered spinners in Chapter 5. We’re not using this activity in our app. public class MainActivity extends Activity { String[] colors = new String[] {"Red", "Orange", "Yellow", "Green", "Blue"}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Spinner spinner = ( ArrayAdapter< , ) findViewById(R.id.spinner); > adapter = new ArrayAdapter<>( android.R.layout.simple_spinner_item, colors); } } spinner. (adapter); This displays each value in the array as a single row in the spinner. Note: each snippet from the pool can only be used once! colors setAdapter this colors Spinner String Answers on page 287. you are here 4 275 handling clicks Add resources TopLevelActivity DrinkCategoryActivity DrinkActivity How we handled clicks in TopLevelActivity Earlier in this chapter, we needed to get TopLevelActivity to react to the user clicking on the first item in the list view, the Drinks option, by starting DrinkCategoryActivity. To do that, we had to create an OnItemClickListener, implement its onItemClick() method, and assign it to the list view. Here’s a reminder of the code: AdapterView.OnItemClickListener itemClickListener = Create the listener. new AdapterView.OnItemClickListener(){ public void onItemClick(AdapterView> listView, View itemView, int position, long id) { The list view The item view that was clicked, its position in the list, and the row ID of the underlying data. if (position == 0) { Intent intent = new Intent(TopLevelActivity.this, }; } } startActivity(intent); DrinkCategoryActivity.class); ListView listView = (ListView) findViewById(R.id.list_options); listView.setOnItemClickListener(itemClickListener); We had to set up an event listener in this way because list views aren’t hardwired to respond to clicks in the way that buttons are. So how should we get DrinkCategoryActivity to handle user clicks? 276 Chapter 7 Add the listener to the list view. list views and adapters Add resources TopLevelActivity DrinkCategoryActivity DrinkActivity Pass the ID of the item that was clicked by adding it to an intent When you use a category activity to display items in a list view, you’ll usually use the onItemClick() method to start another activity that displays details of the item the user clicked. To do this, you create an intent that starts the second activity. You then add the ID of the item that was clicked as extra information so that the second activity can use it when the activity starts. In our case, we want to start DrinkActivity and pass it the ID of the drink that was selected. DrinkActivity will then be able to use this information to display details of the right drink. Here’s the code for the intent: Intent drinkId DrinkCategoryActivity DrinkActivity DrinkCategoryActivity needs to start DrinkActivity. Intent intent = new Intent(DrinkCategoryActivity.this, DrinkActivity.class); intent.putExtra(DrinkActivity.EXTRA_DRINKID, (int) id); startActivity(intent); We’re using a constant for the name of the extra information in the intent so that we know DrinkCategoryActivity and DrinkActivity are using the same String. We’ll add this constant to DrinkActivity when we create the activity. Add the ID of the item that was clicked to the intent. This is the index of the drink in the drinks array. It’s common practice to pass the ID of the item that was clicked because it’s the ID of the underlying data. If the underlying data is an array, the ID is the index of the item in the array. If the underlying data comes from a database, the ID is the ID of the record in the table. Passing the ID of the item in this way means that it’s easier for the second activity to get details of the data, and then display it. That’s everything we need to make DrinkCategoryActivity start DrinkActivity and tell it which drink was selected. The full activity code is on the next page. you are here 4 277 DrinkCategoryActivity code Add resources TopLevelActivity DrinkCategoryActivity DrinkActivity The full DrinkCategoryActivity code Here’s the full code for DrinkCategoryActivity.java (add the new method to your code, then save your changes): package com.hfad.starbuzz; import import import import import import import android.app.Activity; android.os.Bundle; android.widget.ArrayAdapter; android.widget.ListView; android.view.View; android.content.Intent; android.widget.AdapterView; Starbuzz app/src/main We're using these extra classes so we need to import them. public class DrinkCategoryActivity extends Activity { java com.hfad.starbuzz DrinkCategory Activity.java @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_drink_category); ArrayAdapter activity_top_level.xml 1 DevicelistAdapter = new ArrayAdapter<>( this, android.R.layout.simple_list_item_1, Drink.drinks); ListView listDrinks = (ListView) findViewById(R.id.list_drinks); listDrinks.setAdapter(listAdapter); Create a listener to listen for clicks. //Create the listener AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener(){ public void onItemClick(AdapterView> listDrinks, View itemView, This gets called when an item int position, in the list view is clicked. long id) { //Pass the drink the user clicks on to DrinkActivity When the user clicks Intent intent = new Intent(DrinkCategoryActivity.this, on a drink, pass its ID DrinkActivity.class); to DrinkActivity and intent.putExtra(DrinkActivity.EXTRA_DRINKID, (int) id); start it. startActivity(intent); We’ll add DrinkActivity next, so } don't worry if Android Studio }; says it doesn't exist. } } //Assign the listener to the list view listDrinks.setOnItemClickListener(itemClickListener); 278 Chapter 7 list views and adapters A detail activity displays data for a single record Add resources TopLevelActivity DrinkCategoryActivity DrinkActivity As we said earlier, DrinkActivity is an example of a detail activity. A detail activity displays details for a particular record, and you generally navigate to it from a category activity. We’re going to use DrinkActivity to display details of the drink the user selects. The Drink class includes the drink’s name, description, and image resource ID, so we’ll display this data in our layout. We’ll include an image view for the drink image resource, and text views for the drink name and description. To create the activity, select the com.hfad.starbuzz package in the app/src/main/ java folder, then go to File→New...→Activity→Empty Activity. Name the activity “DrinkActivity”, name the layout “activity_drink”, make sure the package name is com.hfad.starbuzz, and uncheck the Backwards Compatibility (AppCompat) checkbox. Then replace the contents of activity_drink.xml with this: Make sure you create the new activity. If prompted for the activity’s source language, select the option for Java. Now that you’ve created the layout of your detail activity, we can populate its views. you are here 4 279 get a drink Retrieve data from the intent As you’ve seen, when you want a category activity to start a detail activity, you have to make items in the category activity list view respond to clicks. When an item is clicked, you create an intent to start the detail activity. You pass the ID of the item the user clicked as extra information in the intent. Add resources TopLevelActivity DrinkCategoryActivity DrinkActivity When the detail activity is started, it can retrieve the extra information from the intent and use it to populate its views. In our case, we can use the information in the intent that started DrinkActivity to retrieve details of the drink the user clicked. When we created DrinkCategoryActivity, we added the ID of the drink the user clicked as extra information in the intent. We gave it the label DrinkActivity.EXTRA_DRINKID, which we need to define as a constant in DrinkActivity.java: public static final String EXTRA_DRINKID = "drinkId"; As you saw in Chapter 3, you can retrieve the intent that started an activity using the getIntent() method. If this intent has extra information, you can use the intent’s get*() methods to retrieve it. Here’s the code to retrieve the value of EXTRA_DRINKID from the intent that started DrinkActivity: int drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID); Once you’ve retrieved the information from the intent, you can use it to get the data you need to display in your detail record. In our case, we can use drinkId to get details of the drink the user selected. drinkId is the ID of the drink, the index of the drink in the drinks array. This means that you can get details about the drink the user clicked on using: Drink drink = Drink.drinks[drinkId]; This gives us a Drink object containing all the information we need to update the views attributes in the activity: name=”Latte” description=”A couple of espresso shots with steamed milk” imageResourceId=R.drawable.latte drink 280 Chapter 7 list views and adapters Update the views with the data When you update the views in your detail activity, you need to make sure that the values they display reflect the data you’ve derived from the intent. Our detail activity contains two text views and an image view. We need to make sure that each of these is updated to reflect the details of the drink. name description imageResourceId drink Drink Magnets See if you can use the magnets below to populate the DrinkActivity views with the correct data. ... //Get the drink from the intent int drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID); Drink drink = Drink.drinks[drinkId]; //Populate the drink name TextView name = (TextView)findViewById(R.id.name); name. (drink.getName()); setText //Populate the drink description TextView description = (TextView)findViewById(R.id.description); description. setContent (drink.getDescription()); setContentDescription //Populate the drink image ImageView photo = (ImageView)findViewById(R.id.photo); photo. (drink.getImageResourceId()); photo. (drink.getName()); ... setImageResourceId setImageResource setText you are here 4 281 magnets solution Drink Magnets Solution See if you can use the magnets below to populate the DrinkActivity views with the correct data. ... //Get the drink from the intent int drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID); Drink drink = Drink.drinks[drinkId]; //Populate the drink name TextView name = (TextView)findViewById(R.id.name); name. Use setText() to set the text in a text view. setText (drink.getName()); //Populate the drink description TextView description = (TextView)findViewById(R.id.description); description. setText (drink.getDescription()); //Populate the drink image You set the source of the image using setImageResource(). This is needed to make the app more accessible. ImageView photo = (ImageView)findViewById(R.id.photo); photo. photo. ... setImageResource (drink.getImageResourceId()); setContentDescription (drink.getName()); You didn't need to use these. setContent 282 Chapter 7 setImageResourceId list views and adapters Add resources TopLevelActivity DrinkCategoryActivity DrinkActivity The DrinkActivity code Here’s the full code for DrinkActivity.java (replace the code the wizard gave you with the code below, then save your changes): package com.hfad.starbuzz; import android.app.Activity; import android.os.Bundle; import android.widget.ImageView; We're using these classes so we need to import them. import android.widget.TextView; Starbuzz app/src/main Make sure your activity extends the Activity class. com.hfad.starbuzz public class DrinkActivity extends Activity { public static final String EXTRA_DRINKID = "drinkId"; @Override java DrinkActivity.java Add EXTRA_DRINKID as a constant. protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_drink); //Get the drink from the intent int drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID); Drink drink = Drink.drinks[drinkId]; //Populate the drink name Use the drinkId to get details of the drink the user chose. TextView name = (TextView)findViewById(R.id.name); name.setText(drink.getName()); Populate the views with the drink data. //Populate the drink description TextView description = (TextView)findViewById(R.id.description); description.setText(drink.getDescription()); //Populate the drink image ImageView photo = (ImageView)findViewById(R.id.photo); photo.setImageResource(drink.getImageResourceId()); } } photo.setContentDescription(drink.getName()); you are here 4 283 what happens Add resources TopLevelActivity DrinkCategoryActivity DrinkActivity What happens when you run the app 1 When the user starts the app, it launches TopLevelActivity. TopLevelActivity Device 2 The onCreate() method in TopLevelActivity creates an onItemClickListener and links it to the activity’s ListView. TopLevelActivity 3 ListView When the user clicks on an item in the ListView, the onItemClickListener’s onItemClick() method gets called. If the Drinks item was clicked, the onItemClickListener creates an intent to start DrinkCategoryActivity. Intent onItemClick() ListView 4 onItemClickListener onItemClickListener DrinkCategoryActivity DrinkCategoryActivity displays a single ListView. The DrinkCategoryActivity list view uses an ArrayAdapter activity_drink.xml android:layout_width="190dp" to display a list of drink names. ListView DrinkCategoryActivity ArrayAdapter 284 Chapter 7 Drink.drinks list views and adapters The story continues 5 When the user chooses a drink from DrinkCategoryActivity's ListView, onItemClickListener’s onItemClick() method gets called. onItemClick() DrinkCategoryActivity 6 onItemClickListener ListView The onItemClick() method creates an intent to start DrinkActivity, passing along the drink ID as extra information. Intent drinkId=0 DrinkCategoryActivity 7 DrinkActivity DrinkActivity launches. It retrieves the drink ID from the intent, and gets details for the correct drink from the Drink class. It uses this information to update its views. drinks[0]? That’s a latte, a fine choice. Here’s everything I know about lattes. drinks[0] DrinkActivity Latte Drink you are here 4 285 test drive Test drive the app When you run the app, TopLevelActivity gets displayed. We’ve implemented the Drinks part of the app. The other items won’t do anything if you click on them. When you click on the Drinks item, DrinkCategoryActivity is launched. It displays all the drinks from the Drink java class. When you click on one of the drinks, DrinkActivity is launched and details of the selected drink are displayed. Using these three activities, you can see how to structure your app into top-level activities, category activities, and detail/edit activities. In Chapter 15, we’ll revisit the Starbuzz app to explain how you can retrieve the drinks from a database. 286 Chapter 7 We clicked on the Latte option... ...and here are details of the latte. list views and adapters Pool Puzzle Solution Your goal is to create an activity that binds a Java array of colors to a spinner. Take code snippets from the pool and place them into the blank lines in the activity. You may not use the same snippet more than once, and you won’t need to use all the snippets. ... public class MainActivity extends Activity { String[] colors = new String[] {"Red", "Orange", "Yellow", "Green", "Blue"}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Spinner spinner = ( Spinner ) findViewById(R.id.spinner); ArrayAdapter< String > adapter = new ArrayAdapter<>( We're using an array of type String. this , android.R.layout.simple_spinner_item, colors); } } spinner. setAdapter (adapter); You didn’t need to use these code snippets. colors Use setAdapter() to get the spinner to use the array adapter. colors you are here 4 287 toolbox CHAPTER 7 Your Android Toolbox You’ve got Chapter 7 under your belt and now you’ve added list views and app design to your toolbox. Sort your ideas for activities into top-level activities, category activities, and detail/edit activities. Use the category activities to navigate from the top-level activities to the detail/ edit activities. A list view displays items in a list. Add it to your layout using the element. Use android:entries in your layout to populate the items in your list views from an array defined in strings.xml. 288 Chapter 7 You can download the full code for the chapter from https://tinyurl.com/ HeadFirstAndroid. An adapter acts as a bridge between an AdapterView and a data source. ListViews and Spinners are both types of AdapterView. An ArrayAdapter is an adapter that works with arrays. Handle click events on Buttons using android:onClick in the layout code. Handle click events elsewhere by creating a listener and implementing its click event. 8 support libraries and app bars Taking Shortcuts See, Whiskey, I said we’d get home eventually. We’d have got here hours ago if she’d known about the Up button. Harrumph! Everybody likes a shortcut. And in this chapter you’ll see how to add shortcuts to your apps using app bars. We’ll show you how to start activities by adding actions to your app bar, how to share content with other apps using the share action provider, and how to navigate up your app’s hierarchy by implementing the app bar’s Up button. Along the way we'll introduce you to the powerful Android Support Libraries, which are key to making your apps look fresh on older versions of Android. this is a new chapter 289 app structure Great apps have a clear structure In the previous chapter, we looked at ways of structuring an app to create the best user experience. Remember that one way of creating an app is to organize the screens into three types: Top-level screens Bits and Pizzas This is usually the first activity in your app that your user sees. Pizzas Pasta Stores Create Order Category screens This is a rough sketch of a Pizza app. It contains details of pizzas, pasta dishes, and stores. It also allows the user to order a meal. Pizzas Pasta Stores Diavolo Spaghetti Bolognese Cambridge Category screens show the data that belongs to a particular category, often in a list. They allow the user to navigate to detail/edit screens. Detail/edit screens These display details for a particular record, let the user edit the record, or allow the user to enter new records. They also have great shortcuts If a user’s going to use your app a lot, they’ll want quick ways to get around. We’re going to look at navigational views that will give your user shortcuts around your app, providing more space in your app for actual content. Let’s begin by taking a closer look at the top-level screen in the above Pizza app. 290 Chapter 8 Create Order support libraries and app bars Different types of navigation In the top-level screen of the Pizza app, there’s a list of options for places in the app the user can go to. This is the Pizza app’s top-level activity. Bits and Pizzas Pizzas Pasta These link to category screens. Stores Create Order This takes you to a detail/edit screen where you can create a new order. The top three options link to category activities; the first presents the user with a list of pizzas, the second a list of pasta, and the third a list of stores. They allow the user to navigate around the app. The fourth option links to a detail/edit activity that allows the user to create an order. This option enables the user to perform an action. In Android apps, you can add actions to the app bar. The app bar is the bar you often see at the top of activities; it’s sometimes known as the action bar. You generally put your app’s most important actions in the app bar so that they’re prominent at the top of the screen. These are like the navigation options we looked at in Chapter 7. This is an app bar. In the Pizza app, we can make it easy for the user to place an order wherever they are in the app by making sure there’s an app bar at the top of every activity that includes a Create Order button. This way the user will have access to it wherever they are. Let’s look at how you create app bars. This is the Create Order button. you are here 4 291 steps Here’s what we’re going to do This is the app bar we’ll add. There are a few things we’re going to cover in this chapter. 1 Add a basic app bar. We’ll create an activity called MainActivity and add a basic app bar to it by applying a theme. 2 Replace the basic app bar with a toolbar. To use the latest app bar features, you need to replace the basic app bar with a toolbar. This looks the same as the basic app bar, but you can use it to do more things. 3 Add a Create Order action. We’ll create a new activity called OrderActivity, and add an action to MainActivity’s app bar that opens it. MainActivity app bar 4 OrderActivity Implement the Up button. We’ll implement the Up button on OrderActivity’s app bar so that users have an easy way of navigating back to MainActivity. The Up button features a button that (confusingly) points to the left. MainActivity 5 OrderActivity app bar Add a share action provider. We’ll add a share action provider to MainActivity’s app bar so that users can share text with other apps and invite their friends to join them for pizza. Intent MainActivity app bar ACTION_SEND type: “text/plain” messageText: ”Hi!” Let’s start by looking at how you add a basic app bar. 292 Chapter 8 AppActivity You’ll find out what action providers are later in the chapter. support libraries and app bars Add an app bar by applying a theme An app bar has a number of uses: ¥ isplaying the app or activity name so that the user knows where in the D app they are. As an example, an email app might use the app bar to indicate whether the user is in their inbox or junk folder. ¥ aking key actions prominent in a way that’s predictable—for example, M sharing content or performing searches. ¥ Navigating to other activities to perform an action. Basic app bar Toolbar Action Up button Share action To add a basic app bar, you need to use a theme that includes an app bar. A theme is a style that’s applied to an activity or application so that your app has a consistent look and feel. It controls such things as the color of the activity background and app bar, and the style of the text. Android comes with a number of built-in themes that you can use in your apps. Some of these, such as the Holo themes, were introduced in early releases of Android, and others, such as the Material themes, were introduced much later to give apps a more modern appearance. The Holo themes have been in Android since API level 11. But there’s a problem. You want your apps to look as modern and up-to-date as possible, but you can only use themes from the version of Android they were released in. As an example, you can’t use the native Material themes on devices that are running a version of Android older than Lollipop, as the Material themes were introduced with API level 21. The problem isn’t just limited to themes. Every new release of Android introduces new features that people want to see in their apps, such as new GUI components. But not everyone upgrades to the latest version of Android as soon as it comes out. In fact, most people are at least one version of Android behind. The Material themes were introduced in API level 21. These themes look a bit different from the one on the previous page, as they haven’t had any extra styling applied to them. You’ll find out how to add styling later in the chapter. So how can you use the latest Android features and themes in your apps if most people aren’t using the latest version? How can you give your users a consistent user experience irrespective of what version of Android they’re using without making your app look old-fashioned? you are here 4 293 support libraries Basic app bar Toolbar Action Up button Share action Support libraries allow you to use new features in older versions of Android The Android team solved this problem by coming up with the idea of Support Libraries. The Android Support Libraries provide backward compatibility with older versions of Android. They sit outside the main release of Android, and contain new Android features that developers can use in the apps they’re building. The Support Libraries mean that you can give users on older devices the same experience as users on newer devices even if they’re using different versions of Android. Here are some of the Support Libraries that are available for you to use: v4 Support Library Includes the largest set of features, such as support for application components and user interface features. Constraint Layout Library Allows you to create constraint layouts. You used features from this library in Chapter 6. v7 AppCompat Library Includes support for app bars. v7 Cardview Library Adds support for the CardView widget, allowing you to show information inside cards. t por p Su v7 RecyclerView Library Adds support for the RecyclerView widget. ies ar r lib These are just some of the Support Libraries. Design Support Library Adds support for extra components such as tabs and navigation drawers. Each library includes a specific set of features. The v7 AppCompat Library contains a set of up-to-date themes that can be used with older versions of Android: in practice, they can be used with nearly all devices, as most people are using API level 19 or above. We’re going to use the v7 AppCompat Library by applying one of the themes it contains to our app. This will add an app bar that will look up-to-date and work the same on all versions of Android that we’re targeting. Whenever you want to use one of the Support Libraries, you first need to add it to your app. We’ll look at how you do this after we’ve created the project. 294 Chapter 8 support libraries and app bars Create the Pizza app We’ll start by creating a prototype of the Pizza app. Create a new Android project for an application named “Bits and Pizzas” with a company domain of “hfad.com”, making the package name com. hfad.bitsandpizzas. The minimum SDK should be API level 19 so that it works with most devices. You’ll need an empty activity called “MainActivity” and a layout called “activity_main”. Make sure you check the Backwards Compatibility (AppCompat) checkbox (you’ll see why a few pages ahead). Basic app bar Toolbar Action Up button Share action Unlike in previous chapters, you need to make sure the Backwards Compatibility (AppCompat) checkbox is ticked. Next, we’ll look at how you add a Support Library to the project. you are here 4 295 AppCompat support library Add the v7 AppCompat Support Library We’re going to use one of the themes from the v7 AppCompat Library, so we need to add the library to our project as a dependency. Doing so means that the library gets included in your app, and downloaded to the user’s device. To manage the Support Library files that are included in your project, choose File→Project Structure. Then click on the app module and choose Dependencies. You’ll be presented with the following screen: The Dependencies option shows you the to Support Libraries that have been added your project. Android Studio will probably have added some for you automatically. Android Studio may have already added the AppCompat Support Library for you automatically. If so, you will see it listed as appcompat-v7, as shown above. If the AppCompat Library hasn’t been added for you, you will need to add it yourself. Click on the “+” button at the bottom or right side of the Project Structure screen. Choose the Library Dependency option, select the appcompat-v7 library, then click on the OK button. Click on OK again to save your changes and close the Project Structure window. Once the AppCompat Support Library has been added to your project, you can use its resources in your app. In our case, we want to apply one of its themes in order to give MainActivity an app bar. Before we do that, however, we need to look at the type of activity we’re using for MainActivity. 296 Chapter 8 Basic app bar Toolbar Action Up button Share action support libraries and app bars AppCompatActivity lets you use AppCompat themes So far, all of the activities we’ve created have extended the Activity class. This is the base class for all activities, and it’s what makes your activity an activity. If you want to use the AppCompat themes, however, you need to use a special kind of activity, called an AppCompatActivity, instead. The AppCompatActivity class is a subclass of Activity. It lives in the AppCompat Support Library, and it’s designed to work with the AppCompat themes. Your activity needs to extend the AppCompatActivity class instead of the Activity class whenever you want an app bar that provides backward compatibility with older versions of Android. As AppCompatActivity is a subclass of the Activity class, everything you’ve learned about activities so far still applies. AppCompatActivity works with layouts in just the same way, and inherits all the lifecycle methods from the Activity class. The main difference is that, compared to Activity, AppCompatActivity contains extra smarts that allow it to work with the themes from the AppCompat Support Library. Here’s a diagram showing the AppCompatActivity class hierarchy: Activity onCreate(Bundle) onStart() Activity class (android.app.Activity) The Activity class implements default versions of the lifecycle methods. onRestart() onResume() onPause() onStop() onDestroy() onSaveInstanceState() FragmentActivity FragmentActivity class (android.support.v4.app.FragmentActivity) The base class for activities that need to use support fragments. You’ll find out about fragments in the next chapter. AppCompatActivity AppCompatActivity class (android.support.v7.app.AppCompatActivity) The base class for activities that use the Support Library app bar. YourActivity onCreate(Bundle) YourActivity class (com.hfad.foo) yourMethod() We’ll make sure MainActivity extends AppCompatActivity on the next page. you are here 4 297 MainActivity code Basic app bar Toolbar Action Up button Share action MainActivity needs to be an AppCompatActivity We want to use one of the AppCompat themes, so we need to make sure our activities extend the AppCompatActivity class instead of the Activity class. Happily, this should already be the case if you checked the Backwards Compatibility (AppCompat) checkbox when you first created the activity. Open the file MainActivity.java, then make sure your code matches ours below: package com.hfad.bitsandpizzas; The AppCompatActivity class lives in the v7 AppCompat Support Library. import android.support.v7.app.AppCompatActivity; BitsAndPizzas import android.os.Bundle; app/src/main public class MainActivity extends AppCompatActivity { @Override Make sure your activity extends AppCompatActiv ity. java protected void onCreate(Bundle savedInstanceState) { com.hfad.bitsandpizzas super.onCreate(savedInstanceState); } } setContentView(R.layout.activity_main); MainActivity.java Now that we’ve confirmed that our activity extends AppCompatActivity, we can add an app bar by applying a theme from the AppCompat Support Library. You apply a theme in the app’s AndroidManifest.xml file, so we’ll look at this file next. Q: What versions of Android can the Support Libraries be used with? A: It depends on the version of the Support Library. Prior to version 24.2.0, Libraries prefixed with v4 could be used with API level 4 and above, and those prefixed with v7 could be used with API level 7 and above. When version 24.2.0 of the Support Libraries was released, the minimum API for all Support Libraries became API level 9. The minimum API level is likely to increase in the future. 298 Chapter 8 Q: In earlier chapters, Android Studio gave me activities that already extended AppCompatActivity. Why’s that? A: When you create an activity in Android Studio, the wizard includes a checkbox asking if you want to create a Backwards Compatible (AppCompat) activity. If you left this checked in earlier chapters, Android Studio would have generated activities that extend AppCompatActivity. Q: I’ve seen code that extends ActionBarActivity. What’s that? A: In older versions of the AppCompat Support Library, you used the ActionBarActivity class to add app bars. This was deprecated in version 22.1 in favor of AppCompatActivity. support libraries and app bars AndroidManifest.xml can change your app bar’s appearance As you’ve seen earlier in the book, an app’s AndroidManifest.xml file provides essential information about the app, such as what activities it contains. It also includes a number of attributes that have a direct impact on your app bars. Here’s the AndroidManifest.xml code Android Studio created for us (we’ve highlighted the key areas): BitsAndPizzas app/src/main AndroidManifest.xml The android:icon attribute assigns an icon to the app. The icon is used as the launcher icon for the app, and if the theme you’re using displays an icon in the app bar, it will use this icon. android:roundIcon may be used instead on devices running Android 7.1 or above. The icon is a mipmap resource. A mipmap is an image that can be used for application icons, and they’re held in mipmap* folders in app/src/main/ res. Just as with drawables, you can add different images for different screen densities by adding them to an appropriately named mipmap folder. As an example, an icon in the mipmap-hdpi folder will be used by devices with high-density screens. You refer to mipmap resources in your layout using @ mipmap. The android:label attribute describes a user-friendly label that gets displayed in the app bar. In the code above, it’s used in the The theme ... tag to apply a label to the entire app. You can also add it to the tag to assign a label to a single activity. The android:theme attribute specifies the theme. Using this attribute in the element applies the theme to the entire app. Using it in the element applies the theme to a single activity. We’ll look at how you apply the theme on the next page. Android Studio automatically added icons to our mipmap* folders when we created the project. you are here 4 299 apply a theme How to apply a theme When you want to apply a theme to your app, you have two main options: ¥ Hardcode the theme in AndroidManifest.xml. ¥ Apply the theme using a style. Let’s look at these two approaches. 1. Hardcoding the theme To hardcode the theme in AndroidManifest.xml, you update the android:theme attribute in the file to specify the name of the theme you want to use. As an example, to apply a theme with a light background and a dark app bar, you’d use: This approach works well if you want to apply a basic theme without making any changes to it. 2. Using a style to apply the theme This is a simple way of applying a basic theme, but it means you can’t, for example, change its colors. Most of the time, you’ll want to apply the theme using a style, as this approach enables you to tweak the theme’s appearance. You may want to override the theme’s main colors to reflect your app’s brand, for example. To apply a theme using a style, you update the android:theme attribute in AndroidManifest.xml to the name of a style resource (which you then need to create). In our case, we’re going to use a style resource named AppTheme, so update the android:theme attribute in your version of AndroidManifest.xml to the following: The @style prefix tells Android that the theme the app’s using is a style that’s defined in a style resource file. We’ll look at this next. 300 Chapter 8 BitsAndPizzas app/src/main AndroidManifest.xml support libraries and app bars Basic app bar Toolbar Action Up button Share action Define styles in a style resource file The style resource file holds details of any themes and styles you want to use in your app. When you create a project in Android Studio, the IDE will usually create a default style resource file for you called styles.xml located in the app/src/main/res/values folder. If Android Studio hasn’t created the file, you’ll need to add it yourself. Switch to the Project view of Android Studio’s explorer, highlight the app/src/main/res/values folder, go to the File menu, and choose New. Then choose the option to create a new Values resource file, and when prompted, name the file “styles”. When you click on OK, Android Studio will create the file for you. A basic style resource file looks like this: This is the theme used in the app. There may be extra code here to customize the theme. We’ll look at this a couple of pages ahead. A style resource file can contain one or more styles. Each style is defined through the values styles.xml These three lines of code modify the theme by changing three of the colors. The above code includes three modifications, each one described by a separate- . Each
- has a name attribute that indicates what part of the theme you want to change, and a value that specifies what you want to change it to, like this:
- @color/colorPrimary
This will change the colorPrimary part of the theme so it has a value of @color/colorPrimary. name="colorPrimary" refers to the main color you want to use for your app. This color gets used for your app bar, and to “brand” your app with a particular color. colorPrimary is the color of the app bar. colorPrimaryDark is the color of the status bar. name="colorPrimaryDark" is a darker variant of your main color. It gets used as the color of the status bar. name="colorAccent" refers to the color of any UI controls such as editable text views or checkboxes. You set a new color for each of these areas by giving each- a value. The value can either be a hardcoded hexadecimal color value, or a reference to a color resource. We’ll look at color resources on the next page. colorAccent is the color of any UI controls. There are a whole host of other theme properties you can change, but we're not going to cover them here. To find out more, visit https://developer. android.com/guide/topics/ui/themes.html. you are here 4 303 color resources Basic app bar Toolbar Action Up button Share action Define colors in a color resource file A color resource file is similar to a String resource file except that it contains colors instead of Strings. Using a color resource file makes it easy to make changes to the color scheme of your app, as all the colors you want to use are held in one place. The color resource file is usually called colors.xml, and it’s located in the app/src/main/res/values folder. When you create a project in Android Studio, the IDE will usually create this file for you. If Android Studio hasn’t created the file, you’ll need to add it yourself. Switch to the Project view of Android Studio’s explorer, highlight the app/src/main/res/values folder, go to the File menu, and choose New. Then choose the option to create a new Values resource file, and when prompted, name the file “colors”. When you click on OK, Android Studio will create the file for you. Next, open colors.xml and make sure that your version of the file matches ours below: BitsAndPizzas
app/src/main res Each of these is a color resource. The code above defines three color resources. Each one has a name and a value. The value is a hexadecimal color value: This says it's a color resource.#3F51B5 #303F9F #FF4081 #3F51B5 The color resource has a name of “colorPrimary”, and a value of #3F51B5 (blue). The style resource file looks up colors from the color resource file using @color/colorName. For example:- @color/colorPrimary
overrides the primary color used in the theme with the value of colorPrimary in the color resource file. Now that we’ve seen how to add an app bar by applying a theme, let’s update MainActivity’s layout and take the app for a test drive. 304 Chapter 8 valuescolors.xml support libraries and app bars Basic app bar Toolbar Action Up button Share action The code for activity_main.xml For MainActivity’s layout, we’re going to display some default text in a linear layout. Here’s the code to do that; update your version of activity_main.xml to match ours below: res layout We’re just displaying some basic placeholder text in MainActivity’s layout because right now we want you to focus on app bars. activity_ main.xml Test drive the app When you run the app, MainActivity gets displayed. At the top of the activity there’s an app bar. This is the app bar. The default color's been overridden so that it's blue. The background is light, as we've used a theme of Theme.AppCompat.Light.DarkActionBar. This theme also gives us dark text in the main body of the activity, and white text in the app bar. The status bar’s default color has been overridden so it's a darker shade of blue than the app bar. That’s everything you need to apply a basic app bar in your activities. Why not experiment with changing the theme and colors? Then when you’re ready, turn the page and we’ll move on to the next step. you are here 4 305 toolbars Basic app bar Toolbar Action Up button Share action ActionBar vs. Toolbar So far, you’ve seen how to add a basic app bar to the activities in your app by applying a theme that includes an app bar. Adding an app bar in this way is easy, but it has one disadvantage: it doesn’t necessarily include all the latest app bar features. Behind the scenes, any activity that acquires an app bar via a theme uses the ActionBar class for its app bar. The most recent app bar features, however, have been added to the Toolbar class in the AppCompat Support Library instead. This means that if you want to use the most recent app bar features in your app, you need to use the Toolbar class from the Support Library. Using the Toolbar class also gives you more flexibility. A toolbar is a type of view that you add to your layout just as you would any other type of view, and this makes it much easier to position and control than a basic app bar. A toolbar looks just like the app bar you had previously, but it gives you more flexibility and includes the most recent app bar features. How to add a toolbar We’re going to change our activity so that it uses a toolbar from the Support Library for its app bar. Whenever you want to use the Toolbar class from the Support Library, there are a number of steps you need to perform: 1 Add the v7 AppCompat Support Library as a dependency. This is necessary because the Toolbar class lives in this library. 2 Make sure your activity extends the AppCompatActivity class. Your activity must extend AppCompatActivity (or one of its subclasses) in order to use the Support Library toolbar. 3 Remove the existing app bar. You do this by changing the theme to one that doesn’t include an app bar. 4 Add a toolbar to the layout. The toolbar is a type of view, so you can position it where you want and control its appearance. 5 Update the activity to set the toolbar as the activity’s app bar. This allows the activity to respond to the toolbar. We’ll go through these steps now. 306 Chapter 8 support libraries and app bars Basic app bar Toolbar Action Up button Share action 1. Add the AppCompat Support Library Before you can use the Toolbar class from the Support Library in your activities, you need to make sure that the v7 AppCompat Support Library has been added to your project as a dependency. In our particular case, the library has already been added to our project, as we needed it for the AppCompat themes. To double-check that the Support Library is there, in Android Studio choose File→Project Structure, click on the app module, and choose Dependencies. You should see the v7 AppCompat Library listed as shown below: Here's the v7 AppCompat Support Library. 2. Extend the AppCompatActivity class When you want to use a theme from the AppCompat Library, you have to make sure that your activities extend the AppCompatActivity class. This is also the case if you want to use a toolbar from the Support Library as your app bar. We’ve already completed this step because, earlier in this chapter, we changed MainActivity.java to use AppCompatActivity: BitsAndPizzas app/src/main ... import android.support.v7.app.AppCompatActivity; java public class MainActivity extends AppCompatActivity { } ... com.hfad.bitsandpizzas ity. Our MainActivity already extends AppCompatActiv MainActivity.java The next thing we need to do is remove the existing app bar. you are here 4 307 NoActionBar theme Basic app bar Toolbar Action Up button Share action 3. Remove the app bar You remove the existing app bar in exactly the same way that you add one—by applying a theme. When we wanted to add an app bar to our app, we applied a theme that displayed one. To do this, we used the theme attribute in AndroidManifest.xml to apply a style called AppTheme:android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Hello World!" /> The theme was then defined in styles.xml like this: ... app/src/main This looks up the theme from styles.xml.AndroidManifest.xml This is the theme we’re using. It displays a dark app bar. BitsAndPizzas The theme Theme.AppCompat.Light.DarkActionBar gives your activity a light background with a dark app bar. To remove the app bar, we’re going to change the theme to Theme.AppCompat. Light.NoActionBar instead. Your activity will look the same as it did before except that no app bar will be displayed. app/src/main res values To change the theme, update styles.xml like this:styles.xml We customized the theme by overriding some of the colors. You can leave this code in place. Now that we’ve removed the current app bar, we can add the toolbar. 308 Chapter 8 Change the theme from DarkActionBar to NoActionBar. This removes the app bar. support libraries and app bars Basic app bar Toolbar Action Up button Share action 4. Add a toolbar to the layout As we said earlier, a toolbar is a view that you add to your layout. Toolbar code looks like this: This defines the toolbar. Give the toolbar an ID so you can refer to it in your activity code. android:id="@+id/toolbar" android:layout_width="match_parent" Set the toolbar's size. You start by defining the toolbar using: This is the full path of the Toolbar class in the Support Library. where android.support.v7.widget.Toolbar is the fully qualified path of the Toolbar class in the Support Library. Once the toolbar has been defined, you then use other view attributes to give it an ID, and specify its appearance. As an example, to make the toolbar as wide as its parent and as tall as the default app bar size from the underlying theme, you’d use: android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" The toolbar is as wide as its parent, and as tall as the default app bar. The ?attr prefix means that you want to use an attribute from the current theme. In this particular case, ?attr/actionBarSize is the height of an app bar that’s specified in our theme. You can also change your toolbar’s appearance so that it has a similar appearance to the app bar that we had before. To do this, you can change the background color, and apply a theme overlay like this: android:background="?attr/colorPrimary" Make the toolbar's background the same color as the app bar we had previously. android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" A theme overlay is a special type of theme that alters the current theme by overwriting some of its attributes. We want our toolbar to look like our app bar did when we used a theme of Theme.AppCompat. Light.DarkActionBar, so we’re using a theme overlay of ThemeOverlay.AppCompat.Dark.ActionBar. On the next page we’ll add the toolbar to the layout. This gives the toolbar the same appearance as the app bar we had before. We have to use a theme overlay, as the NoActionBar theme doesn't style app bars in the same way as the DarkActionBar theme did. you are here 4 309 we’re not doing this (in our app) Basic app bar Toolbar Action Up button Share action Add the toolbar to the layout... If your app contains a single activity, you can add the toolbar to your layout just as you would any other view. Here is an example of the sort of code you would use in this situation (we’re using a different approach, so don’t update your layout with the code below): This code displays the toolbar at the top of the activity. We’re using a linear layout, so the text view will be positioned below the toolbar. This code displays the toolbar at the top of the activity. We’ve positioned the text view Android Studio gave us so that it’s displayed underneath the toolbar. Remember that a toolbar is a view like any other view, so you need to take this into account when you’re positioning your other views. Adding the toolbar code to your layout works well if your app contains a single activity, as it means that all the code relating to your activity’s appearance is in a single file. It works less well, however, if your app contains multiple activities. If you wanted to display a toolbar in multiple activities, you would need to define the toolbar in the layout of each activity. This means that if you wanted to change the style of the toolbar in some way, you’d need to edit every single layout file. So what’s the alternative? 310 Chapter 8 Later in the chapter we’ll add a second activity to our app, so we’re not using this approach. So you don’t need to change your layout code to match this example. support libraries and app bars Basic app bar Toolbar Action Up button Share action ...or define the toolbar as a separate layout An alternative approach is to define the toolbar in a separate layout, and then include the toolbar layout in each activity. This means that you only need to define the toolbar once, and if you want to change the style of your toolbar, you only need to edit one file. MainActivity MainActivity has a layout, activity_main.activity_ main.xml android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" /> activity_main toolbar_main activity_main doesn't explicitly contain the toolbar code. Instead, it contains a reference to the toolbar's layout. The toolbar's layout is contained in a separate file. If your activity contains multiple activities, each one can reference the toolbar's layout. We’re going to use this approach in our app. Start by creating a new layout file. Switch to the Project view of Android Studio’s explorer, highlight the app/src/res/main/layout folder in Android Studio, then go to the File menu and choose New → Layout resource file. When prompted, give the layout file a name of “toolbar_main” and then click on OK. This creates a new layout file called toolbar_main.xml. Next, open toolbar_main.xml, and replace any code Android Studio has created for you with the following: This code is almost identical to the toolbar code you’ve already seen. The main difference is that we’ve left out the toolbar’s id attribute, as we’ll define this in the activity’s main layout file activity_main.xml instead. This toolbar code goes in a separate layout file so multiple activities can reference it. toolbar_ main.xml On the next page we’ll look at how you include the toolbar layout in activity_main.xml. you are here 4 311 include toolbar Basic app bar Toolbar Action Up button Share action Include the toolbar in the activity’s layout You can display one layout inside another using the tag. This tag must contain a layout attribute that specifies the name of the layout you want to include. As an example, here’s how you would use the tag to include the layout toolbar_main.xml: The @layout tells Android to look for a layout called toolbar_main. We want to include the toolbar_main layout in activity_main.xml. Here’s our code; update your version of activity_main.xml to match ours: Now that we’ve added the toolbar to the layout, there’s one more change we need to make. 312 Chapter 8 BitsAndPizzas app/src/main res layout activity_ main.xml support libraries and app bars Basic app bar Toolbar Action Up button Share action 5. Set the toolbar as the activity's app bar The final thing we need to do is tell MainActivity to use the toolbar as its app bar. So far we’ve only added the toolbar to the layout. While this means that the toolbar gets displayed at the top of the screen, the toolbar doesn’t yet have any app bar functionality. As an example, if you were to run the app at this point, you’d find that the title of the app isn’t displayed in the toolbar as it was in the app bar we had previously. If you don't update your activity code after adding a toolbar to your layout, your toolbar will just appear as a plain strip with nothing in it. To get the toolbar to behave like an app bar, we need to call the AppCompatActivity’s setSupportActionBar() method in the activity’s onCreate() method, which takes one parameter: the toolbar you want to set as the activity’s app bar. Here’s the code for MainActivity.java; update your code to match ours: package com.hfad.bitsandpizzas; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.Toolbar; BitsAndPizzas We're using the Toolbar class, so we need to import it. app/src/main public class MainActivity extends AppCompatActivity { java com.hfad.bitsandpizzas @Override MainActivity.java protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); } } setSupportActionBar(toolbar); We need to use setSupportActionBar(), as we’re using the toolbar from the Support Library. Get a reference to the toolbar, and set it as the activity's app bar. That’s all the code that you need to replace the activity’s basic app bar with a toolbar. Let’s see how it looks. you are here 4 313 test drive Basic app bar Toolbar Action Up button Share action Test drive the app When you run the app, a new toolbar is displayed in place of the basic app bar we had before. It looks similar to the app bar, but as it’s based on the Support Library Toolbar class, it includes all the latest Android app bar functionality. Here's our new toolbar. It looks like the app bar we had before, but it gives you more flexibility. You’ve seen how to add an app bar, and how to replace the basic app bar with a toolbar. Over the next few pages we’ll look at how to add extra functionaility to the app bar. Q: You’ve mentioned app bars, action bars, and toolbars. Is there a difference? A: An app bar is the bar that usually appears at the top of your activities. It’s sometimes called an action bar because in earlier versions of Android, the only way of implementing an app bar was via the ActionBar class. The ActionBar class is used behind the scenes when you add an app bar by applying a theme. If your app doesn’t rely on any new app bar features, this may be sufficient for your app. An alternative way of adding an app bar is to implement a toolbar using the Toolbar class. The result looks similar to the default theme-based app bar, but it includes newer features of Android. 314 Chapter 8 Q: I’ve added a toolbar to my activity, but when I run the app, it just looks like a band across the top of the screen. It doesn’t even include the app name. Why’s that? A: First, check AndroidManifest.xml and make sure that your app has been given a label. This is where the app bar gets the app’s name from. Also, check that your activity calls the setSupportActionBar() method in its onCreate() method, as this sets the toolbar as the activity’s app bar. Without it, the name of the app or activity won’t get displayed in the toolbar. Q: I’ve seen the tag in some of the code that Android Studio has created for me. What does it do? A: The tag is used to include one layout inside another. Depending on what version of Android Studio you’re using and what type of project you create, Android Studio may split your layout code into one or more separate layouts. support libraries and app bars Basic app bar Toolbar Action Up button Share action Add actions to the app bar In most of the apps you create, you’ll probably want to add actions to the app bar. These are buttons or text in the app bar that you click on to make something happen. We’re going to add a “Create Order” button to the app bar. When you click on it, it will start a new activity we’ll create called OrderActivity: We’ll create a new Create Order action that will start OrderActivity. Create OrderActivity We’ll start by creating OrderActivity. Select the com.hfad. bitsandpizzas package in the app/src/main/java folder, then go to File→New...→Activity→Empty Activity. Name the activity “OrderActivity”, name the layout “activity_order”, make sure the package name is com.hfad.bitsandpizzas, and check the Backwards Compatibility (AppCompat) checkbox. If prompted for the activity’s source language, select the option for Java. We want OrderActivity to display the same toolbar as MainActivity. See if you can complete the code for activity_ order.xml below to display the toolbar. The code for adding the toolbar needs to go here. you are here 4 315 solution We want OrderActivity to display the same toolbar as MainActivity. See if you can complete the code for activity_ order.xml below to display the toolbar.Update activity_order.xml We’ll start by updating activity_order.xml so that it displays a toolbar. The toolbar will use the same layout we created earlier. Here’s our code; update yours so that it matches ours: 316 Chapter 8 BitsAndPizzas app/src/main res layout activity_ order.xml support libraries and app bars Basic app bar Toolbar Action Up button Share action Update OrderActivity.java Next we’ll update OrderActivity so that it uses the toolbar we set up in the layout as its app bar. To do this, we need to call the setSupportActionBar() method, passing in the toolbar as a parameter, just as we did before. Here’s the full code for OrderActivity.java; update your version of the code so that it matches ours: package com.hfad.bitsandpizzas; BitsAndPizzas import android.support.v7.app.AppCompatActivity; import android.os.Bundle; app/src/main import android.support.v7.widget.Toolbar; java public class OrderActivity extends AppCompatActivity { @Override com.hfad.bitsandpizzas Make sure the activity extends AppCompatActivity. OrderActivity.java protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_order); Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); } } setSupportActionBar(toolbar); Set the toolbar as the activity's app bar. Add a String resource for the activity’s title Before we move on to creating an action to start OrderActivity, there’s one more change we’re going to make. We want to make it obvious to users when OrderActivity gets started, so we’re going to change the text that’s displayed in OrderActivity’s app bar to make it say “Create Order” rather than the name of the app. To do this, we’ll start by adding a String resource for the activity’s title. Open the file strings.xml in the app/src/main/res/values folder, then add the following resource: Create Order We’ll update the text that gets displayed in the app bar on the next page. BitsAndPizzas app/src/main res We'll use this to display “Create Order” in OrderActivity’s app bar. valuesstrings.xml you are here 4 317 add a label Basic app bar Toolbar Action Up button Share action Change the app bar text by adding a label As you saw earlier in the chapter, you tell Android what text to display in the app bar by using the label attribute in file AndroidManifest.xml. Here’s our current code for AndroidManifest.xml. As you can see, the code includes a label attribute of @string/app_name inside the element. This means that the name of the app gets displayed in the app bar for the entire app. We want to override the label for OrderActivity so that the text “Create Order” gets displayed in the app bar whenever OrderActivity has the focus. To do this, we’ll add a new label attribute to OrderActivity’s The label attribute tells Android what text to display in the app bar. app/src/main AndroidManifest.xml This is the entry for MainActivity that we had before. ... This is the entry for OrderActivity. Android added this for us when we created the new activity.element to display the new text: We’ll show you this code in context on the next page. 318 Chapter 8 Adding a label to an activity means that for this activity,the activity’s label gets displayed in its app bar instead of the app’s label. support libraries and app bars Basic app bar Toolbar Action Up button Share action The code for AndroidManifest.xml Here’s our code for AndroidManifest.xml. Update your code to reflect our changes. Adding a label to OrderActivity overrides the app's label for this activity. It means that different text gets displayed in the app bar. That’s everything we need for OrderActivity. Next we’ll look at how you add an action to the app bar so that we can start it. How to add an action to an app bar To add an action to the app bar, you need to do four things: 1 Add resources for the action’s icon and text. 2 Define the action in a menu resource file. This tells Android what actions you want on the app bar. 3 Get the activity to add the menu resource to the app bar. You do this by implementing the onCreateOptionsMenu() method. 4 Add code to say what the action should do when clicked. You do this by implementing the onOptionsItemSelected() method. We’ll start by adding the action’s icon and text resources. you are here 4 319 add resources Basic app bar Toolbar Action Up button Share action 1. Add the action’s resources When you add an action to an app bar, you generally assign it an icon and a short text title. The icon usually gets displayed if the action appears in the main area of the app bar. If the action doesn’t fit in the main area, it’s automatically moved to the app bar overflow, and the title appears instead. This is the app bar’s overflow. Android moves actions into the overflow that don’t fit on the main area of the app bar. We’ll start with the icon. Add the icon If you want to display your action as an icon, you can either create your own icon from scratch or use one of the icons provided by Google. You can find the Google icons here: https://material.io/icons/. We’re going to use the “add” icon ic_add_white_24dp, and we’ll add a version of it to our project’s drawable* folders, one for each screen density. Android will decide at runtime which version of the icon to use depending on the screen density of the device. The icon for the new action First, switch to the Project view of Android Studio’s explorer if you haven’t done so already, highlight the app/src/main/res folder, and then create folders called drawable-hdpi, drawable-mdpi, drawable-xhdpi, drawable-xxhdpi, and drawable-xxxhdpi if they’re not already there. Then go to https://git.io/v9oet, and download the ic_add_white_24dp.png Bits and Pizzas images. Add the image in the drawable-hdpi folder to the drawable-hdpi folder in your project, then repeat this process for the other folders. Add the action’s title as a String resource In addition to adding an icon for the action, we’ll also add a title. This will get used if Android displays the action in the overflow area of the app bar, for example if there’s no space for the action in the main area of the app bar. BitsAndPizzas We’ll create the title as a String resource. Open the file strings.xml in the app/src/main/res/values folder, then add the following String resource: app/src/main The app's label is the BitsAndPizzas default label for the entire app. app/src/main We don't need to change the code for MainActivity. AndroidManifest.xml MainActivity has nolabel of its own, so it ... will use the label in the element. Create Order Now that we’ve added resources for the action’s icon and title, we can create the menu resource file. 320 Chapter 8 We’ll use this as the action item’s title. res valuesstrings.xml support libraries and app bars Basic app bar Toolbar Action Up button Share action 2. Create the menu resource file A menu resource file tells Android what actions you want to appear on the app bar. Your app can contain multiple menu resource files. For example, you can create a separate menu resource file for each set of actions; this is useful if you want different activities to display different actions on their app bars. We’re going to create a new menu resource file called menu_main.xml in the folder app/src/main/res/menu. All menu resource files go in this folder. To create the menu resource file, select the app/src/main/res folder, go to the File menu, and choose New. Then choose the option to create a new Android resource file. You’ll be prompted for the name of the resource file and the type of resource. Give it a name of “menu_main” and a resource type of “Menu”, and make sure that the directory name is menu. When you click on OK, Android Studio will create the file for you , and add it to the app/src/main/res/menu folder. Android Studio may have already created this file for you. If it has, simply replace its contents with the code below. Here’s the code to add the new action. Replace the contents of menu_ main.xml with the code below: TopLevelActivity.java activity_drink_ category.xml Drink.java 2 activity_drink.xml 3 DrinkCategoryActivity.java DrinkActivity.java DrinkActivity and DrinkCategoryActivity are still accessing Drink.java. 658 Chapter 16 basic cursors The new Starbuzz app structure There are two activities that use the Drink class: DrinkActivity and DrinkCategoryActivity. We need to change these activities so that they read data from the SQLite database with assistance from the SQLite helper. Here’s what the new structure of the Starbuzz app will look like: Starbuzz databaseactivity_top_level.xml Device activity_drink_ category.xml SQLite Helper DrinkCategoryActivity.java TopLevelActivity.java activity_drink.xml DrinkActivity.java We’ll change the activities that access the Drink class so that they use the database instead. We will no longer be using the Drink class. Drink.java We’ll start by updating DrinkActivity, and we’ll change DrinkCategoryActivity later on in the chapter. Do this! We’re going to update the Starbuzz app in this chapter, so open your Starbuzz project in Android Studio. you are here 4 659 steps What we’ll do to change DrinkActivity to use the Starbuzz database There are a number of steps we need to go through to change DrinkActivity so that it uses the Starbuzz database: 1 Get a reference to the Starbuzz database. We’ll do this using the Starbuzz SQLite helper we created in Chapter 15. DrinkActivity.java SQLite Helper Starbuzz database 2 Create a cursor to read drink data from the database. We need to read the data held in the Starbuzz database for the drink the user selects in DrinkCategoryActivity. The cursor will give us access to this data. (We’ll explain cursors soon.) 3 Navigate to the drink record. Before we can use the data retrieved by the cursor, we need to explicitly navigate to it. 4 Display details of the drink in DrinkActivity. Once we’ve navigated to the drink record in the cursor, we need to read the data and display it in DrinkActivity. DrinkActivity displays details of the drink the user selected. Before we begin, let’s remind ourselves of the DrinkActivity.java code we created in Chapter 7. 660 Chapter 16 basic cursors Database reference Create cursor Navigate to record Display drink The current DrinkActivity code Below is a reminder of the current DrinkActivity.java code. The onCreate() method gets the drink ID selected by the user, gets the details of that drink from the Drink class, and then populates the activity’s views using the drink attributes. We need to change the code in the onCreate() method to get the data from the Starbuzz database. package com.hfad.starbuzz; Starbuzz We're not showing you the import statements. ... app/src/main public class DrinkActivity extends Activity { public static final String EXTRA_DRINKID = "drinkId"; java com.hfad.starbuzz @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_drink); DrinkActivity.java This is the drink the user selected. //Get the drink from the intent int drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID); Drink drink = Drink.drinks[drinkId]; //Populate the drink name We need to populate the views in the layout with values from the database, not from the Drink class. Use the drink ID from the intent to get the drink details from the Drink class. We’ll need to change this so the drink comes from the database. TextView name = (TextView)findViewById(R.id.name); name.setText(drink.getName()); //Populate the drink description TextView description = (TextView)findViewById(R.id.description); description.setText(drink.getDescription()); //Populate the drink image ImageView photo = (ImageView)findViewById(R.id.photo); photo.setImageResource(drink.getImageResourceId()); } } photo.setContentDescription(drink.getName()); you are here 4 661 get a database reference Get a reference to the database The first thing we need is to get a reference to the Starbuzz database using the SQLite helper we created in the last chapter. To do that, you start by getting a reference to the SQLite helper: Database reference Create cursor Navigate to record Display drink SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); You then call the SQLite helper’s getReadableDatabase() or getWritableDatabase() to get a reference to the database. You use the getReadableDatabase() method if you need readonly access to the database, and the getWritableDatabase() method if you need to perform any updates. Both methods return a SQLiteDatabase object that you can use to access the database: This is a Context, in this case the current activity. SQLiteDatabase db = starbuzzDatabaseHelper.getReadableDatabase(); or: SQLiteDatabase db = starbuzzDatabaseHelper.getWritableDatabase(); If Android fails to get a reference to the database, a SQLiteException is thrown. This can happen if, for example, you call the getWritableDatabase() to get read/write access to the database, but you can’t write to the database because the disk is full. In our particular case, we only need to read data from the database, so we’ll use the getReadableDatabase() method. If Android can’t get a reference to the database and a SQLiteException is thrown, we’ll use a Toast (a pop-up message) to tell the user that the database is unavailable: SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); try { SQLiteDatabase db = starbuzzDatabaseHelper.getReadableDatabase(); //Code to read data from the database } catch(SQLiteException e) { Toast toast = Toast.makeText(this, "Database unavailable", } toast.show(); Toast.LENGTH_SHORT); This line displays the toast. Once you have a reference to the database, you can get data from it using a cursor. We’ll look at cursors next. 662 Chapter 16 These lines create a Toast that will display the message “Database unavailable” for a few seconds. basic cursors Database reference Create cursor Navigate to record Display drink Get data from the database with a cursor As we said in Chapter 15, a cursor lets you read from and write to the database. You specify what data you want access to, and the cursor brings back the relevant records from the database. You can then navigate through the records supplied by the cursor. This is the database. This is the data in the database you want to be able to read. The cursor reads the data you want in the database. _id You navigate through each record supplied by the cursor and read its values. NAME DESCRIPTION IMAGE_RESOURCE_ID 1 “Latte” "Espresso and steamed milk" 54543543 2 “Cappuccino” "Espresso, hot milk and 3 “Filter” steamed-milk foam" "Our best drip coffee" 654334453 44324234 You create a cursor using a database query. A database query lets you specify which records you want access to from the database. As an example, you can say you want to access all the records from the DRINK table, or just one particular record. These records are then returned in the cursor. You create a cursor using the SQLiteDatabase query() method: The query() method returns a Cursor object. Cursor cursor = db.query(...); The query() method parameters go here. s. We'll look at them over the next few page There are many overloaded versions of this method with different parameters, so rather than go into each variation, we’re only going to show you the most common ways of using it. you are here 4 663 query() Database reference Create cursor Navigate to record Display drink Return all the records from a table The simplest type of database query is one that returns all the records from a particular table in the database. This is useful if, for instance, you want to display all the records in a list in an activity. As an example, here’s how you’d return the values in the _id, NAME, and DESCRIPTION columns for every record in the DRINK table: This is the name of the table. We want to return the values in these columns. Cursor cursor = db.query("DRINK", Set these parameters to null when you want to return all records from a table. new String[] {"_id","NAME", "DESCRIPTION"}, null, null, null, null, null); The query returns all the data from the _id, NAME, and DESCRIPTION columns in the DRINK table. _id NAME 1 “Latte” 2 “Cappuccino” "Espresso, hot milk and 3 “Filter” Cursor cursor = db.query("TABLE_NAME", "Our best drip coffee" You specify each column whose value you want to return in an array of Strings. new String[] {"COLUMN1","COLUMN2"}, null, null, null, null, null); Next we’ll look at how you can return the records in a particular order. 664 Chapter 16 "Espresso and steamed milk" steamed-milk foam" To return all the records from a particular table, you pass the name of the table as the query() method’s first parameter, and a String array of the column names as the second. You set all of the other parameters to null, as you don’t need them for this type of query. There are five extra parameters you need to set to null. DESCRIPTION Behind the scenes, Android uses the query() method to construct an SQL SELECT statement. basic cursors Database reference Create cursor Navigate to record Display drink Return records in a particular order If you want to display data in your app in a particular order, you use the query to sort the data by a particular column. This can be useful if, for example, you want to display drink names in alphabetical order. By default, the data in the table appears in _id order, as this was the order in which data was entered: _id NAME DESCRIPTION IMAGE_RESOURCE_ID FAVORITE 1 2 “Latte” “Cappuccino” "Espresso, hot milk and "Espresso and steamed milk" 54543543 654334453 steamed-milk foam" "Our best drip coffee" 44324234 0 0 3 “Filter” 1 If you wanted to retrieve data from the _id, NAME, and FAVORITE column in ascending NAME order, you would use the following: Cursor cursor = db.query("DRINK", new String[] {"_id", "NAME", "FAVORITE"}, null, null, null, null, "NAME ASC"); Order by NAME in ascending order. _id NAME FAVORITE 2 “Cappuccino” 3 “Filter” 1 “Latte” 0 1 0 NAME FAVORITE The ASC keyword means that you want to order that column in ascending order. Columns are ordered in ascending order by default, so if you want you can omit the ASC. To order the data in descending order instead, you’d use DESC. You can sort by multiple columns too. As an example, here’s how you’d order by FAVORITE in descending order, followed by NAME in ascending order: Cursor cursor = db.query("DRINK", new String[] {"_id", "NAME", "FAVORITE"}, null, null, null, null, "FAVORITE DESC, NAME"); Order by FAVORITE in descending order, then NAME in ascending order. _id 3 “Filter” 2 “Cappuccino” 1 “Latte” 1 0 0 Next we’ll look at how you return selected records from the database. you are here 4 665 specify conditions Database reference Create cursor Navigate to record Display drink Return selected records You can filter your data by declaring conditions the data must meet, just as you did in Chapter 15. As an example, here’s how you’d return records from the DRINK table where the name of the drink is “Latte”: Cursor cursor = db.query("DRINK", These are the columns we want to return. new String[] {"_id", "NAME", "DESCRIPTION"}, "NAME = ?", new String[] {"Latte"}, null, null, null); The third and fourth parameters in the query describe the conditions the data must meet. The third parameter specifies the column in the condition. In the above example we want to return records where the value of the NAME column is “Latte”, so we use "NAME = ?". We want the value in the NAME column to be equal to some value, and the ? symbol is a placeholder for this value. The fourth parameter is an array of Strings that specifies what the value of the condition should be. In the above example we want to update records where the value of the NAME column is “Latte”, so we use: We want to return records where the value of the NAME column is “Latte”. _id 1 NAME DESCRIPTION “Latte” "Espresso and steamed milk" The query returns all the data from the NAME and DESCRIPTION columns in the DRINK table where the value of the NAME column is “Latte". new String[] {"Latte"}; The value of the condition must be an array of Strings, even if the column you’re applying the condition to contains some other type of data. As an example, here’s how you’d return records from the DRINK table where the drink _id is 1: Cursor cursor = db.query("DRINK", new String[] {"_id", "NAME", "DESCRIPTION"}, "_id = ?", new String[] {Integer.toString(1)}, null, null, null); You’ve now seen the most common ways of using the query() method to create a cursor, so try the following exercise to construct the cursor we need for DrinkActivity.java. 666 Chapter 16 Convert the int 1 to a String value. re ways of , o m t u o d od To fin ery() meth ase u q e h t g in us LiteDatab visit the SQtion: documenta om/ r.android.c e/ e p lo e v e d / https:/ android/databas reference/ iteDatabase.html sqlite/SQL basic cursors Code Magnets In our code for DrinkActivity, we want to get the name, description, and image resource ID for the drink ID passed to it in an intent. Can you create a cursor that will do that? ... int drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID); //Create a cursor SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); try { SQLiteDatabase db = starbuzzDatabaseHelper.getReadableDatabase(); Cursor cursor = db.query( , new String[] { " , , }, ", new String[] { }, null, null, null); } catch(SQLiteException e) { Toast toast = Toast.makeText(this, "Database unavailable", Toast.LENGTH_SHORT); toast.show(); } ... You won't need to use all of the magnets. toString ) _id "NAME" "DESCRIPTI ON" drinkId "IMAGE_RESOURCE_ID" "DRINK" ? = Integer . ( id you are here 4 667 magnets solution Code Magnets Solution In our code for DrinkActivity we want to get the name, description, and image resource ID for the drink passed to it in an intent. Can you create a cursor that will do that? ... int drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID); //Create a cursor SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); try { SQLiteDatabase db = starbuzzDatabaseHelper.getReadableDatabase(); We want to access the DRINK table. Get the NAME, DESCRIPTION, and IMAGE_RESOURCE_ID. Cursor cursor = db.query( "DRINK" , "NAME" new String[] { " _id = new String[] { null, null, null); } catch(SQLiteException e) { ? ", Integer , "DESCRIPTI ON" , "IMAGE_RESOURCE_ID" }, Where _id matches the drinkId . toString ( drinkId ) }, drinkId is an int, so needed to be converted to a String. Toast toast = Toast.makeText(this, "Database unavailable", Toast.LENGTH_SHORT); } toast.show(); ... You didn’t need to use this magnet. id 668 Chapter 16 basic cursors Database reference Create cursor Navigate to record Display drink The DrinkActivity code so far We want to change DrinkActivity.java’s onCreate() method so that DrinkActivity gets its drink data from the Starbuzz database instead of from the Drink Java class. Here’s the code so far (we suggest you wait until we show you the full code—a few pages ahead—before you update your version of DrinkActivity.java): package com.hfad.starbuzz; ... public class DrinkActivity extends Activity { Our Starbuzz code uses the Activity class, but we could have changed the code to use AppCompatActivity if we'd wanted to. public static final String EXTRA_DRINKID = "drinkId"; e() method. We’ll add the code to the onCreat @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_drink); Starbuzz app/src/main java com.hfad.starbuzz //Get the drink from the intent int drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID); Drink drink = Drink.drinks[drinkId]; We’re no DrinkActivity.java longer getting the drinks from Drink.java. //Create a cursor SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); Get a try { reference SQLiteDatabase db = starbuzzDatabaseHelper.getReadableDatabase(); the to Cursor cursor = db.query ("DRINK", e. databas new String[] {"NAME", "DESCRIPTION", "IMAGE_RESOURCE_ID"}, Create a cursor to get "_id = ?", the name, description, and image resource ID new String[] {Integer.toString(drinkId)}, of the drink the user null, null, null); selected. } catch(SQLiteException e) { Toast toast = Toast.makeText(this, "Database unavailable", Toast.LENGTH_SHORT); toast.show(); Display a pop-up message if a } we SQLiteException is thrown. code more There’s ... haven’t changed yet, } but you don’t need to } see it right now. Now that we’ve created our cursor, the next thing we need to do is get the drink name, description, and image resource ID from the cursor so that we can update DrinkActivity’s views. you are here 4 669 navigate records Database reference Create cursor Navigate to record Display drink To read a record from a cursor, you first need to navigate to it You’ve now seen how to create a cursor; you get a reference to a SQLiteDatabase, and then use its query() method to say what data you want the cursor to return. But that’s not the end of the story—once you have a cursor, you need to read values from it. Whenever you need to retrieve values from a particular record in a cursor, you first need to navigate to that record. You specify what records you want by creating a query on the database. The cursor contains the records described by the query. _id You need to move to a record in the cursor to read values from it. NAME DESCRIPTION 1 “Latte” 2 “Cappuccino” "Espresso, hot milk and 3 “Filter” "Espresso and steamed milk" 54543543 steamed-milk foam" "Our best drip coffee" In our particular case, we have a cursor that’s composed of a single record that contains details of the drink the user selected. We need to navigate to that record in order to read details of the drink. 670 Chapter 16 IMAGE_RESOURCE_ID 654334453 44324234 basic cursors Database reference Create cursor Navigate to record Display drink Navigate cursors There are four main methods you use to navigate through the records in a cursor: moveToFirst(), moveToLast(), moveToPrevious(), and moveToNext(). To go to the first record in a cursor, you use its moveToFirst() method. This method returns a value of true if it finds a record, and false if the cursor hasn’t returned any records): if (cursor.moveToFirst()) { }; //Do something if (cursor.moveToLast()) { //Do something The moveToPrevious() method moves you to the previous record in the cursor. It returns true if it succeeds in moving to the previous record, and false if it fails (for example, if it’s already at the first record): if (cursor.moveToPrevious()) { //Do something The moveToNext() method works in a similar way to the moveToPrevious() method, except that it moves you to the next record in the cursor instead: if (cursor.moveToNext()) { }; //Do something In our case, we want to read values from the first (and only) record in the cursor, so we’ll use the moveToFirst() method to navigate to this record. DESCRIPTION "Espresso and steamed milk" Cappuccino "Espresso, hot milk and steamed-milk foam" NAME “Latte” "Our best drip coffee" DESCRIPTION "Espresso and steamed milk" Cappuccino "Espresso, hot milk and steamed-milk foam" Filter To move through the records in the cursor, you use the moveToPrevious() and moveToNext() methods. }; NAME “Latte” Filter To go to the last record, you use the moveToLast() method. Just like the moveToFirst() method, it returns a value of true if it finds a record, and false if it doesn’t: }; Move to the first row. "Our best drip coffee" Move to the last row. NAME “Latte” DESCRIPTION "Espresso and steamed milk" Cappuccino "Espresso, hot milk and steamed-milk foam" Filter "Our best drip coffee" Move to the previous row. NAME “Latte” DESCRIPTION "Espresso and steamed milk" Cappuccino "Espresso, hot milk and steamed-milk foam" Filter "Our best drip coffee" Move to the next row. Once you’ve navigated to a record in your cursor, you can access its values. We’ll look at how to do that next. you are here 4 671 get values Database reference Create cursor Navigate to record Display drink Get cursor values You retrieve values from a cursor’s current record using the cursor’s get*() methods: getString(), getInt(), and so on. The exact method you use depends on the type of value you want to retrieve. To get a String value, for example, you’d use the getString() method, and to get an int value you’d use getInt(). Each of the methods takes a single parameter: the index of the column whose value you want to retrieve, starting at 0. As an example, we’re using the following query to create our cursor: Cursor cursor = db.query ("DRINK", new String[] {"NAME", "DESCRIPTION", "IMAGE_RESOURCE_ID"}, "_id = ?", new String[] {Integer.toString(1)}, null, null, null); The cursor has three columns: NAME, DESCRIPTION, and IMAGE_RESOURCE_ID. The first two columns, NAME and DESCRIPTION, contain Strings, and the third column, IMAGE_RESOURCE_ID, contains int values. Suppose you wanted to get the value of the NAME column for the current record. NAME is the first column in the cursor, and contains String values. You’d therefore use the getString() method, passing it a parameter of 0 for the column index: String name = cursor.getString(0); These are the cursor’s columns. Column 0 Column 1 NAME DESCRIPTION “Latte” "Espresso and steamed milk" Column 2 IMAGE_ RESOURCE_ID 54543543 NAME is column 0 and contains Strings. Similarly, suppose you wanted to get the contents of the IMAGE_RESOURCE_ID column. This has a column index of 2 and contains int values, so you’d use the code: int imageResource = cursor.getInt(2); IMAGE_RESOURCE_ID is column 2 and contains ints. Finally, close the cursor and the database Once you’ve finished retrieving values from the cursor, you need to close the cursor and the database in order to release their resources. You do this by calling the cursor and database close() methods: cursor.close(); db.close(); These lines close the cursor and the database. We’ve now covered all the code you need to replace the code in DrinkActivity so that it gets its data from the Starbuzz database. Let’s look at the revised code in full. 672 Chapter 16 d details You can fincursor get of all the http:// methods in ndroid.com/ developer.a android/ reference/ ursor.html. database/C basic cursors Database reference Create cursor Navigate to record Display drink The DrinkActivity code Here’s the full code for DrinkActivity.java (apply the changes in bold to your code, then save your work): package com.hfad.starbuzz; import android.app.Activity; Starbuzz import android.os.Bundle; import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; We’re using these extra classes in the code. import android.database.Cursor; app/src/main java com.hfad.starbuzz import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; DrinkActivity.java public class DrinkActivity extends Activity { public static final String EXTRA_DRINKID = "drinkId"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_drink); //Get the drink from the intent This is the ID of the drink the user chose. int drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID); Drink drink = Drink.drinks[drinkId]; //Create a cursor We’re no longer getting our data from the drinks array, so we need to delete this line. SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); try { SQLiteDatabase db = starbuzzDatabaseHelper.getReadableDatabase(); Cursor cursor = db.query ("DRINK", Create a cursor that gets the NAME, DESCRIPTION, and IMAGE_ RESOURCE_ID data from the DRINK table where _id matches drinkId. new String[] {"NAME", "DESCRIPTION", "IMAGE_RESOURCE_ID"}, "_id = ?", new String[] {Integer.toString(drinkId)}, null, null, null); The code continues on the next page. you are here 4 673 code, continued Database reference Create cursor Navigate to record Display drink The DrinkActivity code (continued) //Move to the first record in the Cursor There’s only one record in the cursor, but we still need to move to it. if (cursor.moveToFirst()) { The name of the drink is the first item in the cursor, the description is the second column, and the image resource ID is the third. That’s because we told the cursor to use the NAME, DESCRIPTION, and IMAGE_ RESOURCE_ID columns from the database in that order. //Get the drink details from the cursor String nameText = cursor.getString(0); String descriptionText = cursor.getString(1); Starbuzz int photoId = cursor.getInt(2); app/src/main //Populate the drink name java TextView name = (TextView)findViewById(R.id.name); name.setText(drink.getName()); name.setText(nameText); Set the drink name to the value from the database. com.hfad.starbuzz DrinkActivity.java //Populate the drink description TextView description = (TextView)findViewById(R.id.description); description.setText(drink.getDescription()); description.setText(descriptionText); Use the drink description from the database. //Populate the drink image ImageView photo = (ImageView)findViewById(R.id.photo); photo.setImageResource(drink.getImageResourceId()); photo.setContentDescription(drink.getName()); photo.setImageResource(photoId); } Set the image resource ID and description to the values from the database. photo.setContentDescription(nameText); cursor.close(); db.close(); Close the cursor and database. } catch(SQLiteException e) { Toast toast = Toast.makeText(this, "Database unavailable", } } } toast.show(); Toast.LENGTH_SHORT); If a SQLiteException is thrown, this means there’s a problem with the database. In this case, we’ll use a toast to display a message to the user. So that’s the complete DrinkActivity code. Let’s review where we’ve got to, and what we need to do next. 674 Chapter 16 Connecting your activities to a database takes more code than using a Java class. But if you take your time working through the code in this chapter, you’ll be fine. basic cursors What we’ve done so far Now that we’ve finished updating the DrinkActivity.java code, let’s look at the app structure diagram to see what we’ve done, and what we need to do next. activity_top_level.xml Device activity_drink_ category.xml TopLevelActivity.java How much SQL do I need to know to create cursors? It’s useful to have an understanding of SQL SELECT statements, as behind the scenes the query() method translates to one. In general, your queries probably won’t be too complex, but SQL knowledge is a useful skill. If you want to learn more about SQL, we suggest getting a copy of Head First SQL by Lynn Beighley. Drink.java DrinkCategoryActivity.java DrinkActivity now gets all of its drink data from the Starbuzz database. Next, we need to update the code in DrinkCategoryActivity so that it uses data from the database rather than from the Java Drink class. We’ll look at the steps to do this on the next page. Q: A: DrinkCategoryActivity still gets its drink data from the Drink class. Starbuzz database Q: activity_drink.xml DrinkActivity.java SQLite Helper DrinkActivity has been changed so it gets its data from the Starbuzz database via the SQLite helper. You said that if the database can’t be accessed, a SQLiteException is thrown. How should I deal with it? A: First, check the exception details. The exception might be caused by an error in SQL syntax, which you can then rectify. How you handle the exception depends on the impact it has on your app. As an example, if you can get read access to the database but can’t write to it, you can still give the user read-only access to the database, but you might want to tell the user that you can’t save their changes. Ultimately, it all depends on your app. you are here 4 675 more steps What we’ll do to change DrinkCategoryActivity to use the Starbuzz database When we updated DrinkActivity to get it to read data from the Starbuzz database, we created a cursor to read data for the drink the user selected, and then we used the values from the cursor to update DrinkActivity’s views. The steps we need to go through to update DrinkCategoryActivity are slightly different. This is because DrinkCategoryActivity displays a list view that uses the drink data as its source. We need to switch the source of this data to be the Starbuzz database. Here are the steps we need to go through to change DrinkCategoryActivity so that it uses the Starbuzz database: 1 Create a cursor to read drink data from the database. As before, we need to get a reference to the Starbuzz database. Then we’ll create a cursor to retrieve the drink names from the DRINK table. Cursor 2 Database Replace the list view’s array adapter with a cursor adapter. The list view currently uses an array adapter to get its drink names. This is because the data’s held in an array in the Drink class. Because we’re now accessing the data using a cursor, we’ll use a cursor adapter instead. ListView CursorAdapter Before we get started on these tasks, let’s remind ourselves of the DrinkCategoryActivity.java code we created in Chapter 7. 676 Chapter 16 Cursor basic cursors Create cursor Cursor adapter The current DrinkCategoryActivity code Here’s a reminder of what the current DrinkCategoryActivity.java code looks like. The onCreate() method populates a list view with drinks using an array adapter. The onListItemClick() method adds the drink the user selects to an intent, and then starts DrinkActivity: DrinkCategoryActivity displays a list of drinks. package com.hfad.starbuzz; ... public class DrinkCategoryActivity extends Activity { Starbuzz @Override protected void onCreate(Bundle savedInstanceState) { app/src/main super.onCreate(savedInstanceState); At the setContentView(R.layout.activity_drink_category); java moment, we’re ArrayAdapter listAdapter = new ArrayAdapter<>( using an this, com.hfad.starbuzz ArrayAdapter android.R.layout.simple_list_item_1, to bind an Drink.drinks); array to the DrinkCategory ListView listDrinks = (ListView) findViewById(R.id.list_drinks); Activity.java ListView. listDrinks.setAdapter(listAdapter); We need to replace this code so that the data comes from a database instead. } } //Create a listener to listen for clicks in the list view AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener(){ public void onItemClick(AdapterView> listDrinks, View itemView, int position, long id) { //Pass the drink the user clicks on to DrinkActivity Intent intent = new Intent(DrinkCategoryActivity.this, DrinkActivity.class); intent.putExtra(DrinkActivity.EXTRA_DRINKID, (int) id); startActivity(intent); } }; //Assign the listener to the list view listDrinks.setOnItemClickListener(itemClickListener); you are here 4 677 get a database reference Create cursor Cursor adapter Get a reference to the Starbuzz database... We need to change DrinkCategoryActivity so that it gets its data from the Starbuzz database. Just as before, this means that we need to create a cursor to return the data we need. We start by getting a reference to the database. We only need to read the drink data and not update it, so we’ll use the getReadableDatabase() method as we did before: SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); SQLiteDatabase db = starbuzzDatabaseHelper.getReadableDatabase(); ...then create a cursor that returns the drinks To create the cursor, we need to specify what data we want it to contain. We want to use the cursor to display a list of drink names, so the cursor needs to include the NAME column. We’ll also include the _id column to get the ID of the drink: we need to pass the ID of the drink the user chooses to DrinkActivity so that DrinkActivity can display its details. Here’s the cursor: cursor = db.query("DRINK", new String[]{"_id", "NAME"}, We get a reference to the database in exactly the same way that we did earlier in this chapter. This cursor returns the _id and NAME of every record in the DRINK table. null, null, null, null, null); Putting this together, here’s the code to get a reference to the database and create the cursor (you’ll add this code to DrinkCategoryActivity.java later when we show you the full code listing): SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); try { SQLiteDatabase db = starbuzzDatabaseHelper.getReadableDatabase(); cursor = db.query("DRINK", new String[]{"_id", "NAME"}, null, null, null, null, null); //Code to use data from the database } catch(SQLiteException e) { Toast toast = Toast.makeText(this, If the database is unavailable, a SQLiteException gets thrown. If this happens, we’ll use a toast to display a pop-up message as before. "Database unavailable", } toast.show(); Next we’ll use the cursor’s data to populate DrinkCategoryActivity’s list view. 678 Chapter 16 Toast.LENGTH_SHORT); basic cursors Create cursor Cursor adapter How do we replace the array data in the list view? When we wanted DrinkCategoryActivity’s list view to display data from the Drink.drinks array, we used an array adapter. As you saw back in Chapter 7, an array adapter is a type of adapter that works with arrays. It acts as a bridge between the data in an array and a list view: This is the array. We created an array adapter to bind the list view to the array. Drink. drinks This is the database. Database data ListView Array Adapter Now that we’re getting our data from a cursor, we’ll use a cursor adapter to bind the data to our list view. A cursor adapter is just like an array adapter, except that instead of getting its data from an array, it reads the data from a cursor: The cursor reads data from the database. Cursor This is the list view. Our data comes from a cursor, so we’ll use a cursor adapter to bind it to the ListView. Cursor Adapter ListView We’ll look at this in more detail on the next page. ListViews and Spinners can use any subclass of the Adapter class for their data. This includes ArrayAdapter, CursorAdapter, and SimpleCursorAdapter (a subclass of CursorAdapter). you are here 4 679 how simple cursor adapters work Create cursor Cursor adapter A simple cursor adapter maps cursor data to views We’re going to create a simple cursor adapter, a type of cursor adapter that can be used in most cases where you need to display cursor data in a list view. It takes columns from a cursor, and maps them to text views or image views, for example in a list view. In our case, we want to display a list of drink names. We’ll use a simple cursor adapter to map the name of each drink returned by our cursor to DrinkCategoryActivity’s list view. Here’s how it will work: 1 The list view asks the adapter for data. ListView 2 The adapter asks the cursor for data from the database. Simple CursorAdapter 3 Simple CursorAdapter Cursor Database The adapter returns the data to the list view. The name of each drink is displayed in the list view as a separate text view. Each of the drinks is displayed in the list view as a separate text view. Latte Cappuccino Filter Let’s construct the simple cursor adapter. 680 Chapter 16 ListView Simple CursorAdapter basic cursors Create cursor Cursor adapter How to use a simple cursor adapter You use a simple cursor adapter in a similar way to how you use an array adapter: you initialize the adapter, then attach it to the list view. We’re going to create a simple cursor adapter to display a list of drink names from the DRINK table. To do this, we’ll create a new instance of the SimpleCursorAdapter class, passing in parameters to tell the adapter what data to use and how it should be displayed. Finally, we’ll assign the adapter to the list view. Here’s the code (you’ll add it to DrinkCategoryActivity.java later in the chapter): “this” is the current activity. SimpleCursorAdapter listAdapter = new SimpleCursorAdapter(this, This is the cursor. This is the same layout we used with the array adapter. It displays a single value for each row in the list view. Display the contents of the NAME column in the ListView text views. android.R.layout.simple_list_item_1, cursor, new String[]{"NAME"}, new int[]{android.R.id.text1}, 0); listDrinks.setAdapter(listAdapter); Use setAdapter() to connect the adapter to the list view. The general form of the SimpleCursorAdapter constructor looks like this: This is usually the current activity. SimpleCursorAdapter adapter = new SimpleCursorAdapter(Context context, The cursor you create. The cursor should include the _id column, and the data you want to appear. The context and layout parameters are exactly the same ones you used when you created an array adapter: context is the current context, and layout says how you want to display the data. Instead of saying which array we need to get our data from, we need to specify which cursor contains the data. You then use fromColumns to specify which columns in the cursor you want to use, and toViews to say which views you want to display them in. The flags parameter is usually set to 0, which is the default. The alternative is to set it to FLAG_REGISTER_CONTENT_OBSERVER to register a content observer that will be notified when the content changes. We’re not covering this alternative here, as it can lead to memory leaks (you’ll see how to deal with changing content in the next chapter). int layout, Cursor cursor, How to display the data String[] fromColumns, int[] toViews, int flags) Used to determine the behavior of the cursor Which columns in the cursor to match to which views Any cursor you use with a cursor adapter MUST include the _id column or it won't work. you are here 4 681 close the things that you open Create cursor Cursor adapter Close the cursor and database When we introduced you to cursors earlier in the chapter, we said that you needed to close the cursor and database after you’d finished with it in order to release their resources. In our DrinkActivity code, we used a cursor to retrieve drink details from the database, and once we’d used these values with our views, we immediately closed the cursor and database. When you use a cursor adapter, (including a simple cursor adapter) it works slightly differently; the cursor adapter needs the cursor to stay open in case it needs to retrieve more data from it. Let’s look in more detail at how cursor adapters work to see why this might happen. 1 The list view gets displayed on the screen. When the list is first displayed, it will be sized to fit the screen. Let’s say it has space to show five items. These are the items the list view has space to display. We’re using five to keep things simple, but in practice it’s likely to be more. 2 ListView The list view asks its adapter for the first five items. Yep, I’ll go and get them. Hey, Adapter, can I have the first five data items? ListView 3 CursorAdapter The cursor adapter asks its cursor to read five rows from the database. No matter how many rows the database table contains, the cursor only needs to read the first five rows. CursorAdapter 682 Chapter 16 Cursor Database basic cursors Create cursor Cursor adapter The story continues 4 The user scrolls the list. As the user scrolls the list, the adapter asks the cursor to read more rows from the database. This works fine if the cursor’s still open. But if the cursor’s already been closed, the cursor adapter can’t get any more data from the database. As the user scrolls through the list view, more items get uncovered so more data is required. Hey, Cursor, I need more... Cursor? Hey, buddy, are you there? Hey Adapter, I need more data. Cursor ListView CursorAdapter If you close the cursor too soon, the cursor adapter won’t be able to get more data from the cursor. This means that you can’t immediately close the cursor and database once you’ve used the setAdapter() method to connect the cursor adapter to your list view. Instead, we’ll close the cursor and database in the activity’s onDestroy() method, which gets called just before the activity is destroyed. Because the activity’s being destroyed, there’s no further need for the cursor or database connection to stay open, so they can be closed: public void onDestroy(){ super.onDestroy(); cursor.close(); } db.close(); Close the cursor and database when the activity is destroyed. That’s everything you need to know in order to update the code for DrinkCategoryActivity, so have a go at the exercise on the next page. you are here 4 683 exercise Pool Puzzle Your job is to take code segments from the pool and place them into the blank lines in DrinkCategoryActivity.java. You may not use the same code segment more than once, and you won’t need to use all the code segments. Your goal is to populate the list view with a list of drinks from the database. Starbuzz public class DrinkCategoryActivity extends Activity { app/src/main private SQLiteDatabase db; private Cursor cursor; java com.hfad.starbuzz @Override protected void onCreate(Bundle savedInstanceState) { DrinkCategory Activity.java super.onCreate(savedInstanceState); setContentView(R.layout.activity_drink_category); ListView listDrinks = (ListView) findViewById(R.id.list_drinks); starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); try { db = starbuzzDatabaseHelper. ; cursor = db.query("DRINK", new String[]{ }, null, null, null, null, null); Note: each segment in the pool can only be used once! The code continues on the next page getWritableDatabase() cursor , "DESCRIPTION" 684 Chapter 16 db SimpleCursorAdapter "NAME" "_id" "NAME" cursor SQLiteOpenHelper getReadableDatabase() SQLiteException , DatabaseException basic cursors SimpleCursorAdapter listAdapter = new android.R.layout.simple_list_item_1, , new String[]{ (this, }, new int[]{android.R.id.text1}, 0); listDrinks.setAdapter(listAdapter); } catch( e) { Toast toast = Toast.makeText(this, "Database unavailable", Toast.LENGTH_SHORT); } toast.show(); //Create the listener AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener(){ public void onItemClick(AdapterView> listDrinks, View itemView, int position, long id) { //Pass the drink the user clicks on to DrinkActivity Intent intent = new Intent(DrinkCategoryActivity.this, DrinkActivity.class); intent.putExtra(DrinkActivity.EXTRA_DRINKID, (int) id); }; } startActivity(intent); //Assign the listener to the list view } listDrinks.setOnItemClickListener(itemClickListener); Starbuzz app/src/main java com.hfad.starbuzz @Override public void onDestroy(){ super.onDestroy(); DrinkCategory Activity.java .close(); } } .close(); you are here 4 685 solution Pool Puzzle Solution Your job is to take code segments from the pool and place them into the blank lines in DrinkCategoryActivity.java. You may not use the same code segment more than once, and you won’t need to use all the code segments. Your goal is to populate the list view with a list of drinks from the database. Starbuzz public class DrinkCategoryActivity extends Activity { app/src/main private SQLiteDatabase db; private Cursor cursor; java com.hfad.starbuzz @Override protected void onCreate(Bundle savedInstanceState) { DrinkCategory Activity.java super.onCreate(savedInstanceState); setContentView(R.layout.activity_drink_category); ListView listDrinks = (ListView) findViewById(R.id.list_drinks); You get a reference to the database using a SQLiteOpenHelper. SQLiteOpenHelper try { starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); db = starbuzzDatabaseHelper. getReadableDatabase() cursor = db.query("DRINK", new String[]{ "_id" , "NAME" null, null, null, null, null); These code snippets were not needed. ; We’re reading from the database, so we just need read-only access. }, The cursor must include the _id and NAME columns. We want to pass the drink ID to DrinkActivity, and we need to display the drink names. getWritableDatabase() , "DESCRIPTION" 686 Chapter 16 DatabaseException basic cursors SimpleCursorAdapter listAdapter = new Use the cursor we just created. We’re using a SimpleCursorAdapter. SimpleCursorAdapter (this, android.R.layout.simple_list_item_1, Display the contents of the NAME column. cursor , new String[]{ "NAME" }, new int[]{android.R.id.text1}, 0); listDrinks.setAdapter(listAdapter); } catch( SQLiteException e) { If the database is unavailable, we’ll catch the SQLiteException. Toast toast = Toast.makeText(this, "Database unavailable", Toast.LENGTH_SHORT); } toast.show(); //Create the listener AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener(){ public void onItemClick(AdapterView> listDrinks, View itemView, int position, long id) { //Pass the drink the user clicks on to DrinkActivity Intent intent = new Intent(DrinkCategoryActivity.this, DrinkActivity.class); intent.putExtra(DrinkActivity.EXTRA_DRINKID, (int) id); }; } startActivity(intent); Starbuzz //Assign the listener to the list view } listDrinks.setOnItemClickListener(itemClickListener); DrinkCategory Activity.java public void onDestroy(){ super.onDestroy(); cursor db } java com.hfad.starbuzz @Override } app/src/main .close(); .close(); Close the cursor before you close the database. you are here 4 687 DrinkCategoryActivity code Create cursor Cursor adapter The revised code for DrinkCategoryActivity Here’s the full code for DrinkCategoryActivity.java, with the array adapter replaced by a cursor adapter (the changes are in bold); update your code to match ours: package com.hfad.starbuzz; Starbuzz import android.app.Activity; import android.os.Bundle; app/src/main import android.widget.ListView; import android.view.View; java import android.content.Intent; com.hfad.starbuzz import android.widget.AdapterView; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteOpenHelper; import android.widget.SimpleCursorAdapter; import android.widget.Toast; DrinkCategory Activity.java We’re using these extra classes, so you need to import them. public class DrinkCategoryActivity extends Activity { private SQLiteDatabase db; private Cursor cursor; We’re adding these as private variables so can close the database and cursor in our onDestroy()wemet hod. @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_drink_category); ArrayAdapter listAdapter = new ArrayAdapter<>( this, We’re no longer android.R.layout.simple_list_item_1, using an array Drink.drinks); adapter, so delete these ListView listDrinks = (ListView) findViewById(R.id.list_drinks); lines of code. SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); try { db = starbuzzDatabaseHelper.getReadableDatabase(); cursor = db.query("DRINK", Create the cursor. 688 Chapter 16 new String[]{"_id", "NAME"}, null, null, null, null, null); Get a reference to the database. The code continues on the next page. basic cursors The DrinkCategoryActivity code (continued) Create cursor Cursor adapter SimpleCursorAdapter listAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1, Map the contents Create the cursor adapter. cursor, of the NAME new String[]{"NAME"}, column to the text new int[]{android.R.id.text1}, in the ListView. 0); listDrinks.setAdapter(listAdapter); Set the adapter to the ListView. } catch(SQLiteException e) { Toast toast = Toast.makeText(this, "Database unavailable", Toast.LENGTH_SHORT); toast.show(); Display a message to the user if a } SQLiteException gets thrown. //Create a listener to listen for clicks in the list view AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener(){ public void onItemClick(AdapterView> listDrinks, View itemView, int position, long id) { We didn’t need to //Pass the drink the user clicks on to DrinkActivity change any of the Intent intent = new Intent(DrinkCategoryActivity.this, listener code. DrinkActivity.class); intent.putExtra(DrinkActivity.EXTRA_DRINKID, (int) id); startActivity(intent); } Starbuzz }; } } //Assign the listener to the list view listDrinks.setOnItemClickListener(itemClickListener); @Override public void onDestroy(){ super.onDestroy(); cursor.close(); db.close(); } app/src/main java com.hfad.starbuzz We’re closing the database and cursor in the activity’s onDestroy() method. The cursor will stay open until the cursor adapter no longer needs it. DrinkCategory Activity.java Let’s try running our freshly updated app. you are here 4 689 test drive Create cursor Cursor adapter Test drive the app When you run the app, TopLevelActivity gets displayed. When you click on the Drinks item, DrinkCategoryActivity is launched. It displays all the drinks from the Starbuzz database. We clicked on the Latte option... When you click on one of the drinks, DrinkActivity is launched and details of the selected drink are displayed. ...and here are details of the Latte. The app looks exactly the same as before, but now the data is being read from the database. In fact, you can now delete the Drink.java code, because we no longer need the array of drinks. Every piece of data we need is now coming from the database. 690 Chapter 16 basic cursors Your Android Toolbox You can download the full code for the chapter from https://tinyurl.com/ HeadFirstAndroid. A cursor lets you read from and write to the database. Navigate through a cursor using the moveTo*() methods. You create a cursor by calling the SQLiteDatabase query() method. Behind the scenes, this builds a SQL SELECT statement. Get values from a cursor using the get*() methods. Close cursors and database connections after you’ve finished with them. The getWritableDatabase() method returns a SQLiteDatabase object that allows you to read from and write to the database. A cursor adapter is an adapter that works with cursors. Use SimpleCursorAdapter to populate a list view with the values returned by a cursor. The getReadableDatabase() returns a SQLiteDatabase object. This gives you read-only access to the database. It may also allow you to write to the database, but this isn’t guaranteed. you are here 4 691 CHAPTER 16 You’ve got Chapter 16 under your belt and now you’ve added basic cursors to your toolbox. 17 cursors and asynctasks Staying in the Background My doInBackground() method’s awesome. If I left it all to Mr. Main Event, can you imagine how slow he’d be? In most apps, you’ll need your app to update its data. o far you’ve seen how to create apps that read data from a SQLite database. But what S if you want to update the app’s data? In this chapter you’ll see how to get your app to respond to user input and update values in the database. You’ll also find out how to refresh the data that’s displayed once it’s been updated. Finally, you’ll see how writing efficient multithreaded code with AsyncTasks will keep your app speedy. this is a new chapter 693 update data We want our Starbuzz app to update database data In Chapter 16, you learned how to change your app to read its data from a SQLite database. You saw how to read an individual record (a drink from the Starbuzz data) and display that record’s data in an activity. You also learned how to populate a list view with database data (in this case drink names) using a cursor adapter. In both of these scenarios, you only needed to read data from the database. But what if you want users to be able to update the data? We’re going to change the Starbuzz app so that users can record which drinks are their favorites. We’ll do this by adding a checkbox to DrinkActivity; if it’s checked, it means the current drink is one of the user’s favorites: Users can say which drinks are their favorites by checking a checkbox. We need to add this checkbox to DrinkActivity and get it to update the database. We’ll also add a new list view to TopLevelActivity, which contains the user’s favorite drinks: In the real world, you’d probably want to use tab navigation for these items. We’re deliberately keeping the app pretty basic because we want you to focus on databases. We’ll add a ListView to TopLevelActivity, which contains the user’s favorite drinks. 694 Chapter 17 When the user clicks on a drink, it takes them to that drink’s details in DrinkActivity. cursors and asynctasks We’ll update DrinkActivity first In Chapter 15, we added a FAVORITE column to the DRINK table in the Starbuzz database. We’ll use this column to let users indicate whether a particular drink is one of their favorites. We’ll display its value in the new checkbox we’re going to add to DrinkActivity, and when the user clicks on the checkbox, we’ll update the FAVORITE column with the new value. Here are the steps we’ll go through to update DrinkActivity: 1 Update DrinkActivity’s layout to add a checkbox and text label. We’ll add this checkbox and label to activity_drink.xml. 2 Display the value of the FAVORITE column in the checkbox. To do this, we’ll need to retrieve the value of the FAVORITE column from the Starbuzz database. 3 Update the FAVORITE column when the checkbox is clicked. We’ll update the FAVORITE column with the value of the checkbox so that the data in the database stays up to date. Let’s get started. Do this! We’re going to update the Starbuzz app in this chapter, so open your Starbuzz project in Android Studio. you are here 4 695 add favorites Update layout Show favorite Update favorite Add a checkbox to DrinkActivity’s layout We’ll start by adding a new checkbox to DrinkActivity’s layout to indicate whether the current drink is one of the user’s favorites. We’re using a checkbox, as it’s an easy way to display true/false values. First, add a String resource called "favorite" to strings.xml (we’ll use this as a label for the checkbox): Favorite Starbuzz app/src/main res Then add the checkbox to activity_drink.xml. We’ll give the checkbox an ID of favorite so that we can refer to it in our activity code. We’ll also set its android:onClick attribute to "onFavoriteClicked" so that it calls the onFavoriteClicked() method in DrinkActivity when the user clicks on the checkbox. Here’s the layout code; update your code to reflect our changes (they’re in bold): valuesstrings.xml the onFavoriteClicked() method in DrinkActivity will get called. Next we’ll change the DrinkActivity code to get the checkbox We need to write this method. android:text="@string/favorite" to reflect the value of the FAVORITE column from the database. 696 Chapter 17 cursors and asynctasks Update layout Show favorite Update favorite Display the value of the FAVORITE column In order to update the checkbox, we first need to retrieve the value of the FAVORITE column from the database. We can do this by updating the cursor we’re using in DrinkActivity’s onCreate() method to read drink values from the database. Starbuzz app/src/main Here’s the cursor we’re currently using to return data for the drink the user has selected: java Cursor cursor = db.query("DRINK", com.hfad.starbuzz new String[]{"NAME", "DESCRIPTION", "IMAGE_RESOURCE_ID"}, "_id = ?", DrinkActivity.java new String[]{Integer.toString(drinkId)}, null, null, null); To include the FAVORITE column in the data that’s returned, we simply add it to the array of column names returned by the cursor: Add the FAVORITE column to the cursor. Cursor cursor = db.query("DRINK", new String[]{"NAME", "DESCRIPTION", "IMAGE_RESOURCE_ID", "FAVORITE"}, "_id = ?", new String[]{Integer.toString(drinkId)}, null, null, null); Once we have the value of the FAVORITE column, we can update the favorite checkbox accordingly. To get this value, we first navigate to the first (and only) record in the cursor using: You don’t need to update your version of the code yet. We’ll show you the full set of changes for DrinkActivity.java soon. cursor.moveToFirst(); We can then get the value of the column for the current drink. The FAVORITE column contains numeric values, where 0 is false and 1 is true. We want the favorite checkbox to be ticked if the value is 1 (true), and unticked if the value is 0 (false), so we’ll use the following code to update the checkbox: boolean isFavorite = (cursor.getInt(3) == 1); Get the value of the FAVORITE column. It’s stored in the database as 1 for true, 0 for false. CheckBox favorite = (CheckBox) findViewById(R.id.favorite); favorite.setChecked(isFavorite); That’s all the code we need to reflect the value of the FAVORITE column in the favorite checkbox. Next, we need to get the checkbox to respond to clicks so that it updates the database when its value changes. Set the value of the favorite checkbox. you are here 4 697 respond to clicks Respond to clicks to update the database Update layout Show favorite Update favorite When we added the favorite checkbox to activity_drink.xml, we set its android:onClick attribute to onFavoriteClicked(). This means that whenever the user clicks on the checkbox, the onFavoriteClicked() method in the activity will get called. We’ll use this method to update the database with the current value of the checkbox. If the user checks or unchecks the checkbox, the onFavoriteClicked() method will save the user’s change to the database. In Chapter 15, you saw how to use SQLiteDatabase methods to change the data held in a SQLite database: the insert() method to insert data, the delete() method to delete data, and the update() method to update existing records. You can use these methods to change data from within your activity. As an example, you could use the insert() method to add new drink records to the DRINK table, or the delete() method to delete them. In our case, we want to update the DRINK table’s FAVORITE column with the value of the checkbox using the update() method. As a reminder, the update() method looks like this: db.update(String table, ContentValues The table whose data you want to update The new values values, String conditionClause, String[] conditionArguments); The criteria for updating the data where table is the name of the table you want to update, and values is a ContentValues object containing name/value pairs of the columns you want to update and the values you want to set them to. The conditionClause and conditionArguments parameters lets you specify which records you want to update. You already know everything you need to get DrinkActivity to update the FAVORITE column for the current drink when the checkbox is clicked, so have a go at the following exercise. update() DrinkActivity 698 Chapter 17 Starbuzz database cursors and asynctasks Code Magnets In our code for DrinkActivity we want to update the FAVORITE column in the database with the value of the favorite checkbox. Can you construct the onFavoriteClicked() method so that it will do that? public class DrinkActivity extends Activity { ... //Update the database when the checkbox is clicked public void onFavoriteClicked( ){ int drinkId = (Integer) getIntent().getExtras().get(EXTRA_DRINKID); CheckBox favorite = (CheckBox) findViewById(R.id.favorite); drinkValues = new drinkValues.put( ; , favorite.isChecked()); SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); try { SQLiteDatabase db = starbuzzDatabaseHelper. db.update( , ; , , new String[] {Integer.toString(drinkId)}); db.close(); } catch(SQLiteException e) { Toast toast = Toast.makeText(this, "Database unavailable", Toast.LENGTH_SHORT); } } } toast.show(); "FAVORITE" "DRINK" ContentValues() getWritableDatabase() drinkValues View view You won’t need to use all the magnets. "_id = ?" ase() getReadableDatab ContentValues favorite you are here 4 699 magnets solution Code Magnets Solution In our code for DrinkActivity we want to update the FAVORITE column in the database with the value of the favorite checkbox. Can you construct the onFavoriteClicked() method so that it will do that? public class DrinkActivity extends Activity { ... //Update the database when the checkbox is clicked View view public void onFavoriteClicked( ){ int drinkId = (Integer) getIntent().getExtras().get(EXTRA_DRINKID); CheckBox favorite = (CheckBox) findViewById(R.id.favorite); ContentValues drinkValues.put( drinkValues = new "FAVORITE" ContentValues() ; , favorite.isChecked()); SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); try { SQLiteDatabase db = starbuzzDatabaseHelper. db.update( "DRINK" db.close(); drinkValues , "_id = ?" , getWritableDatabase() ; We need read/write access to the database to update it. , new String[] {Integer.toString(drinkId)}); } catch(SQLiteException e) { Toast toast = Toast.makeText(this, "Database unavailable", Toast.LENGTH_SHORT); } } } toast.show(); You didn’t need to use these magnets. ase() getReadableDatab 700 Chapter 17 favorite cursors and asynctasks Update layout Show favorite Update favorite The full DrinkActivity.java code We’ve now done everything we need to change DrinkActivity so that it reflects the contents of the FAVORITE column in the favorite checkbox. It then updates the value of the column in the database if the user changes the value of the checkbox. Here’s the full code for DrinkActivity.java, so update your version of the code so that it reflects ours (our changes are in bold): package com.hfad.starbuzz; import android.app.Activity; Starbuzz import android.os.Bundle; app/src/main import android.widget.ImageView; import android.widget.TextView; java import android.widget.Toast; com.hfad.starbuzz import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; DrinkActivity.java import android.database.sqlite.SQLiteOpenHelper; import android.view.View; import android.widget.CheckBox; import android.content.ContentValues; We’re using these extra classes, so you need to import them. public class DrinkActivity extends Activity { public static final String EXTRA_DRINKID = "drinkId"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_drink); //Get the drink from the intent int drinkId = (Integer) getIntent().getExtras().get(EXTRA_DRINKID); The code continues on the next page. you are here 4 701 code, continued Update layout Show favorite Update favorite DrinkActivity.java (continued) //Create a cursor SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); try { SQLiteDatabase db = starbuzzDatabaseHelper.getReadableDatabase(); Cursor cursor = db.query("DRINK", new String[]{"NAME", "DESCRIPTION", "IMAGE_RESOURCE_ID", "FAVORITE"}, "_id = ?", Add the FAVORITE column to the cursor. new String[]{Integer.toString(drinkId)}, null, null, null); //Move to the first record in the Cursor Starbuzz if (cursor.moveToFirst()) { //Get the drink details from the cursor app/src/main String nameText = cursor.getString(0); String descriptionText = cursor.getString(1); java int photoId = cursor.getInt(2); boolean isFavorite = (cursor.getInt(3) == 1); //Populate the drink name com.hfad.starbuzz If the FAVORITE column has a value of 1, this indicates a true value. DrinkActivity.java TextView name = (TextView) findViewById(R.id.name); name.setText(nameText); //Populate the drink description TextView description = (TextView) findViewById(R.id.description); description.setText(descriptionText); //Populate the drink image ImageView photo = (ImageView) findViewById(R.id.photo); photo.setImageResource(photoId); photo.setContentDescription(nameText); //Populate the favorite checkbox If the drink is a favorite, put a checkmark in the favorite checkbox. CheckBox favorite = (CheckBox)findViewById(R.id.favorite); } favorite.setChecked(isFavorite); 702 Chapter 17 The code continues on the next page. cursors and asynctasks Update layout Show favorite Update favorite DrinkActivity.java (continued) cursor.close(); db.close(); } catch (SQLiteException e) { Toast toast = Toast.makeText(this, Starbuzz app/src/main "Database unavailable", Toast.LENGTH_SHORT); } } java toast.show(); com.hfad.starbuzz DrinkActivity.java //Update the database when the checkbox is clicked public void onFavoriteClicked(View view){ int drinkId = (Integer) getIntent().getExtras().get(EXTRA_DRINKID); //Get the value of the checkbox CheckBox favorite = (CheckBox) findViewById(R.id.favorite); ContentValues drinkValues = new ContentValues(); drinkValues.put("FAVORITE", favorite.isChecked()); Add the value of the favorite checkbox to the drinkValues ContentValues object. //Get a reference to the database and update the FAVORITE column SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); try { SQLiteDatabase db = starbuzzDatabaseHelper.getWritableDatabase(); db.update("DRINK", drinkValues, "_id = ?", new String[] {Integer.toString(drinkId)}); Update the drink’s FAVORITE column in the database to the value of the checkbox. db.close(); } catch(SQLiteException e) { Toast toast = Toast.makeText(this, "Database unavailable", Toast.LENGTH_SHORT); } } } toast.show(); Display a message if there’s a problem with the database. Let’s check what happens when we run the app. you are here 4 703 test drive Test drive the app When we run the app and navigate to a drink, the new favorite checkbox is displayed (unchecked): Here’s the new checkbox we added with its label. When we click on the checkbox, a checkmark appears to indicate that the drink is one of our favorites: When we click on the checkbox, a checkmark appears, and the value gets written to the database. When we close the app and navigate back to the drink, the checkmark remains. The value of the checkbox has been written to the database. That’s everything we need to display the value of the FAVORITE column from the database, and update the database with any changes to it. 704 Chapter 17 Update layout Show favorite Update favorite cursors and asynctasks Display favorites in TopLevelActivity The next thing we need to do is display the user’s favorite drinks in TopLevelActivity. Here are the steps we’ll go through to do this: 1 Add a list view and text view to TopLevelActivity’s layout. 2 Populate the list view and get it to respond to clicks. We’ll create a new cursor that retrieves the user’s favorite drinks from the database, and attach it to the list view using a cursor adapter. We’ll then create an onItemClickListener so that we can get TopLevelActivity to start DrinkActivity when the user clicks on one of the drinks. 3 Refresh the list view data when we choose a new favorite drink. If we choose a new favorite drink in DrinkActivity, we want it to be displayed in TopLevelActivity’s list view when we navigate back to it. Applying all of these changes will enable us to display the user’s favorite drinks in TopLevelActivity. The favorites list view will get its data from the database using a cursor. Cursor Starbuzz database We’ll go through these steps over the next few pages. When you click on a drink in the favorites list view, DrinkActivity will start, displaying details of the drink. you are here 4 705 display favorites Display the favorite drinks in activity_top_level.xml As we said on the previous page, we’re going to add a list view to activity_top_level.xml, which we’ll use to display a list of the user’s favorite drinks. We’ll also add a text view to display a heading for the list. Starbuzz app/src/main First, add the following String resource to strings.xml (we’ll use this for the text view’s text): res activity_drink.xml When the checkbox is clicked, Your favorite drinks: res values Next, we’ll add the new text view and list view to the layout. Here’s our code for activity_top_level.xml; update your version of the code to match our changes:strings.xml Those are all the changes we need to make to activity_top_level.xml. Next, we’ll update TopLevelActivity.java. 706 Chapter 17 The list_favorites ListView will display the user’s favorite drinks. activity_ top_level.xml cursors and asynctasks Refactor TopLevelActivity.java Update layout Populate list view Refresh data Before we write any code for our new list view, we’re going to refactor our existing TopLevelActivity code. This will make the code a lot easier to read later on. We’ll move the code relating to the options list view into a new method called setupOptionsListView(). We’ll then call this method from the onCreate() method. Here’s our code for TopLevelActivity.java (update your version of the code to reflect our changes). package com.hfad.starbuzz; ... public class TopLevelActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_top_level); setupOptionsListView(); Call the new setupOptionsListView() } Starbuzz app/src/main java method. com.hfad.starbuzz TopLevel Activity.java private void setupOptionsListView() { //Create an OnItemClickListener AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener(){ All of this public void onItemClick(AdapterView> listView, code was View itemView, in the int position, onCreate() long id) { method. if (position == 0) { We're putting Intent intent = new Intent(TopLevelActivity.this, it in a new method to DrinkCategoryActivity.class); make the startActivity(intent); If the Drink option code tidier. } in the list_options list } view is clicked, start }; DrinkCategoryActivity. //Add the listener to the list view ListView listView = (ListView) findViewById(R.id.list_options); listView.setOnItemClickListener(itemClickListener); } } you are here 4 707 TopLevelActivity changes Update layout Populate list view Refresh data What changes are needed for TopLevelActivity.java We need to display the user’s favorite drinks in the list_ favorites list view we added to the layout, and get it to respond to clicks. To do this, we need to do the following: 1 Populate the list_favorites list view using a cursor. The cursor will return all drinks where the FAVORITE column has been set to 1—all drinks that the user has flagged as being a favorite. Just as we did in our code for DrinkCategoryActivity, we can connect the cursor to the list view using a cursor adapter. Latte Cappuccino ListView Filter 2 CursorAdapter Cursor Create an onItemClickListener so that the list_favorites list view can respond to clicks. If the user clicks on one of their favorite drinks, we can create an intent that starts DrinkActivity, passing it the ID of the drink that was clicked. This will show the user details of the drink they’ve just chosen. Intent drinkId TopLevelActivity You’ve already seen all the code that’s needed to do this. In fact, it’s almost identical to the code we wrote in earlier chapters to control the list of drinks in DrinkCategoryActivity. The only difference is that this time, we only want to display drinks with a value of 1 in the FAVORITE column. We’ve decided to put the code that controls a list view in a new method called setupFavoritesListView(). We’ll show you this method on the next page before adding it to TopLevelActivity.java. 708 Chapter 17 DrinkActivity Database cursors and asynctasks Ready Bake Code Update layout Populate list view Refresh data The setupFavoritesListView() method populates the list_favorites list view with the names of the user’s favorite drinks. Make sure you understand the code below before turning the page. Get the list_favorites list view. private void setupFavoritesListView() { //Populate the list_favorites ListView from a cursor ListView listFavorites = (ListView) findViewById(R.id.list_favorites); try{ SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); db = starbuzzDatabaseHelper.getReadableDatabase(); favoritesCursor = db.query("DRINK", Create a cursor that gets the values of the _id and NAME columns where FAVORITE=1. new String[] { "_id", "NAME"}, "FAVORITE = 1", null, null, null, null); Get the names of the user’s favorite drinks. Starbuzz app/src/main java CursorAdapter favoriteAdapter = Create a new cursor adapter. new SimpleCursorAdapter(TopLevelActivity.this, Use the cursor in the cursor adapter. com.hfad.starbuzz android.R.layout.simple_list_item_1, favoritesCursor, new String[]{"NAME"}, Display the names of the drinks in the list view. TopLevel Activity.java new int[]{android.R.id.text1}, 0); listFavorites.setAdapter(favoriteAdapter); } catch(SQLiteException e) { Toast toast = Toast.makeText(this, "Database unavailable", Toast.LENGTH_SHORT); } toast.show(); Display a message if there’s a problem with the database. //Navigate to DrinkActivity if a drink is clicked listFavorites.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView> listView, View v, int position, long id) { This will get called if an item in the list view is } clicked. } }); Intent intent = new Intent(TopLevelActivity.this, DrinkActivity.class); intent.putExtra(DrinkActivity.EXTRA_DRINKID, (int)id); startActivity(intent); If the user clicks on one of the items in the list_favorites list view, create an intent to start DrinkActivity, including the ID of the drink as extra information. you are here 4 709 TopLevelActivity code Update layout Populate list view Refresh data The new TopLevelActivity.java code We’ve updated TopLevelActivity to populate the list_favorites list view and make it respond to clicks. Update your version of TopLevelActivity.java to match ours (there’s a lot of new code, so go through it carefully and take your time): package com.hfad.starbuzz; Starbuzz import android.app.Activity; import android.os.Bundle; app/src/main import android.content.Intent; java import android.widget.AdapterView; import android.widget.ListView; com.hfad.starbuzz import android.view.View; import android.database.Cursor; TopLevel Activity.java import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteDatabase; import android.widget.SimpleCursorAdapter; import android.widget.CursorAdapter; import android.widget.Toast; We’re using all these extra classes, so we need to import them. public class TopLevelActivity extends Activity { private SQLiteDatabase db; private Cursor favoritesCursor; We're adding the database and cursor as private variables so that we can access them in the setUpFavoritesListView() and onDestroy() methods. @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_top_level); setupOptionsListView(); } setupFavoritesListView(); Call the setupFavoritesListView() method from the onCreate() method. The code continues on the next page. 710 Chapter 17 cursors and asynctasks Update layout Populate list view Refresh data The TopLevelActivity.java code (continued) We don't need to change this method. private void setupOptionsListView() { //Create an OnItemClickListener AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener(){ public void onItemClick(AdapterView> listView, View itemView, int position, long id) { if (position == 0) { Intent intent = new Intent(TopLevelActivity.this, }; } } startActivity(intent); DrinkCategoryActivity.class); Starbuzz app/src/main //Add the listener to the list view java ListView listView = (ListView) findViewById(R.id.list_options); } listView.setOnItemClickListener(itemClickListener); private void setupFavoritesListView() { com.hfad.starbuzz This is the method we created to populate the list_favorites list view and make it respond to clicks. TopLevel Activity.java //Populate the list_favorites ListView from a cursor ListView listFavorites = (ListView) findViewById(R.id.list_favorites); try{ SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); db = starbuzzDatabaseHelper.getReadableDatabase(); favoritesCursor = db.query("DRINK", The list_favorites list view will use this cursor for its data. Get a reference to the database. new String[] { "_id", "NAME"}, "FAVORITE = 1", null, null, null, null); The code continues on the next page. you are here 4 711 code, continued Update layout Populate list view Refresh data The TopLevelActivity.java code (continued) CursorAdapter favoriteAdapter = new SimpleCursorAdapter(TopLevelActivity.this, Use the cursor in a cursor adapter. android.R.layout.simple_list_item_1, favoritesCursor, new String[]{"NAME"}, new int[]{android.R.id.text1}, 0); listFavorites.setAdapter(favoriteAdapter); } catch(SQLiteException e) { Set the curser adapter to the list view. Toast toast = Toast.makeText(this, "Database unavailable", Toast.LENGTH_SHORT); } toast.show(); Get the list_favorites list view to respond to clicks. //Navigate to DrinkActivity if a drink is clicked listFavorites.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView> listView, View v, int position, long id) { Intent intent = new Intent(TopLevelActivity.this, DrinkActivity.class); intent.putExtra(DrinkActivity.EXTRA_DRINKID, (int)id); } }); } startActivity(intent); Start DrinkActivity, passing it the ID of the drink that was clicked on. Starbuzz //Close the cursor and database in the onDestroy() method @Override public void onDestroy(){ super.onDestroy(); favoritesCursor.close(); } } db.close(); The onDestroy() method gets called just before the activity is destroyed. We'll close the cursor and database in this method, as we no longer need them if the activity's being destroyed. The above code populates the list_favorites list view with the user’s favorite drinks. When the user clicks on one of these drinks, an intent starts DrinkActivity and passes it the ID of the drink. Let’s take the app for a test drive and see what happens. 712 Chapter 17 app/src/main java com.hfad.starbuzz TopLevel Activity.java cursors and asynctasks Test drive the app Update layout Populate list view Refresh data When you run the app, the new text view and list_favorites list view are displayed in TopLevelActivity. If you’ve marked a drink as being a favorite, it appears in the list view. If you click on that drink, DrinkActivity starts and details of the drink are displayed. Here’s the new list_favorites list view we created. It displays a latte, as we marked this as a favorite drink earlier in the chapter. When we click on the drink, its details are displayed. But there’s a problem. If you select a new drink as being a favorite, when you go back to TopLevelActivity the list_ favorites list view doesn’t include the new drink. The new drink is only included in the list view if you rotate the device. We’ve marked a cappuccino as a favorite, but it’s not appearing in the list view. Why do you think the new drink we chose as a favorite doesn’t appear in the list view until we rotate the device? Give this some thought before turning the page. When we rotate the device, the cappuccino appears in the list view. Why’s that? you are here 4 713 out-of-date data Update layout Populate list view Refresh data Cursors don’t automatically refresh If the user chooses a new favorite drink by navigating through the app to DrinkActivity, the new favorite drink isn’t automatically displayed in the list_favorites list view in TopLevelActivity. This is because cursors retrieve data when the cursor gets created. In our case, the cursor is created in the activity onCreate() method, so it gets its data when the activity is created. When the user navigates through the other activities, TopLevelActivity is stopped. It’s not destroyed and recreated, so neither is the cursor. When you start a second activity, the second activity is stacked on top of the first. The first activity isn’t destroyed. Instead, it’s paused and then stopped, as it loses the focus and stops being visible to the user. Cursors don’t automatically keep track of whether the underlying data in the database has changed. So if the underlying data changes after the cursor’s been created, the cursor doesn’t get updated: it still contains the original records, and none of the changes. That means that if the user marks a new drink as being a favorite after the cursor is created, the cursor will be out of date. _id NAME DESCRIPTION IMAGE_RESOURCE_ID 1 “Latte” 2 “Cappuccino” and 654334453 _id "Espresso, NAME hot milk DESCRIPTION 3 “Filter” "Espresso and steamed milk" 54543543 If you update the data in the database... ...the cursor won’t see the new data if FAVORITE the cursor’s already 1 been created. 1 0 IMAGE_RESOURCE_ID FAVORITE steamed-milk foam" 0 “Latte” "Espresso and steamed milk" 54543543 2 "Our best drip coffee" 44324234 “Cappuccino” "Espresso, hot milk and 3 “Filter” steamed-milk foam" "Our best drip coffee" So how do we get around this? 714 Chapter 17 654334453 0 0 44324234 0 cursors and asynctasks Update layout Populate list view Refresh data Change the cursor with changeCursor() The solution is to change the underlying cursor used by the list_ favorites list view to an updated version. To do this, you define a new version of the cursor, get a reference to the list view’s cursor adapter, and then call the cursor adapter’s changeCursor() method to change the cursor. Here are the details: 1. Define the cursor You define the cursor in exactly the same way as you did before. In our case, we want the query to return the user’s favorite drinks, so we use: Cursor newCursor = db.query("DRINK", This is the same query that we had before. new String[] { "_id", "NAME"}, "FAVORITE = 1", null, null, null, null); 2. Get a reference to the cursor adapter You get a reference to the list view’s cursor adapter by calling the list view’s getAdapter() method. This method returns an object of type Adapter. As our list view is using a cursor adapter, we can cast the adapter to a CursorAdapter: ListView listFavorites = (ListView) findViewById(R.id.list_favorites); CursorAdapter adapter = (CursorAdapter) listFavorites.getAdapter(); 3. Change the cursor using changeCursor() You change the cursor used by the cursor adapter by calling its changeCursor() method. This method takes one parameter, the new cursor: adapter.changeCursor(newCursor); You get the ListView’s adapter using the getAdapter() method. Change the cursor used by the cursor adapter to the new one. The changeCursor() method replaces the cursor adapter’s current cursor with the new one. It then closes the old cursor, so you don’t need to do this yourself. We’re going to change the cursor used by the list_favorites list view in TopLevelActivity’s onRestart() method. This means that the data in the list view will get refreshed when the user returns to TopLevelActivity. Any new favorite drinks the user has chosen will be displayed, and any drinks that are no longer flagged as favorites will be removed from the list. We’ll show you the full code for TopLevelActivity.java over the next few pages. you are here 4 715 code, continued Update layout Populate list view Refresh data The revised TopLevelActivity.java code Here’s the full TopLevelActivity.java code; update your code to reflect our changes (in bold). package com.hfad.starbuzz; Starbuzz import android.app.Activity; import android.os.Bundle; app/src/main import android.content.Intent; import android.widget.AdapterView; import android.widget.ListView; import android.view.View; import android.database.Cursor; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteException; java com.hfad.starbuzz TopLevel Activity.java import android.database.sqlite.SQLiteDatabase; import android.widget.SimpleCursorAdapter; import android.widget.CursorAdapter; import android.widget.Toast; public class TopLevelActivity extends Activity { You don’t need to change any of the code on this page. private SQLiteDatabase db; private Cursor favoritesCursor; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_top_level); setupOptionsListView(); } setupFavoritesListView(); The code continues on the next page. 716 Chapter 17 cursors and asynctasks Update layout Populate list view Refresh data The TopLevelActivity.java code (continued) private void setupOptionsListView() { //Create an OnItemClickListener AdapterView.OnItemClickListener itemClickListener = new AdapterView.OnItemClickListener(){ public void onItemClick(AdapterView> listView, View itemView, You don’t need to int position, change any of the code on this page. long id) { if (position == 0) { Intent intent = new Intent(TopLevelActivity.this, DrinkCategoryActivity.class); startActivity(intent); } Starbuzz } }; app/src/main } //Add the listener to the list view ListView listView = (ListView) findViewById(R.id.list_options); listView.setOnItemClickListener(itemClickListener); java com.hfad.starbuzz TopLevel private void setupFavoritesListView() { Activity.java //Populate the list_favorites ListView from a cursor ListView listFavorites = (ListView) findViewById(R.id.list_favorites); try{ SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); db = starbuzzDatabaseHelper.getReadableDatabase(); favoritesCursor = db.query("DRINK", new String[] { "_id", "NAME"}, "FAVORITE = 1", null, null, null, null); CursorAdapter favoriteAdapter = new SimpleCursorAdapter(TopLevelActivity.this, android.R.layout.simple_list_item_1, favoritesCursor, new String[]{"NAME"}, new int[]{android.R.id.text1}, 0); listFavorites.setAdapter(favoriteAdapter); The code continues on the next page. you are here 4 717 more code The TopLevelActivity.java code (continued) Update layout Populate list view Refresh data } catch(SQLiteException e) { Toast toast = Toast.makeText(this, "Database unavailable", Toast.LENGTH_SHORT); toast.show(); } } //Navigate to DrinkActivity if a drink is clicked listFavorites.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView> listView, View v, int position, long id) { Intent intent = new Intent(TopLevelActivity.this, DrinkActivity.class); intent.putExtra(DrinkActivity.EXTRA_DRINKID, (int)id); startActivity(intent); } Starbuzz }); called Add the onRestart() method. This will getctiv ity. evelA TopL to when the user navigates back app/src/main @Override java public void onRestart() { super.onRestart(); com.hfad.starbuzz Cursor newCursor = db.query("DRINK", new String[] { "_id", "NAME"}, Create a new version of the cursor. "FAVORITE = 1", TopLevel Activity.java null, null, null, null); ListView listFavorites = (ListView) findViewById(R.id.list_favorites); CursorAdapter adapter = (CursorAdapter) listFavorites.getAdapter(); adapter.changeCursor(newCursor); Switch the cursor being used by the list_favorites list view to the new cursor. favoritesCursor = newCursor; } value of favoritesCursor to the new cursor so Change the we can close it in the activity’s onDestroy() method. } //Close the cursor and database in the onDestroy() method @Override public void onDestroy(){ super.onDestroy(); favoritesCursor.close(); db.close(); } Let’s see what happens when we run the app now. 718 Chapter 17 cursors and asynctasks Update layout Populate list view Refresh data Test drive the app When we run the app, our favorite drinks are displayed in TopLevelActivity as before. When we click on one of the drinks, its details are displayed in DrinkActivity. If we uncheck the favorite checkbox for that drink and return to TopLevelActivity, the data in the list_favorites list view is refreshed and the drink is no longer displayed. The list_favorites ListView initially contains a latte and a cappuccino. When we click on a latte, its details are displayed. We then uncheck the checkbox to indicate the drink is no longer a favorite. I’ve been thinking... Using databases in my app clearly has a lot of advantages, but doesn’t opening and reading from the database slow the app down? When we return to TopLevelActivity, the latte is no longerlisted in the list_favorites list view. Databases are powerful, but they can be slow. That means that even though our app works, we need to keep an eye on performance... you are here 4 719 meanwhile... Databases can make your app go in sloooow-moooo.... Think about what your app has to do when it opens a database. It first needs to go searching for the database file. If the database file isn’t there, it needs to create a blank database. Then it needs to run all of the SQL commands to create tables inside the database and any initial data it needs. Finally, it needs to fire off some queries to get the data out of there. That all takes time. For a tiny database like the one used in the Starbuzz app, it’s not a lot of time. But as a database gets bigger and bigger, that time will increase and increase. Before you know it, your app will lose its mojo and will be slower than YouTube on Thanksgiving. There’s not a lot you can do about the speed of creating and reading from a database, but you can prevent it from slowing down your interface. Life is better when threads work together The big problem with accessing a slow database is that can make your app feel unresponsive. To understand why, you need to think about how threads work in Android. Since Lollipop, there are three kinds of threads you need to think about: ¥ The main event thread This is the real workhorse in Android. It listens for intents, it receives touch messages from the screen, and it calls all of the methods inside your activities. ¥ The render thread You don’t normally interact with this thread, but it reads a list of requests for screen updates and then calls the device’s low-level graphics hardware to repaint the screen and make your app look pretty. ¥ Any other threads that you create If you’re not careful, your app will do almost all of its work on the main event thread because this thread runs your event methods. If you just drop your database code into the onCreate() method (as we did in the Starbuzz app), then the main event thread will be busy talking to the database, instead of rushing off to look for any events from the screen or other apps. If your database code takes a long time, users will feel like they’re being ignored or wonder if the app has crashed. So the trick is to move your database code off the main event thread and run it in a custom thread in the background. We’ll go through how you do this using the DrinkActivity code we wrote earlier in the chapter. As a reminder, the code updates the FAVORITE column in the Starbuzz database when the user clicks on the favorite checkbox, and displays a message if the database is unavailable. 720 Chapter 17 cursors and asynctasks We’re going to run the DrinkActivity code to update the database in a background thread, but before we rush off and start hacking code, let’s think about what we need to do. The code that we have at the moment does three different things. Choose the type of thread you think each should run on. We’ve completed the first one to start you off. A Set up the interface. int drinkId = (Integer) getIntent().getExtras().get(EXTRA_DRINKID); CheckBox favorite = (CheckBox) findViewById(R.id.favorite); ContentValues drinkValues = new ContentValues(); drinkValues.put("FAVORITE", favorite.isChecked()); Main event thread A background thread This code must run on the main event thread, as it needs to access the activity's views. B Talk to the database. SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); SQLiteDatabase db = starbuzzDatabaseHelper.getWriteableDatabase(); db.update("DRINK",...); Main event thread C A background thread Update what’s displayed on the screen. Toast toast = Toast.makeText(...); toast.show(); Main event thread A background thread you are here 4 721 sharpen solution We’re going to run the DrinkActivity code to update the database in a background thread, but before we rush off and start hacking code, let’s think about what we need to do. The code that we have at the moment does three different things. Choose the type of thread you think each should run on. We’ve completed the first one to start you off. A Set up the interface. int drinkId = (Integer) getIntent().getExtras().get(EXTRA_DRINKID); CheckBox favorite = (CheckBox) findViewById(R.id.favorite); ContentValues drinkValues = new ContentValues(); drinkValues.put("FAVORITE", favorite.isChecked()); Main event thread B A background thread Talk to the database. SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); SQLiteDatabase db = starbuzzDatabaseHelper.getWriteableDatabase(); db.update("DRINK",...); Main event thread C A background thread Update what’s displayed on the screen. Toast toast = Toast.makeText(...); toast.show(); Main event thread 722 Chapter 17 We want to run the database code in the background. We must run the code to display a message on the screen on the main event thread; otherwise, we’ll get an exception. A background thread cursors and asynctasks What code goes on which thread? When you use databases in your app, it’s a good idea to run database code in a background thread, and update views with the database data in the main event thread. We’re going to work through the onFavoritesClicked() method in the DrinkActivity code so that you can see how to approach this sort of problem. Starbuzz app/src/main Here’s the code for the method (we’ve split it into sections, which we’ll describe below): //Update the database when the checkbox is clicked public void onFavoriteClicked(View view){ java com.hfad.starbuzz DrinkActivity.java 1 int drinkId = (Integer) getIntent().getExtras().get(EXTRA_DRINKID); CheckBox favorite = (CheckBox) findViewById(R.id.favorite); ContentValues drinkValues = new ContentValues(); drinkValues.put("FAVORITE", favorite.isChecked()); 2 SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); try { SQLiteDatabase db = starbuzzDatabaseHelper.getWritableDatabase(); db.update("DRINK", drinkValues, "_id = ?", new String[] {Integer.toString(drinkId)}); db.close(); } catch(SQLiteException e) { 3 } } Toast toast = Toast.makeText(this, "Database unavailable", Toast.LENGTH_SHORT); toast.show(); 1 Code that needs to be run before the database code The first few lines of code get the value of the favorite checkbox, and put it in the drinkValues ContentValues object. This code must be run before the database code. 2 Database code that needs to be run on a background thread This updates the DRINK table. 3 Code that needs to be run after the database code If the database is unavailable, we want to display a message to the user. This must run on the main event thread. We’re going to implement the code using an AsyncTask. What’s that, you ask? you are here 4 723 AsyncTask AsyncTask performs asynchronous tasks An AsyncTask lets you perform operations in the background. When they’ve finished running, it then allows you to update views in the main event thread. If the task is repetitive, you can even use it to publish the progress of the task while it’s running. You create an AsyncTask by extending the AsyncTask class, and implementing its doInBackground() method. The code in this method runs in a background thread, so it’s the perfect place for you to put database code. The AsyncTask class also has an onPreExecute() method that runs before doInBackground(), and an onPostExecute() method that runs afterward. There’s an onProgressUpdate() method if you need to publish task progress. You add your AsyncTask class as an inner class to the activity that needs to use it. Here’s what an AsyncTask looks like: private class MyAsyncTask extends AsyncTask The layout already contains the Starbuzz logo and list view. Starbuzz app/src/main res layout This method is optional. It runs before the to run in the background. You must implement this method. It contains the code you want to run in the background. doInBackground(Params... params) { protected void onPreExecute() { code you want //Code to run before executing the task } protected Result //Code that you want to run in a background thread } This method is optional. It lets you publish progress of the code running in the background. protected void onProgressUpdate(Progress... values) { //Code that you want to run to publish the progress of your task } This method is also optional. It runs after the code has finished running in the background. } protected void onPostExecute(Result result) { //Code that you want to run when the task is complete } AsyncTask is defined by three generic parameters: Params, Progress, and Results. Params is the type of object used to pass any task parameters to the doInBackground() method, Progress is the type of object used to indicate task progress, and Result is the type of the task result. You can set any of these to Void if you’re not going to use them. We’ll go through this over the next few pages by creating a new AsyncTask called UpdateDrinkTask we can use to update drinks in the background. Later on, we’ll add this to our DrinkActivity code as an inner class. 724 Chapter 17 cursors and asynctasks The onPreExecute() method We’ll start with the onPreExecute() method. This gets called before the background task begins, and it’s used to set up the task. It’s called on the main event thread, so it has access to views in the user interface. The onPreExecute() method takes no parameters, and has a void return type. onPreExecute() In our case, we’re going to use the onPreExecute() method to get the value of the favorite checkbox, and put it in the drinkValues ContentValues object. This is because we need access to the checkbox view in order to do this, and it must be done before any of our database code can be run. We’re using a separate attribute outside the method for the drinkValues ContentValues object so that other methods in the class can access the ContentValues object (we’ll look at these methods over the next few pages). Here’s the code: private class UpdateDrinkTask extends AsyncTask { private ContentValues drinkValues; protected void onPreExecute() { Before we run the database code, we need. to get the value of the favorite checkbox CheckBox favorite = (CheckBox)findViewById(R.id.favorite); drinkValues = new ContentValues(); } drinkValues.put("FAVORITE", favorite.isChecked()); ... } Next, we’ll look at the doInBackground() method. you are here 4 725 doInBackground() The doInBackground() method The doInBackground() method runs in the background immediately after onPreExecute(). You define what type of parameters the task should receive, and what the return type should be. We’re going to use the doInBackground() method for our database code so that it runs in a background thread. We’ll pass it the ID of the drink we need to update; because the drink ID is an int value, we need to specify that the doInBackground() method receives Integer objects. We’ll use a Boolean return value so we can tell whether the code ran successfully: onPreExecute() doInBackground() private class UpdateDrinkTask extends AsyncTask { You change this to You change this to Integer to Boolean to match the the of er paramet the match return type of the method. () kground doInBac doInBackground() method. ... This code runs in a background thread. This is an array of Integers, protected Boolean doInBackground(Integer... drinks) { but we’ll just include one item, int drinkId = drinks[0]; the drink ID. private ContentValues drinkValues; SQLiteOpenHelper starbuzzDatabaseHelper = try { new StarbuzzDatabaseHelper(DrinkActivity.this); SQLiteDatabase db = starbuzzDatabaseHelper.getWritableDatabase(); db.update("DRINK", drinkValues, db.close(); return true; "_id = ?", new String[] {Integer.toString(drinkId)}); } catch(SQLiteException e) { } } } return false; The update() method uses the drinkValues object that the onPreExecute() method created. ... Next, we’ll look at the onProgressUpdate() method. 726 Chapter 17 cursors and asynctasks The onProgressUpdate() method onPreExecute() The onProgressUpdate() method is called on the main event thread, so it has access to views in the user interface. You can use this method to display progress to the user by updating views on the screen. You define what type of parameters the method should have. The onProgressUpdate() method runs if a call to publishProgress() is made by the doInBackground() method like this: doInBackground() protected Boolean doInBackground(Integer... count) { for (int i = 0; i < count; i++) { } } publishProgress(i); This calls the onProgressUpdate() method, passing in a value of i. onProgressUpdate() protected void onProgressUpdate(Integer... progress) { } setProgress(progress[0]); In our app, we’re not publishing the progress of our task, so we don’t need to implement this method. We’ll indicate that we’re not using any objects for task progress by changing the signature of UpdateDrinkTask: We’re not using the onProgressUpdate() method, so this is Void. private class UpdateDrinkTask extends AsyncTask { ... } Finally, we’ll look at the onPostExecute() method. you are here 4 727 onPostExecute() The onPostExecute() method onPreExecute() The onPostExecute() method is called after the background task has finished. It’s called on the main event thread, so it has access to views in the user interface. You can use this method to present the results of the task to the user. The onPostExecute() method gets passed the results of the doInBackground() method, so it must take parameters that match the doInBackground() return type. We’re going to use the onPostExecute() method to check whether the database code in the doInBackground() method ran successfully. If it didn’t, we’ll display a message to the user. We’re doing this in the onPostExecute() method, as this method can update the user interface; the doInBackground() method runs in a background thread, so it can’t update views. Here’s the code: doInBackground() onProgressUpdate() onPostExecute() private class UpdateDrinkTask extends AsyncTask { ... protected void This is set to Boolean, as our doInBackground() method returns a Boolean. Pass the toast the onPostExecute(Boolean success) { DrinkActivity context. if (!success) { Toast toast = Toast.makeText(DrinkActivity.this, } } } toast.show(); "Database unavailable", Toast.LENGTH_SHORT); Now that we’ve written the code for our AsyncTask methods, let’s revisit the AsyncTask class parameters. 728 Chapter 17 cursors and asynctasks The AsyncTask class parameters When we first introduced the AsyncTask class, we said it was defined by three generic parameters: Params, Progress, and Results. You specify what these are by looking at the type of parameters used by your doInBackground(), onProgressUpdate(), and onPostExecute() methods. Params is the type of the doInBackground() parameters, Progress is the type of the onProgressUpdate() parameters, and Result is the type of the onPostExecute() parameters: private class MyAsyncTask extends AsyncTask protected void onPreExecute() { //Code to run before executing the task } protected Result doInBackground(Params... params) { //Code that you want to run in a background thread } protected void onProgressUpdate(Progress... values) { //Code that you want to run to publish the progress of your task } } protected void onPostExecute(Result result) { //Code that you want to run when the task is complete } In our example, doInBackground() takes Integer parameters, onPostExecute() takes a Boolean parameter, and we’re not using the onProgressUpdate() method. This means that in our example, Params is Integer, Progress is Void, and Result is Boolean: This is Void because we didn’t implement the onProgressUpdate() method. private class UpdateDrinkTask extends AsyncTask { ... protected Boolean doInBackground(Integer... drinks) { ... } } protected void onPostExecute(Boolean... success) { ... } We’ll show you the full UpdateDrinkTask class on the next page. you are here 4 729 inner class code The full UpdateDrinkTask class Here’s the full code for the UpdateDrinkTask class. It needs to be added to DrinkActivity as an inner class, but we suggest you wait to do that until we show you how to execute it and show you the full DrinkActivity.java code listing. private class UpdateDrinkTask extends AsyncTask { private ContentValues drinkValues; protected void onPreExecute() { We’ve defined drinkValues as a private variable, as it’s used by the onExecute() and doInBackground() methods. CheckBox favorite = (CheckBox) findViewById(R.id.favorite); drinkValues = new ContentValues(); } drinkValues.put("FAVORITE", favorite.isChecked()); Our database code goes in the doInBackground() method. protected Boolean doInBackground(Integer... drinks) { Before we run the database code, we need to put the value of the favorite checkbox in the drinkValues ContentValues object. int drinkId = drinks[0]; SQLiteOpenHelper starbuzzDatabaseHelper = try { new StarbuzzDatabaseHelper(DrinkActivity.this); SQLiteDatabase db = starbuzzDatabaseHelper.getWritableDatabase(); db.update("DRINK", drinkValues, "_id = ?", new String[] {Integer.toString(drinkId)}); db.close(); return true; } catch(SQLiteException e) { } } return false; After the database code has run in the background, check whether it ran successfully. If it didn’t, display a message. protected void onPostExecute(Boolean success) { if (!success) { Toast toast = Toast.makeText(DrinkActivity.this, "Database unavailable", Toast.LENGTH_SHORT); } } } 730 Chapter 17 toast.show(); We have to put the code to display a mess the onPostExecute() method, as it needs toagebein on the main event thread to update the screen.run cursors and asynctasks Execute the AsyncTask... You run the AsyncTask by calling the AsyncTask execute() method and passing it any parameters required by the doInBackground() method. As an example, we want to pass the drink the user chose to our AsyncTask’s doInBackground() method, so we call it using: int drinkId = (Integer) getIntent().getExtras().get(EXTRA_DRINKID); new UpdateDrinkTask().execute(drinkId); Execute the AsyncTask and pass it the drink ID. The type of parameter you pass with the execute() method must match the type of parameter expected by the AsyncTask doInBackground() method. We’re passing an integer value (the drink ID), which matches the type of parameter expected by our doInBackground() method: protected Boolean doInBackground(Integer... drinks) { } ... ...in DrinkActivity’s onFavoritesClicked() method Our UpdateDrinkTask class (the AsyncTask we created) needs to update the FAVORITE column in the Starbuzz database whenever the favorite checkbox in DrinkActivity is clicked. We therefore need to execute it in DrinkActivity’s onFavoritesClicked() method. Here’s what the new version of the method looks like: //Update the database when the checkbox is clicked public void onFavoriteClicked(View view){ int drinkId = (Integer)getIntent().getExtras().get(EXTRA_DRINKID); } new UpdateDrinkTask().execute(drinkId); We’ll show you the new DrinkActivity.java code over the next few pages. The new version of the onFavoritesClicked() method no longer contains code to update the FAVORITE column. Instead, it calls the AsyncTask, which performs the update in the background. you are here 4 731 DrinkActivity code The full DrinkActivity.java code Here’s the complete code for DrinkActivity.java; update your version of the code to reflect our changes: package com.hfad.starbuzz; import import import import import import import import import import import import import Starbuzz android.app.Activity; android.os.Bundle; app/src/main android.widget.ImageView; android.widget.TextView; java android.widget.Toast; android.database.Cursor; com.hfad.starbuzz android.database.sqlite.SQLiteDatabase; android.database.sqlite.SQLiteException; DrinkActivity.java android.database.sqlite.SQLiteOpenHelper; android.view.View; android.widget.CheckBox; android.content.ContentValues; We’re using the AsyncTask class, so we need to import it. android.os.AsyncTask; public class DrinkActivity extends Activity { public static final String EXTRA_DRINKID = "drinkId"; We don’t need to change the onCreate() method, @Override we’re just showing it for completeness. protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_drink); //Get the drink from the intent int drinkId = (Integer) getIntent().getExtras().get(EXTRA_DRINKID); //Create a cursor SQLiteOpenHelper starbuzzDatabaseHelper = new StarbuzzDatabaseHelper(this); try { SQLiteDatabase db = starbuzzDatabaseHelper.getReadableDatabase(); Cursor cursor = db.query("DRINK", new String[]{"NAME", "DESCRIPTION", "IMAGE_RESOURCE_ID", "FAVORITE"}, "_id = ?", The code continues new String[]{Integer.toString(drinkId)}, on the next page. null, null, null); 732 Chapter 17 cursors and asynctasks The full DrinkActivity.java code (continued) //Move to the first record in the Cursor if (cursor.moveToFirst()) { //Get the drink details from the cursor Starbuzz String nameText = cursor.getString(0); app/src/main String descriptionText = cursor.getString(1); java int photoId = cursor.getInt(2); boolean isFavorite = (cursor.getInt(3) == 1); com.hfad.starbuzz //Populate the drink name DrinkActivity.java TextView name = (TextView) findViewById(R.id.name); name.setText(nameText); //Populate the drink description TextView description = (TextView) findViewById(R.id.description); description.setText(descriptionText); //Populate the drink image ImageView photo = (ImageView) findViewById(R.id.photo); photo.setImageResource(photoId); photo.setContentDescription(nameText); //Populate the favorite checkbox CheckBox favorite = (CheckBox)findViewById(R.id.favorite); } favorite.setChecked(isFavorite); cursor.close(); db.close(); } catch (SQLiteException e) { None of the code on this page needs to change. Toast toast = Toast.makeText(this, "Database unavailable", Toast.LENGTH_SHORT); } } toast.show(); The code continues on the next page. you are here 4 733 code, continued The full DrinkActivity.java code (continued) //Update the database when the checkbox is clicked public void onFavoriteClicked(View view){ int drinkId = (Integer) getIntent().getExtras().get(EXTRA_DRINKID); //Get the value of the checkbox Starbuzz CheckBox favorite = (CheckBox) findViewById(R.id.favorite); ContentValues drinkValues = new ContentValues(); app/src/main drinkValues.put("FAVORITE", favorite.isChecked()); java //Get a reference to the database and update the FAVORITE column SQLiteOpenHelper starbuzzDatabaseHelper = try { com.hfad.starbuzz new StarbuzzDatabaseHelper(this); DrinkActivity.java SQLiteDatabase db = starbuzzDatabaseHelper.getWritableDatabase(); db.update("DRINK", drinkValues, "_id = ?", new String[] {Integer.toString(drinkId)}); Delete all these lines of code, as we’re now using an AsyncTask for these actions. db.close(); } catch(SQLiteException e) { Toast toast = Toast.makeText(this, "Database unavailable", Toast.LENGTH_SHORT); } } toast.show(); new UpdateDrinkTask().execute(drinkId); Execute the task. The code continues on the next page. 734 Chapter 17 cursors and asynctasks The full DrinkActivity.java code (continued) Add the AsyncTask to the activity as an inner class //Inner class to update the drink. private class UpdateDrinkTask extends AsyncTask { private ContentValues drinkValues; Before the database code runs, put the value of the checkbox in the drinkValues ContentValues object. protected void onPreExecute() { CheckBox favorite = (CheckBox) findViewById(R.id.favorite); drinkValues = new ContentValues(); } drinkValues.put("FAVORITE", favorite.isChecked()); Run the database code in a background thread. protected Boolean doInBackground(Integer... drinks) { int drinkId = drinks[0]; SQLiteOpenHelper starbuzzDatabaseHelper = try { new StarbuzzDatabaseHelper(DrinkActivity.this); SQLiteDatabase db = starbuzzDatabaseHelper.getWritableDatabase(); db.update("DRINK", drinkValues, "_id = ?", new String[] {Integer.toString(drinkId)}); db.close(); return true; } catch(SQLiteException e) { } } Update the value of the FAVORITE column. Starbuzz return false; app/src/main java protected void onPostExecute(Boolean success) { if (!success) { Toast toast = Toast.makeText(DrinkActivity.this, com.hfad.starbuzz DrinkActivity.java "Database unavailable", Toast.LENGTH_SHORT); } } } } . toast.show(); If the database code didn’t run correctly, display a message to the user. That’s everything you need in order to create an AsyncTask. Let’s check what happens when we run the app. you are here 4 735 test drive Test drive the app When we run the app and navigate to a drink, we can indicate that the drink is a favorite drink by checking the “favorite” checkbox. Clicking on the checkbox still updates the FAVORITE column in the database with its value, but this time the code is running on a background thread. In an ideal world, all of your database code should run in the background. We’re not going to change our other Starbuzz activities to do this, but why not make this change yourself? Q: I’ve written code before that just ran the database code and it was fine. Do I really need to run it in the background? A: For really small databases, like the one in the Starbuzz app, you probably won’t notice the time it takes to access the database. But that’s just because the database is small. If you use a larger database, or if you run an app on a slower device, the time it takes to access the database will be significant. So yes, you should always run database code in the background. 736 Chapter 17 Our app still writes data to the database, but this time it does it on a background thread. Q: Remind me—why is it bad to update a view from the background thread? A: The short answer is that it will throw an exception if you try. The longer answer is that multithreaded user interfaces are hugely buggy. Android avoided the problem by simply banning them. Q: Which part of the database code is slowest: opening the database, or reading data from it? A: There’s no general way of knowing. If your database has a complex data structure, opening the database for the first time will take a long time because it will need to create all the tables. If you’re running a complex query, that might take a very long time. In general, play it safe and run everything in the background. Q: If it take a few seconds to read data from the database, what will the user see? A: Q: The user will see blank views until the database code sets the values. Why have we put the database code for just one activity in an AsyncTask? A:AsyncTask We wanted to show you how to use s in one activity as an example. In the real world, you should do this for the database code in all your activities. cursors and asynctasks Your Android Toolbox You’ve got Chapter 17 under your belt and now you’ve added writing to SQLite databases to your toolbox. The CursorAdapter changeCursor() method replaces the cursor currently used by a cursor adapter with a new cursor that you provide. It then closes the old cursor. Run your database code in a background thread using AsyncTask. A summary of the AsyncTask steps onPreExecute() doInBackground() 1 onPreExecute() is used to set up the task. It’s called before the background task begins, and runs on the main event thread. 2 doInBackground() runs in the background thread. It runs immediately after onPreExecute(). You can specify what type of parameters it has, and what its return type is. 3 onProgressUpdate() is used to display progress. It runs in the main event thread when the doInBackground() method calls publishProgress(). 4 onPostExecute() is used to display the task outcome to the user when doInBackground has finished. It runs in the main event thread and takes the return value of doInBackground() as a parameter. onProgressUpdate() onPostExecute() you are here 4 737 CHAPTER 17 You can download the full code for the chapter from https://tinyurl.com/ HeadFirstAndroid. 18 started services At Your Service Did I mention I’ve started a ProtectionRacketService? There are some operations you want to keep on running, irrespective of which app has the focus. If you start downloading a file, for instance, you don’t want the download to stop when you switch to another app. In this chapter we’ll introduce you to started services, components that run operations in the background. You’ll see how to create a started service using the IntentService class, and find out how its lifecycle fits in with that of an activity. Along the way, you’ll discover how to log messages, and keep users informed using Android’s built-in notification service. this is a new chapter 739 services Services work in the background An Android app is a collection of activities and other components. The bulk of your app’s code is there to interact with the user, but sometimes you need to do things in the background, such as download a large file, stream a piece of music, or listen for a message from the server. These kinds of tasks aren’t what activities are designed to do. In simple cases, you can create a thread, but if you’re not careful your activity code will start to get complex and unreadable. That’s why services were invented. A service is an application component like an activity but without a user interface. They have a simpler lifecycle than activities, and they come with a bunch of features that make it easy to write code that will run in the background while the user is doing something else. In addition to writing your own services, you can use Android’s built-in ones. Built-in services include the notification service, location service, alarm service, and download service. There are three types of service Services come in three main flavors: ¥ Started services A started service can run in the background indefinitely, even when the activity that started it is destroyed. If you wanted to download a large file from the Internet, you would probably create it as a started service. Once the operation is done, the service stops. ¥ Bound services A bound service is bound to another application component such as an activity. The activity can interact with it, send requests, and get results. A bound service runs as long as components are bound to it. When the components are no longer bound, the service is destroyed. If you wanted to create an odometer to measure the distance traveled by a vehicle, for example, you’d probably use a bound service. This way, any activities bound to the service could keep asking the service for updates on the distance traveled. ¥ Scheduled services A scheduled service is one that’s scheduled to run at a particular time. As an example, from API 21, you can schedule jobs to run at an appropriate time. In this chapter, we’re going to look at how you create a started service. 740 Chapter 18 started services We’ll create a STARTED service We’re going to create a new project that contains an activity called MainActivity, and a started service called DelayedMessageService. Whenever MainActivity calls DelayedMessageService, it will wait for 10 seconds and then display a piece of text. MainActivity will use this layout. activity_main.xml The service will display the text after 10 seconds. Text The activity will pass text to the service. MainActivity.java 1...2..3...4...5...6...7 ...8...9...10... Here’s the text. DelayedMessageService.java We’re going to do this in two stages: 1 Display the message in Android’s log. We’ll start by displaying the message in Android’s log so that we can check that the service works OK. We can look at the log in Android Studio. 2 Display the message in a notification. We’ll get DelayedMessageService to use Android’s built-in notification service to display the message in a notification. We’ll create this notification. Create the project We’ll start by creating the project. Create a new Android project for an application named “Joke” with a company domain of “hfad.com”, making the package name com.hfad.joke. The minimum SDK should be API 19 so that it will work with most devices. You’ll need an empty activity named “MainActivity” and a layout named “activity_main” so that your code matches ours. Make sure that you uncheck the Backwards Compatibility (AppCompat) option when you create the activity. The next thing we need to do is create the service. you are here 4 741 IntentService Log Display notification Use the IntentService class to create a basic started service The simplest way of creating a started service is to extend the IntentService class, as it provides you with most of the functionality you need. You start it with an intent, and it runs the code that you specify in a separate thread. We’re going to add a new intent service to our project. To do this, switch to the Project view of Android Studio’s explorer, click on the com.hfad.joke package in the app/src/main/java folder, go to File→New..., and select the Service option. When prompted, choose the option to create a new Intent Service. Name the service “DelayedMessageService” and uncheck the option to include helper start methods to minimize the amount of code that Android Studio generates for you. Click on the Finish button, then replace the code in DelayedMessageService.java with the code here: Name the service. Uncheck this option. package com.hfad.joke; import android.app.IntentService; import android.content.Intent; Extend the IntentService class. public class DelayedMessageService extends IntentService { public DelayedMessageService() { } super("DelayedMessageService"); @Override Put the code you want the service to run in the onHandleIntent() method. protected void onHandleIntent(Intent intent) { } } //Code to do something The above code is all you need to create a basic intent service. You extend the IntentService class, add a public constructor, and implement the onHandleIntent() method. The onHandleIntent() method should contain the code you want to run each time the service is passed an intent. It runs in a separate thread. If it’s passed multiple intents, it deals with them one at a time. We want DelayedMessageService to display a message in Android’s log, so let’s look at how you log messages. 742 Chapter 18 Some versions of Android Studio may ask you what the source language should be. If prompted, select the option for Java. Joke app/src/main java com.hfad.joke DelayedMessage Service.java started services Log Display notification How to log messages Adding messages to a log can be a useful way of checking that your code works the way you want. You tell Android what to log in your Java code, and when the app’s running, you check the output in Android’s log (a.k.a. logcat). You log messages using one of the following methods in the Android.util.Log class: Log.v(String tag, String message) Logs a verbose message. Log.d(String tag, String message) Logs a debug message. Log.i(String tag, String message) Logs an information message. Log.w(String tag, String message) Logs a warning message. Log.e(String tag, String message) Logs an error message. Each message is composed of a String tag you use to identify the source of the message, and the message itself. As an example, to log a verbose message that’s come from DelayedMessageService, you use the Log.v() method like this: Log.v("DelayedMessageService", "This is a message"); You can view the logcat in Android Studio and filter by the different types of message. To see the logcat, select the Android Monitor option at the bottom of your project screen in Android Studio and then select the logcat tab: Select the logcat tab. There’s also a Log.wtf() method you can use to report exceptions that should never happen. According to the Android documentation, wtf means “What a Terrible Failure.” We know it really means “Welcome to Fiskidagurinn,” which refers to the Great Fish Day festival held annually in Dalvik, Iceland. Android developers can often be heard to say, “My AVD just took 8 minutes to boot up. WTF??" as a tribute to the small town that gave its name to the standard Android executable bytecode format. You can filter on the type of message here. This is the logcat area. Any messages you log will appear here. Select the Android Monitor option. you are here 4 743 DelayedMessageService code Log Display notification The full DelayedMessageService code We want our service to get a piece of text from an intent, wait for 10 seconds, then display the piece of text in the log. To do this, we’ll add a showText() method to log the text, and then call it from the onHandleIntent() method after a delay of 10 seconds. Here’s the full code for DelayedMessageService.java (update your version of the code to reflect our changes): package com.hfad.joke; import android.app.IntentService; import android.content.Intent; import android.util.Log; We’re using the Log class, so we need to import it. public class DelayedMessageService extends IntentService { public static final String EXTRA_MESSAGE = "message"; public DelayedMessageService() { } super("DelayedMessageService"); Call the super constructor. This method contains the code you want to run when the service receives an intent. @Override protected void onHandleIntent(Intent intent) { synchronized (this) { try { wait(10000); Use a constant to pass a message from the activity to the service. Joke app/src/main Wait 10 seconds. } catch (InterruptedException e) { } } e.printStackTrace(); Get the text from the intent String text = intent.getStringExtra(EXTRA_MESSAGE); } showText(text); Call the showText() method. private void showText(final String text) { } } Log.v("DelayedMessageService", "The message is: " + text); 744 Chapter 18 This logs a piece of text so we can see it in the logcat through Android Studio. java com.hfad.joke DelayedMessage Service.java started services Log Display notification You declare services in AndroidManifest.xml Just like activities, each service needs to be declared in AndroidManifest.xml so that Android can call it; if a service isn’t declared in this file, Android won’t know it’s there and won’t be able to call it. Android Studio should update AndroidManifest.xml for you automatically whenever you create a new service by adding a newelement. Here’s what our AndroidManifest.xml code looks like: The service name has a “.” in front of it so that Android can combine it with the package name to derive the fully qualified class name. The AndroidManifest.xml android:theme="@style/AppTheme"> element contains two attributes: name and exported. The name attribute tells Android what the name of the service is—in our case, DelayedMessageService. The exported attribute tells Android whether the service can be used by other apps. Setting it to false means that the service will only be used within the current app. Now that we have a service, let’s get MainActivity to start it. you are here 4 745 add button Log Display notification Add a button to activity_main.xml We’re going to get MainActivity to start DelayedMessageService whenever a button is clicked, so we’ll add the button to MainActivity’s layout. First, add the following values to strings.xml: Joke app/src/main What is the secret of comedy? Timing! res valuesThen, replace your activity_main.xml code with ours below so that MainActivity displays a button: strings.xml The button will call an onClick() method whenever the user clicks it, so we’ll add this method to MainActivity. 746 Chapter 18 Joke app/src/main res layout activity_main.xml This creates a button. When it’s clicked, the onClick() method in the activity will get called. started services Log Display notification You start a service using startService() We’ll use MainActivity’s onClick() method to start DelayedMessageService whenever the user clicks on the button. You start a service from an activity in a similar way to how you start an activity. You create an explicit intent that’s directed at the service you want to start, then use the startService() method in your activity to start it: Intent intent = new Intent(this, DelayedMessageService.class); Starting a service is just like starting an activity, except you use startService() instead of startActivity(). startService(intent); Here’s our code for MainActivity.java; update your version to match ours: package com.hfad.joke; Joke import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; app/src/main We’re using these classes, so we need to import them. java com.hfad.joke public class MainActivity extends Activity { We're using Activity here, but you could use AppCompatActivity instead. @Override MainActivity.java protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } setContentView(R.layout.activity_main); This will run when the button gets clicked. public void onClick(View view) { Create the intent. Intent intent = new Intent(this, DelayedMessageService.class); intent.putExtra(DelayedMessageService.EXTRA_MESSAGE, } } startService(intent); getResources().getString(R.string.response)); Start the service. Add text to the intent. That’s all the code we need to get our activity to start the service. Before we take it for a test drive, let’s go through what happens when the code runs. you are here 4 747 what happens Log Display notification What happens when you run the app Here’s what the code does when we run the app: 1 MainActivity starts DelayedMessageService by calling startService() and passing it an intent. The intent contains the message MainActivity wants DelayedMessageService to display, in this case “Timing!”. Intent ”Timing!” MainActivity 2 DelayedMessageService When DelayedMessageService receives the intent, its onHandleIntent() method runs. DelayedMessageService waits for 10 seconds. Some text. I know how to handle that. 1...2...3...4...5... onHandleIntent() DelayedMessageService 3 DelayedMessageService logs the message. Log.v() DelayedMessageService 4 The message is: Timing! Log When DelayedMessageService has finished running, it’s destroyed. DelayedMessageService Let’s take the app for a test drive so we can see it working. 748 Chapter 18 started services Log Display notification Test drive the app When you run the app, MainActivity is displayed. It contains a single button: Here’s the button. Press the button, switch back to Android Studio, and watch the logcat output in the bottom part of the IDE. After 10 seconds, the message “Timing!” should appear in the logcat. This is the logcat window. 03-22 12:39:26.294 12381-18456/com.hfad.joke V/DelayedMessageService: The message is: Timing! Now that you’ve seen DelayedMessageService running, let’s look in more detail at how started services work. After a 10-second delay, the message is displayed in the log. you are here 4 749 started service states The states of a started service When an application component (such as an activity) starts a service, the service moves from being created to running to being destroyed. A started service spends most of its life in a running state; it’s been started by another component such as an activity, and it runs code in the background. It continues to run even if the component that started it is destroyed. When the service has finished running code, it’s destroyed. Service created Service running Service destroyed The service object has been created. The service has started and spends most of its life here. At this point, the service no longer exists. Just like an activity, when a service moves from being created to being destroyed, it triggers key service lifecycle methods, which it inherits. When the service is created, its onCreate() method gets called. You override this method if you want to perform any tasks needed to set up the service. When the service is ready to start, its onStartCommand() method is called. If you’re using an IntentService (which is usually the case for a started service), you don’t generally override this method. Instead, you add any code you want the service to run to its onHandleIntent() method, which is called after onStartCommand(). The onDestroy() method is called when the started service is no longer running and it’s about to be destroyed. You override this method to perform any final cleanup tasks, such as freeing up resources. We’ll take a closer look at how these methods fit into the service states on the next page. 750 Chapter 18 A started service runs after it’s been started. onCreate() gets called when the service is first created, and it’s where you do any service setup. onDestroy() gets called just before the service gets destroyed. started services The started service lifecycle: from create to destroy Here’s an overview of the started service lifecycle from birth to death. Service created 1 The component calls startService() and the service gets created. 2 The onCreate() method runs immediately after the service is created. The onCreate() method is where any service initialization code should go, as this method always gets called after the service has launched but before it starts running. 3 The onStartCommand() method runs when the service is about to start. If your started service extends the IntentService class (which is usually the case), the onStartCommand() method creates a separate thread and onHandleIntent() is called. You add any code you want the service to run in the background to onHandleIntent(). 4 The service spends most of its life running. 5 The onDestroy() method runs when the service has finished running, immediately before it’s destroyed. The onDestroy() method enables you to perform any final cleanup tasks such as freeing up resources. 6 After the onDestroy() method has run, the service is destroyed. The service ceases to exist. onCreate() onStartCommand() onHandleIntent() Service running onDestroy() Service destroyed The onCreate(), onStartCommand(), and onDestroy() methods are three of the main service lifecycle methods. So where do these methods come from? you are here 4 751 lifecycle methods Your service inherits the lifecycle methods As you saw earlier in the chapter, the started service you created extends the android.app.IntentService class. This class gives your service access to the Android lifecycle methods. Here’s a diagram showing the class hierarchy: Context Context abstract class (android.content.Context) An interface to global information about the application environment. Allows access to application resources, classes, and operations. ContextWrapper ContextWrapper class (android.content.ContextWrapper) A proxy implementation for the Context. Service onCreate() onStartCommand() Service class (android.app.Service) The Service class implements default versions of the lifecycle methods. You’ll find out more about it in the next chapter. onDestroy() IntentService onHandleIntent(Intent) YourStartedService onHandleIntent(Intent) yourMethod() IntentService class (android.app.IntentService) The IntentService class gives you an easy way of creating started services. It includes a method, onHandleIntent(), which handles any intents it’s given on a background thread. YourStartedService class (com.hfad.foo) Most of the behavior of your started service is handled by superclass methods your service inherits. All you do is override the methods you need and add a public constructor. Now that you understand more about how started services work behind the scenes, have a go at the following exercise. After that, we’ll look at how we can make DelayedMessageService display its message in a notification. 752 Chapter 18 started services Service Magnets Below is most of the code needed to create a started service called WombleService that plays a .mp3 file in the background, and an activity that uses it. See if you can finish off the code. This is the service. public class WombleService extends { public WombleService() { } super("WombleService"); @Override protected void (Intent intent) { MediaPlayer mediaPlayer = MediaPlayer.create(getApplicationContext(), R.raw.wombling_song); } } mediaPlayer.start(); This uses the Android MediaPlayer class to play a file called wombling_song.mp3. The file is located in the res/raw folder. public class MainActivity extends Activity { This is the activity. @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } setContentView(R.layout.activity_main); public void onClick(View view) { Intent intent = new Intent(this, } ); You won’t need to use all of these magnets. (intent); } IntentService WombleService.class onHandleIntent Underground Overground startActivity startService WombleService you are here 4 753 magnets solution Service Magnets Solution Below is most of the code needed to create a started service called WombleService that plays a .mp3 file in the background, and an activity that uses it. See if you can finish off the code. IntentService public class WombleService extends { This is the service. It extends the IntentService class. public WombleService() { } super("WombleService"); @Override protected void The code needs to run in the onHandleIntent() method. onHandleIntent MediaPlayer mediaPlayer = (Intent intent) { MediaPlayer.create(getApplicationContext(), R.raw.wombling_song); } } mediaPlayer.start(); This is the activity. public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } setContentView(R.layout.activity_main); public void onClick(View view) { Intent intent = new Intent(this, startService } } Create an explicit intent directed at WombleService.class. WombleService.class (intent); Start the service. startActivity Underground 754 Chapter 18 ); Overground You didn’t need to use these magnets. WombleService started services Android has a built-in notification service Log Display notification We’re going to change our Joke app so that our message gets displayed in a notification. Notifications are messages that are displayed outside the app’s user interface. When a notification gets issued, it’s displayed as an icon in the notification area of the status bar. You can see details of the notification in the notification drawer, which you access by swiping down from the top of the screen: d Heads-up notifications are temporarily displaye in a floating window at the top of the screen. These are notification icons. This is the notification drawer. Unlike a toast or snackbar, notifications are available outside the app that issues them, so the user can access them no matter what app they’re currently using (if any). They’re much more configurable than toasts and snackbars, too. To display the notification, we’re going to use one of Android’s built-in services, the notification service. You’ll see how to do this over the next few pages. you are here 4 755 add support library Log Display notification We’ll use notifications from the AppCompat Support Library We’re going to create notifications using classes from the AppCompat Support Library so that our notifications will work consistently across a wide range of Android versions. While it’s possible to create notifications using classes from the main release of Android, recent changes to these classes mean that the newest features won’t be available on older versions. Before we can use the notification classes from the Support Library, we need to add it to our project as a dependency. To do this, choose File→Project Structure, then click on the app module and choose Dependencies. Android Studio may have already added the AppCompat Support Library for you automatically. If so, you will see it listed as appcompat-v7. If it hasn’t been added, you will need to add it yourself. Click on the “+” button at the bottom or right side of the screen, choose the Library Dependency option, select the appcompat-v7 library, and then click on the OK button. Click on OK again to save your changes and close the Project Structure window. Use notifications from the AppCompat Support Library to allow apps running on older versions of Android to include the newest features. Here's the v7 AppCompat Support Library. To get DelayedMessageService to display a notification, there are three things we need to do: create a notification builder, tell the notification to start MainActivity when it’s clicked, and issue the notification. We’ll build up the code over the next few pages, then show you the full code at the end. 756 Chapter 18 started services Log Display notification First create a notification builder The first thing we need to do is create a notification builder. This enables you to build a notification with specific content and features. Each notification you create must include a small icon, a title, and some text as a bare minimum. Here’s the code to do that: The NotificationCompat class comes from the AppCompat Support Library. NotificationCompat.Builder builder = new NotificationCompat.Builder(this) This displays a small icon, in this case a built-in Android icon. .setSmallIcon(android.R.drawable.sym_def_app_icon) .setContentTitle(getString(R.string.question)) .setContentText(text); Set the title and text. To add more features to the notification, you simply add the appropriate method call to the builder. As an example, here’s how you additionally specify that the notification should have a high priority, vibrate the device when it appears, and disappear when the user clicks on it: NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(android.R.drawable.sym_def_app_icon) Make it a high priority and vibrate the device. .setContentTitle(getString(R.string.question)) .setContentText(text) .setPriority(NotificationCompat.PRIORITY_HIGH) .setVibrate(new long[] {0, 1000}) .setAutoCancel(true); This makes the notification disappear when the user clicks on it. Simply chain the method calls together to add more features to the notification. Wait for 0 milliseconds before vibrating the device for 1,000 milliseconds. These are just some of the properties that you can set. You can also set properties such as the notification’s visibility to control whether it appears on the device lock screen, a number to display in case you want to send many notifications from the same app, and whether it should play a sound. You can find out more about these properties (and many others) here: https://developer.android.com/reference/android/support/v4/app/ NotificationCompat.Builder.html Next, we’ll add an action to the notification to tell it which activity to start when it’s clicked. You create a heads-up notification (one that appears in a small floating window) by setting its priority to high, and making it vibrate the device or play a sound. you are here 4 757 add an action to the notification Log Display notification Add an action to tell the notification which activity to start when clicked When you create a notification, it’s a good idea to add an action to it, specifying which activity in your app should be displayed when the user clicks on the notification. As an example, an email app might issue a notification when the user receives a new email, and display the contents of that email when the user clicks on it. In our particular case, we’re going to start MainActivity. You add an action by creating a pending intent to start an activity, which you then add to the notification. A pending intent is an intent that your app can pass to other applications. The application can then submit the intent on your app’s behalf at a later time. To create a pending intent, you first create an explicit intent directed to the activity you want to start when the notification is clicked. In our case, we want to start MainActivity, so we use: This is a normal intent that starts MainActivity. Intent actionIintent = new Intent(this, MainActivity.class); We then use that intent to create a pending intent using the PendingIntent.getActivity() method. PendingIntent actionPendingIntent = PendingIntent.getActivity( This is a flag that’s used if you ever need to retrieve the pending intent. We don’t need to, so we’re setting it to 0. this, 0, A context, in this case the current service. actionIntent, This is the intent we created above. PendingIntent.FLAG_UPDATE_CURRENT); The getActivity() method takes four parameters: a context (usually this), an int request code, the explicit intent we defined above, and a flag that specifies the pending intent’s behavior. In the above code, we’ve used a flag of FLAG_UPDATE_CURRENT. This means that if a matching pending intent already exists, its extra data will be updated with the contents of the new intent. Other options are FLAG_CANCEL_CURRENT (cancel any existing matching pending intents before generating a new one), FLAG_NO_CREATE (don’t create the pending intent if there’s no matching existing one), and FLAG_ONE_SHOT (you can only use the pending intent once). This means that if there’s a matching pending intent, it will be updated. Once you’ve created the pending intent, you add it to the notification using the notification builder setContentIntent() method: builder.setContentIntent(actionPendingIntent); This tells the notification to start the activity specified in the intent when the user clicks on the notification. 758 Chapter 18 This adds the pending intent to the notification. started services Log Display notification Issue the notification using the built-in notification service Finally, you issue the notification using Android’s notification service. To do this, you first need to get a NotificationManager. You do this by calling the getSystemService() method, passing it a parameter of NOTIFICATION_SERVICE: NotificationManager notificationManager = This gives you access to Android’s notification service. (NotificationManager) getSystemService(NOTIFICATION_SERVICE); You then use the notification manager to issue the notification by calling its notify() method. This takes two parameters: a notification ID and a Notification object. The notification ID is used to identify the notification. If you send another notification with the same ID, it will replace the current notification. This is useful if you want to update an existing notification with new information. You create the Notification object by calling the notification builder’s build() method. The notification it builds includes all the content and features you’ve specified via the notification builder. Here’s the code to issue the notification: This is an ID we’ll use for the notification. It’s a random number we made up. public static final int NOTIFICATION_ID = 5453; ... NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); notificationManager.notify(NOTIFICATION_ID, builder.build()); That’s everything we need to create and issue notifications. We’ll show you the full code for DelayedMessageService on the next page. Use the notification service to display the notification we created. you are here 4 759 DelayedMessageService code The full code for DelayedMessageService.java Here’s the full code for DelayedMessageService.java. It now uses a notification to display a message to the user. Update your code to match ours: package com.hfad.joke; Joke import android.app.IntentService; import android.content.Intent; import android.util.Log; app/src/main Delete this line. import android.support.v4.app.NotificationCompat; import android.app.PendingIntent; import android.app.NotificationManager; java We’re using these extra classes, so we need to import them. public class DelayedMessageService extends IntentService { com.hfad.joke DelayedMessage Service.java public static final String EXTRA_MESSAGE = "message"; public static final int NOTIFICATION_ID = 5453; public DelayedMessageService() { } super("DelayedMessageService"); This is used to identify the notification. It could be any number; we just decided on 5453. @Override protected void onHandleIntent(Intent intent) { synchronized (this) { try { wait(10000); } catch (InterruptedException e) { } } e.printStackTrace(); String text = intent.getStringExtra(EXTRA_MESSAGE); } showText(text); The code continues on the next page. 760 Chapter 18 started services The DelayedMessageService.java code (continued) Joke private void showText(final String text) { app/src/main Log.v("DelayedMessageService", "The message is: " + text); java //Create a notification builder com.hfad.joke NotificationCompat.Builder builder = new NotificationCompat.Builder(this) Use a notification builder to specify the content and features of the notification. .setSmallIcon(android.R.drawable.sym_def_app_icon) .setContentTitle(getString(R.string.question)) DelayedMessage Service.java .setContentText(text) .setPriority(NotificationCompat.PRIORITY_HIGH) .setVibrate(new long[] {0, 1000}) .setAutoCancel(true); Create an intent. //Create an action Intent actionIntent = new Intent(this, MainActivity.class); PendingIntent actionPendingIntent = PendingIntent.getActivity( Use the intent to create a pending intent. this, 0, actionIntent, PendingIntent.FLAG_UPDATE_CURRENT); builder.setContentIntent(actionPendingIntent); //Issue the notification Add the pending intent to the notification. NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); } } notificationManager.notify(NOTIFICATION_ID, builder.build()); Display the notification using a notification manager. That’s all the code we need for our started service. Let’s go through what happens when the code runs. you are here 4 761 what happens Log Display notification What happens when you run the code Before you try running the updated app, let’s go through what happens when the code runs: 1 MainActivity starts DelayedMessageService by calling startService() and passing it an intent. The intent contains the message MainActivity wants DelayedMessageService to display. Intent ”Timing!” MainActivity 2 DelayedMessageService DelayedMessageService waits for 10 seconds. 1...2...3...4... DelayedMessageService 3 DelayedMessageService creates a notification builder and sets details of how the notification should be configured. icon=sym_def_app_icon title=”What is the secret of comedy?” text=”Timing!” DelayedMessageService 4 NotificationCompat.Builder DelayedMessageService creates an intent for MainActivity, which it uses to create a pending intent. PendingIntent To: MainActivity DelayedMessageService 762 Chapter 18 started services Log Display notification The story continues 5 DelayedMessageService adds the pending intent to the notification builder. PendingIntent To: MainActivity DelayedMessageService 6 icon=sym_def_app_icon title=”What is the secret of comedy?” text=”Timing!” NotificationCompat.Builder DelayedMessageService creates a NotificationManager object and calls its notify() method. The notification service displays the notification built by the notification builder. PendingIntent notify() DelayedMessageService 7 To: MainActivity NotificationManager Notification icon=sym_def_app_icon title=”What is the secret of comedy?” text=”Timing!” When the user clicks on the notification, the notification uses its pending intent to start MainActivity. Intent Notification MainActivity Now that we’ve gone through what the code does, let’s take the app for a test drive. you are here 4 763 test drive Log Display notification Test drive the app When you click on the button in MainActivity, a notification is displayed after 10 seconds. You’ll receive the notification irrespective of which app you’re in. When the head-up notification disappears, its icon remains in the status bar. Click on the button, and after a delay, a heads-up notification appears. When you open the notification drawer and click on the notification, Android returns you to MainActivity. Clicking on the notification starts MainActivity. You now know how to create a started service that displays a notification using the Android notification service. In the next chapter, we’ll look at how you create a bound service. 764 Chapter 18 started services Your Android Toolbox A service is an application component that can perform tasks in the background. It doesn’t have a user interface. A started service can run in the background indefinitely, even when the activity that started it is destroyed. Once the operation is done, it stops itself. A bound service is bound to another component such as an activity. The activity can interact with it and get results. CHAPTER 18 You’ve got Chapter 18 under your belt and now you’ve added started services to your toolbox. When a started service is created, its onCreate() method gets called, followed by onStartCommand(). If the service is an IntentService, onHandleIntent() is then called in a separate thread. When the service has finished running, onDestroy() gets called before the service is destroyed. The IntentService class inherits lifecycle methods from the Service class. A scheduled service is one that’s scheduled to run at a particular time. You log messages using the Android. util.Log class. You can view these messages in the logcat in Android Studio. You can create a simple started service by extending the IntentService class, overriding its onHandleIntent() method and adding a public constructor. You create a notification using a notification builder. Each notification must include a small icon, a title, and some text as a bare minimum. You declare services in AndroidManifest.xml using the element. You start a started service using the startService() method. You can download the full code for the chapter from https://tinyurl.com/ HeadFirstAndroid. A heads-up notification has its priority set to high, and vibrates the device or plays a sound when it’s issued. You tell the notification which activity to start when it’s clicked by creating a pending intent and adding it to the notification as an action. You issue the notification using a notification manager. You create a notification manager using Android’s notification service. you are here 4 765 19 bound services and permissions Bound Together Today, CALL_PHONE permission. Tomorrow, complete world domination. Bwahaha... Started services are great for background operations, but what if you need a service that’s more interactive? In this chapter you’ll discover how to create a bound service, a type of service your activity can interact with. You’ll see how to bind to the service when you need it, and how to unbind from it when you’re done to save resources. You’ll find out how to use Android’s Location Services to get location updates from your device GPS. Finally, you’ll discover how to use Android’s permission model, including handling runtime permission requests. this is a new chapter 767 bound services Bound services are bound to other components As you saw in Chapter 18, a started service is one that starts when it’s passed an intent. It runs code in the background, and stops when the operation is complete. It continues running even if the component that starts it gets destroyed. A bound service is one that’s bound to another application component, such as an activity. Unlike a started service, the component can interact with the bound service and call its methods. To see this in action, we’re going to create a new odometer app that uses a bound service. We’ll use Android’s location service to track the distance traveled: We’ll ask for regular updates on the device’s current location, calculate the distance traveled, and display the result. On the next page we’ll look at the steps we’ll go through to create the app. 768 Chapter 19 bound services and permissions Here’s what we’re going to do We’re going to build the app in three main steps: 1 Create a basic version of a bound service called OdometerService. We’ll add a method to it, getDistance(), which will return a random number. OdometerService 2 Get an activity, MainActivity, to bind to OdometerService and call its getDistance() method. We’ll call the method every second, and update a text view in MainActivity with the results. getDistance() MainActivity 3 0.23 OdometerService Update OdometerService to use Android’s Location Services. The service will get updates on the user’s current location, and use these to calculate the distance traveled. Are we nearly there yet? OdometerService Create a new Odometer project We’ll start by creating the project. Create a new Android project for an application named “Odometer” with a company domain of “hfad.com”, making the package name com.hfad.odometer. The minimum SDK should be API 19 so that it will work with most devices. You’ll need an empty activity named “MainActivity” and a layout named “activity_ main” so that your code matches ours. Make sure that you uncheck the Backwards Compatibility (AppCompat) option when you create the activity. you are here 4 769 create service OdometerService MainActivity Location Services Create a new service You create a bound service by extending the Service class. This class is more general than the IntentService class we used in Chapter 18, which is used for started services. Extending Service gives you more flexibility, but requires more code. We’re going to add a new bound service to our project, so switch to the Project view of Android Studio’s explorer, click on the com.hfad.odometer package in the app/src/main/ java folder, go to File→New..., and select the Service option. When prompted, choose the option to create a new Service (not an Intent Service), and name the service “OdometerService”. Uncheck the “Exported” option, as this only needs to be true if you want services outside this app to access the service. Make sure that the “checkednabled” option is checked; if it isn’t, the activity won’t be able to run the app. Then replace the code in OdometerService.java with this (shown here in bold): Uncheck the exported option. import android.app.Service; Some versions of Android Studio may ask you what the source language should be. If prompted, select the option for Java. import android.os.IBinder; The class extends the Service class. package com.hfad.odometer; import android.content.Intent; public class OdometerService extends Service { @Override The onBind() method is called when a component wants to bind to the service. public IBinder onBind(Intent intent) { } } //Code to bind the service The above code implements one method, onBind(), which gets called when a component, such as an activity, wants to bind to the service. It has one parameter, an Intent, and returns an IBinder object. IBinder is an interface that’s used to bind your service to the activity, and you need to provide an implementation of it in your service code. We’ll look at how you do this next. 770 Chapter 19 Check the enabled option. Odometer app/src/main java com.hfad.odometer Odometer Service.java bound services and permissions OdometerService MainActivity Location Services Implement a binder You implement the IBinder by adding a new inner class to your service code that extends the Binder class (which implements the IBinder interface). This inner class needs to include a method that activities can use to get a reference to the bound service. We’re going to define a binder called OdometerBinder that MainActivity can use to get a reference to OdometerService. Here’s the code we’ll use to define it: When you create a bound service, you need to provide a Binder implementation. The activity will use this method to get a reference to the OdometerService. public class OdometerBinder extends Binder { OdometerService getOdometer() { } } return OdometerService.this; We need to return an instance of the OdometerBinder in OdometerService’s onBind() method. To do this, we’ll create a new private variable for the binder, instantiate it, and return it in the onBind() method. Update your OdometerService.java code to include our changes below: ... import android.os.Binder; We’re using this extra class, so we need to import it. public class OdometerService extends Service { Odometer app/src/main We’re using a private final variable for our IBinder.object java private final IBinder binder = new OdometerBinder(); public class OdometerBinder extends Binder { OdometerService getOdometer() { } } return OdometerService.this; com.hfad.odometer This is our IBinder implementation. Odometer Service.java @Override public IBinder onBind(Intent intent) { } } return binder; Return the IBinder. We’ve now written all the service code we need to allow MainActivity to bind to OdometerService. Next, we’ll add a new method to the service to make it return a random number. you are here 4 771 getDistance() OdometerService MainActivity Location Services Add a getDistance() method to the service We’re going to add a method to OdometerService called getDistance(), which our activity will call. We’ll get it to return a random number for now, and later on we’ll update it to use Android’s location services. Here’s the full code for OdometerService.java including this change; update your version to match ours: package com.hfad.odometer; Odometer import android.app.Service; import android.content.Intent; app/src/main import android.os.IBinder; import android.os.Binder; import java.util.Random; java We’re using this extra class, so we need to import it. com.hfad.odometer public class OdometerService extends Service { Odometer Service.java private final IBinder binder = new OdometerBinder(); private final Random random = new Random(); public class OdometerBinder extends Binder { We’ll use a Random() object to generate random numbers. OdometerService getOdometer() { } } return OdometerService.this; @Override public IBinder onBind(Intent intent) { } return binder; Add the getDistance() method. public double getDistance() { } } return random.nextDouble(); Return a random double. Next we’ll update MainActivity so that it uses OdometerService. 772 Chapter 19 bound services and permissions OdometerService MainActivity Location Services Update MainActivity’s layout The next step in creating our app is to get MainActivity to bind to OdometerService and call its getDistance() method. We’re going to start by adding a text view to MainActivity’s layout. This will display the number returned by OdometerService’s getDistance() method. Update your version of activity_main.xml to reflect our changes: layout Now that we’ve added a text view to MainActivity’s layout, we’ll update its activity code. Let’s go through the changes we need to make. you are here 4 773 steps OdometerService MainActivity Location Services What MainActivity needs to do To get an activity to connect to a bound service and call its methods, there are a few steps you need to perform: 1 Create a ServiceConnection. This uses the service’s IBinder object to form a connection with the service. Activity 2 ServiceConnection Bind the activity to the service. Once you’ve bound it to the service, you can call the service’s methods directly. Activity 3 Service Service Interact with the service. In our case, we’ll use the service’s getDistance() method to update the activity’s text view. When your activity is bound to the service, you can use it to update your activity. 4 Unbind from the service when you’ve finished with it. When the service is no longer used, Android destroys the service to free up resources. Activity We’ll go through these steps with MainActivity, starting with creating the ServiceConnection. 774 Chapter 19 Service bound services and permissions OdometerService MainActivity Location Services Create a ServiceConnection A ServiceConnection is an interface that enables your activity to bind to a service. It has two methods that you need to define: onServiceConnected() and onServiceDisconnected(). The onServiceConnected() method is called when a connection to the service is established, and onServiceDisconnected() is called when it disconnects. We need to add a ServiceConnection to MainActivity. Here’s what the basic code looks like; update your version of MainActivity.java to matches ours: package com.hfad.odometer; import import import import import android.app.Activity; android.os.Bundle; android.content.ServiceConnection; android.os.IBinder; android.content.ComponentName; public class MainActivity extends Activity Odometer app/src/main We’re using these classes, so we need to import them. java We’re using an Activity here, but you could use com.hfad.odometer App CompatActivity instead. { Main Create a Service Connection object. private ServiceConnection connection = new ServiceConnection() { Activity.java @Override public void onServiceConnected(ComponentName componentName, IBinder binder) { //Code that runs when the service is connected } You need to define these methods. @Override public void onServiceDisconnected(ComponentName componentName) { //Code that runs when the service is disconnected } }; Add MainActivity’s onCreate() method. } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } We’ll update the onServiceConnected() and onServiceDisconnected() methods on the next page. you are here 4 775 onServiceConnected() OdometerService MainActivity Location Services The onServiceConnected() method As we said on the previous page, the onServiceConnected() method is called when a connection is established between the activity and the service. It takes two parameters: a ComponentName object that describes the service that’s been connected to, and an IBinder object that’s defined by the service: @Override The ComponentName identifies the service. It includes the service package and class names. public void onServiceConnected(ComponentName componentName, IBinder binder) { } //Code that runs when the service is connected There are two things we need the onServiceConnected() method to do: ¥ ¥ This is an IBinder defined by the service. We added one to OdometerService earlier. se its IBinder parameter to get a reference to the service we’re connected to, U in this case OdometerService. We can do this by casting the IBinder to an OdometerService.OdometerBinder (as this is the type of IBinder we defined in OdometerService) and calling its getOdometer() method. Record that the activity is bound to the service. Odometer Here’s the code to do these things (update your version of MainActivity.java to include our changes): app/src/main public class MainActivity extends Activity { private OdometerService odometer; private boolean bound = false; Add these variables to record a reference to the service, and whether the activity is bound to it. java com.hfad.odometer private ServiceConnection connection = new ServiceConnection() { @Override Main Activity.java public void onServiceConnected(ComponentName componentName, IBinder binder) { OdometerService.OdometerBinder odometerBinder = (OdometerService.OdometerBinder) binder; odometer = odometerBinder.getOdometer(); } ... }; ... } 776 Chapter 19 bound = true; The activity is bound to the service, so set the bound variable to true. Use the IBinder to get a reference to the service. bound services and permissions OdometerService MainActivity Location Services The onServiceDisconnected() method The onServiceDisconnected() method is called when the service and the activity are disconnected. It takes one parameter, a ComponentName object that describes the service: @Override public void onServiceDisconnected(ComponentName componentName) { } //Code that runs when the service is disconnected There’s only one thing we need the onServiceDisconnected() method to do when it’s called: record that the activity is no longer bound to the service. Here’s the code to do that; update your version of MainActivity.java to match ours: public class MainActivity extends Activity { Odometer app/src/main java private OdometerService odometer; com.hfad.odometer private boolean bound = false; private ServiceConnection connection = new ServiceConnection() { @Override Main Activity.java public void onServiceConnected(ComponentName componentName, IBinder binder) { OdometerService.OdometerBinder odometerBinder = (OdometerService.OdometerBinder) binder; odometer = odometerBinder.getOdometer(); } bound = true; @Override public void onServiceDisconnected(ComponentName componentName) { ... }; } bound = false; Set bound to false, as MainActivity is no longer bound to OdometerService. } Next we’ll look at how you bind to and unbind from the service. you are here 4 777 bindService() OdometerService MainActivity Location Services Use bindService() to bind the service When you bind your activity to a service, you usually do it in one of two places: ¥ I n the activity’s onStart() method when the activity becomes visible. This is appropriate if you only need to interact with the service when it’s visible. ¥ I n the activity’s onCreate() method when the activity gets created. Do this if you need to receive updates from the service even when the activity’s stopped. You don’t usually bind to a service in the activity’s onResume() method in order to keep the processing done in this method to a minimum. In our case, we only need to display updates from OdometerService when MainActivity is visible, so we’ll bind to the service in its onStart() method. To bind to the service, you first create an explicit intent that’s directed at the service you want to bind to. You then use the activity’s bindService() method to bind to the service, passing it the intent, the ServiceConnection object defined by the service, and a flag to describe how you want to bind. To see how you do this, here’s the code you’d use to bind MainActivity to OdometerService (we’ll add this code to MainActivity.java a few pages ahead): @Override protected void onStart() { super.onStart(); OdometerService MainActivity Odometer This is an intent directed to the OdometerService. app/src/main java Intent intent = new Intent(this, OdometerService.class); } bindService(intent, connection, Context.BIND_AUTO_CREATE); This is the ServiceConnection object. In the above code, we’ve used the flag Context.BIND_ AUTO_CREATE to tell Android to create the service if it doesn’t already exist. There are other flags you can use instead; you can see all the available ones in the Android documentation here: https://developer.android.com/reference/android/content/Context.html Next, we’ll look at how you unbind the activity from the service. 778 Chapter 19 com.hfad.odometer The bindService() method uses the intent and service connection to bind the activity to the service. Main Activity.java bound services and permissions OdometerService MainActivity Location Services Use unbindService() to unbind from the service When you unbind your activity from a service, you usually add the code to do so to your activity’s onStop() or onDestroy() method. The method you use depends on where you put your bindService() code: OdometerService MainActivity ¥ I f you bound to the service in your activity’s onStart() method, unbind from it in the onStop() method. ¥ I f you bound to the service in your activity’s onCreate() method, unbind from it in the onDestroy() method. In our case, we used MainActivity’s onStart() method to bind to OdometerService, so we’ll unbind from it in the activity’s onStop() method. You unbind from a service using the unbindService() method. The method takes one parameter, the ServiceConnection object. Here’s the code that we need to add to MainActivity (we’ll add this code to MainActivity.java a few pages ahead): Odometer @Override protected void onStop() { super.onStop(); if (bound) { This uses the ServiceConnection object to unbind from the service. unbindService(connection); } } bound = false; When we unbind, we’ll set bound to false. app/src/main java com.hfad.odometer Main Activity.java In the above code we’re using the value of the bound variable to test whether or not we need to unbind from the service. If bound is true, this means MainActivity is bound to OdometerService. We need to unbind the service, and set the value of bound to false. So far we have an activity that binds to the service when the activity starts, and unbinds from it when the activity stops. The final thing we need to do is get MainActivity to call OdometerService’s getDistance() method, and display its value. you are here 4 779 get distance OdometerService MainActivity Location Services Call OdometerService’s getDistance() method Once your activity is bound to the service, you can call its methods. We’re going to call the OdometerService’s getDistance() method every second, and update MainActivity’s text view with its value. To do this, we’re going to write a new method called displayDistance(). This will work in a similar way to the runTimer() code we used in Chapters 4 and 11. MainActivity will use the results of the OdometerService getDistance() method to update the TextView. Here’s our displayDistance() method. We’ll add it to MainActivity.java a couple of pages ahead: private void displayDistance() { final TextView distanceView = (TextView) findViewById(R.id.distance); Create a new Handler. Call the Handler’s post() method, passing in a new Runnable. final Handler handler = new Handler(); handler.post(new Runnable() { @Override public void run() { double distance = 0.0; if (bound && odometer != null) { } Get the TextView. If we’ve got a reference to the OdometerService and we’re bound to it, call getDistance(). distance = odometer.getDistance(); String distanceStr = String.format(Locale.getDefault(), distanceView.setText(distanceStr); } }); } "%1$,.2f miles", distance); You could use a String resource for “miles”, but we’ve hardcoded it here for simplicity. Post the code in the Runnable to be run again after a delay Odometer of 1 second. As this line of code is included in the Runnable run() method, it will run every second (with a slight lag). handler.postDelayed(this, 1000); We’ll call the displayDistance() method in MainActivity’s onCreate() method so that it starts running when the activity gets created (we’ll add this code to MainActivity.java on the next page): protected void onCreate(Bundle savedInstanceState) { } displayDistance(); Call displayDistance() in MainActivity’s onCreate() method to kick it off. We’ll show you the full code for MainActivity on the next page. 780 Chapter 19 java com.hfad.odometer @Override ... app/src/main Main Activity.java bound services and permissions OdometerService MainActivity Location Services The full MainActivity.java code Here’s the complete code for MainActivity.java; make sure your version of the code matches ours: package com.hfad.odometer; import import import import import import import import import import android.app.Activity; android.os.Bundle; android.content.ServiceConnection; android.os.IBinder; android.content.ComponentName; android.content.Context; android.content.Intent; We're using android.os.Handler; so we need android.widget.TextView; java.util.Locale; Odometer app/src/main java com.hfad.odometer these extra classes, to import them. Main Activity.java public class MainActivity extends Activity { private OdometerService odometer; private boolean bound = false; Use this for the OdometerService. Use this to store whether or not the activity’s bound to the service. We need to define a ServiceConnection. private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder binder) { OdometerService.OdometerBinder odometerBinder = (OdometerService.OdometerBinder) binder; odometer = odometerBinder.getOdometer(); Get a reference to the bound = true; Odomet erService when the } service is connected. @Override public void onServiceDisconnected(ComponentName componentName) { bound = false; } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); displayDistance(); Call the displayDistance() method } when the activity is created. The code continues on the next page. you are here 4 781 code, continued OdometerService MainActivity Location Services The MainActivity.java code (continued) @Override protected void onStart() { super.onStart(); Intent intent = new Intent(this, OdometerService.class); } bindService(intent, connection, Context.BIND_AUTO_CREATE); Bind the service when the activity starts. @Override Odometer protected void onStop() { super.onStop(); app/src/main if (bound) { unbindService(connection); } } bound = false; Unbind the service when the activity stops. Display the value returned by the service's getDistance() method. private void displayDistance() { java com.hfad.odometer Main Activity.java final TextView distanceView = (TextView)findViewById(R.id.distance); final Handler handler = new Handler(); handler.post(new Runnable() { @Override public void run() { double distance = 0.0; if (bound && odometer != null) { } Call OdometerService’s getDistance() method. distance = odometer.getDistance(); String distanceStr = String.format(Locale.getDefault(), distanceView.setText(distanceStr); } } }); } handler.postDelayed(this, 1000); Update the TextView’s value every second. That’s all the code you need to get MainActivity to use OdometerService. Let’s go through what happens when you run the code. 782 Chapter 19 "%1$,.2f miles", distance); bound services and permissions OdometerService MainActivity Location Services What happens when you run the code Before you see the app up and running, let’s walk through what the code does. 1 When MainActivity is created, it creates a ServiceConnection object and calls the displayDistance() method. displayDistance() MainActivity 2 ServiceConnection MainActivity calls bindService() in its onStart() method. The bindService() method includes an intent meant for OdometerService, and a reference to the ServiceConnection. bindService() MainActivity ServiceConnection Intent Odometer Service 3 Android creates an instance of the OdometerService, and passes it the intent by calling its onBind() method. Intent Android Odometer Service onBind() OdometerService you are here 4 783 what happens, continued OdometerService MainActivity Location Services The story continues 4 OdometerService’s onBind() method returns a Binder. The Binder is passed to MainActivity’s ServiceConnection. onBind() Binder MainActivity 5 ServiceConnection OdometerService The ServiceConnection uses the Binder to give MainActivity a reference to OdometerService. MainActivity ServiceConnection OdometerService 6 MainActivity’s displayDistance() method calls OdometerService’s getDistance() method every second. OdometerService returns a random number to MainActivity, in this case 0.56. getDistance() MainActivity 784 Chapter 19 0.56 OdometerService bound services and permissions The story continues 7 When MainActivity stops, it disconnects from OdometerService by calling unbindService(). I’m no longer visible, so I don’t need you anymore. OK. Just call if you change your mind. unbindService() MainActivity 8 OdometerService OdometerService is destroyed when MainActivity is no longer bound to it. MainActivity OdometerService Now that you understand what happens when the code runs, let’s take the app for a test drive. you are here 4 785 test drive OdometerService MainActivity Location Services Test drive the app When we run the app, a random number is displayed in MainActivity. This number changes every second. This is a random number generated by OdometerService. We now have a working service that MainActivity can bind to. We still need to change the service so that the getDistance() method returns the distance traveled instead of a random number. Before we do that, however, we’re going to take a closer look at how bound services work behind the scenes. Q: What’s the difference between a started service and a bound service again? A: A started service is created when an activity (or some other component) calls startService(). It runs code in the background, and when it finishes running, the service is destroyed. A bound service is created when the activity calls bindService(). The activity can interact with the service by calling its methods. The service is destroyed when no components are bound to it. 786 Chapter 19 Q: Can a service be both started and bound? A: Yes. In such cases, the service is created when startService() or bindService() is called. It’s only destroyed when the code it was asked to run in the background has stopped running, and there are no components bound to it. Creating this kind of started-and-bound service is more complicated than creating a service that’s only started or bound. You can find out how to do it in the Android documentation: https://developer.android. com/guide/components/services.html. Q: What’s the difference between a Binder and an IBinder? A: IBinder Binder is an interface. A is a class that implements the IBinder interface. An Q: Can other apps use a service I create? A: exported Yes, but only if you set its attribute to true in AndroidManifest.xml. bound services and permissions The states of a bound service When an application component (such as an activity) binds to a service, the service moves between three states: being created, being bound, and being destroyed. A bound service spends most of its time in a bound state. Service created Service bound Service destroyed The service object has been created. An application component such as an activity has bound to the service. The service spends most of its lifecycle here. At this point, the service no longer exists. Just like a started service, when a bound service is created, its onCreate() method gets called. As before, you override this method if you want to perform any tasks needed to set up the service. The onBind() method runs when a component binds to the service. You override this method to return an IBinder object to the component, which it uses to get a reference to the service. When all components have unbound from the service, its onUnbind() method is called. Finally, the onDestroy() method is called when no components are bound to the service and it’s about to be destroyed. As before, you override this method to perform any final cleanup tasks and free up resources. A bound service is destroyed when no components are bound to it. We’ll take a closer look at how these methods fit into the service states on the next page. you are here 4 787 lifecycle methods The bound service lifecycle: from create to destroy Here’s a more detailed overview of the bound service lifecycle from birth to death. Service created 1 The component calls bindService() and the service gets created. 2 The onCreate() method runs immediately after the service is created. The onCreate() method is where any service initialization code should go, as this method always gets called after the service has launched but before any components have bound to it. 3 The onBind() method runs when the component binds to the service. You override this method to return an IBinder object, which the component can use to get a reference to the service and call its methods. 4 The service is bound for most of its life. 5 The onUnbind() method runs when all components have unbound from the service. 6 The onDestroy() method is called when no components are bound to the service and it’s about to be destroyed. You override this method if you want to perform any final cleanup tasks such as freeing up resources. 7 After the onDestroy() method has run, the service is destroyed. The service ceases to exist. onCreate() onBind() Service bound onUnbind() onDestroy() Service destroyed Now that you have a better understanding of how bound services work, let’s change our Odometer app so that it displays the actual distance traveled by the user. 788 Chapter 19 bound services and permissions We’ll use Android’s Location Services to return the distance traveled OdometerService MainActivity Location Services We need to get our OdometerService to return the distance traveled in its getDistance() method. To do this, we’ll use Android’s Location Services. These allow you to get the user’s current location, request periodic updates, and ask for an intent to be fired when the user comes within a certain radius of a particular location. In our case, we’re going to use the Location Services to get periodic updates on the user’s current location. We’ll use these to calculate the distance the user has traveled. To do this, we’ll perform the following steps: 1 Declare we need permission to use the Location Services. Our app can only use the Location Services if the user grants our app permission to do so. 2 Set up a location listener when the service is created. This will be used to listen for updates from the Location Services. 3 Request location updates. We’ll create a location manager, and use it to request updates on the user’s current location. 4 Calculate the distance traveled. We’ll keep a running total of the distance traveled by the user, and return this distance in the OdometerService’s getDistance() method. 5 Remove location updates just before the service is destroyed. This will free up system resources. Before we start, we’ll add the AppCompat Support Library to our project, as we’ll need to use it in our code. you are here 4 789 add support library Add the AppCompat Support Library To get our location code working properly, there are a couple of classes we need to use from the AppCompat Support Library, so we’ll add it to our project as a dependency. You do this in the same way that you did in earlier chapters. Choose File→Project Structure, then click on the app module and choose Dependencies. You’ll be presented with the following screen: Here’s the AppCompat Support Library. Android Studio may have already added the AppCompat Support Library automatically. If so, you will see it listed as appcompat-v7, as shown above. If the AppCompat Library isn’t listed, you will need to add it yourself. To do this, click on the “+” button at the bottom or right side of the screen, choose the Library Dependency option, select the appcompat-v7 library, then click on the OK button. Click on OK again to save your changes and close the Project Structure window. Next, we’ll look at how to declare we need permission to use Android’s Location Services. 790 Chapter 19 OdometerService MainActivity Location Services bound services and permissions OdometerService MainActivity Location Services Declare the permissions you need Android allows you to perform many actions by default, but there are some that the user needs to give permission for in order for them to work. This can be because they use the user’s private information, or could affect stored data or the way in which other apps function. Location Services is one of those things that the user needs to grant your app permission to use. You declare the permissions your app requires in AndroidManifest.xml using theelement, which you add to the root element. In our case, we need to access the user’s precise location in order to display the distance traveled, so we need to declare the ACCESS_FINE_LOCATION permission. To do this, you add the following declaration to AndroidManifest.xml (update your version of the file to reflect this change): Declare we need a permission. We need to know the user's precise location. How the app uses the above declaration depends on your app’s target SDK (usually the most recent version of Android) and the API level of the user’s device: ¥ I f your target SDK is API level 23 or above, and the user’s device is running 23 or above, the app requests permission at runtime. The user can deny or revoke permission, so whenever your code wants to use the thing that requires permission, it needs to check that permission is still granted. You’ll find out how to do this later in the chapter. ¥ I f your target SDK is API level 22 or below, or the user’s device is running 22 or below, the app requests permission when it’s installed. If the user denies permission, the app isn’t installed. Once granted, permission can’t be revoked except by uninstalling the app. Odometer app/src/mainAndroidManifest.xml Now that we’ve declared that our app needs to know the user’s location, let’s get to work on OdometerService. you are here 4 791 location listener OdometerService MainActivity Location Services Add a location listener to OdometerService You create a location listener by implementing the LocationListener interface. It has four methods that you need to define: onLocationChanged(), onProviderEnabled(), onProviderDisabled(), and onStatusChanged(). onLocationChanged() gets called when the user’s location has changed. We’ll use this method later in the chapter to track the distance the user has traveled. The onProviderEnabled(), onProviderDisabled(), and onStatusChanged() methods are called when the location provider is enabled, when it is disabled, or when its status has changed, respectively. We’ll look at location providers on the next page. We need to set up a location listener when OdometerService is first created, so we’ll implement the interface in OdometerService’s onCreate() method. Update your version of OdometerService.java to include our changes below: ... import android.os.Bundle; import android.location.LocationListener; import android.location.Location; We’re using these extra classes, so we need to import them. public class OdometerService extends Service { ... private LocationListener listener; We’re app/src/main java using a private variable for the LocationListener so com.hfad.odometer other methods can access it. @Override public void onCreate() { Set up the LocationListener. super.onCreate(); Odometer The Location parameter describes the Service.java listener = new LocationListener() { current location. We’ll use this later. @Override public void onLocationChanged(Location location) { //Code to keep track of the distance } We’ll complete this code later in the chapter. @Override public void onProviderDisabled(String arg0) {} @Override public void onProviderEnabled(String arg0) {} } } ... }; 792 Chapter 19 We won't use any of these methods in our OdometerService code, but we still need to declare them. @Override public void onStatusChanged(String arg0, int arg1, Bundle bundle) {} bound services and permissions OdometerService MainActivity Location Services We need a location manager and location provider To get location updates, we need to do three things: create a location manager to get access to Android’s Location Services, specify a location provider, and request that the location provider sends regular updates on the user’s current location to the location listener we added on the previous page. We’ll start by getting a location manager. Create a location manager You create a location manager in a similar way to how you created a notification manager in Chapter 18: using the getSystemService() method. Here’s the code to create a location manager that you can use to access Android’s Location Services (we’ll add the code to OdometerService’s onCreate() method later on): This is how you access the Android location service. LocationManager locManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE); Specify the location provider Next, we need to specify the location provider, which is used to determine the user’s location. There are two main options: GPS or network. The GPS option uses the device’s GPS sensor to establish the user’s location, whereas the network option looks at Wi-Fi, Bluetooth, or mobile networks. Not all devices have both types of location provider, so you can use the location manager’s getBestProvider() method to get the most accurate location provider on the device. This method takes two parameters: a Criteria object you can use to specify criteria such as power requirements, and a flag to indicate whether it should currently be enabled on the device. We used the getSystemService() method in Chapter 18 to get access to Android’s notification service. Odometer app/src/main java com.hfad.odometer Odometer Service.java We want to use the location provider on the device with the greatest accuracy, so we’ll use the following (we’ll add it to OdometerService later): String provider = locManager.getBestProvider(new Criteria(), true); Next we’ll get the location provider to send location updates to the location listener. This gets the most accurate location provider that’s available on the device. you are here 4 793 request updates OdometerService MainActivity Location Services Request location updates... You get the location provider to send updates to the location listener using the location manager’s requestLocationUpdates() method. It takes takes four parameters: the location provider, the minimum time interval between updates in milliseconds, the minimum distance between location updates in meters, and the location listener you want to receive the updates. As an example, here’s how you’d request location updates from the location provider every second when the device has moved more than a meter: The location provider The distance in meters The LocationListener you want to receive updates locManager.requestLocationUpdates(provider, 1000, 1, listener); The time in milliseconds ...but first check that your app has permission You can check your app’s target SDK version by choosing File -> Project Structure, clicking on the app option, and then choosing Flavors. If your app’s target SDK is API level 23 or above, you need to check at runtime whether the user has granted you permission to get their current location. (As we said earlier in the chapter, if your target SDK is API level 23 or above, and the user’s device is running one of these versions, the user may have installed the app without granting Location Services permission. You therefore have to check whether permission’s been granted before running any code that requires Location Services, or your code won’t compile.) Odometer app/src/main You check whether permission’s been granted using the ContextCompat. checkSelfPermission() method. ContextCompat is a class from the AppCompat Support Library that provides backward compatibility with older versions of Android. Its checkSelfPermission() method takes two parameters: the current Context (usually this) and the permission you want to check. It returns a value of PackageManager.PERMISSION_ GRANTED if permission has been granted. In our case, we want to check whether the app’s been granted ACCESS_ FINE_LOCATION permission. Here’s the code to do that: if (ContextCompat.checkSelfPermission(this, java com.hfad.odometer Odometer Service.java Check if the ACCESS_FINE_LOCATION permission has been granted... android.Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) { } locManager.requestLocationUpdates(provider, 1000, 1, listener); ...before requesting location updates. On the next page we’ll show you all the code you need to add to OdometerService.java to request location updates. 794 Chapter 19 bound services and permissions OdometerService MainActivity Location Services Here’s the updated OdometerService code Here’s the full code to request location updates (update your version of OdometerService.java to include our changes): ... import android.content.Context; import android.location.LocationManager; import android.location.Criteria; import android.support.v4.content.ContextCompat; import android.content.pm.PackageManager; public class OdometerService extends Service { ... private LocationManager locManager; We’re using these extra classes, so import them. Odometer app/src/main java com.hfad.odometer We’re using a private variable for the LocationManager so we can access it from other methods. public static final String PERMISSION_STRING Odometer Service.java = android.Manifest.permission.ACCESS_FINE_LOCATION; We’re adding the permission String as a constant. ... @Override public void onCreate() { super.onCreate(); listener = new LocationListener() { Check whether we have permission. }; } Get the LocationManager. locManager = (LocationManager) getSystemService (Context.LOCATION_SERVICE); if (ContextCompat.checkSelfPermission(this, PERMISSION_STRING) Get the most accurate location provider. } ... } == PackageManager.PERMISSION_GRANTED) { String provider = locManager.getBestProvider(new Criteria(), true); if (provider != null) { } locManager.requestLocationUpdates(provider, 1000, 1, listener); Request updates from the location provider. ... Next we’ll get our location listener to deal with the location updates. you are here 4 795 distance traveled OdometerService MainActivity Location Services Calculate the distance traveled So far, we’ve requested that the location listener be notified when the user’s current location changes. When this happens, the listener’s onLocationChanged() method gets called. This method has one parameter, a Location object representing the user’s current location. We can use this object to calculate the distance traveled by keeping a running total of the distance between the user’s current location and their last. You find the distance in meters between two locations using the Location object’s distanceTo() method. As an example, here’s how you’d find the distance between two locations called location and lastLocation: double distanceInMeters = location.distanceTo(lastLocation); This gets the distance between location and lastLocation. Here’s the code we need to add to OdometerService to record the distance traveled by the user (update your version of OdometerService.java to match ours): public class OdometerService extends Service { private static double distanceInMeters; private static Location lastLocation = null; ... @Override public void onCreate() { super.onCreate(); Odometer We’re using static variables to store the distance traveled and the user’s last location so that their values are retained when the service is destroyed. app/src/main java com.hfad.odometer listener = new LocationListener() { @Override public void onLocationChanged(Location location) { if (lastLocation == null) { } lastLocation = location; Set the user’s starting location. distanceInMeters += location.distanceTo(lastLocation); ... } lastLocation = location; Update the distance traveled and the user’s last location. } } } ... ... We’ll use this code to return the distance traveled to MainActivity. 796 Chapter 19 Odometer Service.java bound services and permissions OdometerService MainActivity Location Services Return the miles traveled To tell MainActivity how far the user has traveled, we need to update OdometerService’s getDistance() method. It currently returns a random number, so we’ll change it to convert the value of the distanceInMeters variable to miles and return that value. Here’s the new version of the getDistance() method; update your version of it to match ours: public double getDistance() { Odometer Delete this line. app/src/main return random.nextDouble(); } return this.distanceInMeters / 1609.344; java This converts the distance traveled in meters into miles. We could make this calculation more precise if we wanted to, but it’s accurate enough for our purposes. com.hfad.odometer Odometer Service.java Finally, we’ll stop the listener getting location updates when the service is about to be destroyed. Stop the listener getting location updates We’re going to stop the location listener getting updates using the OdometerService’s onDestroy() method, as this gets called just before the service is destroyed. You stop the updates by calling the location manager’s removeUpdates() method. It takes one parameter, the listener you wish to stop receiving the updates: locManager.removeUpdates(listener); This stops the location listener getting updates. If your app’s target SDK is API level 23 or above, you need to check whether the user has granted ACCESS_FINE_LOCATION permission before calling the removeUpdates() method. This is because you can only use this method if the user’s granted this permission, and Android Studio will complain if you don’t check first. You check whether permission’s been granted in the same way we did earlier, by checking the return value of the ContextCompat.checkSelfPermission() method: We can only remove the updates if we have permission. if (ContextCompat.checkSelfPermission(this, PERMISSION_STRING) == PackageManager.PERMISSION_GRANTED) { } locManager.removeUpdates(listener); Over the next few pages we’ll show you the full code for OdometerService, including the new onDestroy() method. you are here 4 797 OdometerService code OdometerService MainActivity Location Services The full OdometerService.java code We’ve now done everything we need to get OdometerService to return the distance traveled. Update your version of OdometerService.java to match ours. package com.hfad.odometer; Odometer import android.app.Service; import android.content.Context; app/src/main import android.content.Intent; java import android.os.Bundle; import android.os.IBinder; import android.os.Binder; import java.util.Random; We’re no longer returning a random number, so you can delete this import. com.hfad.odometer import android.location.LocationListener; Odometer Service.java import android.location.Location; import android.location.LocationManager; import android.location.Criteria; import android.support.v4.content.ContextCompat; import android.content.pm.PackageManager; public class OdometerService extends Service { private final IBinder binder = new OdometerBinder(); private final Random random = new Random(); private LocationListener listener; Delete the Random() object, as we’re no longer using it. private LocationManager locManager; private static double distanceInMeters; private static Location lastLocation = null; public static final String PERMISSION_STRING = android.Manifest.permission.ACCESS_FINE_LOCATION; public class OdometerBinder extends Binder { OdometerService getOdometer() { } } 798 Chapter 19 return OdometerService.this; The code continues on the next page. bound services and permissions OdometerService MainActivity Location Services The OdometerService.java code (continued) @Override public void onCreate() { super.onCreate(); None of the code on this page has changed. Odometer listener = new LocationListener() { app/src/main @Override java public void onLocationChanged(Location location) { if (lastLocation == null) { } com.hfad.odometer lastLocation = location; distanceInMeters += location.distanceTo(lastLocation); } Odometer Service.java lastLocation = location; @Override public void onProviderDisabled(String arg0) { } @Override public void onProviderEnabled(String arg0) { } @Override public void onStatusChanged(String arg0, int arg1, Bundle bundle) { }; } locManager = (LocationManager) getSystemService (Context.LOCATION_SERVICE); if (ContextCompat.checkSelfPermission(this, PERMISSION_STRING) == PackageManager.PERMISSION_GRANTED) { String provider = locManager.getBestProvider(new Criteria(), true); if (provider != null) { } } } locManager.requestLocationUpdates(provider, 1000, 1, listener); The code continues on the next page. you are here 4 799 code, continued OdometerService MainActivity Location Services The OdometerService.java code (continued) @Override public IBinder onBind(Intent intent) { } Odometer return binder; app/src/main Add the onDestroy() method. @Override java public void onDestroy() { com.hfad.odometer super.onDestroy(); if (locManager != null && listener != null) { if (ContextCompat.checkSelfPermission(this, PERMISSION_STRING) == PackageManager.PERMISSION_GRANTED) { } locManager.removeUpdates(listener); locManager = null; } } listener = null; Odometer Service.java Stop getting locations updates (if we have permission to remove them). Set the LocationManager and LocationListener variables to null. public double getDistance() { } } return this.distanceInMeters / 1609.344; Let’s take the app for a test drive. Q: I noticed I can call checkSelfPermission() directly in my service without using ContextCompat. Why do I have to use the ContextCompat version? 800 Chapter 19 A: checkSelfPermission() Because it’s simpler to use. A method was added to the Context class in API level 23, but this means it’s not available on devices running an older version of Android. bound services and permissions OdometerService MainActivity Location Services Test drive the app When we launch the app, 0.0 miles is initially displayed. When we check the app permissions, permission hasn’t been granted to use Location Services. You can check this on your device by opening the device settings, choosing Apps, selecting the Odometer app, and opening the Permissions section: Location Services permission for the app may be granted on your device by default. If this is the case, see what happens when you switch it off. The miles traveled When we grant Location permission for the Odometer app and return to the app, the location icon is displayed in the status bar, and the number of miles traveled increases when we take the device for a walk. The location icon disappears when we leave the app: The app hasn’t been granted permission to use Location Services. The location icon indicates our device GPS is being used. We’ve switched on Location permissions. The icon disappears when we leave the app. The number of miles increases when we take the device for a walk. The odometer works when we grant the app Location permission, but doesn’t when permission’s been denied. you are here 4 801 request permission Get the app to request permission So far, we’ve made OdometerService check whether it has permission to get the user’s precise location. If permission’s been granted, it uses the Android Location Services to track the distance the user travels. But what if permission hasn’t been granted? If the app doesn’t have permission to get the user’s precise location, OdometerService can’t use the Location Services we require. Rather than just accepting this, the app would work better if it were to ask the user to grant the permission. We’re going to change MainActivity so that if the user hasn’t granted the permission we need, we’ll request it. To achieve this, we’ll get MainActivity to do three things: 1 Before MainActivity binds to the service, request ACCESS_FINE_ LOCATION permission if it’s not already been granted. This will present the user with a permission request dialog. This dialog appears when you request ACCESS_FINE_LOCATION permission at runtime. 2 Check the response, and bind to the service if permission is granted. 3 If permission is denied, issue a notification. We’ll issue this notification if the user doesn’t grant the permission we need. Let’s start by looking at how you make an activity request permissions at runtime. 802 Chapter 19 bound services and permissions Request Granted Denied Check permissions at runtime Earlier in the chapter, you saw how to check whether the user has granted a particular permission using the ContextCompat.checkSelfPermission() method: This code checks whether the user has granted a permission. if (ContextCompat.checkSelfPermission(this, PERMISSION_STRING) == PackageManager.PERMISSION_GRANTED) { } //Run code that needs the user's permission If the user has granted permission, the method returns a value of PackageManager.PERMISSION_GRANTED, and the code requiring the permission will run successfully. But what if permission has been denied? Ask for permissions you don’t have If the user hasn’t granted one or more permissions needed by your code, you can use the ActivityCompat. requestPermissions() method to request permission at runtime. ActivityCompat is a class from the AppCompat Support Library that provides backward compatibility with older versions of Android. Its requestPermissions() method takes three parameters: a Context (usually this), a String array of permissions you want to check, and an int request code for the permission request. As an example, here’s how you’d use the method to request the ACCESS_FINE_ LOCATION permission: ActivityCompat.requestPermissions(this, The requestPermissions() method can only be called by an activity. It can’t be called from a service. Use this method to request permissions at runtime. This is the request code for the permissions request. It can be any int. You’ll see where this gets used in a couple of pages. new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION}, 6854); When the requestPermissions() method is called, it displays one or more dialogs asking the user for each permission. The dialog gives the user a choice between denying or allowing the permission, and there’s also a checkbox they can check if they don’t want to be asked about the permission again. If they check the checkbox and deny the permission, subsequent calls to the requestPermissions() method won’t display the dialog. This is the permissions request dialog. Note that the requestPermissions() method can only be called from an activity. You can’t request permissions from a service. We’re going to update MainActivity so that it requests permission to get the device’s location if it hasn’t already been granted. The user can deny or allow permissions using these options. you are here 4 803 onStart() Request Granted Denied Check for Location Services permissions in MainActivity’s onStart() method We’re currently using MainActivity’s onStart() method to bind the activity to OdometerService. We’re going to change the code so that MainActivity only binds to the service if the user has granted the permission specified by the PERMISSION_STRING constant we defined in OdometerService. If permission’s not been granted, we’ll ask for it. Here’s the updated code for MainActivity.java; update your version of the code with our changes: ... import android.content.pm.PackageManager; We’re using these extra classes, so we need to import them. import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; Odometer public class MainActivity extends Activity { app/src/main ... private final int PERMISSION_REQUEST_CODE = 698; java We’ll use this int for the permission request code. ... @Override protected void onStart() { super.onStart(); com.hfad.odometer If permission hasn’t already been granted... Main Activity.java if (ContextCompat.checkSelfPermission(this, OdometerService.PERMISSION_STRING) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, ...request it at runtime. new String[]{OdometerService.PERMISSION_STRING}, } else { PERMISSION_REQUEST_CODE); Intent intent = new Intent(this, OdometerService.class); } } } bindService(intent, connection, Context.BIND_AUTO_CREATE); If permission has already been granted, bind to the service. Once you’ve requested permission from the user, you need to check the user’s response. We’ll do that next. 804 Chapter 19 bound services and permissions Request Granted Denied Check the user’s response to the permission request When you ask the user to grant a permission using the requestPermissions() method, you can’t determine whether permission was granted by checking its return value. That’s because the permission request happens asynchronously so that the current thread isn’t blocked while you wait for the user to respond. Instead, you check the user’s response by overriding the activity’s onRequestPermissionsResult() method. This has three parameters: an int request code to identify the permissions request, a String array of permissions, and an int array for the results of the requests. To use this method, you first check whether the int request code matches the one you used in the requestPermissions() method. If it does, you then check whether the permission has been granted. The code below checks whether the user granted the permission we requested using the requestPermissions() method on the previous page. If permission’s been granted, it binds to OdometerService. Add this method to your version of MainActivity.java: The onRequestPermissionsResult() method returns the results of your permissions requests. @Override public void onRequestPermissionsResult(int requestCode, switch (requestCode) { String permissions[], int[] grantResults) { case PERMISSION_REQUEST_CODE: { If the request was cancelled, no results will be returned. if (grantResults.length > 0 Check whether the code matches the one we used in our requestPermissions() method. && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Intent intent = new Intent(this, OdometerService.class); bindService(intent, connection, Context.BIND_AUTO_CREATE); } else { } } } } //Code to run if permission was denied We still need to write this code. Finally, if the user doesn’t give us permission to use their current location, we need to inform them that the odometer won’t work. If permission was granted, bind to the service. Odometer app/src/main java com.hfad.odometer MainActivity.java you are here 4 805 we want a notification Issue a notification if we’re denied permission If the user decides not to grant permission to use their current location, the OdometerService won’t be able to tell how far they’ve traveled. If this happens, we’ll issue a notification to inform the user. We’re going to use a notification because it will remain in the notification area until the user has decided what to do. Another advantage of using a notification is that we can get it to start MainActivity when it’s clicked. Doing this means that MainActivity’s onStart() method will run, which will again prompt the user to grant the permission (unless the user has previously selected the option not to be asked again). Request Granted Denied We’ll issue this notification if the user denies permission. See if you can build the notification we require by having a go at the exercise on the next page. Q: I tried turning off Location permissions for the Odometer app when I was in the middle of using it, and the number of miles displayed went back to 0. Why? A: When you switch off the Location permissions for the app, Android may kill the process the app is running in. This resets all of the variables. Q: That sounds drastic. Are there any other times when Android might kill a process? A: Q: Yes, when it’s low on memory, but it will always try to keep alive any processes that are actively being used. Why aren’t we calling the requestPermissions() method from OdometerService? A: Q: Because the requestPermissions() method is only available for activities, not services. Can I change the text that’s displayed in the requestPermissions() dialog? A: No. The text and options it displays are fixed, so Android won’t let you change them. 806 Chapter 19 Q: But I want to give the user more information about why I need a particular permission. Can I do that? A: ActivityCompat. shouldShowRequestPermissionRationale() One option is to call the method before calling requestPermissions(). This returns a true value if the user has previously denied the permission request, and hasn’t checked the checkbox to say they don’t want to be asked again. If this is the case, you can give the user more information outside the permission request before requesting the permission again. Q: What other permissions do I have to declare and ask permission for? A: Generally, you need the user’s permission for any actions that use private data or may affect the way in which other apps function. The online documentation for each class should indicate whether a permission is needed, and Android Studio should highlight this too. You can find a full list of actions requiring permissions here: https://developer.android.com/guide/topics/permissions/requesting. html#normal-dangerous Q: What if I design an app that performs these kinds of actions and don’t ask for permission? A: If your target SDK is API level 23 or above and you don’t request permission, your code won’t compile. bound services and permissions Pool Puzzle Write the code to create this notification. Your goal is to build and issue a headsup notification. The notification should start MainActivity when it’s clicked, then disappear. Take code snippets from the pool, and place them in the blank lines in the code. You may not use the same snippet more than once, and you won’t need to use all the snippets. NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(android.R.drawable.ic_menu_compass) .setContentTitle("Odometer") .setContentText("Location permission required") .setPriority(NotificationCompat. ) .setVibrate(new long[] {0, 1000}) . A built-in drawable to display a compass icon. (true); Intent actionIntent = new Intent(this, MainActivity.class); PendingIntent actionPendingIntent = PendingIntent. (this, 0, actionIntent, PendingIntent.FLAG_UPDATE_CURRENT); builder.setContentIntent( ); NotificationManager notificationManager = (NotificationManager) getSystemService( notificationManager.notify(43, ); ); Note: each thing from the pool can only be used once! NOTIFICATION getService actionPendingIntent HIGH PRIORITY_HIGH PRIORITY_LOW setVanishWhenClicked setAutoCancel builder.build() getActivity getAction actionIntent NOTIFICATION_SERVICE builder LOW you are here 4 807 solution Pool Puzzle Solution Your goal is to build and issue a headsup notification. The notification should start MainActivity when it’s clicked, then disappear. Take code snippets from the pool, and place them in the blank lines in the code. You may not use the same snippet more than once, and you won’t need to use all the snippets. NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(android.R.drawable.ic_menu_compass) .setContentTitle("Odometer") .setContentText("Location permission required") This makes the notification disappear when it’s clicked. .setPriority(NotificationCompat. PRIORITY_HIGH ) .setVibrate(new long[] {0, 1000}) . setAutoCancel (true); Intent actionIntent = new Intent(this, MainActivity.class); Heads-up notifications need to have a high priority. Create a PendingIntent using the getActivity() method. PendingIntent actionPendingIntent = PendingIntent. getActivity (this, 0, actionIntent, PendingIntent.FLAG_UPDATE_CURRENT); builder.setContentIntent( actionPendingIntent ); NotificationManager notificationManager = Add the PendingIntent to the notification so that it starts MainActivity when it’s clicked. (NotificationManager) getSystemService( NOTIFICATION_SERVICE ); notificationManager.notify(43, builder.build() ); Build the notification. You didn’t need to use these snippets. Use the notification service. NOTIFICATION getService setVanishWhenClicked actionIntent HIGH PRIORITY_LOW builder getAction LOW 808 Chapter 19 bound services and permissions Request Granted Denied Add notification code to onRequestPermissionsResults() We’ll update our MainActivity code so that if the user denies our permission request, it issues a heads-up notification. Odometer First, add the following Strings to Strings.xml; we’ll use these for the notification’s title and text: Odometer app/src/main Android Studio may have already added this String. resLocation permission required valuesThen update your version of MainActivity.java with the following code: strings.xml ... import android.support.v4.app.NotificationCompat; import android.app.NotificationManager; import android.app.PendingIntent; Odometer We’re using these extra classes, so we need to import them. app/src/main public class MainActivity extends Activity { ... private final int NOTIFICATION_ID = 423; java We’ll use this constant for the notification ID. ... com.hfad.odometer Main Activity.java @Override public void onRequestPermissionsResult(int requestCode, switch (requestCode) { String permissions[], int[] grantResults) { case PERMISSION_REQUEST_CODE: { if (grantResults.length > 0 ... && grantResults[0] == PackageManager.PERMISSION_GRANTED) { } else { //Create a notification builder NotificationCompat.Builder builder = new NotificationCompat.Builder(this) These settings are needed for all notifications. Add these to make it a heads-up notification. .setSmallIcon(android.R.drawable.ic_menu_compass) .setContentTitle(getResources().getString(R.string.app_name)) .setContentText(getResources().getString(R.string.permission_denied)) .setPriority(NotificationCompat.PRIORITY_HIGH) .setVibrate(new long[] {1000, 1000}) .setAutoCancel(true); The code continues on the next page. This line makes the notification disappear when it’s clicked. you are here 4 809 code, continued Request Granted Denied The notification code (continued) //Create an action Intent actionIntent = new Intent(this, MainActivity.class); PendingIntent actionPendingIntent = PendingIntent.getActivity( Adding a PendingIntent to the notification means that it will start MainActivity when it’s clicked. this, 0, actionIntent, PendingIntent.FLAG_UPDATE_CURRENT); builder.setContentIntent(actionPendingIntent); Build the notification and issue it. } } } } //Issue the notification NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); notificationManager.notify(NOTIFICATION_ID, builder.build()); ... } That’s all the code we need to display a notification if the user decides to deny us ACCESS_FINE_LOCATION permission. We’ll show you the full MainActivity code over the next few pages, then take the app for a final test drive. 810 Chapter 19 Odometer app/src/main java com.hfad.odometer Main Activity.java bound services and permissions Request Granted Denied The full code for MainActivity.java Here’s our complete code for MainActivity.java; make sure your version of the code matches ours: package com.hfad.odometer; import import import import import import import import import import import import import import import import Odometer android.app.Activity; android.os.Bundle; app/src/main android.content.ServiceConnection; android.os.IBinder; java android.content.ComponentName; android.content.Context; com.hfad.odometer android.content.Intent; android.os.Handler; Main android.widget.TextView; Activity.java java.util.Locale; android.content.pm.PackageManager; android.support.v4.app.ActivityCompat; These are all classes from the android.support.v4.content.ContextCompat; AppCompat Support Library. android.support.v4.app.NotificationCompat; android.app.NotificationManager; android.app.PendingIntent; public class MainActivity extends Activity { private private private private We’ve been using the Activity class, but you can use AppCompatActivity if you prefer. OdometerService odometer; boolean bound = false; final int PERMISSION_REQUEST_CODE = 698; final int NOTIFICATION_ID = 423; We need a ServiceConnection in order to bind MainActivity to OdometerService. private ServiceConnection connection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName componentName, IBinder binder) { OdometerService.OdometerBinder odometerBinder = (OdometerService.OdometerBinder) binder; odometer = odometerBinder.getOdometer(); bound = true; } The code @Override continues public void onServiceDisconnected(ComponentName componentName) { on the bound = false; next page. } }; you are here 4 811 MainActivity, continued Request Granted Denied The MainActivity.java code (continued) @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } displayDistance(); If we’ve asked the user for permissions at runtime, check the result. Odometer app/src/main java @Override com.hfad.odometer public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { Main switch (requestCode) { Activity.java case PERMISSION_REQUEST_CODE: { Bind to if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { the service Intent intent = new Intent(this, OdometerService.class); if the user has granted bindService(intent, connection, Context.BIND_AUTO_CREATE); permission. } else { //Create a notification builder NotificationCompat.Builder builder = new NotificationCompat.Builder(this) .setSmallIcon(android.R.drawable.ic_menu_compass) .setContentTitle(getResources().getString(R.string.app_name)) .setContentText(getResources().getString(R.string.permission_denied)) .setPriority(NotificationCompat.PRIORITY_HIGH) Issue a notification if permission’s been denied. } } } } .setVibrate(new long[] { 1000, 1000}) .setAutoCancel(true); //Create an action Intent actionIntent = new Intent(this, MainActivity.class); PendingIntent actionPendingIntent = PendingIntent.getActivity(this, 0, actionIntent, PendingIntent.FLAG_UPDATE_CURRENT); builder.setContentIntent(actionPendingIntent); //Issue the notification NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); notificationManager.notify(NOTIFICATION_ID, builder.build()); 812 Chapter 19 The code continues on the next page. bound services and permissions Request Granted Denied The MainActivity.java code (continued) @Override protected void onStart() { super.onStart(); if (ContextCompat.checkSelfPermission(this, OdometerService.PERMISSION_STRING) Request ACCESS_ != PackageManager.PERMISSION_GRANTED) { FINE_LOCATION permission if we ActivityCompat.requestPermissions(this, don’t have it new String[]{OdometerService.PERMISSION_STRING}, already. PERMISSION_REQUEST_CODE); } else { Intent intent = new Intent(this, OdometerService.class); bindService(intent, connection, Context.BIND_AUTO_CREATE); } Bind to OdometerService } if we’ve been granted the permission it requires. @Override protected void onStop() { super.onStop(); if (bound) { unbindService(connection); bound = false; } } . Display the distance traveled } We’re binding to OdometerService in two different places, so you could put this code in a separate method. Odometer app/src/main Unbind from OdometerService when MainActivity stops. java com.hfad.odometer Main Activity.java private void displayDistance() { final TextView distanceView = (TextView)findViewById(R.id.distance); final Handler handler = new Handler(); handler.post(new Runnable() { @Override public void run() { double distance = 0.0; if (bound && odometer != null) { distance = odometer.getDistance(); } String distanceStr = String.format(Locale.getDefault(), "%1$,.2f miles", distance); distanceView.setText(distanceStr); handler.postDelayed(this, 1000); } }); } you are here 4 813 test drive Request Granted Denied Test drive the app When we run the app with its Location permission switched off, a permission request dialog is displayed. If we click on the Deny option, a notification is issued: If we deny permission, a notification is issued. If we’ve not granted permission, a dialog is displayed requesting it. When we click on the notification, the permission request dialog is displayed again. If we click on the option to allow permission, the Location icon appears in the status bar and the number of miles increases when we take the device on a road trip: The dialog is displayed again when we click on the notification. Location Services are running. The number of miles increases if we go on a walk. We know you’re full of great ideas for improving the Odometer app, so why not try them out? As an example, try adding Start, Stop, and Reset buttons to start, stop, and reset the distance traveled. 814 Chapter 19 bound services and permissions Your Android Toolbox You create a bound service by extending the Service class. You define your own Binder object, and override the onBind() method. To get the current location of the device, you need to declare that the app requires ACCESS_FINE_LOCATION permission in AndroidManifest.xml. Bind a component to a service using the bindService() method. Get location updates using a LocationListener. Use a ServiceConnection so that your activity can get a reference to the service when it’s bound. Unbind a component from a service using the unbindService() method. When a bound service is created, its onCreate() method is called. onBind() gets called when a component binds to the service. When all components have unbound from the service, its onUnbind() method is called. A bound service is destroyed when no components are bound to it. Its onDestroy() method is called just before the service is destroyed. Use the Android Location Services to determine the current location of the device. A LocationManager gives you access to Android’s Location Services. Get the best location provider available on the device using its getBestProvider() method. Request location updates from the provider using requestLocationUpdates(). Use removeUpdates() to stop getting location updates. If your target SDK is API level 23 or above, check at runtime whether your app has been granted a permission using the ContextCompat. checkSelfPermission() method. Request permissions at runtime using ActivityCompat. requestPermissions(). Check the user’s response to a permission request by implementing the activity’s onRequestPermissionsResult() method. you are here 4 815 CHAPTER 19 You’ve got Chapter 19 under your belt and now you’ve added bound services to your toolbox. You can download the full code for the chapter from https://tinyurl.com/ HeadFirstAndroid. Leaving town... It’s been great having you here in Androidville We’re sad to see you leave, b ut there’s nothing like taking what you’ve learned and putting it to use. There are still a few more gems for you in the back of the book and a handy index, and then it’s time to take all these new ideas and put them into practice. Bon voyage! appendix i: relative and grid layouts Meet the Relatives Now remember, it’s layout_row=”18”, layout_column=”56”. Not “behind the white one.” There are two more layouts you will often meet in Androidville. In this book we’ve concentrated on using simple linear and frame layouts, and introduced you to Android’s new constraint layout. But there are two more layouts we’d like you to know about: relative layouts and grid layouts. They’ve largely been superseded by the constraint layout, but we have a soft spot for them, and we think they’ll stay around for a few more years. this is an appendix 817 relative layout A relative layout displays views in relative positions A relative layout allows you to position views relative to their parent layout, or relative to other views in the layout. You define a relative layout using the element like this: ... The layout_width and layout_height specify what size you want the layout to be. There may be other attributes too. This is where you add any views. Positioning views relative to the parent layout If you want a view to always appear in a particular position on the screen, irrespective of the screen size or orientation, you need to position the view relative to its parent. As an example, here’s how you’d make sure a button always appears in the top-center of the layout:The layout contains the button, so the layout is the button’s parent. layout_alignParentTop The lines of code: android:layout_alignParentTop="true" android:layout_centerHorizontal="true" mean that the top edge of the button is aligned to the top edge of the layout, and the button is centered horizontally in the parent layout. This will be the case no matter what the screen size, language, or orientation of your device: 818 Appendix i The parent layout layout_centerHorizontal relative and grid layouts Positioning views to the left or right You can also position a view to the left or right of the parent layout. There are two ways of doing this. The first way is to explicitly position the view on the left or right using: or: android:layout_alignParentLeft="true" android:layout_alignParentRight="true" layout_alignParentLeft=“true” The child view. These lines of code mean that the left (or right) edge of the view is aligned to the left (or right) edge of the parent layout, regardless of the screen size, orientation, or language being used on the device. The child view. android:layout_alignParentRight=“true” Use start and end to take language direction into account For apps where the minimum SDK is at least API 17, you can position views on the left or right depending on the language setting on the device. As an example, you might want views to appear on the left for languages that are read from left to right such as English. For languages that are read from right to left, you might want them to appear on the right instead so that their position is mirrored. To do this, you use: or: android:layout_alignParentStart="true" android:layout_alignParentEnd="true" android:layout_alignParentStart="true" aligns the start edge of the view with that of its parent. The start edge is on the left for languages that are read from left to right, and the right edge for languages that are read from right to left. android:layout_alignParentEnd="true" aligns the end edge of the view with that of its parent. The end edge is on the right for languages that are read from left to right, and the left edge for languages that are read from right to left. android:layout_alignParentEnd=“true” android:layout_alignParentStart=“true” For left-toright languages. For right-toleft languages. The view appears on the left or the right depending on the direction of the language used on the device. For left-toright languages. For right-toleft languages. you are here 4 819 relative to parent Attributes for positioning views relative to the parent layout Here are some of the most common attributes for positioning views relative to their parent layout. Add the attribute you want to the view you’re positioning, then set its value to "true": android:attribute="true" Attribute What it does layout_alignParentBottom Aligns the bottom edge of the view to the bottom edge of the parent. layout_alignParentLeft Aligns the left edge of the view to the left edge of the parent. layout_alignParentRight Aligns the right edge of the view to the right edge of the parent. layout_alignParentTop Aligns the top edge of the view to the top edge of the parent. layout_alignParentStart Aligns the start edge of the view to the start edge of the parent. layout_alignParentEnd Aligns the end edge of the view to the end edge of the parent. layout_centerInParent Centers the view horizontally and vertically in the parent. layout_centerHorizontal Centers the view horizontally in the parent. layout_centerVertical Centers the view vertically in the parent. 820 Appendix i The view is aligned to the parent's left and bottom edges. The view is aligned to the parent's right and top edges. The start is on the left and the end is on the right for leftto-right languages. This is reversed for rightto-left languages. relative and grid layouts Positioning views relative to other views In addition to positioning views relative to the parent layout, you can also position views relative to other views. You do this when you want views to stay aligned in some way, irrespective of the screen size or orientation. In order to position a view relative to another view, the view you’re using as an anchor must be given an ID using the android:id attribute: android:id="@+id/button_click_me" The syntax “@+id” tells Android to include the ID as a resource in its resource file R.java. You must include the “+” whenever you define a new view in the layout. If you don’t, Android won’t add the ID as a resource and you’ll get errors in your code. You can omit the “+” when the ID has already been added as a resource. Here’s how you create a layout with two buttons, with one button centered in the middle of the layout, and the second button positioned underneath the first:The lines: android:layout_alignStart="@id/button_click_me" android:layout_below="@id/button_click_me" When you’re referring to views that have already been defined in the layout, you can use @id instead of @+id. ensure that the second button has its start edge aligned to the start edge of the first button, and is always positioned beneath it. you are here 4 821 relative to views Attributes for positioning views relative to other views Here are attributes you can use when positioning views relative to another view. Add the attribute to the view you’re positioning, and set its value to the view you’re positioning relative to: android:attribute="@+id/view_id" Attribute Remember, you can leave out the “+” if you’ve already defined the view ID in your layout. What it does layout_above Puts the view above the view you’re anchoring it to. layout_below Puts the view below the view you’re anchoring it to. layout_alignTop Aligns the top edge of the view to the top edge of the view you’re anchoring it to. Your view goes above. The view you’re anchoring it to Your view goes below. Align the view’s top edges. Align the view’s bottom edges. layout_alignBottom Aligns the bottom edge of the view to the bottom edge of the view you’re anchoring it to. layout_alignLeft, layout_alignStart Aligns the left (or start) edge of the view to the left (or start) edge of the view you’re anchoring it to. Align the view’s left or start edges. layout_alignRight, layout_alignEnd Aligns the right (or end) edge of the view to the right (or end) edge of the view you’re anchoring it to. Align the view’s right or end edges. layout_toLeftOf, layout_toStartOf Puts the right (or end) edge of the view to the left (or start) of the view you’re anchoring it to. layout_toRightOf, layout_toEndOf Puts the left (or start) edge of the view to the right (or end) of the view you’re anchoring it to. 822 Appendix i Your view goes to the left or start. Your view goes to the right or end. relative and grid layouts A grid layout displays views in a grid A grid layout splits the screen up into a grid of rows and columns, and allocates views to cells: Each of these areas is a cell. How you define a grid layout You define a grid layout in a similar way to how you define the other types of layout, this time using the element: Enter email address You can use android:gravity and android:layout_gravity attributes with grid layouts. You can use layout_gravity in grid layouts too. We’re using fill_horizontal because we want the editable text field to fill the remaining horizontal space. Then you use the android:layout_row and android:layout_column attributes to say which row and column you want each view to appear in. The row and column indices start from 0, so if you want a view to appear in the first column and first row, you use: android:layout_row="0" android:layout_column="0" Columns and rows start at 0, so this refers to the first row and first column. Let’s apply this to our layout code by putting the text view in column 0, and the editable text field in column 1.... These are the same attributes we used for our other layouts. How many columns you want your layout to have (in this case, 2) This is where you add any views. You specify how many columns you want the grid layout to have using: android:columnCount="number" where number is the number of columns. You can also specify a maximum number of rows using: android:rowCount="number" but in practice you can usually let Android figure this out based on the number of views in the layout. Android will include as many rows as is necessary to display the views. you are here 4 823 grid layout Adding views to the grid layout You add views to a grid layout in a similar way to how you add views to a linear layout:Just as with a linear layout, there’s no need to give your views IDs unless you’re explicitly going to refer to them in your activity code. The views don’t need to refer to each other within the layout, so they don’t need to have IDs for this purpose. By default, the grid layout positions your views in the order in which they appear in the XML. So if you have a grid layout with two columns, the grid layout will put the first view in the first position, the second view in the second position, and so on. The downside of this approach is that if you remove one of your views from the layout, it can drastically change the appearance of the layout. To get around this, you can specify where you want each view to appear, and how many columns you want it to span. 824 Appendix i relative and grid layouts Let’s create a new grid layout To see this in action, we’ll create a grid layout that specifies which cells we want views to appear in, and how many columns they should span. The layout is composed of a text view containing the text “To”, an editable text field that contains hint text of “Enter email address”, an editable text field that contains hint text of “Message”, and a button labeled “Send”: Here’s what we’re going to do 1 Sketch the user interface, and split it into rows and columns. This will make it easier for us to see how we should construct our layout. 2 Build up the layout row by row. you are here 4 825 sketch it We’ll start with a sketch The first thing we’ll do to create our new layout is sketch it out. That way we can see how many rows and columns we need, where each view should be positioned, and how many columns each view should span. 1st column 1st row To 2nd column Enter email address The first row has a text view in the first column with text of “To,” and an editable text field in the second column with a hint of “Enter email address.” Message The second row has an editable text field with text of “Message.” It starts in the first column and spans across the second. It needs to fill the available space. 2nd row Send 3rd row The third row has a button with text of “Send.” It’s centered horizontally across both columns, which means it needs to span the two columns. The grid layout needs two columns We can position our views how we want if we use a grid layout with two columns: Now that we have the basic grid layout defined, we can start adding views. 826 Appendix i relative and grid layouts Row 0: add views to specific rows and columns The first row of the grid layout is composed of a text view in the first column, and an editable text field in the second column. You start by adding the views to the layout: To Row 0 Column 0 To Row and column indices start at 0. layout_column="n” refers to column n+1 in the display. Column 1 Enter email address you are here 4 827 row 1 Row 1: make a view span multiple columns The second row of the grid layout is composed of an editable text field that starts in the first column and spans across the second. The view takes up all the available space. To get a view to span multiple columns, you start by specifying which row and column you want the view to start in. We want the view to start in the first column of the second row, so we need to use: android:layout_row="1" Row 1 Column 0 Column 1 Message android:layout_column="0" We want our view to go across two columns, and we can do this using the android:layout_columnSpan attribute like this: android:layout_columnSpan="number" where number is the number of columns we want the view to span across. In our case, this is: Column span = 2 android:layout_columnSpan="2" Putting it all together, here’s the code for the message view: These are the views we added on the last page for row 0. Now that we’ve added the views for the first two rows, all we need to do is add the button. 828 Appendix i relative and grid layouts Row 2: make a view span multiple columns We need the button to be centered horizontally across the two columns like this: Column 0 Column 1 Send Row 2 Column span = 2 Layout Magnets We wrote some code to center the Send button in the third row of the grid layout, but a sudden breeze blew some of it away. See if you can reconstruct the code using the magnets below. These are the views we’ve already added. "0" "2" "0" "1" "1" "2" center_horizontal you are here 4 829 magnets solved Layout Magnets Solution We wrote some code to center the Send button in the third row of the grid layout, but a sudden breeze blew some of it away. See if you can reconstruct the code using the magnets below. Column 0 Row 2 Column 1 Send Column span = 2 830 Appendix i fill_horizontal "0" You didn’t need to use these magnets. "1" "1" relative and grid layouts The full code for the grid layout Here’s the full code for the grid layout. 832 Appendix i The button spans two columns, starting from row 2 column 0. It’s centered horizontally. appendix ii: gradle The Gradle Build Tool Take one SDK, add a sprinkling of libraries, mix well, then bake for two minutes. Most Android apps are created using a build tool called Gradle. Gradle works behind the scenes to find and download libraries, compile and deploy your code, run tests, clean the grouting, and so on. Most of the time you might not even realize it’s there because Android Studio provides a graphical interface to it. Sometimes, however, it’s helpful to dive into Gradle and hack it manually. In this appendix we’ll introduce you to some of Gradle’s many talents. this is an appendix 833 introducing gradle has Gradle What have the Romans ever done for us? When you click the run button in Android Studio, most of the actual work is done by an external build tool called Gradle. Here are some of the things that Gradle does: ¥ ¥ Locates and downloads the correct versions of any third-party libraries you need. ¥ ¥ Installs and runs your app on an Android device. alls the correct build tools in the correct sequence to turn all of your source code and C resources into a deployable app. A whole bunch of other stuff, like running tests and checking the quality of your code. It’s hard to list all of the things that Gradle does because it’s designed to be easily extensible. Unlike other XML-based build tools like Maven or Ant, Gradle is written in a procedural language (Groovy), which is used for both configuring a build and adding extra functionality. Your project’s Gradle files Every time you create a new project, Android Studio creates two files called build.gradle. One of these files is in your project folder, and contains a small amount of information that specifies the basic settings of your app, such as what version of Gradle to use, and which online repository: buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.3.0' } } allprojects { repositories { jcenter() } } MyProject build.gradle task clean(type: Delete) { delete rootProject.buildDir } You will normally only need to change the code in this file if you want to install a third-party plug-in, or need to specify another place that contains downloadable libraries. 834 Appendix ii gradle Your app’s main Gradle file The second build.gradle file lives inside the app folder within your project. This file tells Gradle how to build all of the code in your main Android module. It’s where the majority of your application’s properties are set, such as the level of the API that you’re targeting, and the specifics of which external libraries your app will need: apply plugin: 'com.android.application' android { compileSdkVersion 25 buildToolsVersion "25.0.1" defaultConfig { applicationId "com.hfad.example" minSdkVersion 19 targetSdkVersion 25 MyProject app build.gradle versionCode 1 versionName "1.0" } testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" buildTypes { release { minifyEnabled false } } } proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { }) exclude group: 'com.android.support', module: 'support-annotations' compile 'com.android.support:appcompat-v7:25.1.1' compile 'com.android.support.constraint:constraint-layout:1.0.2' } testCompile 'junit:junit:4.12' you are here 4 835 gradle’s built in Gradle comes built in to your project Every time you create a new application, Android Studio includes an install of the Gradle build tool. If you look in the project directory, you will see two files called gradlew and gradlew.bat. These files contains scripts that allow you to build and deploy your app from the command line. To get more familiar with Gradle, open a command prompt or terminal on your development machine and change into the top-level directory of your project. Then run one of the gradlew scripts with the parameter tasks. Gradle will tell you some of the tasks that it can perform for you: We've cut down the actual output, because there are many, many tasks that you can run by default. File Edit Window Help EmacsFTW $ ./gradlew tasks Build tasks ----------assemble - Assembles all variants of all applications and secondary packages. build - Assembles and tests this project. clean - Deletes the build directory. compileDebugSources mockableAndroidJar - Creates a version of android.jar that's suitable for unit tests. Install tasks ------------installDebug - Installs the Debug build. uninstallAll - Uninstall all applications. uninstallDebug - Uninstalls the Debug build. Verification tasks -----------------check - Runs all checks. connectedAndroidTest - Installs and runs instrumentation tests for all flavors on connected devices. lint - Runs lint on all variants. test - Run unit tests for all variants. To see all tasks and more detail, run gradlew tasks --all BUILD SUCCESSFUL Total time: 6.209 secs $ Let’s take a quick tour of some of the most useful ones. 836 Appendix ii gradle The check task The check task performs static analysis on the source code in your application. Think of it as your coding buddy, who at a moment’s notice can scoot through your files looking for coding errors. By default, the check task uses the lint tool to look for common Android programming errors. It will generate a report in app/build/reports/lint-results.html: MyProject app build reports lint-results.html The clean installDebug task This task will do a complete compile and install of your application on your connected device. You can obviously do this from the IDE, but it can be useful to do this from the command line if, for example, you want to automatically build your application on an Integration Server. you are here 4 837 show dependencies The androidDependencies task For this task, Gradle will automatically pull down any libraries your application requires, and some of those libraries will automatically pull down other libraries, which might pull down other libraries and… well, you get the idea. Even though your app/build.gradle file might only mention a couple of libraries, your application might need to install many dependent libraries for your app. So it’s sometimes useful to see which libraries your application requires, and why. That’s what the androidDependencies task is for: it displays a tree of all of the libraries in your app: File Edit Window Help EmacsFTW $ ./gradlew androidDependencies Incremental java compilation is an incubating feature. :app:androidDependencies debug +--- com.android.support:appcompat-v7:25.1.1@aar | +--- com.android.support:support-annotations:25.1.1@jar | +--- com.android.support:support-v4:25.1.1@aar | | +--- com.android.support:support-compat:25.1.1@aar | | | \--- com.android.support:support-annotations:25.1.1@jar | | +--- com.android.support:support-media-compat:25.1.1@aar | | | +--- com.android.support:support-annotations:25.1.1@jar | | | \--- com.android.support:support-compat:25.1.1@aar | | | \--- com.android.support:support-annotations:25.1.1@jar | | +--- com.android.support:support-core-utils:25.1.1@aar | | | +--- com.android.support:support-annotations:25.1.1@jar | | | \--- com.android.support:support-compat:25.1.1@aar ... 838 Appendix ii gradle gradlew you are here 4 831 code, continued The grid layout code (continued) The real reason that Android apps are commonly built with Gradle is because it’s easily extended. All Gradle files are written in Groovy, which is a general-purpose language designed to be run by Java. That means you can easily add your own custom tasks. MyProject As an example, add the following code to the end of your app/build.gradle file: task javadoc(type: Javadoc) { app build.gradle source = android.sourceSets.main.java.srcDirs classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) destinationDir = file("$project.buildDir/javadoc/") } failOnError false This code creates a new task called javadoc, which generates javadoc for your source code. You can run the task with: ./gradlew javadoc The generated files will be published in app/build/javadoc: MyProject app build javadoc index.html you are here 4 839 plug-ins Gradle plug-ins As well as writing your own tasks, you can also install Gradle plugins. A plug-in can greatly extend your build environment. Want to write Android code in Clojure? Want your build to automatically interact with source control systems like Git? How about spinning up entire servers in Docker and then testing your application on them? You can do these things, and many, many more by using Gradle plug-ins. For details on what plug-ins are available, see https:// plugins.gradle.org. 840 Appendix ii appendix iii: art The Android Runtime So that’s what’s going on under the hood... Ever wonder how Android apps can run on so many kinds of devices? Android apps run in a virtual machine called the Android runtime (ART), not the Oracle Java Virtual Machine (JVM). This means that your apps are quicker to start on small, low-powered devices and run more efficiently. In this appendix, we’ll look at how ART works. this is an appendix 841 what’s art? What is the Android runtime? The Android Runtime (ART) is the system that runs your compiled code on an Android device. It first appeared on Android with the release of KitKat and became the standard way of running code in Lollipop. ART is designed to run your compiled Android apps quickly and efficiently on small, low-powered devices. Android Studio uses the Gradle build system to do all the work of creating and installing apps for you, but it can be useful to understand what happens behind the scenes when you click the Run button. Let’s see what really goes on. You don’t need to understand the info in this appendix in order to create cool Android apps. So if you’re not into the nitty-gritty of what’s going on behind the scenes when an Android device runs an app, feel free to skip this appendix. Previously, Java code ran on the Oracle JVM Java has been around for a very long time, and compiled Java applications have almost always been run on the Oracle Java Virtual Machine (JVM). In that scenario, Java source code gets compiled down to .class files. One .class file gets created for every Java class, interface, or enum in your source code: javac .java The .class files contain Java bytecodes, which can be read and executed by the JVM. The JVM is a software emulation of a Central Processing Unit (CPU), like the chip at the heart of your development machine. But because it’s emulated, it can be made to work on almost any device. That’s why Java code is designed to be written once, run anywhere. So is that what happens on Android devices? Well…not quite. The Android Runtime performs the same kind of job as the JVM, but it does it in a very different way. 842 Appendix iii .class ART ART compiles code into DEX files When you do Android development, your Java source code is compiled down into .dex files. A .dex file serves a similar purpose to a .class file, because it contains executable bytecodes. But instead of being JVM bytecodes, they are in a different format called Dalvik. DEX stands for Dalvik EXecutable. Rather than creating a .dex file for each and every class file, your app will normally be compiled down into a single file called classes.dex. That single .dex file will contain bytecodes for all of your source code, and for every library in your app. .class classes.dex The DEX format can only cope with 65,535 methods, so if your app contains a lot of code or some large libraries, your file might need to be converted into multiple .dex files: .class .dex You can find out more about creating multi-DEX apps here: https://developer.android.com/studio/build/multidex.html. you are here 4 843 .class to .dex How DEX files get created When Android builds your app, it uses a tool called dx, which stitches .class files into a DEX file: .class dx .dex Libraries It may seem a bit weird that the compilation process involves two stages of compilation: first to .class files, and then from .class files to the DEX format. Why doesn’t Google just create a single tool that goes straight from .java source code to DEX bytecodes? For a while Google was developing a compiler called JACK and an associated linker called JILL that could create DEX code straight from Java code, but there was a problem. Some Java tools don’t just work at the source code level; they work directly with .class files, and modify the code that those files contain. For example, if you use a code coverage tool to see which code your tests are actually running, the coverage tool will likely want to modify the contents of the generated .class files to add in additional bytecodes to keep track of the code as it’s executed. If you used the JACK compiler, no .class files were generated. So back in March 2017, Google announced that it was shelving JACK, and was putting all of its efforts into making the dx tool work really well with the latest Java .class formats. This has the additional advantage that any new Java language features—so long as they don’t require new Java byte codes—will automatically be supported by Android. 844 Appendix iii ART DEX files are zipped into APK files But Android apps aren’t shipped around as .dex files. There’s a whole bunch of other files that make up your app: images, sounds, metadata, and so on. All of these resources and your DEX bytecode is wrapped up into a single zip file called an Android Package or .apk file. The .apk file is created by another tool called the Android Asset Packing Tool, or aapt. classes.dex aapt .apk Resources When you download an app from the Google Play Store, what actually gets downloaded is an APK file. In fact, when you run your app from Android Studio, the build system will first build an .apk file and then install it onto your Android device. You may need to sign the .apk file If you’re going to distribute your app through the Google Play Store, you will need to sign it. Signing an app package means that you store an additional file in the .apk that is based on a checksum of the contents of the .apk and a separately generated private key. The .apk file uses the standard jarsigner tool that comes as part of Oracle’s Java Development Kit. The jarsigner tool was created to sign .jar files, but it will also work with .apk files. If you sign the .apk file, you will then also need to run it through a tool called zipalign, which will make sure that the compressed parts of the file are lined up on byte boundaries. Android wants them byte-aligned so it can read them easily without needing to uncompress the file. jarsigner zipalign .apk signed .apk Android Studio will do all of this for you if you choose Generate Signed APK from the Build menu. So that’s how your app gets converted from Java source code into an installable file. But how does it get installed and run on your device? you are here 4 845 hello adb Say hello to the Android Debug Bridge (adb) All communication between your development machine and your Android device takes place over the Android Debug Bridge. There are two sides to the bridge: a command-line tool on your dev machine called adb, and a daemon process on your Android device called adbd (the Android Debug Bridge Daemon). The adb command on your development machine will run a copy of itself in the background, called the ADB server. The server receives commands over network port 5037, and sends them to the adbd process on the device. If you want to copy or read a file, install an app, or look at the logcat information for an app, then all of this information passes back and forth across the debug bridge. So when the build system wants to install your APK file, it does so by sending a command like this to the debug bridge: adb install bitsandpizzas.apk The file will then be transferred to a virtual device, or over a USB cable to a real device, and be installed into the /data/app/ directory, where it will sit waiting for the app to be run. How apps come alive: running your APK file When your app is run, whether it’s been started by a user pressing its launch icon or because the IDE has started it, the Android device needs to turn that .apk file into a process running in memory. It does this using a process called Zygote. Zygote is like a halfstarted process. It’s allocated some memory and already contains the main Android libraries. It has everything it needs, in fact, except for the code that’s specific to your app. When Android runs your app, it first creates a copy (a.k.a. fork) of the Zygote process, and then tells the copied process to load your application code. So why does Android leave this half-started process hanging around? Why not just start a fresh process from scratch for each app? It’s because of performance. It can take Android a long time to create a new process from scratch, but it can fork a copy of an existing process in a split second. The Zygote process is a mostly started Android app. 846 Appendix iii Zygote process fork() App process The new app process will be a complete copy of the Zygote process. ART Android converts the .dex code to OAT format The new app process now needs to load the code that’s specific to your app. Remember, your app code is stored in the classes.dex file inside your .apk package. So the classes.dex file is extracted from the .apk and placed into a separate directory. But rather than simply use the classes.dex file, Android will convert the Dalvik byte codes in classes.dex into native machine code. Technically, the classes.dex will be converted into an ELF shared object. Android calls this library format OAT, and calls the tool that converts the classes.dex file dex2oat. dex2oat classes.dex classes.dex (OAT version) The converted file is stored into a directory named something like this: /data/dalvik-cache/data@app@com.hfad.bitsandpizzas@base.apk@classes.dex This file can then be loaded as a native library by the application process, and the app appears on the screen. you are here 4 847 appendix iv: adb The Android Debug Bridge What better gift for the girl who has everything than a new command-line tool? Why, darling, that’s so thoughtful. In this book, we’ve focused on using an IDE for all your Android needs. But there are times when using a command-line tool can be plain useful, like those times when Android Studio can’t see your Android device but you just know it’s there. In this chapter, we’ll introduce you to the Android Debug Bridge (or adb), a command-line tool you can use to communicate with the emulator or Android devices. this is an appendix 849 adb adb: your command-line pal Every time your development machine needs to talk to an Android device, whether it’s a real device connected with a USB cable, or a virtual device running in an emulator, it does so by using the Android Debug Bridge (adb). The adb is a process that’s controlled by a command that’s also called adb. The adb command is stored in the platform-tools directory of the Android System Developer’s Kit on your computer. If you add the platform-tools directory to your PATH, you will be able to run adb from the command line. In a terminal or at a command prompt, you can use it like this: Interactive Session $ adb devices List of devices attached emulator-5554 device $ The adb devices command means “Tell me which Android devices you are connected to.” The adb command works by talking to an adb server process, which runs in the background. The adb server is sometimes called the adb dæmon or adbd. When you enter an adb command in a terminal, a request is sent to network port 5037 on your machine. The adbd listens for commands to come in on this port. When Android Studio wants to run an app, or check the log output, or do anything else that involves talking to an Android device, it will do it via port 5037. adb adbd adb command adb daemon process Device Device When the adbd receives a command, it will forward it to a separate adbd process that’s running in the relevant Android device. This process will then be able to make changes to the Android device or return the requested information. 850 Appendix iv adb Sometimes, if the adb server isn’t running, the adb command will need to start it: Interactive Session $ adb devices * daemon not running. starting it now on port 5037 * * daemon started successfully * List of devices attached emulator-5554 device $ Likewise, if ever you plug in an Android device and Android Studio can’t see it, you can manually kill the adb server and restart it: Interactive Session $ adb devices List of devices attached $ adb kill-server $ adb start-server * daemon not running. starting it now on port 5037 * * daemon started successfully * $ adb devices List of devices attached emulator-5554 device $ By killing and restarting the server, you force adb to get back in touch with any connected Android devices. you are here 4 851 shell Running a shell Most of the time you won’t use adb directly; you’ll let an IDE like Android Studio do the work for you. But there are times when it can be useful to go to the command line and interact with your devices directly. One example is if you want to run a shell on your device: Interactive Session $ adb shell root@generic_x86:/ # The adb shell command will open up an interactive shell directly on the Android device. If you have more than one device attached, you can indicate which device you mean with the -s option, followed by the name given by the adb devices command. For example, adb -s emulator-5554 shell will open a shell on the emulator. Once you open a shell to your device, you can run a lot of the standard Linux commands: Interactive Session $ adb shell root@generic_x86:/ # ls acct cache charger config d data default.prop dev etc file_contexts .... 1|root@generic_x86:/ # df Filesystem Size /dev 439.8M /mnt/asec 439.8M /mnt/obb 439.8M /system 738.2M /data 541.3M /cache 65.0M /mnt/media_rw/sdcard 196.9M /storage/sdcard 196.9M root@generic_x86:/ # 852 Appendix iv Used 60.0K 0.0K 0.0K 533.0M 237.8M 4.3M 4.5K 4.5K Free 439.8M 439.8M 439.8M 205.2M 303.5M 60.6M 196.9M 196.9M Blksize 4096 4096 4096 4096 4096 4096 512 512 adb Useful shell commands If you open a shell to Android, you have access to a whole bunch of command line tools. Here are just a few: Command Description Example (and what it does) pm Package management tool. pm list packages (this lists all installed apps) pm path com.hfad.bitzandpizzas (find where an app is installed) pm –help (show other options) ps Process status. ps (lists all processes and their IDs) dexdump Display details of an APK. dexdump -d /data/app/com.hfad. bitzandpizzas-2/base.apk (disassemble an app) lsof List a process’s open files and other connections. lsof -p 1234 (show what process with id 1234 is doing) screencap Take a screenshot. screencap -p /sdcard/screenshot.png (save top Show busiest processes. top -m 5 (show the top five processes) current screenshot to /sdcard/screenshot.png, and get it off the device with adb pull /sdcard/screenshot.png) Each of these examples works from an interactive shell prompt, but you can also pass them direct to the shell command from your development machine. For example, this command will show the apps installed on your device: Interactive Session $ adb shell pm list packages you are here 4 853 get output Kill the adb server Sometimes the connection can fail between your development machine and your device. If this happens, you can reset the connection by killing the adb server: Interactive Session $ adb kill-server The next time you run an adb command, the server will restart and a fresh connection will be made. Get the output from logcat All of the apps running on your Android device send their output to a central stream called the logcat. You can see the live output from the logcat by running the adb logcat command: Interactive Session $ adb logcat --------- beginning of system I/Vold ( 936): Vold 2.1 (the revenge) firing up D/Vold ( 936): Volume sdcard state changing -1 (Initializing) -> 0 (No-Media) W/DirectVolume( 936): Deprecated implied prefix pattern detected, please use ‘/devices/platform/goldfish_mmc.0*’ instead ... The logcat output will keep streaming until you stop it. It can be useful to run adb logcat if you want to store the output in a file. The adb logcat command is used by Android Studio to produce the output you see in the Devices/logcat panel. 854 Appendix iv adb Copying files to/from your device The adb pull and adb push commands can be used to transfer files back and forth. For example, here we are copying the /default.prop/ properties file into a local file called 1.txt: Interactive Session $ adb pull /default.prop 1.txt 28 KB/s (281 bytes in 0.009s) $ cat 1.txt # # ADDITIONAL_DEFAULT_PROPERTIES # ro.secure=0 ro.allow.mock.location=1 ro.debuggable=1 ro.zygote=zygote32 dalvik.vm.dex2oat-Xms=64m dalvik.vm.dex2oat-Xmx=512m dalvik.vm.image-dex2oat-Xms=64m dalvik.vm.image-dex2oat-Xmx=64m ro.dalvik.vm.native.bridge=0 persist.sys.usb.config=adb $ And much, much more... There are many, many commands that you can run using adb: you can back up and restore databases (very useful if you need to debug a problem with a database app), start the adb server on a different port, reboot machines, or just find out a lot of information about the running devices. To find out all the options available, just type adb on the command line: Interactive Session $ adb Android Debug Bridge version 1.0.32 -a - directs adb to listen on all interfaces for a connection -d - directs command to the only connected USB device returns an error if more than one USB device is present. -e - directs command to the only running emulator. returns an error if more than one emulator is .... you are here 4 855 appendix v: the android emulator Speeding Things Up Make it go faster! Ever felt like you were spending all your time waiting for the emulator? There’s no doubt that using the Android emulator is useful. It allows you to see how your app will run on devices other than the physical ones you have access to. But at times it can feel a little...sluggish. In this appendix, we’ll explain why the emulator can seem slow. Even better, we’ll give you a few tips we’ve learned for speeding it up. this is an appendix 857 speed Why the emulator is so slow When you’re writing Android apps, you’ll spend a lot of time waiting for the Android emulator to start up or deploy your code. Why is that? Why is the Android emulator so sloooooow? If you’ve ever written iPhone code, you know how fast the iPhone simulator is. If it’s possible for the iPhone, then why not for Android? There’s a clue in the names: the iPhone simulator and the Android emulator. The iPhone simulator simulates a device running the iOS operating system. All of the code for iOS is compiled to run natively on the Mac and the iPhone simulator runs at Macnative speed. That means it can simulate an iPhone boot-up in just a few seconds. The Android emulator works in a completely different way. It uses an open source application called QEMU (or Quick Emulator) to emulate the entire Android hardware device. It runs code that interprets machine code that’s intended to be run by the device’s processor. It has code that emulates the storage system, the screen, and pretty much every other piece of physical equipment on an Android device. AVD AVD AVD AVD AVD QEMU Emulator An emulator like QEMU creates a much more realistic representation of a virtual device than something like the iPhone simulator does, but the downside is that it has to do far more work for even simple operations like reading from disk or displaying something on a screen. That’s why the emulator takes so long to boot up a device. It has to pretend to be every little hardware component inside the device, and it has to interpret every single instruction. 858 Appendix v All the Android Virtual Devices run on an emulator called QEMU. android emulator How to speed up your Android development 1. Use a real device The simplest way to speed up your development process is by using a real device. A real device will boot up much faster than an emulated one, and it will probably deploy and run apps a lot more quickly. If you want to develop on a real device, you may want to go into “Developer options” and check the Stay Awake option. This will prevent your device from locking the screen, which is useful if you are repeatedly deploying to it. 2. Use an emulator snapshot Booting up is one of the slowest things the emulator does. If you save a snapshot of the device while it’s running, the emulator will be able to reset itself to this state without having to go through the boot-up process. To use a snapshot with your device, open the AVD manager from the Android Studio menu by selecting Tools→Android→AVD Manager, edit the AVD by clicking on the Edit symbol, and then check the “Store a snapshot for faster startup” option. This will save a snapshot of what the memory looks like when the device is running. The emulator will be able to restore the memory in this state without booting the device. 3. Use hardware acceleration By default, the QEMU emulator will have to interpret each machine code instruction on the virtual device. That means it’s very flexible because it can pretend to be lots of different CPUs, but it’s one of the main reasons why the emulator is slow. Fortunately, there’s a way to get your development machine to run the machine code instructions directly. There are two main types of Android Virtual Device: ARM machines and x86 machines. If you create an x86 Android device and your development machine is using a particular type of Intel x86 CPU, then you can configure your emulator to run the Android machine code instructions directly on your Intel CPU. You will need to install Intel’s Hardware Accelerated Execution Manager (HAXM). At the time of writing , you can find HAXM here: https://software.intel.com/en-us/android/articles/intel-hardware-acceleratedexecution-manager HAXM is a hypervisor. That means it can switch your CPU into a special mode to run virtual machine instructions directly. However, HAXM will only run on Intel processors that support Intel Virtualization Technology. But if your development machine is compatible, then HAXM will make your AVD run much faster. If it’s moved, a quick web search should track it down. you are here 4 859 instant run 4. Use Instant Run Since Android Studio 2.3, it’s been possible to redeploy apps much more quickly using the Instant Run utility. This utility allows Android Studio to recompile just the files that have changed, and then patch the application currently running on the device. This means that instead of waiting for a minute or so while the application is recompiled and deployed, you can see your changes running in just a few seconds. To use Instant Run, click on the Apply Changes option in the Run menu, or click on the lightning bolt icon in the toolbar: Click on this button in the toolbar to apply changes using Instant Run. There are a couple of conditions on using Instant Run. First, your app’s target needs to be at least API 15. Second, you need to deploy to a device that runs API 21 or above. So long as you satisfy both these conditions, you’ll find that Instant Run is by far the fastest way of getting your code up and running. 860 Appendix v appendix vi: leftovers The Top Ten Things (we didn’t cover) Oh my, look at the tasty treats we have left... Even after all that, there’s still a little more. There are just a few more things we think you need to know. We wouldn’t feel right about ignoring them, and we really wanted to give you a book you’d be able to lift without extensive training at the local gym. Before you put down the book, read through these tidbits. this is an appendix 861 distribution 1. Distributing your app Once you’ve developed your app, you’ll probably want to make it available to other users. You’ll likely want to do this by releasing your app through an app marketplace such as Google Play. There are two stages to this process: preparing your app for release, and then releasing it. Preparing your app for release Before you can release your app, you need to configure, build, and test a release version of it. This includes tasks such as deciding on an icon for your app and modifying AndroidManifest.xml so that only devices that are able to run your app are able to download it. Before you release your app, make sure that you test it on at least one tablet and one phone to check that it looks the way you expect and its performance is acceptable. You can find further details of how to prepare your app for release here: http://developer.android.com/tools/publishing/preparing.html Releasing your app This stage includes publicizing your app, selling it, and distributing it. To release your app on the Play Store, you need to register for a publisher account and use the Developer Console to publish your app. You can find further details here: http://developer.android.com/distribute/googleplay/start.html For ideas on how to best target your app to your users and build a buzz about it, we suggest you explore the documents here: http://developer.android.com/distribute/index.html 862 appendix vi leftovers 2. Content providers You’ve seen how to use intents to start activities in other apps. As an example, you can start the Messaging app to send the text you pass to it. But what if you want to use another app’s data in your own app? For example, what if you want to use Contacts data in your app to perform some task, or insert a new Calendar event? You can’t access another app’s data by interrogating its database, Instead, you use a content provider, which is an interface that allows apps to share data in a controlled way. It allows you to perform queries to read the data, insert new records, and update or delete existing records. If you want to access another app’s database, you have to go through me. YourActivity Content Provider Database If you want other apps to use your data, you can create your own content provider. You can find out more about the concept of content providers here: http://developer.android.com/guide/topics/providers/content-providers.html Here’s a guide on using Contacts data in your app: http://developer.android.com/guide/topics/providers/contacts-provider.html And here’s a guide on using Calendar data: http://developer.android.com/guide/topics/providers/calendar-provider.html you are here 4 863 loaders and sync adapters 3. Loaders If you do a lot of work with databases or content providers, sooner or later you’ll encounter loaders. A loader helps you load data so that it can be displayed in an activity or fragment. Loaders run on separate threads in the background, and make it easier to manage threads by providing callback methods. They persist and cache data across configuration changes so that if, for example, the user rotates their device, the app doesn’t create duplicate queries. You can also get them to notify your app when the underlying data changes so that you can deal with the change in your views. The Loader API includes a generic Loader class, which is the base class for all loaders. You can create your own loader by extending this class, or you can use one of the built-in subclasses: AsyncTaskLoader or CursorLoader. AsyncTaskLoader uses an AsyncTask, and CursorLoader loads data from a content provider. You can find out more about loaders here: https://developer.android.com/guide/components/loaders.html 4. Sync adapters Sync adapters allow you to synchronize data between an Android device and a web server. This allows you to do things like back up the user’s data to a web server, for instance, or transfer data to a device so that it can be used offline. Sync adapters have a number of advantages over designing your own data transfer mechanism. ¥ hey allow you to automate data transfer based on specific criteria—for T example, time of day or data changes. ¥ hey automatically check for network connectivity, and only run when T the device has a network connection. ¥ ync adapter–based data transfers run in batches, which helps improve S battery performance. ¥ They let you add user credentials or a server login to the data transfer. You can find out how to use sync adapters here: https://developer.android.com/training/sync-adapters/index.html 864 appendix vi leftovers 5. Broadcasts Suppose you want your app to react in some way when a system event occurs. You may, for example, have built a music app, and you want it to stop playing music if the headphones are removed. How can your app tell when these events occur? Android broadcasts system events when they occur, including things like the device running low on power, a new incoming phone call, or the system getting booted. You can listen for these events by creating a broadcast receiver. Broadcast receivers allow you to subscribe to particular broadcast messages so that your app can respond to particular system events. Hey, Activity, Android says the battery’s running low on power. The battery’s running low, in case anyone’s interested. Android Broadcast Receiver OK, I’ll hold off on expensive tasks for now. YourActivity Your app can also send custom broadcast messages to notify other apps of events. You can find out more about broadcasts here: https://developer.android.com/guide/components/broadcasts.html you are here 4 865 WebView 6. The WebView class If you want to provide your users with access to web content, you have two options. The first option is to open the web content with an external app like Chrome or Firefox. The second option is to display the content inside your app using the WebView class. The WebView class allows you to display the contents of a web page inside your activity’s layout. You can use it to deliver an entire web app as a client application, or to deliver individual web pages. This approach is useful if there’s content in your app you might need to update, such as an end-user agreement or user guide. You add a WebView to your app by including it in your layout: You then tell it which web page it should load by calling its loadUrl() method: WebView webView = (WebView) findViewById(R.id.webview); webView.loadUrl("http://www.oreilly.com/"); You also need to specify that the app must have Internet access by adding the INTERNET permission to AndroidManifest.xml: You can find out more about using web content in your apps here: http://developer.android.com/guide/webapps/index.html 866 appendix vi leftovers 7. Settings Many apps include a settings screen so that the user can record their preferences. As an example, an email app may allow the user to specify whether they want to see a confirmation dialog before sending an email: This is the settings screen for the Gmail app. You build a settings screen for your app using the Preferences API. This allows you to add individual preferences, and record a value for each preference. These values are recorded in a shared preferences file for your app. You can find out more about creating settings screens here: https://developer.android.com/guide/topics/ui/settings.html you are here 4 867 animation 8. Animation As Android devices increasingly take advantage of the power of their built-in graphics hardware, animation is being used more and more to improve the user’s app experience. There are several types of animation that you can perform in Android: Property animation Property animation relies on the fact that the visual components in an Android app use a lot of numeric properties to describe their appearance. If you change the value of a property like the height or the width of a view, you can make it animate. That's what property animation is: smoothly animating the properties of visual components over time. View animations A lot of animations can be created declaratively as XML resources. So you can have XML files that use a standard set of animations (like scaling, translation, and rotation) to create effects that you can call from your code. The wonderful thing about declarative view animations is that they are decoupled from your Java code, so they are very easy to port from one app project to another. Activity transitions Let’s say you write an app that displays a list of items with names and images. You click on an item and you’re taken to a detail view of it. The activity that shows you more detail will probably use the same image that appeared in the previous list activity. Activity transitions allow you to animate a view from one activity that will also appear in the next activity. So you can make an image from a list smoothly animate across the screen to the position it takes in the next activity. This will give your app a more seamless feel. To learn more about Android animations see: https://developer.android.com/guide/topics/graphics/index.html To learn about activity transitions and material design, see: https://developer.android.com/training/material/animations.html 868 appendix vi leftovers 9. App widgets An app widget is a small application view that you can add to other apps or your home screen. It gives you direct access to an app’s core content or functionality from your home screen without you having to launch the app. Here’s an example of an app widget: This is an app widget. It gives you direct access to the app’s core functionality without you having to launch the app. To find out how you create your own app widgets, look here: http://developer.android.com/guide/topics/appwidgets/index.html you are here 4 869 testing 10. Automated testing All modern development relies heavily on automated testing. If you create an app that’s intended to be used by thousands or even millions of people, you will quickly lose users if it’s flaky or keeps crashing. There are many ways to automatically test your app, but they generally fall into two categories: unit testing and on-device testing (sometimes called instrumentation testing). Unit tests Unit tests run on your development machine, and they check the individual pieces—or units—of your code. The most popular unit testing framework is JUnit, and Android Studio will probably bundle JUnit into your project. Unit tests live in the app/src/test folder of your project, and a typical test method looks something like this: @Test public void returnsTheCorrectAmberBeers() { BeerExpert beerExpert = new BeerExpert(); assertArrayEquals(new String[]{"Jack Amber", "Red Moose"}, } beerExpert.getBrands("amber").toArray()); You can find more out about JUnit here: http://junit.org 870 appendix vi leftovers On-device tests On-device tests run inside an emulator or physical device and check the fully assembled app. They work by installing a separate package alongside your app that uses a software layer called instrumentation to interact with your app in the same way as a user. An increasingly popular framework for on-device testing is called Espresso, and Android Studio will probably bundle it in to your project. On-device tests live in the app/src/androidTest folder, and Espresso tests look something like this: @Test public void ifYouDoNotChangeTheColorThenYouGetAmber() { onView(withId(R.id.find_beer)).perform(click()); onView(withId(R.id.brands)).check(matches(withText( } "Jail Pale Ale\nGout Stout\n"))); When you run an on-device test, you will see the app running on your phone or tablet and responding to keypresses and gestures just as if a person were using it. You can find out more about Espresso testing here: https://developer.android.com/training/testing/ui-testing/espresso-testing.html you are here 4 871 Index A action bar, 314. See also app bar ActionBarActivity class, 298 ActionBar class, 306, 314 ActionBarDrawerToggle class, 607 actions about, 100 in app bar adding, 315–317, 319–325 icons for, 320, 322 menu for, 321, 323 method for, 324–325 title for, 320, 322 category specified with, 106, 117 determining activities for, 105–106 share actions, 332–335 specifying in an intent, 100–104 types of, 101 activities about, 2, 12, 31, 38, 120–121 backward compatibility for, 295, 298 calling custom Java classes from, 71–74 calling methods from, 59–62 class hierarchy for, 139, 297 class name for, 85 converting to fragments, 438–449 creating, 13–14, 61, 67–68 declaring in manifest file, 85 default, 41 editing. See code editor; design editor location in project, 17 main, specifying, 84, 120 multiple chaining, 78 creating, 78–83 passing text to, 90–95 retrieving text from, 96–97 starting with intents, 86–89 navigating. See navigation organizing, 249–255, 290–291 category activities, 249–250, 252–255, 267–268, 277–278 detail/edit activities, 249–250, 253–255, 279–283 top-level activities, 249–250, 252–255 in other apps determining from actions, 105–106 none available, 117 starting with intents, 99–104 users always choosing, 112–117 users choosing default, 111 users choosing if multiple, 100, 104, 111 parent activity, 328 states of. See activity lifecycle Activity class, 61, 139, 297 activity lifecycle about, 137–138, 146–147 class hierarchy for, 139 compared to fragment lifecycle, 439 methods associated with, 137–139, 147, 167, 439 states in based on app’s focus, 154–157 based on app’s visibility, 146–147 list of, 137–138, 146–147 saving and restoring, 140–141, 145 ActivityNotFoundException, 103, 117 activity transitions, 868 adapters array. See ArrayAdapter class decoupling, with interface, 572–574 recycler view. See recycler view adapter sync adapters, 864 AdapterView class, 261, 269 adb (Android Debug Bridge) about, 846, 850–851 copying files, 855 killing adb server, 854 logcat output, 854 running a shell, 852–853 this is the index 873 the index ADD COLUMN command, 650 addDrawerListener() method, DrawerLayout, 607 ALTER TABLE command, 649 Android application package. See APK files Android apps. See also projects about, 120–121 activities in. See activities adapting for different device sizes, 340–341, 394–398, 402–412 app bar for. See app bar back button functionality for, 413–415 building, 7–10, 13–15, 39 configuration of, affected by rotation, 134–136, 145, 156 designing, 248–250 devices supported by, 10, 340–341. See also fragments distributing, 862 examples of. See examples focus/visibility of, 146–147, 154–157 icons for, 84, 299 Java for. See Java languages used in, 4 layouts for. See layouts manifest file for. See AndroidManifest.xml file minimum SDK for, 10–11 navigating. See navigation package name for, 9, 84 processes used by, 120–121, 133 resource files for. See resource files running activities in other apps, 100–108 running in emulator, 23–30, 49, 117 running on physical devices, 109–111, 117 settings screen for, 867 structuring, 249–250, 252–255, 290–291 theme for. See themes Android debug bridge. See ADB Android devices about, 2 adapting apps for different sizes of, 340–341, 394–398, 402–412. See also fragments API level on. See Android SDK (API level) rotation of configuration affected by, 134–136, 145, 156 saving activity state for, 140–141 saving fragment state for, 427–431 running apps on, 109–111, 117 virtual, for testing. See AVD (Android virtual device) 874 index Android emulator about, 23 performance of, 858–860 running apps in, 23–30, 49, 117 AndroidManifest.xml file about, 17, 84–85 activity hierarchy in, 328 app bar attributes in, 299–301, 318–319 intent filters in, 105–106 language settings in, 172 main activity in, 120, 132 permissions needed in, 791 screen size settings in, 403 started services in, 745 themes in, 589 Android platform about, 2–3 versions of. See Android SDK (API level) Android Runtime (ART), 3, 30, 842–843 Android SDK (API level) about, 5 features specific to, 171, 173, 293, 297, 298, 314, 328 libraries for, 16, 294 list of, 11 permissions affected by, 791, 794 specifying for AVD, 25, 400 specifying minimum for apps, 10 Android Studio about, 5, 7 alternatives to, 7 console in, 28 editors. See code editor; design editor installing, 6 projects, creating, 8–10 system requirements, 6 Android virtual device. See AVD animated toolbar. See collapsing toolbar; scrolling toolbar animation, 868 API level. See Android SDK (API level) APK files, 27, 30, 845–846 app bar about, 291–293 activity titles in, 317 adding actions to, 315–317, 319–325 attributes in manifest file for, 299–301, 318–319 icon in, 299 label for, 299, 318–319 the index removing, 308 replacing with toolbar, 292, 306–313 sharing content on, 331–335 tabs in, 499–500 themes required for, 293, 296–298, 299–300 Up button for, 327–330 AppBarLayout element, 499–500, 518–519 AppCompatActivity class, 297–298, 307 AppCompat Library. See v7 AppCompat Library app folder, 17 application framework, 3. See also APIs application name, 9 applications, core. See core applications app_name string resource, 51 apps. See Android apps AppTheme attribute, 589–590 app widgets, 869 ArrayAdapter class, 269–271, 275, 287, 375–377 @array reference, 57 array resources, 56–57, 210, 259 ART (Android Runtime), 3, 30, 842–843 AsyncTask class, 724, 729, 731, 737 ?attr reference, 309 automated testing, 870 AVD (Android virtual device) about, 23 compared to design editor, 49 creating for smartphone, 24–26 creating for tablet, 399–401 AWT, 4 B Back button compared to Up button, 327 enabling, 413–415 background, services running in. See services background thread, 720–731, 736 back stack, 414–415 backward compatibility, for activities, 295, 298 Bates, Bert (Head First Java), 4 Beer Adviser app, 38–76 activities, 61–69 button, 59–60 Java class, 70–74 layout, 41–48 project, 40 spinner, 56–58 String resources, 50–54 Beighley, Lynn (Head First SQL), 675 biases for views, 231 Binder class, 786 bindService() method, activity, 778 blueprint tool about, 226 aligning views, 238 biases, setting, 231 centering views, 230 horizontal constraints, 227 inferring contraints, 240–241, 245 margins, setting, 228 size of views, changing, 232–233 vertical constraints, 228 view properties, editing, 241 widgets, adding, 226, 240, 242–243 books and publications Head First Java (Sierra; Bates), 4 Head First SQL (Beighley), 675 bound services about, 740, 768 binding to an activity, 771, 774–778 calling methods from, 780–785 in combination with started services, 786 compared to started services, 786 creating, 770, 772 displaying results of, 773 lifecycle of, 787–788 other apps using, 786 broadcasts, 865 build folder, 17 build.gradle files, 834–835 built-in services about, 740 location. See Location Services notification. See notification service Bundle class about, 145 restoring state data from, 141, 144 saving state data in, 140, 143 Button element. See also FAB (floating action button) about, 203 adding, 43 you are here � 875 the index calling methods in activity, 60–62 in fragment, 452–460 code for, 44–47, 80 images added to, 213 widget for, in blueprint tool, 226 C CalledFromWrongThreadException, 127 cardCornerRadius attribute, card view, 543 cardElevation attribute, card view, 543 CardView element, 543 CardView Library, 294, 542 card views about, 542 adding data to, 545, 550 creating, 543–544 displaying in recycler view, 558, 560 CatChat app, 581–620 about, 581–584 Drafts option, 586 Feedback option, 592 header for, 594–595 Help option, 591 Inbox option, 585 menu for, 596–601 SentItems option, 587 Trash option, 588 category activities, 249–250, 252–255, 267–268, 277– 278, 290 centering views, 230 chaining activities, 78 changeCursor() method, cursor adapter, 715–717 checkableBehavior attribute, 598 CheckBox element, 206–207, 696–700 checkSelfPermission() method, ContextCompat, 794, 800 check task, Gradle, 837 choosers, 112–117 classes. See Java classes clean task, Gradle, 837 closeDrawer() method, DrawerLayout, 614 close() method, cursor, 672, 683 close() method, SQLiteDatabase, 672, 683 876 index code editor about, 18, 32 activity file in, 21–22 layout file in, 19–20, 41, 44–46 collapsing toolbar, 507, 517–525 CollapsingToolbarLayout element, 518–521 color resources, 304 colors, for themes, 303 columnCount attribute, GridLayout, 823 company domain, 9 constraint layout about, 223 aligning views, 238 alternatives to, 245 biases, setting, 231 centering views, 230 horizontal constraints, adding, 227 inferring contraints, 240–241, 245 library for, 224 margins, setting, 228–229 size of views, changing, 232–233 specifying in main activity, 225 vertical constraints, adding, 228 widgets, adding, 226, 240, 242–243 XML code for, 229 Constraint Layout Library, 224, 294 contentDescription attribute, ImageView, 212 content providers, 863 contentScrim attribute, CollapsingToolbarLayout, 523 ContentValues object, 632, 645 Context class, 139, 752 ContextThemeWrapper class, 139 ContextWrapper class, 139, 752 CoordinatorLayout element, 508–511, 518, 527 core applications, 3 core libraries, 3. See also libraries createChooser() method, Intent, 112–117 CREATE TABLE command, 631 Cursor class, 624 cursors, SQLite about, 624, 663 adapter for, 679–681, 688–689, 715 closing, 672, 682–683 creating, 663–666, 678 navigating to records in, 670–671 the index refreshing, 714–718 retrieving values from, 672–674 custom Java classes. See Java classes drop-down list. See Spinner element DROP TABLE command, 650 dynamic fragments. See fragments: dynamic D E data adapters for. See adapters from other apps, content providers for, 863 loaders for, 864 sharing with other apps. See share action provider storing as string resources, 259 storing in classes, 256, 268–271, 360 web content, 866 database. See SQLite database delete() method, SQLiteDatabase, 647 density-independent pixels (dp), 171 design editor. See also blueprint tool about, 18, 32, 42 adding GUI components, 43 compared to AVD, 49 XML changes reflected in, 48 Design Support Library about, 294, 506 AppBarLayout element, 499–500 collapsing toolbar, 517–525 FAB (floating action button), 507, 526–529 navigation drawers, 584 scrolling toolbar, 508–515 snackbar, 507, 526, 530–533 TabLayout element, 499, 501 detail/edit activities, 249–250, 253–255, 279–283, 290 development environment. See Android SDK; Android Studio devices. See Android devices DEX files, 843–845, 847 dimension resources, 174 dimens.xml file, 174 distributing apps, 862 doInBackground() method, AsyncTask, 724, 726, 731 dp (density-independent pixels), 171 drawable attributes, Button, 213 @drawable reference, 212 drawable resource folders. See image resources DrawerLayout element, 602–603. See also navigation drawers editors. See blueprint tool; code editor; design editor EditText element about, 80, 178, 202 code for, 80 hint, 178 email app example. See CatChat app email grid layout example, 825–832 emulator. See Android emulator entries attribute, ListView, 259 entries attribute, Spinner, 57, 210 Espresso tests, 871 event listeners about, 199 for card views, 572–576 compared to onClick attribute, 264 for ListView element, 261–263 for fragments, 455–460 for list fragments, 385–387 for ListView element, 708, 710–712 location listeners, 792 for navigation drawers, 608 for snackbar actions, 530 examples Beer Adviser app. See Beer Adviser app CatChat app. See CatChat app email grid layout, 825–832 Joke app. See Joke app My Constraint Layout app. See My Constraint Layout app My First App. See My First App My Messenger app. See My Messenger app Odometer app. See Odometer app Pizza app. See Pizza app source code for, xxxvi Starbuzz Coffee app. See Starbuzz Coffee app Stopwatch app. See Stopwatch app Workout app. See Workout app execSQL() method, SQLiteDatabase, 631, 650 execute() method, AsyncTask, 731 you are here � 877 the index F FAB (floating action button), 507, 526–529 findViewById() method, view, 63–64, 68 FloatingActionButton element, 527 focus activities having, 154–157 views handling, 199 FragmentActivity class, 354 fragment element, 352, 463 fragment lifecycle, 365–366, 439 fragment manager, 362, 419 FragmentPagerAdapter class, 491–493 fragments about, 342 activities using adding fragment to, 352–353 button linking main to detail activity, 346–347 creating, 345 interactions with, 359–369 referencing fragment from, 363, 367 setting values for, 367–369 adding to project, 348–349 app structure for, 343–344 back button functionality with, 413–415 code for, 349–350 converting activities to, 438–449 data for, 360 dynamic about, 434 adding, 435–449, 469–477 calling methods from, 450–460 code for, 440–446, 458–460 layout for, 447–449 ID for, 361–362 layouts for, 349–351, 408–410 list fragments connecting data to, 375–377 connecting to detail activity, 384–389 creating, 372–374 displaying in main activity, 378–381 listener for, in main activity, 385–387 methods associated with, 365–366, 439 nested, 474–477 replacing programmatically, 416–425 state of, saving, 427–431, 464–467 878 index swiping through, 489–493 for tab navigation, 483–488 v7 AppCompat Library for, 345, 354 view for, creating, 350 FragmentStatePagerAdapter class, 491, 493 fragment transactions, 419–423, 463–467, 472–475 FrameLayout element about, 188–190, 193 gravity for view placement, 190 height, 188 nesting, 191–192 order of views in, 190 replacing fragments programmatically using, 416–425, 464–467, 471 width, 188 G getActivity() method, PendingIntent, 758 getBestProvider() method, LocationManager, 793 getCheckedRadioButtonId() method, RadioGroup, 208 getChildFragmentManager() method, fragment, 474– 475 getContext() method, LayoutInflator, 376 getCount() method, FragmentPagerAdapter, 491–492 getFragmentManager() method activity, 362 fragment, 472–473 getIntent() method, activity, 92, 96, 280 getIntExtra() method, Intent, 92 getItemCount() method, RecyclerView.Adapter, 547 getItem() method, FragmentPagerAdapter, 491–492 get*() methods, cursor, 672 getReadableDatabase() method, SQLiteOpenHelper, 662, 678 getSelectedItem() method, view, 67, 69, 210 getStringExtra() method, Intent, 92, 96 getString() method, string resource, 115 getSupportActionBar() method, activity, 329 getSupportFragmentManager() method, activity, 362 getText() method, EditText, 202 getView() method, fragment, 367, 370 getWritableDatabase() method, SQLiteOpenHelper, 662 GPS location provider, 793 Gradle build tool about, 7, 834 build.gradle files, 834–835 the index check task, 837 custom tasks, 839 dependencies used by, 838 gradlew and gradlew.bat files, 836 plugins, 840 gradlew and gradlew.bat files, 836 gravity attribute, view, 182–183 GridLayout element about, 823 adding views to, 824, 827–829 creating, 825–832 dimensions, defining, 823, 826 GridLayoutManager class, 556–557 group element, 598–599 GUI components. See also views; specific GUI components adding, 43 inherited from View class, 44, 197–198 referencing, 63–64 H Handler class, 128–129 HAXM (Hardware Accelerated Execution Manager), 859 headerLayout attribute, 602 Head First Java (Sierra; Bates), 4 Head First SQL (Beighley), 675 heads-up notification, 755, 757 hint attribute, EditText, 178, 202 horizontal constraints, 227 HorizontalScrollView element, 215 I IBinder interface, 786 IBinder object, 770–771, 776 icon attribute, 299, 322 icons for actions in app bar, 320 for app, default, 84, 299 built-in icons, 597 id attribute, view, 44, 175 IDE Android Studio as. See Android Studio not using, 7 ImageButton element, 214 image resources. See also icons; mipmap resource folders adding, 189, 211, 257, 512 for Button elements, 213 for ImageButton elements, 214 for ImageView element, 212, 258 for navigation drawer header, 594 R.drawable references for, 212, 257 resolution options, folders for, 211 ImageView element, 211–212, 258, 523 include tag, 312, 314 inflate() method, LayoutInflator, 350 inflators, layout, 350, 365 inputType attribute, EditText, 202 insert() method, SQLiteDatabase, 632–633 installDebug task, Gradle, 837 Instant Run, 860 IntelliJ IDEA, 5 intent filter, 105–106 intents about, 86 alternatives to, 91 category for, 106, 117 creating, 86–87, 277 implicit and explicit, 101, 105, 117 passing text using, 90–95 resolving activities and actions, 105–106 retrieving data from, 280–282 retrieving text from, 96–97 sharing content using, 331 starting activities, 86–89 starting activities in other apps, 99–104 IntentService class, 742, 744, 752 internationalization. See localization isChecked() method, CheckBox, 206 J Java about, 2 activities. See activities required knowledge of, xxxvi, 4 source file location, 16–17 Java classes about, 38 calling from activities, 71–74 custom, creating, 70, 256 data stored in, 256, 268–271 you are here � 879 the index java folder, 17 Java Virtual Machine (JVM), 842 JDBC, 624 Joke app logging messages, 743–746 notifications, 755–766 started services, 741–766 JUnit, 870 JVM (Java Virtual Machine), 842 L label attribute, application, 299, 314, 318 layout_above attribute, RelativeLayout, 822 layout_align* attributes, RelativeLayout, 820, 822 layout_behavior attribute, ViewPager, 510, 519 layout_below attribute, RelativeLayout, 822 layout_center* attributes, RelativeLayout, 820 layout_collapseMode attribute, Toolbar, 519, 523 layout_column attribute, GridLayout, 827 layout_columnSpan attribute, GridLayout, 828 layout_gravity attribute, view, 47, 184–185, 190 layout_height attribute FrameLayout, 188 LinearLayout, 171 view, 44, 46, 47, 175, 180, 232–233 LayoutInflator class, 350, 365, 376 layout manager, for recycler view, 556–557 layout_margin attributes, view, 47, 176, 228–229 layout resource folders, 402–408 layout_row attribute, GridLayout, 827 layouts about, 2, 12, 31, 38, 170 code for, 19–20, 33, 41, 44–46, 80 constraint. See constraint layout creating, 13–14 default, 41 editing, 41–48 for fragments. See fragments frame. See FrameLayout element grid. See GridLayout element inherited from ViewGroup class, 198, 200 linear. See LinearLayout element nesting, 191–192, 222 relative. See RelativeLayout element toolbars as, 311–313, 316 880 index layout_scrollFlags attribute, Toolbar, 510, 519 layout_to* attributes, RelativeLayout, 822 layout_weight attribute, view, 179–181 layout_width attribute FrameLayout, 188 LinearLayout, 171 view, 44, 46, 47, 175, 232–233 libraries. See also specific libraries about, 3 adding to project, 224 location in project, 16 Support Libraries, list of, 294 LinearLayout element about, 41, 45, 171, 187 dimension resources, 174 gravity for view contents, 182–183 gravity for view placement, 184–185 height, 171 nesting, 191–192, 222 order of views in, 175 orientation, 172 padding, 173 weight of views in, 179–181 width, 171 xmlns:android attribute, 171 LinearLayoutManager class, 556–557 Linux kernel, 3 Listener interface, 385–387, 572–576 listeners. See event listeners ListFragment class connecting data to, 375–377 connecting to detail activity, 384–389 creating, 372–374 displaying in main activity, 378–381 listener for, in main activity, 385–387 ListView element about, 251 class hierarchy for, 570 creating, 259–260, 705–709 event listeners for, 261–263, 708, 710–712 loaders, 864 localization right-to-left languages, 172 String resources, 50, 55 LocationListener class, 792 LocationManager class, 793 the index Location Services about, 789 distance traveled, calculating, 796–797 library for, 790 listener for, 792 manager for, 793 permissions needed checking for, 794, 800 declaring, 791 notification if denied, 806, 809–810 requesting from user, 802–804, 806 provider for, 793 requesting location updates, 794–795 stopping location updates, 797 logcat, viewing, 743, 854 Log class, 743 logging messages, 743–746 M main activity, 84, 120 manifest file. See AndroidManifest.xml file material design, 506. See also Design Support Library menu attribute, NavigationView, 602 menu element, 321 menu resource folders, 321 messages. See also notification service logging, 743–746 pop-up messages (toasts), 216 methods. See also specific methods calling from activities, 59–62, 81 creating, 62–63, 65–66, 81 name of, 69 mipmap resource folders, 299 moveToFirst() method, cursor, 671 moveToLast() method, cursor, 671 moveToNext() method, cursor, 671 moveToPrevious() method, cursor, 671 My Constraint Layout app, 223–246 activities, 225 layout, 226–243 library, 224 String resources, 225 My First App, 7–36 activities, 12–15, 31 editors, 18–22 emulator, 23–30 folder structure, 16–17 layout, 31–34 project, 8–11 My Messenger app, 79–118 activities, 82–83, 90 choosers, 112–117 intents, 86–89, 92–108 layout, 80, 82–83, 91 manifest file, 84–85 running on device, 109–111 String resources, 81 N navigation. See also app bar; ListView element; navigation drawers; tab navigation; toolbar about, 250–251, 253–255, 291 Back button, 327, 413–415 Up button, 292, 327–330 navigation drawers about, 580–583 adding to main activity, 583, 602–604 closing the drawer, 606, 614 compared to tab navigation, 580 drawer toggle for, 606–607 fragments for, 583, 585–592 header for, 583, 593–595 libraries for, 584 menu click behavior, 606, 608–613 menu options for, 583, 593, 596–601 multiple, 614 submenu in, 600 theme for, 589–590 toolbar for, 589 NavigationView element, 602–603, 608 NestedScrollView element, 513, 518–519 network location provider, 793 NotificationManager class, 759 Notification object, 759 notification service about, 755, 762–763 action for, adding, 758 heads-up notification, 755, 757 issuing a notification, 759, 806, 809–810 library for, 756 you are here � 881 the index notification builder for, 757 notification manager for, 759 notify() method, NotificationManager, 759 O OAT format, 847 Odometer app, 769–816 bound services, 769–788 library, 790 Location Services, 789–800 permissions, 791, 802–816 onActivityCreated() method, fragment, 365 onAttach() method, fragment, 365 onBind() method, Service, 770, 787–788 onBindViewHolder() method, RecyclerView.Adapter, 550, 571 onButtonClicked() method, activity, 203, 214 onCheckboxClicked() method, CheckBox, 207 onClick attribute Button, 60, 125, 203, 264 CheckBox, 207, 698–700 ImageButton, 214 RadioButton, 209 Switch, 205 ToggleButton, 204 onClickDone() method, activity, 529 OnClickListener interface, 455–460 onClickListener() method, view, 530 onClick() method fragment, 456 Listener, 572 OnClickListener, 455 onCreate() method activity, 61, 81, 96, 121, 133, 137, 138, 147, 167, 787–788 fragment, 365, 429, 430 service, 751 SQLiteOpenHelper, 628, 634–635 onCreateOptionsMenu() method, activity, 323 onCreateViewHolder() method, ViewHolder, 549 onCreateView() method, fragment, 349–350, 365, 374, 376 onDestroy() method activity, 137, 138, 147, 167, 683, 787–788 882 index fragment, 365 service, 751 onDestroyView() method, fragment, 365 onDetach() method, fragment, 365 on-device tests, 871 onDowngrade() method, SQLiteOpenHelper, 637, 641 onHandleEvent() method, IntentService, 742 onHandleIntent() method, IntentService, 744 OnItemClickListener class, 261–262 onItemClick() method, OnItemClickListener, 261–262 onListItemClick() method, list fragment, 373, 386 onLocationChanged() method, LocationListener, 792 onNavigationItemSelected() method, activity, 608–609 onOptionsItemSelected() method, activity, 324 onPause() method activity, 154–157, 159, 167 fragment, 365 onPostExecute() method, AsyncTask, 724, 728 onPreExecute() method, AsyncTask, 724–725 onProgressUpdate() method, AsyncTask, 727 onProviderDisabled() method, LocationListener, 792 onProviderEnabled() method, LocationListener, 792 onRadioButtonClicked() method, RadioGroup, 209 onRequestPermissionResult() method, activity, 805 onRestart() method, activity, 146, 147, 153, 167 onResume() method activity, 154–157, 159, 167 fragment, 365 onSaveInstanceState() method activity, 140, 142, 143, 146 fragment, 429, 431 onServiceConnected() method, ServiceConnection, 775–776 onServiceDisconnected() method, ServiceConnection, 775, 777 onStartCommand() method, Service, 751 onStart() method activity, 146, 147, 153, 167 fragment, 365, 367 onStatusChanged() method, LocationListener, 792 onStop() method activity, 146, 147, 148–150, 167 fragment, 365 onSwitchClicked() method, Switch, 205 onToggleClicked() method, ToggleButton, 204 the index onUnbind() method, Service, 787–788 onUpgrade() method, SQLiteOpenHelper, 628, 637, 640, 642–650 Oracle JVM (Java Virtual Machine), 842 orderInCategory attribute, 322 organizing ideas. See also Starbuzz Coffee app orientation attribute, LinearLayout, 45, 172 P package name, 84 padding attributes, LinearLayout, 173 parent activity, 328 PendingIntent class, 758 performance of Android emulator, 858–860 SQLite database affecting, 720 permissions API levels affecting, 791, 794 checking for permissions granted, 794, 800 declaring permissions needed, 791 denied, issuing notification of, 806, 809–810 requesting from user, 802–804, 806 Pizza app, 290–338, 482–536, 538–578 actions, 315–326 activities, 297–298 adapters, 571–574 app bar, 291–293, 299 card views, 543–546, 550, 560 collapsing toolbar, 517–525 color resources, 304 FABs, 526–529 fragments, 485–488 layout, 305 libraries, 294–296, 307, 506 recycler views, 538–539, 545–570, 575–576 scrollbars, 508–515 share action provider, 331–336 snackbar, 526, 530–534 tab navigation, 498–504 themes and styles, 300–303 toolbar, 306–314 Up button, 327–329 ViewPager, 489–493 Play Store, releasing apps to, 862 pop-up messages (toasts), 216 postDelayed() method, Handler, 128–129 post() method, Handler, 128–129 Preferences API, 867 processes, apps using, 120–121, 133. See also services; threads projects. See also Android apps application name, 9 company domain, 9 creating, 8–10, 40–41 files in, 15–17, 34 libraries for, adding, 224 location, 9 property animation, 868 publishProgress() method, AsyncTask, 727 putExtra() method, Intent, 92, 101 put() method, ContentValues, 632 Q QEMU (Quick Emulator), 858 query() method, SQLiteDatabase, 663–666 R RadioButton element, 208 RadioGroup element, 208–209 Random class, 772 R.drawable reference, 212, 257 recycler view adapter, 545–550, 554, 571–574 RecyclerView.Adapter class, 545–546 RecyclerView element, 554 RecyclerView Library, 294 recycler views about, 538–539 class hierarchy for, 570 creating, 553–555, 562–565 data for, 547, 550 layout manager for, 556–557 responding to clicks, 566–576 scrollbars for, 554 views for, 548–549 RecyclerView-v7 Library, 542 RelativeLayout element about, 818 positioning relative to other views, 821–822 positioning relative to parent, 818–820 you are here � 883 the index releasing apps, 862 removeUpdates() method, LocationManager, 797 RENAME TO command, 649 render thread, 720 requestLocationUpdates() method, LocationManager, 794 requestPermissions() method, ActivityCompat, 803–804, 806 res-auto namespace, 543 res folder, 17 resolution of images, 211 resource files. See also array resources; image resources; String resources about, 2 color resources, 304 dimension resources, 174 layout resources, 402–408 menu resources, 321 mipmap resources, 299 style resources, 300–301 types of, folders for, 16–17, 403 resources. See books and publications; website resources resources element, 54 R.java file, 17, 63, 69 rotation of device configuration changed by, 134–136, 145, 156 saving activity state for, 140–141 saving fragment state for, 427–431 roundIcon attribute, 299 rowCount attribute, GridLayout, 823 Runnable class, 128 S Safari Books Online, xl scale-independent pixels (sp), 201 scheduled services, 740 screens activities in. See activities density of, images based on, 211 layouts for. See layouts size of, adapting apps for, 340–341, 394–398, 402–412 scrollbars attribute, recycler view, 554 scrolling toolbar, 507, 508–515 ScrollView element, 215 SDK. See Android SDK (API level) 884 index Service class, 752, 770 ServiceConnection interface, 775–777 service element, 745 services about, 740 bound. See bound services built-in, 740 location. See Location Services notification. See notification service scheduled, 740 started. See started services setAction() method, Snackbar, 530 setAdapter() method ListView, 270 RecyclerView, 554 ViewPager, 493 setContentDescription() method, ImageView, 212, 282 setContentIntent() method, notification builder, 758 setContentView() method, activity, 61, 133, 137, 363 setDisplayHomeAsUpEnabled() method, ActionBar, 329 setImageResource() method, ImageView, 212, 282 setListAdapter() method, fragment, 376 setNavigationItemSelectedListener() method, NavigationView, 608 setShareIntent() method, ShareActionProvider, 333 setSupportActionBar() method, AppCompatActivity, 313, 314, 317 setText() method, TextView, 64, 201, 282 settings screen, 867 setType() method, Intent, 101 setupWithViewPager() method, TabLayout, 501 share action provider, 292, 331–335 shell, running with adb, 852–853 shortcuts. See app bar showAsAction attribute, 322 Sierra, Kathy (Head First Java), 4 signing APK files, 845 SimpleCursorAdapter class, 681 slider. See Switch element snackbar, 507, 526, 530–533 SnackBar class, 530 Spinner element about, 47, 48, 210 setting values in, 64 values for, 56–57 sp (scale-independent pixels), 201 the index SQLite database about, 623–624 accessing in background thread, 720–731, 736 adding columns, 649, 650 alternatives to, 624 closing, 672, 682–683 conditions for columns, 647 conditions for queries, 666 creating, 629, 634–635 cursors about, 624, 663 adapter for, 679–681, 688–689, 715 closing, 672, 682–683 creating, 663–666, 678 navigating to records in, 670–671 refreshing, 714–718 retrieving values from, 672–674 data types in, 630 deleting records, 647 deleting tables, 650 downgrading, 641 DrinkActivity. See DrinkActivity files for, 623 getting a reference to, 662, 678 helper, 624, 626–628, 634–635 inserting data in, 632–633 location of, 623, 624 ordering records from query, 665 performance of, 720, 736 querying records, 663–666 renaming tables, 649 security for, 624 tables in, 630–631 updating records programmatically, 645–647 from user input, 695–703, 705–706, 708–712 upgrading, 637–640, 642–650 version number of, 637–639 SQLiteDatabase class, 624 SQLiteException, 662, 675 SQLiteOpenHelper class, 624, 627–628 SQL (Structured Query Language), 631, 675 src attribute, ImageButton, 214 src attribute, ImageView, 212 src folder, 17 StaggeredGridLayoutManager class, 556–557 Starbuzz Coffee app, 248–288, 622–656, 658–692, 694–738 activities, 248–249, 252–255, 262–264, 267–272 adapters, 269–271 database, 626–656, 659–692, 694–718 DrinkActivity. See DrinkActivity image resources, 257 intents, 277–285 Java classes, 256 layout, 258–260 listeners, 261–262, 276 navigation, 250–251 threads, 720–738 top level activity adding favorites to. See top level activity startActivity() method, activity, 86, 112, 117, 121 started services about, 740–741, 748 class hierarchy for, 752 in combination with bound services, 786 compared to bound services, 786 creating, 741–742, 744 declaring in AndroidManifest.xml, 745 lifecycle of, 750–751 methods associated with, 750–752 starting, 746–747 startService() method, Intent, 747, 751 states, of activities. See activity lifecycle Stopwatch app, 122–168, 434–480 activities, 125–127, 130–133 activity lifecycle, 138–139, 146–163 activity states, 134–144 dynamic fragments, 434–436, 444–460 fragment lifecycle, 439 fragment transactions, 463–469, 472–475 handlers, 128–129 layout, 123–124, 471 project, 122 String resources, 123 string-array element, 56 string element, 54 @string reference, 52, 81 String resources about, 38, 50, 54, 55 action titles in, 320 adding, 512, 517 you are here � 885 the index arrays of, 56–57, 210, 259 creating, 51, 81 getting value of, 115 location of, 54, 55 referencing strings in, 52, 81 updated in R.java file, 69 strings.xml file. See Array resources; String resources Structured Query Language. See SQL style element, 301 @style reference, 300 style resources, 300–301 styles applying themes using, 300–301 customizing themes using, 303 Support Libraries. See also v7 AppCompat Library adding to project, 296 list of, 294 supportsRtl attribute, application, 172 Swing, 4 swiping through fragments, 489–493 Switch element, 205 sync adapters, 864 syncState() method, ActionBarDrawerToggle, 607 system image. See Android SDK (API level) T TabLayout element, 499, 501 tab navigation about, 482–484, 493 adding tabs to layout, 498–504 compared to navigation drawers, 580 fragments for, 483–488 swiping between tabs, 489–493 tasks, 78 testing automated testing, 870 emulator compared to device, 117 on-device tests, 871 unit tests, 870 text attribute Button, 44, 203 CheckBox, 206 TextView, 33, 34, 50, 201 text field. See EditText element 886 index textOff attribute Switch, 205 ToggleButton, 204 textOn attribute Switch, 205 ToggleButton, 204 textSize attribute, TextView, 201 TextView element about, 33, 44, 201 code for, 33–34, 44–47, 91 setting text in, 64 theme attribute AppBarLayout, 519 application, 299, 300 themes about, 84 app bar requiring, 293 applying to project, 299–300 built-in, list of, 302 customizing, 303 for navigation drawers, 589–590 removing app bar using, 308 v7 AppCompat Library for, 294, 296–298 threads about, 720 background thread, 720–731, 736 main thread, 127, 720 render thread, 720 title attribute, 322 toasts, 216 ToggleButton element, 204 toolbar adding as layout, 311–313, 316 collapsing, 507, 517–525 for navigation drawers, 589 replacing app bar with, 292, 306–313 scrolling, 507, 508–515 Toolbar class, 306, 309, 314 top-level activities, 249–250, 252–255, 290 transactions, for fragments. See fragment transactions U unbindService() method, ServiceConnection, 779 unit tests, 870 the index Up button, 292, 327–330 update() method, SQLiteDatabase, 646, 698 USB debugging, 109 USB driver, installing, 109 uses-permission element, 791 V v4 Support Library, 294. See also Design Support Library v7 AppCompat Library about, 294 adding to project, 296, 307 AppCompatActivity class in, 297–298 fragments using, 345 Location Services using, 790 navigation drawers using, 584 notifications in, 756 v7 CardView Library, 294, 542 v7 RecyclerView Library, 294 values resource folders. See string resources; dimension resources variables, setting, 126, 127 vertical constraints, 228 view animations, 868 ViewGroup class, 198, 200 ViewHolder class, 546, 548 ViewPager class, 489–493, 501 views. See also specific GUI components about, 44, 198–199 aligning, 238 biases for, 231 centering, 230 getting and setting properties, 199 gravity for view contents, 182–183 gravity for view placement, 184–185, 190 height, 175, 180, 232–233 ID, 44, 175, 199 margins, 176, 228–229 methods associated with, 199 weight, 179–181 width, 175, 232–233 W website resources activity actions, types of, 101 source code for examples, xxxvi USB drivers, 109 WebView class, 866 widgets. See also GUI components about, 869 adding, in blueprint tool, 226 constraints for. See constraint layout Workout app, 340–392, 394–432, 434–480 activities, 346–347, 359–363, 381, 389, 421–423, 470 adapters, 375–376 back button, 413–415 device sizes, supporting, 340–341, 394–398 dynamic fragments, 434–436, 444–460 fragment lifecycle, 439 fragments, 342–344, 348–356, 359–363, 365–369, 416–421 fragment state, 427–431 fragment transactions, 463–469, 472–475 Java classes, 360 layout, 471 libraries, 345 listener interface, 384–388 list fragments, 372–374, 377–378 screen-specific resources, 402–409 tablet AVD, 399–401 wrap_content setting, width and height, 232 X xmlns:android attribute, LinearLayout, 171 Z zipalign tool, 845 Zygote process, 846 you are here � 887 Get up to speed on Agile. Quickly learn what Agile development is all about on Safari, O’Reilly’s online learning platform. Through books, videos, interactive tutorials, and live online training, you’ll learn how to design and build products incrementally, test constantly, and release as often as you need to. Check out The Agile Sketchpad, a video course by David and Dawn Griffiths that helps you: ■■ ■■ ■■ ■■ See what it's like to work on an Agile team. Discover how Agile helps you deliver value early, respond to change, and manage risk. ■■ ■■ Master the secrets of accurate estimation. Explore how test-driven development, continuous integration, and the 10-minute build help you deliver better software. ■■ Learn why programming in a pair can be more effective than programming alone. Find out how Agile techniques such as Kanban can help you identify and avoid disruptions to your development process. See how the five key values — communication, courage, feedback, simplicity, and respect— drive everything you do in an Agile project. Try Safari for free for 10 days and get up to speed with Agile. oreilly.com/go/agile-sketchpad D3610 ...
Source Exif Data:
File Type : PDF File Type Extension : pdf MIME Type : application/pdf PDF Version : 1.6 Linearized : No Page Layout : SinglePage Tagged PDF : No XMP Toolkit : Adobe XMP Core 5.2-c001 63.139439, 2010/09/27-13:37:26 Modify Date : 2017:07:27 10:09:33-04:00 Create Date : 2017:07:21 13:06:36-04:00 Metadata Date : 2017:07:27 10:09:33-04:00 Creator Tool : Adobe InDesign CC 2017 (Macintosh) Format : application/pdf Title : Head First Android Development Creator : Dawn Griffiths and David Griffiths Document ID : uuid:30167307-8351-7f49-966d-1c658614e634 Instance ID : uuid:b1b8626b-a382-4548-a7a3-2f608c293dfb Producer : Adobe PDF Library 15.0 Page Mode : UseOutlines Has XFA : No Page Count : 930 Author : Dawn Griffiths EBX PUBLISHER : O'Reilly MediaEXIF Metadata provided by EXIF.tools