navigate site menu

Become a member and get unlimited access to every course in the library. Try it free for 7 days

Android SDK: Local Data Storage

Android SDK: Local Data Storage

with David Gassner

 


Take your Android programming skills to the next level with the Android built-in framework that enables local data management in text files and SQLite-based relational databases. This course shows you how to create datacentric apps for Android devices, using SQLite, Java, and the built-in android.database packages. Author David Gassner describes how to define shared preferences, work with JSON and XML files in internal and external data stores, and create new local SQLite databases.
Topics include:
  • Exploring local data storage options
  • Creating an Android virtual device
  • Starting a new project
  • Defining preferences with Java and activities
  • Creating and reading JSON and XML data files
  • Creating a new SQLite database
  • Inserting and retrieving data in the database

show more

author
David Gassner
subject
Developer, Mobile Apps, Programming Languages
software
Android , Java
level
Intermediate
duration
3h 41m
released
Jan 25, 2013

Share this course

Ready to join? subscribe


Keep up with news, tips, and latest courses.

submit Course details submit clicked more info

Please wait...

Search the closed captioning text for this course by entering the keyword you’d like to search, or browse the closed captioning text by selecting the chapter name below and choosing the video title you’d like to review.



Introduction
Welcome
00:00(music playing)
00:04My name is David Gassner, and I'd like to welcome you to Android SDK:
00:09Local Data Storage.
00:10This video series shows you how to create data-centric apps for Android devices.
00:16I'll start with shared preferences, simple key/value pairs that are
00:20stored persistently.
00:22I'll then describe how to create files in internal and external persistent
00:26storage, and how to manage JSON and XML data files.
00:31And then I'll show you how to create local SQLite relational databases, using
00:36interfaces and classes that are provided with the Android SDK.
00:41I hope that this course will help you improve your Android programming skills
00:45and get you ready to build data-centric Android apps.
Collapse this transcript
What you should know before starting this course
00:00This video series is designed for software developers who want to build
00:04applications for Android, the mobile operating system that drives
00:09cell phones and tablets.
00:11In order to get the most out of this course, here is what you should already know.
00:15First, you'll need to know basic Java syntax.
00:19You'll need to understand how to create classes, methods, fields, and properties,
00:24and other elements of the Java programming language.
00:27And you'll also need to know how to build very simple Android apps.
00:31And finally, you'll want to be comfortable working in Eclipse, the integrated
00:35development environment in which you build most Android applications.
00:40If you're new to any of these subjects, here are some tips to get started.
00:44If you want to learn about Java, take a look at the lynda.com course
00:48Java Essential Training.
00:51You'll get an introduction to Java syntax, you'll learn how to install Eclipse,
00:56and you'll learn about Java projects, packages, and classes.
01:01And you'll have most of the vocabulary you need to be effective in
01:04Android programming.
01:06If you're brand-new to Android, start off with the course, Android App
01:10Development with Java Essential Training.
01:12In this course, you'll learn how to install the Android SDK and the Developer
01:17Tools, and how to install the Eclipse plugin that I used in this course.
01:22You'll get started with very simple Android applications, and you'll learn how
01:27Java code is critical to creating the logic of your Android apps.
01:32This course is focused on data, and one of its major sections is all about
01:36integrating Android with SQLite.
01:38SQLite is an SQL-compatible database, so the more you know about SQL, the more
01:45effective you can be.
01:47This course includes everything you need to complete its own exercises, with very
01:52simple SQL statements.
01:54But if you want to learn more, take a look at the lynda.com course
01:58SQL Essential Training.
02:00If you understand the basics of Java, simple Android apps, and SQL, you'll be
02:06ready to build data-centric applications for Android devices.
Collapse this transcript
Using the exercise files
00:00This video series includes exercise files that you can use to follow along the
00:05demonstrations that I do onscreen.
00:08I've copied the exercise files to my desktop, but you can place them anywhere on your hard disk.
00:14The Exercise Files folder has five folders for each of the five chapters in the
00:18course, plus two folders named Assets and Solutions.
00:23The chapter folders contain subfolders that are set up as Eclipse project.
00:28You can import these into the version of Eclipse that's included with the
00:32Android Developer Tools.
00:34For example, I'll go to the Android Developer Tools bundle.
00:39This is the bundle for Jelly Bean Android 4.2, and that's the version you'll
00:44need to work along with the course.
00:47From the bundle folder, I'll go to Eclipse and then open the
00:51Eclipse application.
00:52To import one of the projects, I'll select File > Import.
00:57Then I'll select Existing Project into Workspace from the General category.
01:02I'll browse, I'll go to my Exercise Files folder, then I'll go to the chapter
01:08I'm in, and I'll select the project I want to work with.
01:12This is a project named PreferencesWithJava. And then I'll follow the rest of
01:16the prompts to import the project into Eclipse.
01:20Once the project is imported, you'll be able to get to all the Java code; the
01:25resources, including graphic files, layout files, and menus; and everything else
01:31you need to build the application that I'm building.
01:33If you see errors when you import a project, be patient for a few moments.
01:38Eclipse sometimes takes some time to resolve internal errors before it lets
01:43you continue working.
01:45Specifically, if you see a warning that your project has the wrong Java
01:49compliance level, that's a very common issue, and there's a very simple fix for it.
01:54Go to the project name and right-click on it.
01:57Then go down to the bottom of the Context menu and select Android
02:02Tools > Fix Project Properties.
02:05That corrects many common issues that can happen when you import Android
02:09projects into Eclipse.
02:11When you're done with the project, just delete it.
02:14You can press the Delete key, or you can right-click on the project in the
02:18Package Explorer and select Delete.
02:22When prompted, just click OK.
02:24Don't select the option that's labeled Delete project contents on disk.
02:28As it says, you're really deleting the files.
02:31If you leave it unselected and click OK, you've just removed the project from
02:35your workspace listing, but the project is still there on your hard disk.
02:41As I mentioned, the Exercise Files folder also includes an Assets Folder.
02:46You'll find folders there that include various types of files, including
02:50graphics, XML files, and one Java JAR file that I'll use in the section on parsing XML.
02:58And finally, the Solutions folder contains the same five chapters, but the
03:04Eclipse projects in these chapters are the completed versions--
03:07that is, the applications in their state after I've finished each demo.
03:12If you don't want to follow along with the coding but just want to look at the
03:16finished code, you can import these projects into Eclipse.
03:20So, these exercise files should help you follow along with the demonstrations I'm
03:25doing onscreen, and you should be able to see exactly the same results that I'm
03:29getting as you create your Android application.
Collapse this transcript
1. Getting Started
Exploring local data storage options
00:00The Android SDK includes many options for storing data locally with an Android app.
00:07All of these options give you the ability to persist information between
00:11application launches or between device sessions--that is after a device is
00:16shutdown and then restarted--because in all of the options that I'm going
00:20to describe, you'll be saving files to local flash memory or other persistent media.
00:26Here are the three major options for local data storage:
00:30shared preferences, arbitrary files that you simply create and save to disk, or
00:37SQLite relational databases.
00:39Here is a little bit more detail about each of the options.
00:44A shared preference is simply a key-value pair, a key, which is a string, and then
00:50a value, which can be one of many data types.
00:53It's up to you as the Android application developer or designer to decide what
00:58preferences are needed for your application.
01:01Typically, a preference is used either to present specific information to the
01:05user or to determine the behavior or appearance of an application.
01:11You can manage your preferences in one of two ways, either programmatically with
01:15Java using a special API that's a part of the Android SDK, or through a
01:21preference activity, a specific kind of user interface that's defined by the SDK
01:28and will manage your preferences for you.
01:30Regardless of whether you do it programmatically or through an activity,
01:35preferences are stored unencrypted.
01:37They're stored in a simple XML file on your persistent data storage area,
01:42and the creation and then reading of the XML content is handled completely
01:47by the underlying API.
01:50As the developer, you'll create your preferences either programmatically or
01:54through an activity, and then you'll be able to read them programmatically with Java.
01:59But the mechanics of the XML file are handled for you.
02:02Shared preferences are great for very simple values,
02:07but when you need to store more complex information, you might want to go to the
02:12next option: creating your own files.
02:16The Android SDK has a complete system for creating, storing, and reading files,
02:22and you can create and read these files using the persistent media that's a part
02:26of the Android device.
02:28There aren't any special file types.
02:30You can create pretty much anything, including binary files like images, XML,
02:36JSON, or delimited data files, or anything else for which you can program.
02:42You'll be able to choose from a variety of approaches to creating and reading
02:45these files, including APIs that are unique to Android or other APIs that are
02:51available in all implementations of Java.
02:54You can control access to these files by where you place them in your storage.
02:59You can designate a file to be internal, which means it can only be accessed by
03:03the application that created it, or external, which means that it's available to
03:08other applications as well.
03:11Finally, if you need the power of a complete relational database, you can
03:16create an SQLite file.
03:18An SQLite database is a complete relational database that can store multiple
03:23database tables and has statically typed columns, so you can designate a data
03:29type as being a string, an integer, a double, and so on.
03:33SQLite is a very powerful little database, and it's used on a lot of different
03:39platforms, not just Android.
03:41You can find complete information about SQLite from the
03:44homepage: www.sqlite.org.
03:48But the most important information you'll need will be in the documentation for
03:52this package: android.database.sqlite.
03:56Android's implementation of SQLite is pretty plain vanilla, but what's important
04:02is the programming interface that's provided.
04:05It lets you standardize how SQLite files are created and where they're placed on disk.
04:11When you create an SQLite database file programmatically, the database will be
04:17stored locally and it will be associated with the application,
04:21so later on, when the application is uninstalled, the associated database file
04:26will be deleted with it.
04:28If you want to share this sort of complex data with other applications on a
04:31device, consider creating a custom content provider. And I'll show you how to do
04:38that toward the end of this course.
04:40All Android devices, regardless of whether they are cell phones or tablets,
04:45are networked, and so the final way that you can deal with data is through the network.
04:50You can store application data on a networked server and then manage your
04:54network communications using a variety of APIs.
04:58You can use java.net, which is a part of the core Java language, or android.net,
05:04a set of classes that are unique to Android.
05:07And if you're programming for Honeycomb, Android 3 or later, you can use a
05:12class called Loader, that greatly simplifies the loading of data from a networked server.
05:18Data exchange between an Android device and a networked data source is typically
05:24handled using what are called restful web services: simple XML files that are
05:29requested and retrieved from a networked server and then pushed back up to the
05:34server to make requests.
05:37If you're familiar with SOAP-based web services, you won't be able to use those
05:41so easily in Android because there's no built-in support for them.
05:45This course, however, focuses on local data storage, and so the use of networked
05:51data is outside the scope of the course.
05:54But let's get started with local data storage with Android.
Collapse this transcript
Configuring Eclipse and the Android Developer Tools
00:00I'm recording this video series using the latest version of the Android SDK as
00:05of the time of recording: Android 4.2, Jelly Bean.
00:09Jelly Bean is the same nickname as was used for Android 4.1, but the developer
00:14toolkit has been changed in a number of ways.
00:18Most importantly, the SDK now includes a customized version of Eclipse.
00:23So instead of downloading Eclipse from eclipse.org, as you might have done in the
00:27past, you might now want to use the bundled version, which is all set up and
00:31will get you up and running very quickly.
00:34That's the version that I'll be using in this course and to follow along most
00:37effectively, I recommend using it yourself.
00:41To download the free developer tools go to the website
00:44developer.android.com/sdk.
00:49This page shows a link to download the SDK for your operating system.
00:54I'm working on Mac OS X, so I could click and download the SDK from here.
00:59You can find versions of the SDK for other operating systems from this link:
01:04Download for Other Platforms.
01:06There are versions of the bundle for Windows, Mac OS X, and Linux.
01:12There are also tools to download the SDK itself without Eclipse, and if you want
01:17to use your own copy of Eclipse, you can do that as well.
01:21But again, to follow along in this course, I recommend using the bundled version.
01:26When you download the SDK, it will come to you as a compressed file.
01:30Extract it anywhere on your system.
01:32I have extracted it to the desktop.
01:35The name of the folder on my system is adt-bundle-mac, and within that folder
01:41there is an eclipse folder and an sdk folder.
01:44I'll double-click into eclipse and then double-click on the Eclipse application icon.
01:50You'll see that this customized version of Eclipse is branded.
01:53It will show a title of Android Developer Tools, or ADT, instead of Eclipse, but
01:58it is standard Eclipse, and this is based on Eclipse 3.7, or Indigo.
02:04I'll start up the ADT and that will create a new workspace folder.
02:09I'll accept the option to send usage statistics to Google and click Finish and
02:14then take a look at the information and links that are on the Welcome page.
02:18Then I'll close the Welcome page and that will take me to the Eclipse User interface.
02:24I'll expand the Eclipse window to full screen, and I'm ready to get started.
02:29When you first install ADT, it includes the SDK for Android 4.2.
02:35If you want other versions of the SDK, you can go to the menu and choose
02:39Window > Android SDK Manager.
02:43The SDK Manager will show all available versions and show you what's installed
02:48and what's not installed.
02:50The SDK for Android 4.2 is already installed, and that's what I'll be targeting
02:55throughout the course.
02:57If you're targeting the older version of Jelly Bean--Android 4.1--you might want
03:01to install this SDK, or if your focus is on cell phones with older versions of
03:06Android, such as 2.3, you can install those SDKs.
03:12Minimally, I do recommend installing the documentation for Android 4.2.
03:17To do that, check the option, then click Install 1 Package.
03:22Walk through the prompts, accepting terms and conditions as needed, and then click Install.
03:27It will take a few minutes for the documentation to download, but once it's
03:32installed, you'll be able to use the API documentation without necessarily
03:37needing a link to the Internet.
03:40In addition to the documentation, if you're working on Windows, you'll want to
03:45install drivers that will allow you to connect to devices for debugging.
03:50You don't need to do this on Mac;
03:51you can connect devices automatically, but on Windows you will need these
03:55drivers, and they aren't installed automatically.
03:58To do this, go to the Extras folder and then check the option for the Google USB Driver.
04:05On my system it tells me it's not compatible with Mac, but on Windows go
04:10ahead and follow the prompts to install it and then you'll be able to install
04:13the driver on Windows.
04:15I'll be exclusively using an emulator in this course and not external devices,
04:20so you don't need the USB driver to follow along in this video series, but if
04:24you want to do debugging on a real device on Windows, you will need this option.
04:30Once you've installed the SDK, there's one more change I'd like you to make to
04:34follow along in the course.
04:36I'm going to be making a lot of use of Logcat, the logging architecture that
04:40lets you trace what's going on in an application at runtime.
04:43I always add Logcat to my perspective.
04:47I'll go to the menu and choose Window > Show View and then select Other.
04:53Then I'll go to Android and I'll choose Logcat and click OK.
04:58Then with Logcat added to my perspective, I'll save the perspective.
05:03From the menu, I'll choose Window > Save Perspective As, and I'll name my
05:08perspective Android.
05:11That will be the beginning screen setup for everything I do in this video series.
05:16So now, with the SDK and Eclipse configured, it's time to go to the next step:
05:21creating a virtual device for the Android emulator--and I'll show you the steps
05:25I'm following for that in the next video.
Collapse this transcript
Creating an Android virtual device
00:00I'll be using the Android emulator to test and debug my applications
00:04throughout this video series.
00:06Here I'll show you the steps I follow to create my emulator.
00:10I'll go to the ADT menu and choose Window > Android Virtual Device Manager.
00:16You can either go to the Device Definitions screen and choose one of the
00:20preexisting definitions or you can create a definition from scratch on the
00:25Android Virtual Devices tab.
00:26I will start from that tab and click New.
00:30I'll name my new Android virtual device JellyBean.
00:34In this version of ADT, there are four specific device definitions for actual devices:
00:41the Nexus 7, the Galaxy Nexus, the Nexus S, and the Nexus One.
00:47Then there are a bunch of predefined device definitions that are generic.
00:50I'm going to start with a fairly small device, which will make everything very
00:55readable on my screen.
00:56It will be the 3.2 QVGA device definition, which uses a medium pixel density.
01:03I'm setting my target to the only SDK that I have installed: Android 4.2.
01:09Because all of the exercise files for this course are defined for that version
01:13of the SDK, I recommend that you do the same.
01:17Make sure that you've selected the options to allow hardware keyboard input.
01:22This will let you type values in from your own keyboard and the skin that
01:26displays the skin with hardware controls.
01:28I'll add a virtual SD card with a size of 64, and then I'll click OK and that
01:36creates the virtual device.
01:38Then I'll click on the virtual device and I'll click Start.
01:42On the Launch Options screen, I'll accept all of the defaults and click Launch.
01:48That starts up your emulator.
01:50Now this will be for a very small cellphone, and if you want to emulate a more
01:55recent device, such as the Nexus 7, you can choose that too.
02:00I'm using this particular virtual device to make all of the text very
02:04readable on the screen.
02:06You might also see a dialog that pops up, asking you whether you want to use
02:10Logcat to log information from the emulator.
02:14I recommend saying yes.
02:16This will cause the emulator to take quite a while to start up the first
02:19time, but once it's open, you can keep the emulator open throughout all of
02:25your development work and not have to hit this pause every time you want to
02:29test an application.
02:30Once the emulator comes to life, you should see this Welcome screen.
02:36You can click OK and that will take you to the emulator's home screen.
02:40From this screen, you can do any setup you'd like.
02:42For example, the emulator goes to sleep by default after a very short amount of inactive time.
02:50To change that, I'll go to my apps list and then I'll type settings.
02:56That should take me to a search screen and I can choose the
02:59Settings application.
03:03I'll click on Display and then on Sleep, which defaults to sleeping after one
03:08minute of inactivity.
03:10I'll change that to 30 minutes.
03:12Then I'll click the Home button and that takes me back to the Home screen.
03:16So now my virtual device is set up and ready to use, and I'm ready to create
03:21my first application.
Collapse this transcript
Creating a new Android project
00:00In the exercises in this video series, I'll be working on an application called
00:04the Explore California Tour Finder, and I'll start by showing you how to build
00:09the beginning of that application.
00:11I'll go to the menu in ADT, the Android Developer Tools, and select
00:16File > New > Android Application Project.
00:19I'll give my application a name of Explore CA, for California.
00:25The project name will be the same but without any spaces.
00:29For the package name, which uniquely identifies the application on your Android
00:33device, I will start the package with the reverse domain notation of
00:37com.exploreca, and then I'll finish it with .tourfinder.
00:42I'll leave the Minimum Required SDK to API 8 Android 2.2 or Froyo.
00:50In nearly all of the exercises in this course, I'll be using code that works all
00:54the way from 2.2 up through the most recent version of Android, 4.2.
01:00I'll set the Target SDK to the most recent version, Android 4.2, Jelly Bean, and
01:06I'll accept all the other defaults and click Next.
01:09On the screen I'll deselect the option to create a custom launcher icon.
01:14I'll be adding a launcher icon from the exercise files. And then I'll click Next again.
01:20I'll be creating a blank activity as my application's main activity,
01:25so I'll accept the default here and click Next. And I'll use the suggested
01:29Activity Name and Layout Name.
01:32So I'll click Finish and that will create the project.
01:36When the application is first created it opens to the main activity layout.
01:41It's showing here in XML view.
01:43On your computer, it might also open up in the graphical layout, but you can
01:47switch back and forth between them using the tabs at the bottom of the editor.
01:52The default main activity has a single text view object centered on the screen.
01:56We'll leave that in place for the moment, and I'll launch the application in an emulator.
02:02I'll go up to the toolbar and click the Run button.
02:04I already have my emulator running.
02:07If you don't, it might take a few moments to fire up the emulator and load the application.
02:14When the application loads in the emulator, you should see the output Hello World!
02:20I'll go back and add one more feature.
02:22I'll replace the default launcher icon--
02:25that's the little Android robot--with a custom icon that I've included in
02:29the exercise files.
02:31I'll return to Eclipse and because I've placed the exercise files on the
02:35desktop, I can just drag Eclipse over to the right.
02:39I'll open the exercise files and go to the Assets folder and from there to a
02:44folder called Launcher Icons.
02:47I've created four versions of my launcher icon, one in each of the pixel-density folders.
02:53So there's one for low density, one for medium, one for high, and one for extra high.
02:58I'll open the version in the high-density folder and show you that it's a
03:02graphic based on the Explore California Assets, and these are graphics and data
03:07files that I'll be using throughout the course.
03:11So to load these into my application I'll go back to the folder that contains
03:15the folder names that start with drawable.
03:18Then I'll select and copy those four folders to the clipboard.
03:23I'll return to Eclipse and expand it to full screen again.
03:27Then, in the package explorer, I'll go to the RES, or Resources, folder and I'll
03:33paste those folders into place.
03:35I'm pressing Command+V on Mac.
03:38You can press Ctrl+V if you're working on Windows.
03:41When prompted to overwrite, I will say Yes To All, and that will add those
03:46graphics into their respective folders.
03:49I still have the graphics that were created when I created the project.
03:52They are called ic_launcher, but now I have the four versions of my graphic
03:58in the correct places.
03:59To register those graphics, I'll go to the AndroidManifest.xml file.
04:05I'll double-click to open it and if it opens in the graphical interface, as it
04:09did here, I'll click on the XML View tab.
04:13I'll expand this to full screen. Then I'll go down to the icon attribute, and
04:18I'll change this from ic_launcher to ic_exploreca.
04:24To see a list of available drawable resources, you press Ctrl+Space, and then I've
04:30selected ic_exploreca. And the application will automatically load the right
04:35version of the graphic, depending on what kind of device it's running on.
04:40I'll save those changes and I'll run the application in the emulator again.
04:45Many times I'll be launching the application in the emulator using a keyboard shortcut.
04:51I'm pressing Command+Shift+F11 on Mac.
04:54So, I'll click back here, and I'll press my keyboard shortcut to run the application.
04:58Then I'll switch back to the emulator and after a moment, the application is
05:03loaded and I see my launcher icon shows here.
05:07I'll go back to the Home screen in the emulator, and I'll go into my application
05:11list, which in Jelly Bean is represented by this icon in the bottom center of the
05:16hot seat, the area at the bottom of the screen.
05:20My application is shown here as well, and again it uses the appropriate version
05:24of the launcher icon for this device.
05:27I can open the application from here.
05:29I can click and hold and drag the icon into place on the Home screen. I'll remove that.
05:37I'll go back to the application list, and very importantly, I can also uninstall
05:43the application by dragging it up to the Uninstall icon, or get into the App info
05:49and find out such things as how much data is being used by the application.
05:54If there is any personal data, I can clear it from here as well.
05:59So, that's to look at how to get started building the base application for this course.
06:03Once it's been created and once you have the emulator running, I recommend
06:07keeping the emulator open throughout all of your development work, so you don't
06:11have to wait for it to open each time you want to run or debug.
Collapse this transcript
2. Using Application Preferences
Using preferences in Android apps
00:00The simplest and smallest sort of data that you can store persistently on disk
00:04in an Android app is called a preference.
00:07There are a couple of different ways of creating preferences in an
00:10application, and they are sometimes referred to as simply preferences and
00:14sometimes as shared preferences.
00:17You can initialize preferences either in Java code or through a preference
00:22activity--a visual interface that you create just like you do any activity.
00:27Each preference value is a simple key-value pair.
00:31The keys are always strings, but the values can have one of a number of
00:36different types, including Booleans, integers, longs, floats, and strings.
00:43It's also possible to create a single preference value that consists of multiple
00:48strings that are typed as a Java set.
00:52You can create your preferences in Java by using a class
00:55called SharedPreferences.
00:57You start by creating an instance of this class.
01:01Whenever you create a SharedPreferences object, you always assign a mode, and the
01:06most common is called MODE_PRIVATE.
01:09This means that the preferences in this set will be private to the current
01:13application and are only accessible with the application that has this current package.
01:20There are other mode constants available in the API, such as MODE_WORLD_WRITABLE,
01:26but as of Jelly Bean--
01:27that's Android 4.2--sharing of preferences between apps isn't currently supported.
01:33There's a note in the API docs to this effect.
01:36That capability might be supported in the future versions.
01:40When you create a set of preferences, you indicate whether the preferences are
01:45private to the current activity or shared between multiple activities, and you do
01:50this by assigning a name.
01:52If you call the getPreferences method and simply pass in MODE_PRIVATE, you're
01:57saying, create a set of preferences that are unique to the current activity.
02:02If you call getSharedPreferences, then you have to pass in an explicitly
02:06activity-set name as a string, and then you can reuse that name from other
02:11activities and get to those same values.
02:14If you call getPreferences, there's still a name associated with that set.
02:19It's the name of the current activity. But the intention is that those are
02:24private to the activity and that anything you call with getSharedPreferences are
02:28shared with the entire application.
02:31You can also choose to initialize your preferences using an activity.
02:36The advantage of preference activities is that they handle all the creating of
02:40the values and writing them to disk automatically for you.
02:44You don't have to assign a name because preferences that are created through an
02:48activity are always available to the entire application.
02:52There's amore limited set of data types that you can use with preferences
02:56created in this way.
02:57These values can be Boolean, strings, or list of strings.
03:02You can't easily create integers, floats, or longs.
03:06In order to create a preference activity, you'll define it in a layout file.
03:11The code in these layout files will look similar to the layout files used to
03:16create your own custom activities, but the names of the elements are strictly defined.
03:21Once you've defined the preference activity, you can navigate to it using
03:26exactly the same sort of a code that you would with any activity: by creating an
03:30intent and then asking to go to that activity through the intent.
03:35You'll see some differences in how activities are created, depending on what
03:40version of the SDK you're targeting.
03:42Prior to Android 3, Honeycomb, a preference activity was created in single XML file.
03:48Starting in Honeycomb, and continuing through Jelly Bean, you can use the
03:52Fragment API and put together a preference activity using one or more fragments
03:58that are then pieced together at runtime.
04:01I'll show you both approaches to creating preferences--defining them through pure
04:06Java code or defining them through an activity--and then show you how to read
04:11those preferences into memory and how to listen for changes to preferences that
04:15might happen at runtime.
04:17As you decide how to put preferences to work in your application, here are two
04:22very important things to know about them.
04:24First of all, preference values are not encrypted.
04:27They're stored to your Android devices persistent storage as simple
04:32unencrypted XML files.
04:34And that means that you should never store sensitive information that you
04:38haven't explicitly encrypted.
04:41The second thing is that preferences can be easily deleted by the user.
04:45The user can go on to the Settings app or into the application list and say that
04:50they want to remove application data and it will be completely wiped from
04:54persistent storage and won't be recoverable.
04:56So in using preferences in you application, make sure that your app knows how to
05:01get started from scratch if the preferences don't already exist.
05:05I'll be showing you how to code for preferences so that when you read a
05:09preference, you can always provide a default value, a value that you work
05:14with if in fact the preference has never been set or has been previously set and then deleted.
05:20So let's get started learning how to code with preferences, both in Java and with
05:25the preference activity.
Collapse this transcript
Defining preferences with Java
00:00I'm going to start by describing how to set preferences using Java code.
00:04I'm working in a version of my project called PreferencesWithJava.
00:09I've added a few user interface elements, an EditText object, and a couple of
00:14buttons, and the buttons are wired to methods in the main activity class using
00:19attributes in the XML layout file.
00:22Down here, in the button tags, each of the buttons has an onClick attribute.
00:27These are pointing to these methods, which I can jump to by holding down
00:31the Command key on Mac or the Control key on Windows and clicking on the method name.
00:37Right now, these two methods are just outputting messages to the LOGCAT Console.
00:43I'll run the application in the emulator and then click the Set preferences
00:47button and the Show preferences button and show that my LOGCAT messages are
00:52appearing successfully.
00:54Notice that I've filtered my LOGCAT messages using a filter of tag:
00:59and then the tag name that I'm using in my log class calls.
01:04So now, let's take a look at the code that's needed to add and then retrieve
01:08preferences using Java code.
01:11I'll go to my MainActivity class.
01:14As I mentioned previously, each preference that you save persistently is
01:19associated with a string-based key.
01:22I typically declare these keys as constants so I can refer to them successfully
01:26multiple times in my code.
01:28I'll start with the existing constant, the LOGTAG constant, and I'll
01:32duplicate that line of code.
01:35Then I'll change the new one so that the constant is USERNAME and the value is
01:40username in lowercase.
01:42The constant name will be used to refer to the preference in Java code, while the
01:47lowercase value will be associated with the value when it's saved to disk.
01:52Next, I need a reference to something called a SharedPreferences object.
01:57The SharedPreferences class in the Android SDK represents a set of preferences
02:03that are saved as an XML file.
02:05They're exposed to you as a developer as a simple Java class that implements the map interface.
02:11I'll declare a private field, which is an instance of this class.
02:15I'll type in the beginning of the class name, SharedPref, then I'll press Control+
02:20Space and select the class from the list. And I'll name this object settings.
02:26Now, I'll instantiate the object by adding code to my onCreate method.
02:32The onCreate method is called automatically as the activity comes to the screen,
02:37so I'll initialize the value using settings =.
02:41Now, I have two choices.
02:43I can either call the getPreferences method, and that would return a reference to
02:48the preferences that are local to the current activity, or I can call the method
02:52getSharedPreferences and pass in a string identifier.
02:56If you're working with preferences that will only be accessed by the current
03:00activity, call getPreferences.
03:03If you're working with preferences that will be shared through the entire
03:06application, call getSharedPreferences.
03:09I'll call the getPreferences method, and I'll pass in a mode of MODE_PRIVATE.
03:15That means that the preferences are local to the application.
03:19As I mentioned in earlier video, as of Jelly Bean, you can't share preferences
03:24between multiple apps, but that capability might come about in future versions.
03:30So now I have a reference to my settings object and I can use that anywhere in the activity.
03:36I'll go down toward the bottom of the class and I'll add some code to my
03:40setPreference method.
03:41To set a preference, you need something called a SharedPreferences editor object.
03:47The editor object is a class that's a field of SharedPreferences.
03:52So, I'll type in SharedPreferences, then a dot, and I'll choose the
03:58SharedPreferences.Editor class. And I'll name this object editor and I'll get its
04:04reference by calling the settings objects edit method.
04:07This returns a reference to the editor object for the current preferences file.
04:12Now, I need a value to put into the preferences.
04:16To make it a little bit easier to work with my user interface, I've added a
04:19class to this project called UIHelper.
04:23You can find it in the Util subpackage.
04:27This class has methods to manipulate and get values from various user interface
04:31elements, including textViews, edit text, and check boxes.
04:36I'm going to be using the displayText and getText methods in this exercise.
04:41I'll go back to my MainActivity class and down to my setPreference method, and
04:46I'll expand to full screen. And I'll create a new string variable called
04:50prefValue, and I'll get that value by calling UIHelper.getText.
04:58I'll pass in this as the current activity and then the resource ID of the user
05:03interface element from which I want to get the text.
05:07That will be R.i.d.editText1.
05:11That's the I.D. of the editText object in my current layout.
05:16So now I have a value, and I'll add it to my preferences by calling the editor
05:20object and one of its put methods.
05:23The editor object has these six put methods.
05:27You can add a Boolean value, a numeric value as a float integer or long, or a string.
05:33Or if you want to save multiple strings as a set, you call putStringSet.
05:37I'll add a single string value by calling putString.
05:41I'll set the key to the USERNAME constant that I defined at the top of the class,
05:46and I'll pass in the prefValue as the preference value.
05:51I could now add more preferences, but I don't have any more for this exercise, so
05:56I have only one more critical step, and that's to commit my changes.
06:01I'll call editor.commit and that saves the values to persistent storage.
06:07I'll also give the user some visual feedback.
06:10Once again, I'll call my UIHelper class, and this time I'll display some text.
06:16I'll pass in this as the activity, R.i. d.textView1 as the ID of the textView
06:22object, and then a literal string of Preference saved.
06:28I'll save those changes, and now I'll run the application in the emulator by
06:32pressing my keyboard shortcut.
06:34That's Command+Shift+F11 on Mac.
06:37When the application comes to the screen, I'll type in my name as the preference
06:41value and I'll click Set preferences.
06:44And I see the feedback that the preference was saved.
06:48But now, I'll add code to retrieve and display the preference value.
06:52I'll go back to the main activity class, and now I'll add code to the
06:57refreshDisplay method.
06:59I'll declare a string variable that I'll call prefValue, and I'll get its value
07:04by calling a method of my settings object.
07:07I'll call settings.get.
07:10Just as with the put methods of the editor object, there are versions of the get
07:13method for each supported data type, plus one called getAll that returns all of
07:18the values in the current object as a map object.
07:22I'll call getString, and I'll pass in my USERNAME constant as the key, and then
07:28I'm asked for a default value.
07:30I'll type a literal of Not found, and if there is no preference with this key,
07:36that's the value that I'll get back.
07:38Now, to display the value once again, I'll use my UIHelper class.
07:42This time I'll use displayText, and I'll pass in this as the activity,
07:48R.i.d.textView1 as the ID of the textView object, and then prefValue as the
07:55value I want to display.
07:57I'll save and run the application again and when it refereshes, I'll click Show
08:02preferences without setting the value again.
08:05This demonstrates that the value has been saved to persistent storage.
08:09It will persist between launches of the application and even after a device has
08:14been powered down and then powered back up again.
08:17At a later point, if I want to restore to a blank slate--that is, delete all
08:22of my personal data--I can go to the Home screen, then from there to the
08:26application list, I can click on the application icon, and drag it up to App info.
08:37Once the emulator has finished calculating the amount of data that's been
08:40used, I can click Clear data and all of the application's data will be
08:44completely deleted.
08:48I can then go back to the application and run it again, and when I click on Show
08:53preferences, I get Not found.
08:56I'll type in my name and click Set preferences, I'll leave the activity, I'll
09:01start it up again, I'll click Show preferences, and show that once again the
09:06value has been saved persistently.
09:09So, that's all the code you need to work with preferences using pure Java.
09:13Again, you determine whether preferences are local to the activity or shared
09:17with the entire application by which method you call--
09:20GetPreferences or GetSharedPreferences--and from that point forward, the code
09:25is exactly the same to get and retrieve values to use in your application.
Collapse this transcript
Defining shared preferences with an activity
00:00In a previous video, I described how to set user preferences using Java code.
00:05Now, I'll show you how to create a PreferenceActivity, a user interface
00:10that lets the user type in values and save those values to persistent
00:14storage automatically.
00:16I'll be using a version of my project called Preference Activity that still
00:19has the buttons to set and show preferences but now no longer has an EditText control.
00:25I'll be handling user input through my new activity.
00:29I'll start by creating a new layout.
00:31In the menu, I'll select File > New > Android XML file.
00:37I'll set the Resource Type to Preference, and then I'll give the file a name of settings.
00:43The .xml extension will be added automatically, and the new file will be saved
00:48into an XML subfolder underneath my Resources Area.
00:53I'll click Next, and I'll accept all the defaults here and click Finish.
00:58That creates my new XML file.
01:00The file might open to this view--the XML view--or might open to the Structure view.
01:06I'm going to start in the Structure view and add elements and categories here.
01:11When you add up preference to this screen, you can either group it within a
01:15category or edit directly to the screen's root.
01:18I'll start with the category.
01:20With the screen selected, I'll click the Add button and I'll choose Preference
01:24Category and click OK.
01:27I'll set the category's attributes.
01:29I'll click Attributes from Preference. I'll set a title--
01:33That's what the user will see--and I'll use a title of General settings.
01:39Now I'm ready to add preferences.
01:41I'll click back on the category element and click Add again.
01:45You can add a CheckBoxPreference for a Boolean preference, an EditText for a
01:50single string, or one of the list controls for the sets of strings.
01:55I'll start with an EditTextPreference for the user name.
01:58I'll select that item and click OK and then open up the Attributes.
02:04The Key is a string that you'll use in your Java code to address this preference.
02:09I'll use a string of pref_username.
02:13The Title is a string that the user will see on the screen, and I'll type in User name.
02:19The Summary is a string that you can add also, and if you set it, it will be
02:23visible to the user and this can be used to describe what the preference is used for.
02:27I'll leave the Summary and all of the other values blank and I'll go on to
02:31create another preference.
02:33I'll go back to the category and click Add again, and this time I'll use
02:38a CheckBoxPreference.
02:40I'll go to the attributes and I'll set the Key for this one to pref_viewimages.
02:48I'll set the title to View images, and I'll set the Summary to a string of
02:53Determines whether tours are shown with images.
02:58I'll click back on the tree and that refreshes its view.
03:01Then I'll go to the XML view.
03:03I'll double-click the tab to expand to full screen, and then I'll reformat so
03:08it's a little bit more readable.
03:09I'll press Command+A--that's Ctrl+A on Windows to select all--and then Command+I
03:15or Ctrl+I on Windows and that reformats the XML layout.
03:20So this is what your XML looks like.
03:22The category is a child of the screen and each preference is a child of the category.
03:27And again, the category is optional.
03:30I'll save those changes. My layout is done.
03:32Now, I'm ready for the next step.
03:35Just as when you create an activity for some functionality in your application,
03:39you need to create a Java class that will use this layout.
03:44I'll go to my Package Explorer.
03:46I'll go to my default package, and I'll create a new Java class.
03:51I'll name this class SettingsActivity, and I'll set the Superclass to a class
03:57named PreferenceActivity.
03:58This is a class that's a part of the Android SDK. And I'll click Finish.
04:05Just as when creating any activity, you have to bind the activity class to
04:09the activity layout.
04:11I'll move the cursor into the class declaration and create a little bit of space,
04:16and then I'll override the onCreate method.
04:20I'll type the beginning of the method name and press Ctrl+Space and select onCreate.
04:25Within the new onCreate method, I'll delete that TODO comment, and then after the
04:31call to the Superclass's onCreate method, I'll call addPreferencesFromResource,
04:38and I'll pass in the resource ID of the new layout XML file I've just created.
04:43That will be R.xml.settings.
04:47Now, when this activity is brought to the screen, the settings layout will be presented.
04:53Notice that you're getting a warning indicating that the
04:56addPreferencesFromResource method is deprecated.
04:59That's because in this version of the SDK--I'm starting back in Honeycomb--
05:04the SDK prefers that you use something called the Fragment API to put together
05:08your preference activities.
05:10This approach, however, will still work all the way back to older versions of
05:14Android, all the way back to 1.X. So I'm going to use this code for now, but
05:20this is an interesting warning to keep track of, because the
05:23addPreferencesFromResource method might go away in future versions.
05:27For the moment though, I'll get rid of this warning.
05:30I'll go to the icon on the left.
05:32I'll click it and select addSuppressWarnings, and that gets rid of the warning icon.
05:38The addPreferences method name is still struck out, and that will be a useful
05:43warning to me in the future.
05:45I'll save those changes.
05:46That's all I need to do for the Java class.
05:49Next, I need to register the activity in my Application Manifest.
05:54I'll open AndroidManifest.xml.
05:57It might open in the Manifest view or it might open in the XML view.
06:02I'll go to the XML view and expand to full screen.
06:06Just as with any activity, you have to register the Java class in the manifest.
06:11I'll move the cursor past the one and only activity--that's the MainActivity--
06:16and I'll create a new activity tag.
06:20I'll assign a name attribute, and I'll set it to the name of my class, starting
06:25with a period representing the default package and then the name of the
06:30class, SettingsActivity.
06:33I'll complete the tag and that adds the end tag, and that's all I need to do.
06:38I don't need to add filters or anything else.
06:41Now the activity is a part of the application.
06:44I'll save those changes.
06:46I have one more step.
06:48I need to add code to the MainActivity class so that when the user says they
06:52want to look at the settings, I launch this new PreferenceActivity.
06:56So I'll go to my MainActivity.java class.
07:00I'll expand that to full screen, and I'll go down to my setPreference method,
07:04which in this version of the application now doesn't have any useful code.
07:11Within the setPreference method I'll create a new intent object.
07:15I'll declare it with the Intent class and name it intent, and I will
07:19instantiate it using the intent class constructor new Intent.
07:24I'll pass in this as the parent and the name of my settings class,
07:28SettingsActivity, and the .class extension.
07:34So now I have an intent object that will launch the class, and I'll call start
07:39activity, and I'll pass in the intent object.
07:45The set preference method is still being triggered by touching the button.
07:49So now, instead of grabbing a value from a user interface element and EditText,
07:54it will launch the activity.
07:56I'll save my changes and I'll run my application.
08:01Now, to launch my new activity, I just click the Set preferences button.
08:05That opens my new activity.
08:07I can click on the User name and type in a value and then click OK.
08:14I can click the check box to set the View images value to either true or false.
08:20Now I need to modify some code so I can retrieve those settings.
08:25I'll go back to my Java class and show that I already have code that's
08:29looking for the user name.
08:31There are a couple of things I need to change to make this work.
08:34First of all, in the onCreate method, instead of referencing the getPreferences
08:39method, which returns an instance of a preferences object local to the activity,
08:44I'll use a class called PreferenceManager.
08:48I'll call its method, getDefaultSharedPreferences, and pass in this.
08:54That returns a reference to the preferences object that's created by the
08:58preference activity.
09:00Now, I can get to those preferences easily.
09:03Next, I need to change the constant for the user name to match what I set as the
09:08preference key in my preference activity.
09:11I'll go up here and change user name to pref_username.
09:16While I'm here, I'll add another constant for the View images preference.
09:21I'll use a constant of VIEWIMAGE, and I'll set it to pref_viewimages.
09:30That takes care of the user name, but I also need to add code to update my check box,
09:36so I'll go down here and I'll type in UIHelper.setCBChecked.
09:43That's my custom static method that will manage a check box in my user interface.
09:48I'll pass in this for the activity, R.id.checkBox1 for the check box I want to
09:54update, and settings.getBoolean.
09:59I'll pass in VIEWIMAGE as the key I want to look up and false as the default.
10:05Now I'm ready to test again.
10:07I'll relaunch the application in the emulator, and once the application opens,
10:13I'll click Show preferences and that retrieves both the user name and the
10:18Boolean value for view images, and updates my user interface.
10:22So those are the steps for creating a preference activity:
10:25create the activity layout, create the Java class that uses the layout, register
10:30the Java class in your manifest, and then open the activity at runtime by
10:35creating intent and starting the activity.
10:38You can then retrieve the preferences at runtime by calling the
10:41Preferencemanager.getDefaultSharedPreferences method, and that returns the
10:46instance of the preferences object that's managed by the activity.
Collapse this transcript
Listening for changes to shared preferences
00:00There are many scenarios in an Android application where you need to instantly
00:04react when a preference is changed and update the user interface in some way.
00:09When you need to this, you can create a preference listener, an event listener
00:15that will let you know when a preference has been updated and let you add code
00:19that executes when that happens.
00:20I'll work in a new version of the project called PreferenceListener.
00:25Just as in the last video, this version of the project has buttons to set and
00:29show preferences, but I'll update this version of the application so that the
00:34preferences are updated instantly when they're changed at runtime.
00:38I'll go to the application's MainActivity Java class, and I'll start by creating
00:43an instance of a listener object.
00:45I'll declare the listener object as a field of the class, so that it persists as
00:50long as the activity is on the screen.
00:53You'll see that developers sometimes create this listener objects within
00:56an onCreate Method.
00:58The problem with that, though, is that they're weakly referenced and subject to
01:02garbage collection, and so they don't always work correctly.
01:05By declaring the listener outside of the methods, you make sure it lasts as
01:09long as you need it to.
01:11Within the activity, I'll declare a new private field,
01:14and I'll set the datatype as OnsharedPreferenceChangeListener.
01:19That's a long class name,
01:21so I'll just type the beginning of the class and press Ctrl+Space and select the
01:26class, and I'll name the object Listener.
01:29Now, you can't initialize it here;
01:32you have to wait until the activity is coming to the screen.
01:35So, I'll add code to the OnCreate method.
01:40I'll move the cursor after the code that's initializing the settings object, and
01:44then I'll initialize it using this code.
01:48I'll say Listener = new, and then I'll use the class name again and call
01:53its Constructor method.
01:55I'll type in the name of the class.
01:57I'll press Ctrl+Space and I'll use the version that's a member of
02:02SharedPreferences: SharedPreferences .OnSharedPreferenceChangeListener.
02:07I'll select it and that adds the method's signature and also adds the required
02:11method OnSharedPreferenceChanged.
02:14You add the code that you want to execute within that new method, where the TODO comment is.
02:21When this method is called, you'll receive a reference to the SharedPreferences
02:25object and the key to the preference that was changed.
02:29You can write finely tuned code that only reacts to the one preference that was
02:33changed, or, as I'm going to do here, you can call a method in your main activity
02:39that simply updates everything that the preferences might affect.
02:42I will get rid of this TODO comment, and within the class, I'll reach back to the
02:48current instance of the main activity, using MainActivity.this.
02:54And from there, I'll call the method refreshDisplay.
02:58The refreshDisplay method is designed as an event handler method.
03:01It receives a view argument.
03:04I'm going to be calling it without an event, so I don't need to pass in a view reference.
03:09I'll just pass in null.
03:11And that will cause the display to update, taking into account any preferences
03:15that might have changed.
03:16Finally, I need to register the Listener object with the Preferences object.
03:22That's how you bind the two objects together.
03:25When the user makes a change to the preferences, the Preferences object
03:30looks for listeners,
03:31and the only Listeners that have registered with the Preferences object
03:35will receive the event.
03:37After the listener has been created, I'll call
03:39settings. registerOnSharedPreferenceChangeListener.
03:44Once again, I'm using autocomplete to call this very long method name.
03:48And I'll pass in the Listener object.
03:52Now, the refreshDisplay method is already doing what I need it to do.
03:56It's down here at the bottom of the class, and it's updating the display based on
04:00both the USERNAME and the VIEWIMAGES Preferences.
04:04So I'll run the application in the emulator.
04:06Notice that as it opens, the preferences aren't displayed automatically.
04:11I'll click on Show preferences and that still works as it did before.
04:14But now I'll click on Set preferences. That take me to my Preference Activity.
04:20I'll click on the User name, and I'll update it by adding the first initial of my
04:24last name, and then I'll deselect the View images preference.
04:29Then I'll click the Back button.
04:31And when I come back, the screen has been already updated.
04:34I don't need to click to Show preferences button.
04:37And in fact, I'll go back to the application in Eclipse and I'll take out that button.
04:43I'll go to the MainActivity XML file, and I'll click on the Show preferences
04:48button, and then I'll just press Delete and it's gone.
04:53I'll save the changes and run the application again.
04:59And once again, I'll go into Set preferences.
05:02I'll change the User name.
05:04This time I'll add my full name, and I'll select the View images preference
05:10to turn it to true.
05:11And I'll return, and the application has been updated.
05:15So, an event listener will let you react instantly whenever preferences are changed.
05:21You can use the listener on either a SharedPreferences object that's managed by
05:25an activity, as I'm doing here, or on Preference objects that you've created in
05:29Java, using the getPreferences and getSharedPreferences method.
05:34Either way, you'll be able to update your user interface right away, whenever the
05:38user makes a change.
Collapse this transcript
3. Using Internal and External File Storage
Creating and reading files in internal storage
00:00The Android SDK includes all of the tools you need to create and read files.
00:05You can create binary or text files.
00:08And for text files, you can use your own arbitrary format, or you can use common
00:13structured data formats, such as JSON and XML.
00:18The files can be placed in either internal or external storage.
00:22This refers to whether the file is local to the application or shared with
00:26other applications.
00:27I'll start with internal files.
00:30I'm working in a version of the project called Internal Storage.
00:34This project has an Edit Text control in the layout and then two buttons at the
00:38bottom labeled Create file and Read file.
00:41And there's also a TextView object at the bottom that I'll use to
00:45output information.
00:47I'll go to the MainActivity class.
00:50I'll expand to full screen, and I'll start in the onCreate method, after the existing code.
00:57In order to work with internal files, the first step is to create an instance of the file class.
01:04This is the same file class that's used in all Java code.
01:08It represents either a directory or file, and in this case it will represent
01:12the internal files directory for the current application.
01:16You get it by calling a method called getFilesDir.
01:19I'll create a new File object, which I'll name f.
01:23For the import, I'll select the file class from java.io.
01:28I'll name the object f and I'll get its value from getFilesDir.
01:35Next, I'll create a string variable that I'll name path, and I'll get its value
01:39by calling the file object getAbsolutePath.
01:43I'm going to show you exactly where these files are placed on your device or in you emulator.
01:50Next, I'll use my UIHelper class and I'll call the displayText method.
01:55I'll pass in this to reference the current activity, R.id.textView1 as the
02:02textView object I want to use to display the text, and then the path.
02:08I'll save my changes, and I'll run the application in the emulator.
02:11As the application opens, it gets a reference to the internal files directory
02:16and displays the location at the bottom of the activity. It's /data/data and
02:22then the package for the application and then the files folder.
02:27When you're working in the emulator, you can actually see this folder.
02:30I'll go back to Eclipse and I'll go to the DDMS perspective.
02:36From there, I'll go to the File Explorer tab.
02:39I'll open the data folder and then from there, go to the data subfolder.
02:45I'll expand the size of this column so I can see all of the packages.
02:50I'll scroll down and find my application package.
02:53I'll open that, and there is the files folder.
02:56And right now it's empty.
02:58If you try to do the same thing on an actual device, it probably won't work,
03:03unless you've gained root access to the device, or, as it's mostly known, "rooted
03:07the device" and gotten permission to see these directories.
03:12But in the emulator, you have complete access, and you can see exactly what's going on.
03:17So now I'll go back to my coding perspective
03:20and I'll go to a method stub that I've created called createFile.
03:25This method would be called when the user clicks the createFile button.
03:29The first step will be to retrieve a String Value that the user enters, through
03:34an editText object that's a part of the layout.
03:37So I'll create a string that I'll call text, and I'll use my UIHelper class, and
03:43I'll call the getText method.
03:45I'll pass in this and then the resource ID of my editText object R.id.editText1.
03:53So now I have a value that I can add to a file.
03:57I'm going to use an instance of the class FileOutputStream.
04:02I'll type the beginning of the class name, FileOutputStream.
04:06I'll name the object fos, and I'll call a method called openFileOutput.
04:13You pass in the name of the file you want create.
04:16You don't need to include a file extension, but you can if you want.
04:20I'll call it simply myfile.txt.
04:23And then I'll set the mode to MODE_PRIVATE.
04:28At this point, you're working with file io and you might be throwing exceptions,
04:33so you'll be calling methods that require throwing an exception or surrounding
04:38the code with a try-catch block.
04:40To make things simple, I'm just going to add a throws declaration to the current method.
04:45The name of the class will be FileNotFoundException. and it's a part of
04:49the java.io package.
04:52Next, I'll write to the file.
04:53I'll move the cursor after the code that's creating the OutputStream,
04:58and I'll call the method fos.write.
05:01There are a few versions of this method.
05:03I'm going to pass in the one that takes an array of bytes.
05:07And then I'll get the array of bytes by calling the text object, and it's getBytes method.
05:12When you're working with a FileOutputStream and a string, you're working
05:16with classes that were a part of the standard Java Toolkit, not anything
05:20that's unique to Android.
05:22Then when I'm done, I need to make sure I close the FileOutputStream,
05:27so I'll call fos.close.
05:32And finally, I need to let the user know that the action was taken, so I'll call
05:36my UIHelper.displayText method,
05:39I'll pass in this, the textView ID, and a literal string of File written to disk.
05:45I'm still getting errors, so once again, I'll go and do a quick fix.
05:52I'll click on an error icon and once again call Add throws declaration, and
05:57notice that the name of my exception class was changed, not added.
06:02It's now IOException, and that will cover all the possible exceptions that might
06:06occur in this method.
06:08I'll save my changes, and I'll run the application in the emulator.
06:15When the application opens, I'll type some text.
06:18I'll type "Looking forward to visiting California."
06:23I'll click the Create file button and I see the message that the file
06:26was written to disk.
06:28I'll go back to Eclipse, to the DDMS view, and there's the file.
06:33It was added to my disk and placed in the files folder.
06:37And again, I can see it easily in the emulator, but you might not be able to do
06:41the same thing with the real device.
06:44So now let's look at the code that you need to read the file.
06:47I'll go back to my Editing perspective.
06:49I've named mine Android.
06:51And I'll go to the method stub readFile.
06:54I used the FileOutputStream to create the file, so I'll use a FileInputStream to read it.
07:00I'll create an instance of FileInputStream, which I'll call fis, and I'll get its
07:07reference with openFileInput.
07:09And I'll pass in the same string name: myfile.txt.
07:14And you could a constant for that if you prefer.
07:18Now with the file open, I'll use some pretty standard Java code to read the
07:23contents of the file into memory.
07:26I'll create a BufferedInputStream object, which I'll call bis, and
07:30I'll instantiate it with its constructor method, and I'll pass in the
07:34fis, or FileInputStream.
07:37Now I can read the file efficiently.
07:39Next, I'll create a StringBuffer object, which I'll name b, and I'll instantiate it
07:44with the StringBuffer constructor method.
07:46I'll use the StringBuffer to read one character at a time from the stream.
07:52I'll do a loop. I'll type the key word while and press Ctrl+Spaceb, and choose a
07:57while loop with a condition.
07:59I'll set the condition using the available method of the BufferedInputStream.
08:03I'll ask the question, are there any characters available,
08:08with bis.available is not equal to zero?
08:13Each time you call the available method, you are queuing up another character,
08:17and then you can read the character calling the Read method.
08:21Within the while loop, I'll create a char variable, which I'll name c, and
08:26I'll call bis.read.
08:29And then I'll press Command+1 on Mac or Ctrl+1 on Windows and do a quick fix to
08:35add the cast to char.
08:38Then I'll take that char value, or character, and I'll append it to the buffer,
08:43calling b.append, and I'll pass in the character.
08:48Notice that once again, just like in the createFile method, I'm getting warnings
08:53that there are exceptions that might be thrown.
08:56I'll do a quick fix on one of them and add a throws declaration, and that clears
09:01all of those errors.
09:02So now I've read the string into memory, and I'm ready to display it to the user.
09:07And for that, I'll once again use my UIHelper class and its displayText method.
09:12I'll pass in this, the I.D. of the textView object R.id.textView1, and the
09:18toString method of the Buffer object.
09:23To make sure that I've completely cleaned up, I'll call the Close method of the
09:27BufferedInputStream and FileOutputStream.
09:30And I'll call them in reverse order from how I opened them.
09:34I'll call bis.close and fis.close.
09:39I'll save my changes, and I'll run the application, and when the application
09:44opens in the emulator, I'll click the Read file button and I see the message
09:49Looking forward to visiting California.
09:52The important thing here is that the creating and the reading of the file were
09:56in two application sessions.
09:58The data is persisting on disk because I'm saving it using these FileInput
10:03and Output methods.
10:05And again, I'm using arbitrary text here, but in the next couple of videos,
10:10I'll show you how you can use these tools to save and read structured data
10:15using JSON and XML.
Collapse this transcript
Creating and reading JSON data files
00:00JSON, or JavaScript Object Notation, is a concise structured data format.
00:06It's used in Android to move data around the web from server to device, but can
00:11also be used to save structured data locally to persistent storage.
00:16I'll show you how to use JSON in the Android SDK in this project: JSONDataFiles.
00:22The user interface for this version of the project has a very minimal set of controls:
00:28two buttons for creating and reading a file and one TextView object to display text.
00:34I'll go to my MainActivity class and scroll down toward the bottom of the code
00:39and find the method createFile.
00:41In this version of my createFile method I already have code to create a file in
00:46internal storage--that is, a file that's local to the current application.
00:51But I'll add code here to create a JSON data packet.
00:55I'll place the cursor above any of the other code in the method, and I'll start
00:59by creating an instance of the class JSONArray.
01:03It's spelled J-S-O-N, in all uppercase, and then Array.
01:08And I'll name the object dat'.
01:09A JSONArray is a resizable array.
01:14You can add as many objects as you want to it and then it will automatically
01:17serialize the data into a JSON notation string.
01:21I will instantiate the object with the class's constructor method, using new JSONArray.
01:28Next, I'll declare an instance of a class called JSONObject, and I'll name that tour.
01:34I'm not instantiating it quite yet.
01:36I'm just going to start with declaring it.
01:38A JSONObject is a dynamic object that implements the Java Map Interface.
01:45You can add values to the JSONObject as key-value pairs.
01:49The keys are always strings, but then the values can be anything you want.
01:55I'll create a single tour instance.
01:57I will instantiate the object using tour = new JSONObject.
02:04Then I'll add a couple of properties.
02:07I'll name my two properties tour and price.
02:10The tour will be a string--that will be the name of the tour--and the price
02:14will be a numeric value.
02:15Because I'll be working in JavaScript style, the data types will be dynamically
02:20inferred from my syntax.
02:23First, I'll create the tour name.
02:25I'll call tour.put, and I'll pass in a string of tour and a value of Salton Sea.
02:33Because the string Salton Sea is surrounded in quotes, its data type will be a string.
02:39Notice that I'm getting a warning icon.
02:41This is telling me I need to add a throws declaration.
02:44Whenever you start creating JSON objects or JSON arrays, you might throw an
02:49exception called JSONException.
02:52I'll add that to the throws clause by clicking on the icon and doing a quick fix.
02:58That adds JSONException to the list of exceptions that might be thrown from this method.
03:03I'll come back to my call to the put method and I'll duplicate that.
03:09Then I'll change the key for the second value from tour to price.
03:14I'll change the value from a string to a number.
03:17This is a pure numeric literal, and it will be translated as a numeric data type
03:23by the JavaScript notation code.
03:25This will be a simple version of an object, and now I'm ready to add it to the array.
03:29So, I'll call data.put and I'll pass in the tour object.
03:34So, that's all you need to do to create an object and add it to the array.
03:37I'll duplicate those lines of code a couple of times.
03:42And for the second and third versions, I'll change the tour and price.
03:46For the second tour, the tour property will be Death Valley and the price will be 600.
03:52And for the third one, the tour name will be San Francisco and the price will be 1200.
03:58Notice that I'm not including a comma here.
04:01This is a pure numeric literal.
04:03And because we're working in Java 6 compatibility, you can't add an underscore
04:08as you might in Java 7.
04:10That won't be recognized correctly.
04:12Just leave it as is, without the underscore or any other notation, and it will
04:17be correctly data typed as a number.
04:19So now my JSONArray is ready to use. And I'll come down here where I'm setting a
04:24string variable called text, and
04:26I'll get its value by calling the data object's toString method.
04:30And that serializes the value to a string, and the string can then be saved to disk.
04:36I'm going to change the name of my file.
04:39And instead of myfile.txt, I'll name this file tours.
04:43I won't use any file extension.
04:47Finally, instead of just telling the user that the file was written to disk,
04:51I'll actually display the content that was created.
04:54After File written to disk, I'll add a line feed with a \n, and then I'll append
05:00to that data.toString.
05:03Again, I'm serializing a JSON string.
05:06I'll save my changes and run the application in the emulator.
05:12When the application opens, I'll click Create file, and I see the message that
05:17the file was written to disk, and I see the JSON notation.
05:21The square brackets wrapping the entire string mean that it's an array, and each
05:25JSONObject is notated with curly braces.
05:28The string values have quotes and the numerics don't.
05:32It's pretty easy to read, and it's been written to disk and is ready to use.
05:36I'll add code to retrieve and read the file from disk.
05:41I'll go back to Eclipse and I'll move down to the method readFile.
05:45First, I'll change the name of the file that I'm reading.
05:48Once again, instead of myfile.txt, I'll use tours.
05:53Then I'll move the cursor down to after the content has been read.
05:57I'm going to move a couple of lines of code around.
06:01Before I start processing the JSON content, I want to make sure that I've closed my strings,
06:07so I'll move the calls to the close methods up a bit and then I'll place the
06:11cursor after the close methods and before the call to displayText.
06:16In order to deserialize from a string to a JSON array, declare the JSONArray
06:21object, and once again, I'll call it data, and I'll once again use the JSONArray
06:26constructor method, but this time I'll pass in the string that contains the
06:31JSON-notated content.
06:33I'll pass in b, the StringBuffer, .toString.
06:37And now, I've deserialized the content and I'm ready to deal with it as a Java object.
06:43Just as I did with the createFile method, I'll do a quick fix and add
06:47JSONException to my throws clause.
06:50I'll move down to after I've deserialized the data.
06:54I'll loop through the array.
06:56I'll use a for loop, iterating over an array.
07:01My temporary variable will be i, and I'll change the array that I'm reading to data.
07:07Within the for loop, I'll create a string, and I'll name it tour, and I'll call
07:12the JSONArray class's getJSONObject method.
07:16And I'll pass in the current index that I'm looping on.
07:20So now I'm returning a JSONObject, and I want to retrieve just the string value
07:25representing the tour name.
07:27So, from the JSONObject, I'll call the getString method.
07:30Notice that there are methods for getString, getBoolean, double, int, and long.
07:36These are the data types that are supported by JSON notation.
07:40I'll call getString, and I'll pass in the key tour.
07:44Remember, that's the key that I used when I created the JSON notation.
07:49I still have one error in the for loop, and that's because the code template
07:53I used when I created my for loop treated length as a property of the data object.
07:58But in the JSONArray, it's a method.
08:00So, I'll move my cursor after the length property and press Ctrl+Space, and
08:06that correctly fills in the method call.
08:08So now, I'm retrieving a string for each tour in the array.
08:12I'd like to concatenate them all together into a single string.
08:16I'll place the cursor above the for loop, and I'll create a new StringBuffer
08:20object, which I'll call toursBuffer, and I'll instantiate it with the
08:24constructor method.
08:28Then I'll move the cursor back to within the for loop, and I'll take that tour
08:31string and I'll append it.
08:33I'll call toursBuffer.append and I'll pass in tour, plus a line feed.
08:39Finally, I'll wrap up the coding by going down to the call to displayText, and
08:44instead of outputting the rawBuffer, now I'll output the toursBuffer.
08:51So, let's again review the code.
08:53I'm starting by using streams to read the content into memory.
08:58Then I'm deserializing from the string into a JSONArray object.
09:03Then I'm looping through the JSONArray and getting the data from the objects one at a time.
09:08And finally, I'm outputting the content that I've retrieved to the screen.
09:13I'll save and run the application in the emulator again.
09:17Remember, I've already created the file in persistent storage.
09:20I don't need to create it again.
09:23I'll click Read file, and there's the result.
09:26I've retrieved the data, and I'm displaying it on the screen.
09:29So, that's a look at how you can both create files and read files using the JSON format.
09:35The JSON format is one of the smallest, most concise formats that you can use.
09:40It's incredibly fast on an Android device, and all the tools you need to use are
09:46included in the Android SDK.
Collapse this transcript
Working with files in external storage
00:00Each Android app has both an internal and an external storage area.
00:05Files in internal storage can only be accessed by the application that created them.
00:10Files in external storage are available to all applications on the device.
00:15I'll describe how to create an application that uses external storage using this
00:19project called ExternalStorage.
00:22First, the application must be given permission to acces external storage.
00:27To do this, edit your Android manifest file.
00:31I'll open the Android manifest.
00:33It might open in XML format or in the Manifest view.
00:37Either way, click on the Permissions tab.
00:41Click the Add button and select Users Permission, and click OK.
00:47Pull down the list of available Permissions on the right, scroll all the way
00:51down to the bottom, and choose WRITE_EXTERNAL _STORAGE, and save your changes to the file.
00:57If you forget to do this, when you try to run the application using the code
01:01that I'm about to create, it will crash and you'll get no exception errors and
01:06information in the console view telling you what you missed.
01:10Now that the application has permission, I can add code to read and write
01:15to external storage.
01:17I'll go to my MainActivity class.
01:19I already have code in this class that's accessing internal storage.
01:24I'll go to the bottom of the onCreate method.
01:27It starts up here. And then down towards the bottom, there's a bit of code that
01:31accesses the internal files directory.
01:34It does this using the method getFilesDir.
01:38To access external files, I'll change this from getFilesDir to
01:42getExternalFilesDir.
01:45When you call this method, you're asked to pass in a string value.
01:49You can pass in a constant that references one of the standard subfolders of the
01:54external files directory.
01:55You can create subfolders for music files, for audio files, and for many other
02:00special types of files, but if you just want to get the root directory in your
02:04external storage area, pass in a value of null.
02:08All I'm doing in the onCreate method is getting that directory reference and its
02:12absolute path and then displaying that location in my user interphase.
02:17So I'll save and run the application in the emulator.
02:21When the application opens, it shows the absolute address of the external file's location.
02:28It starts with mnt, then the name of the SD card, then Android, data, the package
02:34for the application, and the directory name files.
02:38You can see this location if you go to the DDMS perspective.
02:43From there, go to the File Explorer tab, go to the mnt folder, from there to
02:49sdcard, then to Android, to data.
02:53You'll find folders there with package names.
02:56Here's the package for my application.
02:59I'll open that, and there's the files folder, and it's currently empty.
03:03So now we know we can get to external storage, and we're ready to write some code
03:08that creates a file there.
03:10I'll go back to my coding perspective, which I've called Android.
03:15Before you access content in the external files area, you should always check
03:19to make sure that it's actually available on the device on which you're running the application.
03:24To do that, create a special method that will return a Boolean value.
03:30I'll scroll down to the bottom of my class and I'll create a new method called
03:34checkExternalStorage.
03:36I'll make it public, and it will return a Boolean value, and here's the method name.
03:42To checkExternalStorage state, call a method called getExternalStorageState,
03:47which is a static method of the environment class. It's a string.
03:52So I'll start by creating a string variable that I'll call state, and I'll get
03:56its value by calling Environment.getExternalStorageState.
04:02Now I'll evaluate the state and determine whether I can write to external storage.
04:07I'll start with an if clause.
04:10I'll type in if and then press Ctrl+Space and choose if else.
04:16For this first conditional, I'll ask whether the state variable equals a value
04:21called MEDIA_MOUNTED.
04:24The code looks like this: state.equals, then I'll pass in
04:28Environment.MEDIA_MOUNTED, a constant.
04:32If you get back this value, that means that external storage is available and
04:37you can write to it.
04:38So I'll return true.
04:40Here are the other possibilities.
04:42For my first else, I'll use an else if, and then I'll ask the question, does the
04:48state have a value of MEDIA_MOUNTED read-only?
04:51I'll take the part between the outer parentheses, copy it, and paste it into this
04:57parentheses, and I'll change this constant to MEDIA_MOUNTED_READ_ONLY.
05:03If that's the condition, I'll output to the screen a string of external
05:07storage is read-only.
05:09I'll use my UIHelper class, I'll pass in this as the activity, and then the
05:16textView object and then the literal string, External storage is read-only.
05:22Finally, I'll put in an else clause, and for this condition, I'll tell the user
05:28that the external storage isn't reachable at all.
05:31I'll duplicate this line of code and move it down, and I'll change the literal
05:36string to External storage is unavailable.
05:41Finally, after all the conditional code, I'll return false.
05:45So the only condition in which our report that external storage is available and
05:49writable is if I get back a value of MEDIA_MOUNTED.
05:54So now, I have code that contest my state and I'm ready to write
05:58to ExternalStorage.
06:00I'm going to scroll up a bit and find the method createFile.
06:04If you have trouble finding the method, restore your editor to its original size
06:08and try using the Outline view on the right.
06:11I've modified my createFile method.
06:14I'm still using JSON data as I did before, but I've taken all of the code that's
06:19creating the JSON packet and put it into its own method, called getNewJSONData.
06:25That reduces the amount of code I'm reading here, and it'll make it easier to add
06:28some new functionality.
06:31In the createFile method, I'll find out whether I can write to external storage.
06:36I'll create an if statement, and I'll set the condition to not
06:41checkExternalStorage.
06:43That's the method I just created.
06:46If that condition is true--that is, if external storage is not available--I'll just return.
06:52That's it. This method will stop operating.
06:56But if external storage is available, the rest of the code will be executed.
07:00I'll create my JSON array and I'll be ready to write it to disk.
07:04Now, before I create the file, I'm going to add some constants and fields to
07:09the top of the class.
07:10First, I'm going to declare a file object that will persist for the lifetime of the activity.
07:16I'll declare this after the existing fields.
07:19I'll set it to private.
07:21I'll give it a data type of File and a name of file, all lowercase.
07:25I'll also create a constant named FILENAME, and this will be the name of the
07:30file I'm about to create.
07:32I'll declare a private static final and string, I'll name it FILENAME, all upper-
07:38case, and then the name of the actual file will be jsondata, all lowercase.
07:45I'll initialize the file in the onCreate method.
07:49I'll go down to the bottom of the onCreate method, and here I'm going to use
07:55this variable that represents my external files directory.
07:58I'll create a new file reference that points to a file in that directory.
08:04Down here, I'll say file = new File.
08:07I'll create a new file that's placed in the external files directory.
08:12So the first argument will be f, which points to that file's directory, and
08:16the second argument will be the name of the file and I'll use my new constant, FILENAME.
08:22To make this a little bit easier to read, I'll refactor and rename this variable.
08:27I'll select the variable f, right- click and choose Refactor > Rename.
08:35I'll name the variable extDir, for External Directory.
08:40So now this file object is going to point to the file and I'll be able to use it
08:44as many times as I need to within the activity.
08:47So now I'll go back to the createFile method.
08:50To make this a little bit easier, I'll restore the editor and then use my
08:54outline over here and go to createFile, and then expand the editor again.
08:59In order to point to my file that I've just defined in external storage, I'll
09:04remove this code that's defining a file in internal storage.
09:08I'll replace it with a bit of code that instantiates FileOutputStream and
09:13passes in the file object.
09:16Now the file is being written to external storage instead of internal storage.
09:21I'll do the same sort of thing to the readFile method.
09:24I'll move down here. And here's where I'm creating my FileInputStream.
09:30I'll remove that code and instead use the FileInputStream's constructor method
09:36and pass in the file object.
09:39Now, this method is also pointing to a file in external storage. I'm done.
09:45I'm ready to test my code.
09:47I'll run the application in the emulator.
09:51When the application opens, I'll click Create file and the file is written to disk.
09:56Then I'll click Read file and the file is read from disk.
10:00Now, to show where the file has been created, I'll go back to Eclipse, to the
10:05DDMS perspective, and I'll refresh my view by clicking on one of the spinners in
10:10the directory tree, and there's my file jsondata.
10:15With the file selected, I'll click the link to pull the file from the device.
10:19I'll place it on my desktop and save the file to disk.
10:24Now, I'll go to Finder.
10:25If you're working on Windows, you can go to Windows Explorer.
10:29I'll go to my desktop, and there's my file. And I'll open it in a text editor.
10:36I'll use TextEdit, but you can use any text editor you like.
10:41Here I can see that the file has been created using JSON format and is available
10:46in the External Files directory.
10:48Because this file is external, you can access it not just in the emulator but
10:53with an actual device.
10:55So at this point, I encourage you to try running the application on an
10:58actual Android device.
11:00Remember I said that with internal storage, you'd be able to access the
11:04directory on an emulator but not so easily on a real device.
11:08With external storage, you should be able to see the file, and you should be
11:12able to pull the file to your development computer and verify the structure and
11:17content of the file that you're creating and reading in an Android app.
Collapse this transcript
Parsing a read-only XML file with XmlPullParser
00:00I've previously described how to create and read files in both internal
00:05and external storage.
00:06You can also package read-only files as a part of your application as a
00:11resource, in the same ways you might package up a layout or a binary file.
00:15I'll describe how to do that and also how to parse XML using a class called the
00:21XML Pull Parser that's included with the Android SDK.
00:26In this version of the application, I've changed my layout so that it contains
00:30a ListView control.
00:33A list view is designed to display a list of data, and I'm going to be retrieving
00:38the data from an XML file that's packaged with the application and displaying
00:42its data in this list.
00:44The layout also has a TextView control that I'll be able to use to display any
00:49type of information I want to the user.
00:51The MainActivity class has been changed so that it extends the ListActivity, and
00:57I'll be able to use this class and the layout to easily display a list of data.
01:02First, I'm going to describe how to package up an XML file as part of your
01:07application as an application resource.
01:10I'll work with an XML file that's delivered in the exercise files.
01:15I'll move Eclipse over to the right and go to my exercise files, to the Assets
01:20folder, and from there to Data.
01:24You can open this tours.xml file in any text editor.
01:28I'm going to copy it into my project first and then show you its contents.
01:32I'll copy it to the clipboard. Then I'll go back to Eclipse and I'll go to
01:37my Resources folder.
01:39You can place this XML file anywhere you want.
01:42I'm going to follow a common standard and place it in a subfolder called raw.
01:48I'll create a new folder in the res folder and I'll name it raw.
01:53I'll paste the file into place.
01:56Then I'll open up the raw folder, and I'll open the XML file using the text editor.
02:01I'll right-click and choose Open With > Text Editor.
02:06Here's the structure of the XML file.
02:08It has a root element named tours and child elements named tour.
02:13Within each tour element, there's a tourID, a tourTitle, a packageTitle, and so on.
02:19I won't be using all of the data elements, but I will be using the tourTitle,
02:24the description, the price, and the image.
02:29There are a couple of different ways of parsing XML.
02:32I'm going to start with something called the XML Pull Parser, a class that's
02:37packaged with the Android SDK.
02:39It's not the easiest parser to code with, but it's very high performance and you
02:44don't have to go get any other parsers to use it.
02:47The code is a little bit complex, so I've already created a class that we can use.
02:53Once again, I'll move Eclipse over and I'll go back to my exercise files.
02:56I'll go back to the Assets folder, and this time I'll go to the Code folder.
03:02I'm going to be using two of these classes.
03:04First, I'm going to use the Tour class.
03:07The Tour class is a Java bin class with a little bit of extra code.
03:12It's designed to represent a single instance of the tour entity.
03:16I'll select and copy the Tour class into the clipboard, then return to Eclipse.
03:21I'll go to my source folder, and I'll go to this new empty package
03:26com.exploreca.tourfinder.model, and I'll paste the file into place.
03:35The Tour class has private fields for each of the values I want to store: the
03:39tour ID, the title, the description, the price, and the image.
03:43There are three strings and two numeric values.
03:47Then there are public getters and setters for each of the fields, and down at
03:51the bottom, very importantly, there's a toString method.
03:54The toString method uses a number format object.
03:58That's a class that's a part of the standard Java Development Kit, and it returns
04:02the title and the formatted price as a single string.
04:05I'll be using this toString method to display the data in the list.
04:11I'll go back to my exercise files and I'll take this file, ToursPullParser.
04:17I'll copy that to the clipboard and go back to Eclipse, and I'll paste this into
04:22my MainPackage, the tour finder package.
04:26Here's the code that I'm going to be using to parse the XML file.
04:30First, I've created five constants, each matching a data element that's a
04:35part of the XML file.
04:37I'll use these constants to identify what I'm looking for.
04:41Then there's a little bit of code that's declaring an ArrayList where each item
04:46in the ArrayList is an instance of that new tour class.
04:49There's a method named parseXML.
04:52The parseXML method first creates an instance of the pullParser object.
04:57That code is right here.
05:00The name of the parser is xpp.
05:03There are a few other bits of code above it, but the important part is that
05:06we're creating an instance of the parser and then we'll be using it to read the
05:10contents of the XML file.
05:12Then there's something called an InputStream.
05:14The important part here is the call to the openRawResource method.
05:19It's opening the tours.xml file as an application resource,
05:24using R.raw--remember, that's the name of the folder where we placed the XML
05:29file--and then the beginning of the tours.xml file name.
05:33You don't include the file extension; the .xml is assumed.
05:37The nature of the pull parser is that it's a streaming parser.
05:41A streaming parser streams through the contents of the XML file and then fires
05:47events whenever it reaches certain markers.
05:50The XML Pull Parser knows to look for five different events.
05:54It dispatches an event when it gets to the beginning of the document and to the
05:58end of the document, whenever it hits a start tag and whenever it hits an end
06:02tag, and whenever it hits a text note.
06:06Each time an event occurs, it's up to you as the developer to find out what the
06:10event was and do something with it.
06:12So in this code, I'm starting by calling the first event, by calling a method
06:17called getEventType.
06:19That triggers the first read of the file.
06:22The first event that happens is the start document event, and I don't care about
06:26that. But I do care about getting to the end of the document.
06:30So my while loop is going to continue processing until I get to that event.
06:35So I'm saying, while (eventType != XmlPullParser.END DOCUMENT) due to following.
06:41Then there's a little bit of processing, and at the end of the loop, I'm calling xpp.next.
06:47That means keep reading the XML file until you get to the next event.
06:52Streaming parsers like XML Pull Parser are very high performance.
06:57They're very fast, but they do cause a little bit of fragmented programming,
07:01because it's up to you as the developer to remember where you are and figure out
07:05what you need to do.
07:07Here's the logic that I've used.
07:09I've said that if I've just gotten to a START TAG then call a method called
07:13handleStartTag and pass in the tag name.
07:18That method is down here.
07:20Within handleStartTag, I say, if the name is tour, then create a new instance
07:26of the Tour class and save it to this variable, which is declared as a field of the class.
07:32Then add that to our variable to the tours package.
07:36If the start tag isn't tour, then instead, save the current tag.
07:41I'll get to that later.
07:44Going back to the parse XML method. If I hit an END_TAG, then I set currentTag
07:50to null, meaning I'm not reading a tag right now.
07:53Finally, and this is the critical part, if I get a text event then I want to
07:58handle that text, and I'll go to this method, handleText.
08:03I have conditional code that's looking for the TOUR_ID, the TOUR_TITLE, the
08:07TOUR_DESC, and so on.
08:09For the numeric values, I'm parsing them using parseInt and parseDouble.
08:14For the string values, I'm just taking the value.
08:17And I'm adding the text to the appropriate property of the currentTour object.
08:22So this method will be called five times for each tour.
08:26Going back again, if I hit some text that's not part of a tag I care about, the
08:31currentTag will be null and I'll ignore it.
08:34By the time this loop is finished, I will have read through the entire XML file
08:39and I'll have an ArrayList containing tour objects, and the return statement
08:43returns those object as a list.
08:46So now, in order to use this class, I'll go back to my MainActivity.
08:52I'll go the onCreate method.
08:54This is where I'll parse the data and display it to the user.
08:58I'll click into the onCreate method.
09:01Within this method, I'll create an instance of my ToursPullParser class.
09:07I'll declare that as the data type and I'll name it parser, and I'll instantiate
09:11it using the constructor method.
09:13Next, I'll create a list.
09:15I'll make sure to include the import statement.
09:18I'll say that the data type is Tour.
09:22I'll name this variable tours.
09:25I'll get its value by calling parser.parseXML.
09:29I'll pass in the context, which will be this.
09:32The context will be used in the parser to identify the resource that points to the XML file.
09:38Now I have some data and I'm ready to display it.
09:41I'll use an ArrayAdapter.
09:43The ArrayAdapter class takes a collection and binds it to a list.
09:48The data type will be Tour, my Tour class.
09:52I'll name this adapter, and I'll instantiate it using its constructor method,
09:57with new ArrayAdapter.
10:02For the context, I'll pass in this.
10:04For the resource which points to the layout I'll be using for each item in the list,
10:09I'll use android.R.layout.simple_list_item_1.
10:16This is a built-in renderer for a single list item.
10:19It knows how to display simple text, and I'll describe in a moment where the
10:23text will come from.
10:26Finally, I'll pass in the tours object.
10:30So I'm using a version of the ArrayAdapter constructor which receives the
10:34context, the resource identifier for the list item renderer, and the data which
10:39should be displayed.
10:41Then, after all that, I'll call setListAdapter and I'll pass in the adapter object.
10:46So I've retrieved the XML content and I'm ready to display it on the screen.
10:51When the data is displayed, it's going to use that toString method of the tour
10:56class that I described.
10:58If you want to change the way the tour is displayed and decide you want to
11:01display other fields, this is the method you should modify.
11:05I'll run the application in the emulator.
11:09As the application comes to the screen, its onCreate method is executed.
11:13It opens the XML file and parses it and displays the contents.
11:18You can scroll up and down the list and see all the data that was in the XML
11:22file displayed here.
11:24I'll show you in a later video a way to improve the display of this data using a
11:28richer presentation.
11:30So, that's the XML Pull Parser, a parser that's included with the Android SDK
11:35and is very fast, and while it takes a little bit of fragmented programming, once
11:40you've mastered it, you should be able to work with any XML file that makes sense
11:44for your application.
Collapse this transcript
Parsing a read-only XML file with JDOM
00:00I've previously described how to parse XML files using the XML Pull Parser, a
00:06streaming parser that's included with the Android SDK.
00:10I'm now going to describe an alternative approach using a parser called JDOM.
00:15JDOM is a third-party open-source, free solution that works well in both generic
00:20Java applications and in Android apps.
00:23You can download the binaries for JDOM, but I've included the JAR file.
00:28That is the library that you need to include in your application in
00:32the exercise files.
00:33I'll get out of the browser and I'll go to my Exercise Files folder.
00:39Under the Assets folder, there's a subfolder called Software, and there's a
00:43file there called jdom-2.0.3.jar.
00:48You can use this or any later version.
00:52I'll copy the JAR file to the clipboard. Then I'll go back to Eclipse.
00:57Within Eclipse, I'll go to the libs folder of my new project, XMLJDOMParser.
01:03I'll go to that libs folder and I'll paste the file into place.
01:08In addition to adding the JAR file to the libs folder, you can also explicitly
01:13add the JAR file to the build path for the application, and that will ensure
01:18that the JAR file is exported with the application.
01:21I'll right-click on the JAR file. Then I'll go to the Build Path menu choice and
01:26choose Add to Build Path.
01:29That adds JDOM to the referenced libraries for the application.
01:33I'm ready for some code.
01:34In the existing version of the application, I'm using the ToursPullParser class,
01:40a class I created that parses the file with the XML Pull Parser and streams
01:46through the file, listening to events for various tags.
01:51As I've described, the code for this parser is somewhat fragmented.
01:55You have to keep track of where you are in the streaming and then write
01:59conditional logic to handle each of the elements.
02:02The reason to use JDOM is because it will significantly decrease the amount of
02:07code that you need to write and the fragmentation of that code.
02:11As with the XML Pull Parser, I've created a class that uses JDOM.
02:17I'll move Eclipse over to the right and go to my exercise files, and I'll go to
02:22my Assets folder and from there, to code.
02:26I'll choose this file: ToursJDOMParser.java.
02:32I'll copy it to the clipboard. Then I'll return to Eclipse and I'll make it full screen again.
02:40I'll close down the Pull Parser class, and I'll paste my new class into the
02:45project's default package.
02:51Here's the JDOM parser code.
02:53Just as with the Pull Parser, it starts by creating some constants.
02:57With JDOM, in addition to the elements for each minor data element, you'll need
03:02to define a tag name for the major data element--that is, for the tour--and I've
03:07done that right here.
03:09Just as with the pull parser class, I've created a method called parseXML, and
03:15then I'm using the context that's passed in that points to the current
03:19activity and current application, and I'm opening the tours XML file, using its resource ID.
03:27For JDOM, I'm creating an InputStream and then passing that to something
03:31called a SAXBuilder.
03:32JDOM includes both a DOMBuilder--
03:36that stands for Document Object Model-- and a SAXBuilder, the simple API for XML.
03:42Comparing SAX with DOM is a matter of performance and memory management.
03:48With DOM, or Document Object Model, the entire XML document is always stored in
03:53memory all at the same time.
03:55In contrast, the SAXBuilder uses streaming, in a process that's very similar to
04:01the XML Pull Parser.
04:02What JDOM gives you though is that it hides that process in the background and
04:07makes it feel like you have the entire document in memory at all times.
04:12Just as with the PullParser class, I'm creating a list of tour objects, and
04:17then I'm ready to parse.
04:19Using JDOM, I create a document object.
04:22This is a document object that's specific to JDOM, and it's a part of the org.jdom2 package.
04:29I get that document object by calling a method called build, passing in a stream
04:34that's pointing to the XML file.
04:36Then I get a reference to the rootNode of the document.
04:40I'm doing that, calling a method called getRootElement.
04:43That returns an instance of the Element class.
04:46Notice when I'm working with the JDOM element class I'm having to explicitly
04:51name the entire package.
04:53That's because there's an element class that's a part of the Android SDK that
04:57otherwise would collide.
05:00Then I'm creating a list containing an instance of that element class, and I'm doing
05:04that by calling a method called getChildren and passing in the name of the tag
05:09that contains each tour.
05:10So now, I have a list of tours in memory.
05:13Now, I'm looping through the list.
05:16I'm using a for-each-style loop.
05:19I'm saying for each node in the list, create a new tour object, then go get
05:24the data for each of the data elements: the tour ID, the tour title,
05:29description, and so on.
05:31The tour ID is being parsed into an integer, the tour price into a double, and
05:35all the other values are string, or text, values.
05:39Then I'm taking all that data packaged up as a tour object and adding it to the list.
05:45Finally, at the end of try block I'm returning the tours object.
05:49Unlike the PullParser class, there isn't any other code. This is it.
05:53The nature of DOM programming is that it allows you to put all your code in a
05:58single place and say, I want to get this field and I want to get that field.
06:03And the mechanics of parsing the XML file are hidden from you.
06:07It's a simpler programming model, and it takes a lot less code to do the work.
06:12Depending on your device and the nature of your XML file, it might even be faster.
06:17If you're not sure whether this is the right approach for you, based on
06:20performance, I encourage you to do your own benchmarking. But this class is now
06:25a part of our project.
06:26So switching from the XML Pull Parser to the JDOM Parser will be a simple matter
06:32of changing which class we're using.
06:33I'll go back to my Package Explorer and open up my MainActivity.
06:38I'll go to the bottom of the onCreate method and I'll change the parser that I'm using.
06:45I'll duplicate this line of code, and then I'll change from ToursPullParser
06:50to ToursJDOMParser.
06:55I'll change it both in the variable declaration and in the constructor method call.
07:00I'll comment out the version that's creating the PullParser object and
07:06everything else is exactly the same.
07:08I'll save my changes and I'll run the application in the emulator.
07:14When the application opens, I'll see that the data is retrieved, parsed, and
07:18displayed, looking exactly the same as it did before.
07:22Just as with the pull parser, I'm getting back a list of tour objects, and I'm
07:27depending on the toString method of the tour class to determine how the data is
07:32displayed in each list item.
07:35So, it's your choice.
07:36The XML Pull Parser is a tool that's a part of the Android SDK, whereas JDOM
07:41has to be added to your application and will add a little bit to the size of your final app.
07:47But the JDOM parsing model is a lot cleaner and results in a lot less code.
07:51Either of these parsers will do the job,
07:55parsing XML and delivering the data to your users.
Collapse this transcript
4. Working with SQLite Databases
Creating a new SQLite database
00:00So far in this course, I focused on two major ways of saving data locally in an Android app.
00:06I've described how to use shared preferences to store simple key value pairs and
00:12how to create and read files in internal and external storage and how to use
00:16particular formats such as JSON and XML that are really useful for storing
00:22structured but not strongly-typed data.
00:25You will find, however, that many Android apps need more power.
00:29You might need the ability to store strongly-typed data or the ability to have
00:33multiple tables that you can relate to each other at runtime.
00:37For these purposes, there's nothing like a relational database.
00:40Fortunately, the Android SDK incorporates SQLite.
00:44SQLite is a very powerful third- party open source database engine.
00:50You can find out everything about the raw SQLite product at this website,
00:55www.sqlite.org, but everything you really need is included in the Android SDK,
01:02including all the libraries that make up the Android engine, all of the classes
01:07and interfaces you need to program with it, and all of the documentation.
01:12The classes and interfaces you use in Android to work with SQLite are all in a
01:17single package, android.database.sqlite.
01:23You can find the documentation at this web page, developer.android.com and so on.
01:28Here are the important classes that we'll use when working with an SQLite database.
01:35The SQLite Database class represents a database file.
01:38Each SQLite database is stored as a single file on your Android persistent storage area.
01:45You can add a file extension of .db if you like, but it's not required.
01:50When you create the database, you'll control the name of the file that contains
01:54the database, and it will be a single file, unlike some other databases that
01:59scatter information over multiple files.
02:01All of the other classes in this list will be used for various tasks.
02:06The SQLiteOpenHelper is a base class that you'll extend to manage your
02:11particular database, and then you'll use classes like SQLite Query and SQLite Statement to represent queries and SQL statements.
02:19You'll use the cursor to loop through the results from a query and SQLite Query Builder
02:24to build and manage your queries, and when problems occur, they'll be
02:29exposed to you as SQLite exception or subclasses of that class.
02:34So in the remaining videos of this chapter and the one after this, I'll
02:38describe how to get started with SQLite, how to create your initial database,
02:43and then how to manage the database at runtime to contain strongly-typed
02:48relational data for your Android app.
Collapse this transcript
Defining a database with SQLiteOpenHelper
00:00The first step in working with SQLite in Android is to define your database and
00:05table structure, and the best practice for this is to create a Java class that
00:10extends a class called SQLiteOpenHelper.
00:12I'll describe how to do this in this project named CreateDatabase.
00:18In this version of my project, I moved my XML parser classes to a new package
00:24that ends with .xml, and I've created another new package that ends with .db.
00:30This is where I'll place my java classes that manage my database.
00:34I'll right-click on the new package and create a new Java class.
00:38This database will contain tours data. So I'll name it ToursDBOpenHelper.
00:45I'll set its superclass, I'll click Browse, and I'll type sqliteo, and I'll select the first class that
00:54appears, SQLiteOpenHelper, and click OK.
00:58I'll make sure I have the option to create method stubs for Inherited abstract methods and click Finish.
01:06That will generate the Java class and add two methods named onCreate and onUpgrade.
01:12I'll come back to these methods in a moment.
01:15When you create the class, you'll automatically see a warning.
01:18I'll move the cursor over the warning icon on the left, and I'll see that it's
01:23telling me that I must define an explicit constructor method.
01:27I'll click on the icon and I'll choose the first quick fix to add a constructor method with four arguments.
01:34I'll come back to that constructor method in a moment, too, but for the moment,
01:38I'll just save my changes and make sure that the errors go away.
01:43You need a lot of bits of information to create a database and its table structures.
01:48That includes the name of the database, the names of the tables, and the names of columns.
01:53It's a common practice to define all of these as constants, and it takes a good bit of typing.
02:00So to make it go a little bit more quickly, I've included a file called typinghelp.txt in this project.
02:07I'll open the file and I'll select all of it's contents and copy it to the clipboard.
02:12Then I'll come back to my class, ToursDBOpenHelper, I'll expand it to full screen,
02:18I'll place the cursor inside the class declaration and before my new constructor,
02:24and I'll paste my constants into place.
02:26Let's do a quick review of all of these constants.
02:29First, there's a LOGTAG that I'll use to output to the LogCat console.
02:33Then there's the name of the database.
02:35You can name your database file anything you want.
02:39It's common to use a file extension of .db, but it's not required.
02:43The database version is required.
02:46It's an integer value and it always starts at 1.
02:49Each time you change the structure of the database, you should increment this value by 1.
02:54You can't decrement it.
02:56The rest of the constants define the names of the table and the columns I'll be creating.
03:02And finally, there's a constant named Table Create that defines an SQL statement that will create my table.
03:09The name of the table will be Tours. It'll have five columns.
03:13The primary key column will be an integer and it will auto-increment and then
03:18there are three text columns and one numeric column.
03:22For more information about the available data types in SQLite, see the
03:25SQLite documentation and other information that's included in the Android API docs documentation.
03:33Now let's go to that constructor method.
03:36When I generated it, it was given four arguments, but that's more than it really needed.
03:41The explicit constructor method will only be called by my own code so I can
03:47determine how I structure it and I'd like to simplify it.
03:51I'm going to remove the last three arguments so this constructor method only receives the context.
03:58That's how this class will be connected to the current activity, but when I
04:03called the super class' constructor method, I do need to pass those four values in.
04:08I'll still pass in the context argument, but the next value is the database name.
04:14And I'll use this constant, the database name that I defined up here.
04:19I'll select it and copy it and then paste it into the constructor method.
04:25I don't need to pass in a factory value, so I'll set that to null, but I do need to pass in a version.
04:33Once again, I'll use one of my constants. This time I'll use DATABASE_VERSION.
04:40Each time I want to work with the database in my code, I'll create an instance
04:44of this open helper class, and I'll call this constructor method.
04:48I'll pass in the context, but the values for the database name and the database
04:53version will come from the constants that are defined in this class.
04:57That's all the work I need to do on the constructor method.
05:00So I'll get rid of the TODO comment.
05:03Now let's go to the onCreate and onUpgrade methods.
05:06These methods will be called automatically by the Android SDK.
05:11Each time I say to the Android SDK I want to get a connection to my database,
05:16Android will determine whether the database exists or not.
05:19If it doesn't exist yet, Android will call the onCreate method.
05:24If the database already exists, but I've indicated through the database version
05:29value that I'm changing the version, that is that I've incremented it, then the onUpgrade method will be called.
05:36I will never call these two methods directly. They'll only be called by the SDK.
05:42In the onCreate method, you should add code that creates your database tables
05:47and if you like, you can also add code to add data.
05:50I'm just going to create the table and its structure.
05:53To do that, I'll remove the TODO comment, and I'll use the database argument that's being passed in.
06:00It's named db, and I'll call a method called execute SQL or execSQL for short,
06:08and I'll pass in my constant that contains the SQL command that will create the table, that'll be TABLE_CREATE.
06:18So, that command is called and my table is created.
06:22Then I'll give myself a little bit of LogCat output.
06:26I'll call the Log class, and I'll make sure I've included the import. I'll use the I method.
06:33I'll pass in my LOGTAG constant and a literal string as a message, Table has
06:39been created, and that's all I need to do in the onCreate method.
06:44When the onUpgrade method is called, I'll receive arguments named oldVersion and
06:49newVersion, and I might want to write some very finely-tuned code that examines
06:54those values and upgrades the database in some complex way, but again, I'm going to keep this simple.
07:00I'm just going to drop the existing table, the tours table, and then I'll recreate it.
07:06So I'll move the cursor into the onCreate method.
07:09I'll call db.execSQL and I'll pass in this explicit SQL Command, DROP TABLE IF EXISTS,
07:19and then I'll append to that the name of the table, TABLE_TOURS.
07:26Then once I've dropped the table, in order to recreate it, I'll simply call my onCreate method.
07:32This is the one exception to what I just said that I wouldn't call the onCreate method directly.
07:39I'll call it within this class but not from the rest of the application.
07:43I'll call the onCreate method.
07:45I'll pass in the db argument, and now I've recreated the table structure.
07:50So, that's what a basic open helper class looks like.
07:54It typically defines the name of the database and the version and then
07:57assigns constants for all the tables and column names and also useful SQL commands to create the tables.
08:05This open helper class only defines a single table, but in a complete
08:09application, you can define as many tables as you want.
08:14Now I have one warning which I see here.
08:16So I'll go to my Problems tab and I'll see that I have an import statement
08:21left over from my original constructor method signature.
08:24So I'll delete that and save those changes.
08:27I'm going to add some code to my MainActivity to use this code that I've just created.
08:32I'll go to my MainActivity class.
08:34Now, eventually I'm going to have a special class called a data source that
08:39deals directly with the database Open Helper and I'll show you that in the next
08:43video, but to keep things a little bit simpler, I'll call the database open
08:48helper class directly from my activity in this exercise.
08:51So I'll go to my MainActivity, and first I'll add a field that will represent
08:57an instance of my open helper class.
08:59I'll declare it using the Superclass data type, SQLiteOpenHelper, and I'll name it dbhelper.
09:08Then I'll also create a reference to a database object.
09:12That database type will be SQLiteDatabase, and I'll name that database.
09:18Now I'll go down to my onCreate method, and I'll place the cursor right here
09:24before I call the ArrayAdapter code and I'll instantiate the dbhelper object.
09:30I'll say dbhelper = new, and then I'll use my new class constructor method and I'll pass in
09:37this as the context, and then I'll get a reference to the database.
09:42I'll call database = dbhelper.
09:46And then I'll call a method that this class has inherited from SQLite database.
09:52The name of the method will be getWritableDatabase.
09:56This returns a reference to the connection to the database, and I'll be able to
10:00use that connection to do things like inserting data, retrieving data, updating, and deleting.
10:08Simply by calling the method, that will trigger the onCreate method of my
10:12database open helper class, and in turn that will create the table structure.
10:17I'll save my changes, and now I'm ready to test. Before I test, I'll go to my LogCat window.
10:25I'll reset my perspective to bring back the LogCat window.
10:31Then I'll expand it to full screen.
10:34Notice that I've added a tag filter, so that I'll only be seeing messages that come in with that tag.
10:41Now, I'll run the application in the emulator.
10:44As the application comes to the screen, it executes the code that opens my
10:49database open helper class, the onCreate method is called, and the result is that
10:54the database and the table have been created.
10:57To prove that the database is there, let's look at it in the File Explorer.
11:02I'll go to my DDMS perspective, and in the File Explorer tab, I'll go to the data
11:08folder and then from there, to the sub-folder named data.
11:12Then I'll go to the package that represents my application, com.exploreca.tourfinder.
11:19I'll open that and I have a new databases folder that didn't exist before, and
11:25there's the database that's been created.
11:27This is where your databases will be automatically placed, in internal storage,
11:33inside the same parent folder that contains any internal files.
11:37You can see it easily when you're working with the emulator, but just as with
11:41any other files that are stored in internal storage, you won't be able to easily
11:46reach them with a real device.
11:48If you root the device and you change the permissions, then you should be able to see them.
11:53But to keep it simple, use the emulator when you're testing this.
11:57So, now we have a database open helper class and we're ready to go to the next
12:02step in following best practices in working with SQLite and that's creating a class called a data source.
12:08I'll show you how to do that in the next video.
Collapse this transcript
Managing the database with a DataSource class
00:00Once you've created your Database Open Helper class, the next step in
00:04following the best practices for managing SQLite is to create another class called a data source.
00:11Its methods, the data sources methods, will be called by the rest of the app.
00:16You can create multiple data sources in an application.
00:18For example, you might create one data source for each table in a database or
00:24one data source for each set of tasks. How you architect that is up to you.
00:29In my example, I only have a single table right now, so I'll use a single data source class.
00:36I'm working in a version of the project named data source
00:39that already has a Database Open Helper class named ToursDBOpenHelper, and right now
00:45its methods are being called directly by the main activity.
00:48I'll create a new data source class, and I'll put it in the same package as the Open Helper.
00:54I'll right-click on the .db package and I'll create the new Java class and I'll name this ToursDataSource.
01:01Data source classes don't extend any particular Superclass.
01:08In fact, my data source class won't have an explicit Superclass at all.
01:12I'll click Finish and that creates the class structure.
01:17The first step in creating the data source class is to create a public constructor method.
01:22I'll move the cursor inside the class declaration, and I'll type in public, then
01:28the name of the class, and the constructor method will receive a single
01:33argument, the context of the current activity.
01:37I'll type the name of the context class and press Ctrl+Space and import it,
01:43and I'll name the argument context.
01:45So, that's the only constructor method that the data source class will have.
01:50In order to instantiate this class, the activity must pass in the context.
01:56Next, I'll declare the instances of the Database Open Helper and the Database.
02:02Right now, I've done this in the MainActivity class,
02:05so I'll go steal that code from the activity and bring it into the data source.
02:11I'll go to the MainActivity class.
02:13I'll expand this to full screen to make it a little bit easier to read, and here
02:18are two declarations I need, one for the Open Helper and one for the Database.
02:23I'll copy those to the clipboard.
02:26I'll come back to the data source class and I'll paste them inside the
02:30class declaration but outside of any methods, and Eclipse automatically adds the required imports.
02:38I'll instantiate the Database Open Helper class when the data source is instantiated.
02:43So I'll add code to the ToursDataSource constructor method.
02:48Once again, I have a code that will do this.
02:51I'll go back to the MainActivity and scroll down toward the bottom of the onCreate method.
02:57I'll take these two lines of code that are instantiating the Open Helper and the Database.
03:04I'll copy those to the clipboard.
03:06I'll come back to the data source and then I'll place the cursor inside the
03:10constructor method and paste that code into place.
03:14I need to make one change.
03:16When I instantiated the Open Helper from the activity, I passed in this as the context.
03:22This refers to the data source, not the activity, but I'm receiving the activity context right here.
03:30So I'll just take that context argument and pass it into the Open Helper's constructor.
03:35In a way, that's all I really need to do, but I'm going to make this code even
03:40easier to use by adding another couple of methods called open and close.
03:45They'll be public methods, and the idea will be that any activity in my
03:49application can open or close the database connection anytime it wants to.
03:54So I'll create two new methods. First, I'll create a method called open.
04:00It will return void, and then I'll take that bit of code and duplicate it.
04:06For the second version, I'll name it close.
04:09Whenever each of these methods are called, I'm going to want to log them in the LogCat console.
04:16So I'm going to set up a constant that I can use as my LogCat tag.
04:21Once again, I'll get that from the MainActivity class.
04:24I'll go to the MainActivity to the top of the class, and I'll copy the Log tag constant.
04:30Then I'll come back to the DataSource and paste it into place.
04:35Then I'll go to the open method.
04:38I'll type in the name of the Log class, and I'll add an import, then I'll call the .i method for info.
04:45I'll pass in the LOGTAG argument and then a message of Database opened and then
04:52I'll repeat that for the close method.
04:55I'll duplicate that line of code, move it into place, and change the message to Database closed.
05:02I'll add code to actually open and close the database.
05:06The code I need to open the database is already here.
05:09It's actually in the constructor method. When you call the getWritableDatabase method that opens the database connection.
05:17So I'm just going to take this line of code and move it into the open method.
05:22I'll hold down my Option key on Mac or the Alt key on Windows and then press the
05:27down arrow and move the code into place.
05:31Then for the close method, I'll call the Open Helper's close method.
05:37So now I have all the pieces I need for a basic data source class.
05:42I have a constructor method that instantiates my Open Helper, and I have code
05:47to open and close the database connection. Now I'll modify my MainActivity.
05:52The MainActivity is no longer going to deal directly with the database open helper or the database.
05:58That will all be hidden inside the data source object.
06:02So I'll delete these two declarations, and I'll replace them with the declaration
06:08of an instance of ToursDataSource, my new data source class.
06:13I'll name it datasource and I won't instantiate it yet.
06:17I'll wait 'til I get to the onCreate method.
06:21Now I'll go down to the onCreate method, and once again, I'll get rid of the code
06:25that's dealing directly with the database Open Helper and the Database, and I'll
06:29replace it with instantiating the data source. I'll pass in this as the context.
06:37Finally, if you want to maintain a persistent database connection for the entire
06:42lifetime of the activity, you can add code to the methods onPause and onResume.
06:49These are methods that are automatically called by Android during the life cycle of the activity.
06:55As the activity comes to the screen, it's onResume method is called.
07:00So I'll go down to the bottom of the activity code,
07:04I'll place the cursor after any of the existing methods, I'll type in onResume,
07:10I'll press Ctrl+Space, and choose the onResume method.
07:16I'll get rid of the comment and then after the call to the Superclass' OnResume
07:21method, I'll call datasource.open.
07:25Similarly, you should explicitly close the data source connection whenever the activity pauses.
07:32The onPause method is called as the activity closes down.
07:36So I'll type in onPause. I'll press Control+Space.
07:41I'll choose the onPause method that creates the override.
07:46After the call to the Superclass method, I'll call datasource.close.
07:51Now we're ready to test the whole thing.
07:55Remember, my data source is outputting to the LogCat console whenever its open
08:00and close methods are called.
08:02So I'll easily be able to track when these operations are happening.
08:07I'll go to my LogCat console, and you can clear any existing output by clicking
08:12on the Clear Log button, then I'll run the application in the emulator.
08:19As the application comes to the screen, the onPause method is called
08:23automatically as a part of the activity life cycle.
08:26And I see the message database open.
08:29I'll leave the activity by clicking the Back button.
08:32I'm going to move the emulator out of the way so we can see these messages.
08:36And I see the message database closed.
08:39I'll go to my application list, and I'll reopen the application, and once again,
08:45the activity opens and the database opens.
08:48I'll click the Back button and the database closes.
08:52Every activity in your application can open and close the database connection as needed.
08:58It's also useful to know that you can add as many calls to the open method as you want to.
09:03The connection object within any activity is cached.
09:07So you don't have to worry about calling the open method too many times, but you
09:12should make sure that you're explicitly closing the connection whenever the activity is going away.
09:18That will eliminate the possibility of what are known as database connection leaks.
09:23A database connection leak can cause memory and performance issues.
09:27So by adding the code to explicitly open and close the database when needed,
09:31will make your application work well, work fast, and give you a reliable way
09:36to work with structured data.
Collapse this transcript
Inserting data into a database table
00:00Once you've created your Open Helper and Data Source classes, you can start
00:04adding code to manage your database, including code to insert, update, and delete rows,
00:09and to read data from the database tables. I'll start with inserting data in this project.
00:16I've opened the class ToursDataSource.java, and I'll add a new method to the class that I'll name create.
00:23I'll place this at the end of the existing methods.
00:27It will be public so that it can be called from anywhere in the application and
00:32it will return an instance of my tour class which is in my model package.
00:37I'll make sure to add the import statement.
00:39The name of the method is create, and it will receive a single argument,
00:44also data type as Tour and named tour.
00:50In order to pass data into the database, you could execute an explicit SQL
00:55statement, but then it's up to you to handle all sorts of coding, including
00:59escaping special characters, using single quotes where you shouldn't be using double quotes, and so on.
01:06Instead, you want to package your values into an instance of a class called
01:11ContentValues and then call a method of the database object called insert.
01:17That will result in creating a well-formed SQL insert statement, and you don't
01:22have to do the hard work yourself.
01:24So I'll start by creating an instance of ContentValues.
01:28I'll type in the name of the class and then press Ctrl+Space to add the import statement for the class.
01:35I'll name this object values, and I'll instantiate it with the class constructor method.
01:44The ContentValues class implements the map interface.
01:47So you'll be putting items into the map where the key is the name of the column
01:53and the value is the value you want to insert. I'll start with the title of the tour.
01:58I'll call values.put.
02:01For the key, I'll use a constant that's a part of my open helper class.
02:07I'll use ToursDBOpenHelper and then I'll use the COLUMN_TITLE constant.
02:13By using the constant, I'm making sure that the name of the column that I'm
02:17using here matches exactly what's really in the database.
02:22Then I'll get the value from the tour argument that was passed into the method calling tour.getTitle.
02:33So, that code doesn't generate any errors, and I'll duplicate it a few times for the other values.
02:39I'll add three new versions, and then I'll change the names of the columns and the values.
02:45The next item will be the description represented by the constant COLUMN_DESC,
02:52and I'll change the method I'm calling to get the value from getTitle to
02:56getDescription, and I'll make the same changes for the price.
03:01There's the column name and there's the value and for the image.
03:14Notice that I'm not putting a value in for the ID.
03:17That's because for this table, the tours table, the key is an
03:21auto-incrementing integer value. It will be generated automatically.
03:26Now I'm ready to insert the row into the database.
03:30I'm going to call the method insert.
03:32That's going to return a long value, which will be the new ID of the new row.
03:38So I'll create a new variable that our data type is long, and I'll call it insertid.
03:45I'll get it's value by calling the database objects insert method.
03:49Once again, I need to pass in some arguments.
03:52The first is a string which is the name of the table, and I'll again use
03:56a constant for my Open Helper class.
03:59I'll call toursDBOpenHelper and I'll use the constant TABLE_TOURS.
04:07The next argument is named nullColumnHack.
04:11This is a value that you can pass in if for some reason you're being forced to
04:15pass in a content values object that doesn't have anything.
04:19In SQLite, you always have to insert at least one column, and in that case, you
04:25could pass in the name of the column here.
04:27I know that's not a problem for this operation, so I'll just pass in the value
04:32of null and that will work fine.
04:33Then the third argument is my content values object that contains the values that I've placed in it.
04:42So, now I've inserted a row into the database table, and I've received back the
04:47automatically assigned primary key value.
04:50I'll take that value and assign it back to the tour object that I received as an argument.
04:56Calling tour.setId and I'll pass an insertid, but I get an error.
05:04Here's what's going on.
05:05When I defined my tour class, I set the data type of the primary key as an
05:11integer, but this method is returning a long value, and it turns out that when
05:16you're defining a model class--that is a class that represents a single data entity--
05:21and you want to represent an integer value to match up with the Android API,
05:27you should always data type that property in the class as a long and not an int.
05:33So I'm going to go back to that class, the tour class.
05:37I'll go back to my Package Explorer, to my model package, and I'll open
05:42Tour.java, and I'll change the data type of my ID property in three places,
05:49in the declaration here, and then in the getId getter, and I'll change that to long,
05:55and I'll also make the change in the setter.
06:00I'll save those changes, I'll come back to the data source class, and I'll
06:04see the error on this line of code has gone away, because now the data types are compatible.
06:10So I've inserted the new row and I've gotten back the automatically assigned
06:14primary key and I've assigned it to the tour object and now all I have to do is
06:20return the object with return tour and that's my create method.
06:24I receive a tour object, I pass it into the database, I update the tour object, and I return it.
06:32Now let's exercise this code. I'll go to my MainActivity class.
06:37Eventually, I'm going to be adding a whole bunch of data into the database, but
06:41for the moment, I want to keep it simple.
06:43I'm just going to add three tours into the database table.
06:47So I'm going to create a new method which will only be called from within this activity.
06:53I'll call it createData, and then I'll make sure that it's returning void.
07:00Within the new method, I'll create a new instance of my Tour class.
07:05I'll set the data type as Tour, and the name is tour in all lowercase, and I'll
07:10instantiate it with the null arguments constructor method.
07:15Next, I'll set some values.
07:18I'll set the title to Salton Sea, then I'll set the description to a tour to Salton Sea.
07:32I'll set the price to a value of 600, and I'll set the image property to salton_sea, all lowercase.
07:42Next, I'll pass the data into the database by calling the data source object that I've already opened.
07:49I'll call datasource.create and I'll pass in the tour object, and then to find
07:54out what happened--that is what the new primary key value is--I'll say tour = datasource.create(tour)
08:01then I'll do a little bit of logging.
08:05I'll call my Log class and I'll use the I method.
08:10I'll pass in my LOGTAG, and I'll output a message of Tour created with id and
08:17I'll append to that tour.getId.
08:21So, that's all the code you need to create a single row.
08:26I'll copy that code and then I'll paste it in.
08:30For the second version, I'll take away the tour declaration.
08:33It's already declared. I'll set the title to Death Valley.
08:39I'll copy that string and include it in the description.
08:44I'll update the price and the image and then I'll duplicate this code one more
08:53time and for the third one, I'll use San Francisco.
09:01I'll update the description and the price again and the image.
09:08My last step is to call this method from my onCreate method.
09:13So I'll scroll up to the top.
09:15Now just in case the data source hasn't been opened yet, I'll call
09:18datasource.open and then createData.
09:24And remember, it's okay to call the open method more than once.
09:28The database connection is cached and you won't be over-working the database.
09:33And now I'm ready to test. I'll run the application in the emulator.
09:41As the application opens, it executes the createData method, and now let's take
09:46a look at what happened in LogCat.
09:49I'll expand LogCat to full screen and I see that my three tours were created
09:54with IDs of one, two, and three.
09:58So, that's the code you need to create new rows in your database table.
10:03The code goes into the data source class and not into your main application code.
10:08You create a content values object, you populate it with each of the values that
10:12you want to insert into the database row. You call the insert method.
10:17If you're working with an auto- incrementing field, you'll get back a long value
10:21as the new primary key and then you can do whatever you need to do with the data
10:26to retrieve and display it later on.
Collapse this transcript
Retrieving and displaying data
00:00Once you've inserted data into a database table, the next natural thing you'd
00:04like to do is retrieve and display it.
00:07I'm working in a new version of the project called RetrieveData.
00:11In this project's datasource class, I already have the code to add new rows.
00:16It's called the Create Method.
00:17And now I'll add code to retrieve all rows and all columns of the same database table.
00:23I'll start at the top of the class.
00:25I'll define a constant which will be an array of strings.
00:30The array will contain one string for each column in the database table.
00:34I'll create it here as a private static final field, I'll data type it as
00:41String with opening close bracket, which means it's an array, and I'll name it allColumns.
00:47I'll set it using a pair of braces, and then within the braces I'll define
00:53a commented limited list of all of my column names.
00:57The column name constants are in my OpenHelper class.
01:00So I'll start off with ToursDBOpenHelper, and then I'll start with the primary
01:06key column, COLUMN_ID, and I'll add a comma.
01:10Now I'll duplicate that line of code four times, and then I'll go back and change the column names.
01:18I'll include the title and the description and the price and the image.
01:28I don't need the last comma, and I'll close up this code and add the semicolon at
01:33the end, and now I have an array of strings that I can use that identifies all
01:38of the columns of my database table.
01:40Next, I'll create a new method in my data source class that I'll name findAll.
01:46It will retrieve all columns, all rows and return them as a list of tour objects.
01:53I'll scroll down to the bottom of the class and I'll add this new code after the create method.
01:59As with all these methods, it will be public so that it can be called from
02:03anywhere in the application. It will return a list of tours.
02:07I'll type in the name of the list class then I'll press Ctrl+Space and choose
02:12the class to add the import, and I'll set the data type of its members to Tour.
02:18That's my custom class.
02:22The name of the method will be findAll, and it won't receive any arguments.
02:26The first two steps in the findAll method are to define the list object and then to query the database.
02:33I'll create a list of tour objects, which I'll name Tours, and I'll instantiate
02:39it with the ArrayList Constructor Method. Next, I'll query the database.
02:47You can query the database either using the ExecuteSQL method for a raw query,
02:53or for a simpler approach, to query a single table into database, you can use a method called Query.
03:00If you need to do something more complex, such as joining tables together,
03:05you might need to use another method called raw query.
03:08I'm going to be querying just the single table, the tours table,
03:11so I'll use the query method.
03:13The query method returns an instance of class called Cursor.
03:17If you're familiar with JDBC programming in Java, the Cursor class is similar to the ResultSet class.
03:25It's a reference to the data that's returned from the query.
03:29I'll start by typing the name of the class, and then I'll import it from the android.database package.
03:35And I'll name the object Cursor and I'll assign it by calling the Database Query method.
03:42I'll type in database and press period, and that opens a list of all the methods of the database object.
03:48Notice that there are a few different signatures for the query method.
03:52I'll use the second one that starts with the name of the table.
03:57For the table name, I'll use a constant of my OpenHelper, ToursDBOpenHelper.TABLE_TOURS.
04:05Then for the columns, I'll pass in my array of strings that I just finished defining named allColumns.
04:13All of the rest of these arguments can be set to null.
04:16If you like, you can add filters in a couple of different ways, and you can group by and order by.
04:22But I'm just going to accept the default order.
04:25So I'll type in null for each of these values, and I'll do a little bit of code
04:30wrapping to make it a little bit easier to read.
04:32Once you've executed the query, you can immediately find out how many rows were
04:36returned by using a method of the Cursor object called GetCount.
04:40I'll do some LOGCAT output.
04:44I'll use the log class and its I method, I'll pass in my LOGTAG constant and
04:50then a message starting with the string Returned and concatenate cursor.getCount,
04:57and I'll finish the message with a string of rows.
05:02Now remember, this method needs to return a list of tour objects.
05:06So I need to loop through the cursor and deal with one row at a time.
05:11Again, if you're familiar with JDBC, you might remember that in a ResultSet, the
05:16cursor starts before the first row, and the same thing is true of the SQLite cursor object in Android.
05:24So first, I'm going to add some conditional code.
05:27I'll use an if statement.
05:29And I'll check the count and make sure that it's greater than zero, using the
05:33expression if cursor.getCount is greater than zero.
05:39Then if this condition is true, I'll loop through the rows.
05:43I'll use a while loop here.
05:45I'll type while and press Ctrl+Space, and I'll loop with a condition.
05:51The condition will be a call to the method move to next.
05:55This is similar to the JDBC ResultSet Next method.
05:59I'll type in Cursor., and I see a listing of available methods.
06:04There's an isAfterLast and then isBeforeFirst and an isFirst and an isLast,
06:11and so on, and I'll choose moveToNext.
06:13It returns a boolean value, and if it successfully moves to a new row, it will
06:18return true, and if it's already finish with a loop, it will return false.
06:23So then if I successfully move to a new row, I'll create a new instance of
06:28the Tour class, which I'll name Tour.
06:33Then I'll grab the values from the cursor one at a time using methods of the
06:38cursor object that are unique to each data type.
06:41So for example, I'll start with tour.setId, and I'll pass in the following code, cursor.getLong.
06:51Remember, in a previous exercise, I set the data type of my ID property of
06:57the tour object as along, and so I'll call the getLong method now to set the data type correctly.
07:04For the column index that's being requested, you should always call a method
07:08called getcolumnIndex and pass in the name of the column.
07:12The code looks like this, cursor.getColumnIndex and then pass in the column name,
07:18which in this case will be ToursDBOpenHelper.COLUMN_ID.
07:24And so that's all the code you need to retrieve a single value of the correct
07:28data type and then pass it into a model object.
07:32I'll duplicate this code once, and then for the new version, I'll call this
07:37setTitle method of the tour object.
07:40When I retrieve the data, instead of setting the data type as Long, I'll set it as a String.
07:46And then at the end of the call, I'll change the name of the column that I'm
07:49retrieving from COLUMN_ID to COLUMN_TITLE.
07:53I'll duplicate that line of code twice, and I'll retrieve both the description and the image.
07:59For the description, I'll call the tour object setDescription method.
08:04I'm already using a string so I don't need to change the data type, but I do
08:09need to change the name of the column to COLUMN_DESC.
08:14Then I'll do the same thing for the image.
08:17I'll change setTitle to setImage and I'll change the name from title to image.
08:24And finally, I need to deal with the price.
08:27I'll go back to the code that's calling setId, I'll duplicate it, I'll move it
08:32down a few lines, and I'll change the name of the setMethod to setPrice.
08:39I'll change the data type from getLong to getDouble and I'll change the name of
08:44the database column to COLUMN_PRICE.
08:49So that's all the code I need to loop through the cursor, retrieve one row
08:53at a time, and then add the rows data to the Tour object.
08:58At the end of all this code, I'll take that Tour object and add it to my list of tours
09:04using tours.add, and I'll add the tour object.
09:10Finally, at the end of the loop, I'll return the list object.
09:15I'll place the cursor after the entire conditional clause and add return tours.
09:20I see an error indicator showing me that I've missed a semicolon, so I'll add it
09:25there, and all the errors are cleared, and it looks like it's good to go.
09:30I'll save these changes, and now I'll go back to my main activity.
09:38In my MainActivity, I'm currently getting the data from an XML file.
09:43Using the XML PullParser to parse the XML file and getting back a list of tours.
09:50I'm going to comment that out, and instead, I'm going to get the list of tours from my database.
09:56I move the cursor down here to after the call to open the data source.
10:03I'll declare my list of tours, once again calling it tours, and this time
10:09I'll call it datasource.findAll.
10:12If this is the first time I've run the application, there might not be any data in it.
10:17So I'll add a conditional clause, and I'll check the size of the tours object.
10:23If tours.size has a value of zero, then I'll take this createData method and
10:28I'll move it into the conditional block, and then I'll make a copy of this code and I'll re-execute it.
10:37The second time I call it, I don't need to re-declare the list, it's already declared.
10:43So now the entire logic is I'll retrieve all the data.
10:47If there isn't any data there, I'll create the data and then I'll retrieve it again.
10:53And from that point forward, everything should work exactly the way you want.
10:57I already have the code to create an array adaptor and then to pass in the list
11:02of tours and display it on the screen.
11:05So let's see how we did. I'll run the application in the emulator.
11:11If this is the first time the application has run, it will create the data,
11:15and if it's not the first time, it will just read the existing data, and there is the result.
11:20The three tours that I created displayed on the screen instead of the data that
11:25was retrieved from the XML file.
11:28So that's the code you need to retrieve data for display in your application.
11:33Again, you put all the code into your data source class, wrap it up in
11:38public methods, and then you'll be able to retrieve the data from anywhere in the application.
Collapse this transcript
Importing data from XML to SQLite
00:00I've previously described how to read data from an XML file, showing you two
00:05different parsers that you can use, and I've also described how to create new
00:09rows in an SQLite database table. I'm going to combine these two features.
00:15I'm going to use an XML file to seed my application with data and then move the
00:20data over to the SQLite database as the application launches for the first time.
00:26I'll be doing all of the work in the main activity class in this version of my project, ImportToDb.
00:32In the main activity class down at the bottom of the class I have a method
00:36called Create data, and right now I'm creating some fake data.
00:41I'd like to replace this code with code that imports from XML and exports to the database.
00:47So I'm just going to select and delete all of this code.
00:52If you need this code back again, you can find it in the previous version
00:56of the project in the Solutions folder.
00:59I'll go get code to import data from an XML file,
01:02I'll move back up to the On Create method, and I'll locate this code that I
01:09commented out in a previous exercise.
01:11It uses the XML Pull Parser to read the data from an XML file and returns it as a list of tour objects.
01:19I'll cut that code to the clipboard, then I'll go back down to my Create Data method, and I'll paste the code back in
01:28and then I'll uncomment it.
01:31You might need to do a quick fix to add an import statement for the ToursPullParser class.
01:37So now I have the tours from the XML file in memory, and I can loop through
01:42and easily call the methods in my data source object to pass the data to my SQLite database.
01:50I'll use a foreach loop to loop through the list of tours.
01:53I'll type foreach, all one word, I'll press Ctrl+Spacebar, and choose the foreach code template.
02:01Eclipse automatically picks up that it's going to loop through the tours
02:05collection and create a tour object for each item in the collection.
02:10So I'll Tab a few times, and now my cursor is within the for loop.
02:15Within the for loop, I now have a tour object, and all I need to do is call the
02:20Create method of my data source class.
02:24Remember, when I created the data source class which is in the db package, it
02:30receives a tour object and it adds it to the database table as a new row.
02:35So I'll go back to the activity and I'll simply call it datasource.create and
02:40I'll pass in the tour object. And that's it.
02:43I'm pulling the data from the XML file and passing it to the database.
02:48Everything else in this application will remain exactly as it was before.
02:53Up above, in the Uncreate method I'm doing a findAll to get the data from the
02:58tour's table, then I'm checking the size, and if it's 0, I'm calling this
03:03createData that I just modified to pass the data in, and then I'm retrieving the data again.
03:10I'll save my changes.
03:12Now before I test this, I'm going to go to the emulator and clear all of the
03:17existing data to make sure I'm getting a fresh start.
03:21I'll go to the emulator and then I'll go to the Applications list.
03:25I'll click and hold on my application icon, I'll drag it up to App info,
03:31and then after a moment the emulator will let me clear the data.
03:35Then I'll go back to the Home screen, I'll return to Eclipse, and I'll run
03:42the application in the emulator again.
03:45Now as the application opens in the emulator, it will query the tour's table.
03:50It will determine that there isn't any data in the table.
03:54It'll then call the createData method.
03:55It will retrieve the data from the XML file, pass it into the database, requery
04:01the database, and then display the data.
04:05So now you have a good example of how you can seed an application with data that
04:10you package up as an application resource.
04:13To review a couple of things from previous lessons, my XML file is a resource in the resources folder.
04:20I placed it in a folder called raw, and it's a file called tours.xml.
04:25By putting it in the Resources folder, you make it a resource that you can address
04:29with the Resource ID, just like a drawable image, just like a menu item, or a user interface control.
04:38As another reminder, I'm using this class, ToursPullParser.
04:43But if you prefer, you can use the JDOMParser.
04:45Either one will do the job, and which one you use is a matter of taste,
04:51depending on how you like to do your coding and also want a performance because
04:56the two parsers return different results depending on the nature of the XML
05:00and the context of your application.
05:03Finally, in the MainActivity I've added simple logic to determine whether
05:07there's data in the Application already, and if not, I've added code to move
05:12the data from XML to the database.
Collapse this transcript
Filtering and sorting data
00:00So far in my sample application, I'm always showing all tours in my data set,
00:05but I'd like to be able to filter the data.
00:08So I've added a few features to this version of the project named Filter and Sort.
00:13I'll run the application in the emulator and show you the User Interface changes.
00:18Just as before, I'm displaying all the data in the List activity, but now I've
00:23added three items to the Action Bar.
00:26They're labeled All, Cheap, and Fancy. I'll show you where I've set those up.
00:32I'll go the menu which is named activity_main.xml in the menu subfolder under resources.
00:40This is where the menu items are defined.
00:43There's one for All tours, one for Cheap tours, and one for Fancy tours.
00:48And because their showAsAction attribute is set to always, they're showing up in the action bar.
00:54I've kept the strings very short so that they'll fit even on a very small device.
00:59But in your testing, you should take a look at your target devices and determine
01:03whether you can use the action bar in this way.
01:07I've also added code to the MainActivity.java class to listen for the event that
01:12occurs when menu items are selected. That code is down here.
01:17It's called onOptionsItemSelected, and right now I have a switch statement with two cases.
01:23Once for Cheap tours, and one for Fancy tours. I'll add one for All tours.
01:30I'll place the cursor within the switch statement, then add a case with the ID
01:35R.id.menu_all, and I'll add a break statement. And now I'm ready to handle all three cases,
01:45when the user wants to see all tours, and when they want to filter for Cheap and Fancy tours.
01:51I'll save those changes and we'll come back to this in a moment.
01:55The first thing you need to do to filter the data from an SQLite database is to
01:59add methods to your data source class that know how to do the filtering.
02:04So, I'll go to my ToursDataSource class where I already have a method called findAll.
02:10If you want to filter and sort on a single table, you can use exactly the same
02:15syntax as I'm using here that is calling the query method,
02:19and then you'll modify two of the arguments that you pass in.
02:24The third argument, which currently is null, can be set to a string which filters the data.
02:29It's the part of the SQL where clause, after the keyword where.
02:34And then if you want to order the data or sort it, you use the last argument,
02:39and you pass in the name of the column or columns on which we want to sort in
02:44ascending or descending keywords, if you want.
02:47If you don't remember which of the arguments needs to use in the future,
02:50remember, you can move the cursor over the method and you'll see the method
02:54signature, and this tells me that the first argument is the table or table name,
03:00the second is the array of column names, and the third argument is the selection--
03:05that's what I'll be using--and the last is the orderBy clause.
03:09I'm going to create a new version of this findAll method, and it's going to
03:13receive two arguments, a selection value and an orderBy value.
03:19Before I make a copy of this method, I'm going to take some of its code
03:23that's going to be used elsewhere and separate or extract it to its own separate method.
03:29I'll take my declaration of the tours object, and I'll move it down to just above the if condition.
03:37Then I'll select all the code, starting with the declaration of the list and
03:41including the entire conditional clause.
03:44Then I'll right-click on the selected code, and I'll re-factor and say I want to extract a method.
03:52I'll name the method cursorToList.
03:56The Method signature preview at the bottom tells me that this method will
03:59receive a cursor object and return a list of tours.
04:04I'll click OK and that shortens the findAll method.
04:08So, now it's calling cursor to list to process the data and get the list back, and there's the result.
04:14I now have a reasonable method cursorToList that I can call from many versions of my find method.
04:21I'll make a new version of my find method.
04:24I'll select what remains of the findAll method, and I'll duplicate it.
04:29Remember that you can duplicate code by holding down the Command and
04:33Option keys on Mac or the Ctrl and Alt key on Windows and pressing the down cursor arrow.
04:40I'll change the name and signature of my new method.
04:43I'll name this new method findFiltered, and I'll pass in two strings.
04:49The first will be selection and the second will be orderBy.
04:55Next, I'll change the call to the query method and take these arguments and pass
05:01them into the appropriate arguments of query.
05:03I'll pass selection into the third argument and orderBy into the final argument.
05:09And now I have a method that I can call from anywhere in the Application, and it
05:14will let me filter and determine the order of the data that will be returned.
05:20I'll save those changes.
05:22Now I'll come back to my main activity class where I have my on OptionsItemSelected method.
05:29This method will be called whenever the user selects an item from the menu,
05:34either from the main menu or from the action bar.
05:37I'll start with the menu item with an ID of menu_cheap.
05:41I'm going to be updating the tours object.
05:44Before I do this, let's take a look at where the tours object is declared
05:49and make sure that it's persisting for the entire activity.
05:53I'll go back up to the top of the code, and I'll see that in this version of the
05:58Application, the Tours object is declared outside of any methods, and that's exactly what you want.
06:06I also want to re-factor the code that's updating the display so that it's easy to call from anywhere.
06:13Right now in my onCreate method, I have this bit of code that's using an array
06:18adapter and updating the list display.
06:22I'm going to move this into its own separate method.
06:25I'll cut it to the clipboard, then I'll scroll down and find a method named
06:29refreshDisplay that I left from a previous exercise, and I'll paste the code in there.
06:35Then I'll come back up to where that code was before and I'll call the method.
06:41And now it's easy to update the display from anywhere in the activity code.
06:46Now I'm really ready. I'll go the menu_cheap menu item.
06:50I'll update the Tours list using this code, Tours = datasource.findFiltered.
06:56I'm going to filter on the price column. So, I'll pass in a literal string, "price <= 300".
07:06You're working in SQL syntax here, so whatever string you need to pass in must match what the database engine expects.
07:14Then I'll set the order by clause, and once again, this is what goes after the keyword orderBy.
07:21It'll be a string again, and I'll pass in a value of price_ASC for ascending.
07:29That refreshes the Tours list, and now it's up to me to update the display.
07:35So, I'll call my new refreshDisplay method and that should show only the cheap tours.
07:40Now I'll do the same thing for fancy tours.
07:43I'll select these two lines of codes and copy them, and I'll paste them into this menu choice, menu_fancy.
07:50For this menu item selection, I'll say that I only want to see Tours that are at least $1,000.
07:57So, I'll change my selection value to >= 1000, and I'd like to see the most
08:05expensive Tours first this time.
08:07So I'll change from price_ASC for the orderBy to price_DESC, and again, I'm using standard SQL syntax.
08:17Finally, I'll go back to my menu_all command.
08:21I'll paste that code in again, and for this one, instead of calling
08:25findFiletered, I'll once again call findAll. So, these are my three conditions.
08:31When the application first opens, it'll display all the tours, but then the user
08:36can switch back and forth between seeing Cheap or Expensive Tours and can go
08:40back to the full list anytime they want. I'll save my changes and I'll run the code.
08:48As promised, when the application first opens, it shows all the data in its native order.
08:53I'll click Cheap, and now I can only see the most inexpensive Tours.
08:58I'll click on Fancy, and now I see the most expensive tours, and they're ordered from expensive to less expensive.
09:06And I can go back to seeing all tours anytime I want by clicking on the appropriate menu choice.
09:12So, that's how you can add filtering and sorting to your application.
09:16As always, put all of your database manipulation code into your datasource class,
09:21then call the public methods of the datasource from anywhere else in your application.
Collapse this transcript
Accessing a database from the command line
00:00The Android Emulator is packaged with a valuable application that you can use to
00:05explore a database that's on the Emulator.
00:08It's called SQLite3, and I'll show you how to use it here.
00:12I'm running the application, and the Emulator is live.
00:16That's the first thing you need to do before you try to explore the database.
00:21Then go to a Command window. I'm working on a Mac, so I'll use Terminal.
00:26If you're working on Windows, you can run the CMD Command.
00:30The first step is to go to the folder that contains your Android SDK.
00:35I installed the ADT Bundle and the SDK on my desktop.
00:39So, starting from my Home folder, I'll change first to the Desktop folder, and
00:44from there to the ADT Bundle folder Mine is called adt-bundle-mac,
00:49butt if you're working on Windows, it will have a different name.
00:52And then from there, I'll switch to sdk subfolder.
00:55I'll list the contents of the SDK. You want to change to the platform Tools folder.
01:04I'll type cd, then platform- and I'll press Tab, and Terminal auto-completes the name of the folder.
01:11I'll list the contents of this folder. This folder contains Application called adb.
01:18I'll use the adb command to open a shell that lets me deal directly with the emulator's persistent storage.
01:25First, I need to know the device ID of my Emulator.
01:30To find that out, I'll run the adb command.
01:33If you're working on Mac, start with ./ and then A-D-B.
01:38And if you're working on Windows, just type adb then after a space, type in devices.
01:44It has an ID of emulator-5554. Your emulator might have a different device.
01:52Whatever it is, make a note of it. You'll need it for the next step.
01:56Now I'll run the adb command again.
01:59Once again, I'll type ./adb and then I'll pass in the following arguments, -s, then the ID of the emulator.
02:08Mine is emulator-5554 and then the word shell in lowercase.
02:15Press Enter or Return and now you're on the Emulator.
02:19The next step is to start up the SQLite3 command.
02:23When you execute SQLite3, you'll need to know the exact location and name of your database file.
02:30The location will always start with /data/data, but from there it will differ
02:35depending on your application package and your database file name.
02:39For my database, I'll start with sqlite3, then /data/data.
02:46After that, type in the name of your package for your application.
02:51I'll type com.exploreca.tourfinder, then another slash, then the subfolder databases.
02:59That part is always the same. And finally, the name of your database file.
03:05In my code, I named my database file tours.db.
03:09As you type, the command will get very long and as you saw here, it will
03:14shift over to the left. That's okay. It's just what the shell does.
03:19The command is still all there in memory, and when I press Enter or Return,
03:24I've opened SQLite3 and I'm now connected to my database.
03:29In the SQLite command environment, you can either execute commands which always
03:34start with a dot or a period, or you can execute SQL statements.
03:38I'll start with some commands. First, as shown on the screen, I'll type .help.
03:45That gives me a listing of all of the available commands for the Command Line environment.
03:49There are commands to import and export.
03:53There are commands for inputting and outputting, for reading, and for listing
03:57tables and other schema information.
03:59For example, I'm connected to my database, and let's say I wanted to see the names of my tables.
04:06I would type .tables, and I see a listing of all the tables in the database,
04:13including my own table, tours, and another table called android_metadata which is maintained by Android.
04:21Next, I'll get schema information.
04:24When you type the .schema command, if you just type the command itself you'll
04:28get a listing of the structure of every table in your database, but if you
04:32want to get just the structure of one table, type .schema and then the name of the table.
04:38I'll type in tours and I get back the SQL command that created the table.
04:44You can also output not just the table structure but also SQL commands that
04:49represent all of the data that's currently stored in a table.
04:53To do this, type .dump and then the name of the table, and you'll get a listing
04:59of all of the data stated as Insert commands.
05:03You could then copy all of this content to the clipboard, save it in a Text file
05:08and you have a back-up of the entire table, both structure and data.
05:12In addition to these commands, I encourage you to explore other commands that
05:16you can find from the .help screen. You can also execute arbitrary SQL statements.
05:22For example, I'll type select title from tours where price is <= 300.
05:31At the end of an SQL statement, type in a semicolon to indicate that the SQL statement is complete.
05:38You'll get back a listing of the retrieved data. I'll do another select statement.
05:42Select title, price from tours where price is <= 1000 order by price in descending order.
05:53Once again, I'll finish the command with a semicolon and I get back a listing of just those values.
05:59The two columns I requested and only the tours that are $1,000 or less and in descending order by price.
06:07Again, explore what's possible.
06:10Try executing various kinds of SQL statements to see what you can get from the database from the Command Line.
06:17And finally, when you're done, here is how you get out of SQLite.
06:21Type .exit, press Enter or Return, then type exit again, this time without the
06:27dot prefix, and now you're back to your host operating system.
06:31So, that's a guided tour through the SQLite Command Line tool.
06:36You can explore your data structure and your data, find out information
06:40about your tables, and even modify the data in the database that's hosted on your Emulator.
Collapse this transcript
5. Managing and Displaying SQLite Data
Improving the data display
00:00The sample application I've been developing throughout this course displays
00:04data that's stored in a SQLite database.
00:07Each item in the list display displays simple plain text all of the same size.
00:13I'm going to show you some ideas about how you can spiff up the display and
00:17dynamically select graphics at runtime to display.
00:20I'm working in a version of the project that's available in the exercise files named RichDisplay.
00:26I've added some layout resources and a new Java class.
00:30I've also added some graphics that will be displayed along with the tour data.
00:34I'll start with the new layout.
00:37I'll go to the resources folder to the layout subfolder and open the file listitem_tour.xml.
00:44This is a rich layout that's designed to display a map icon and then the title
00:50and the price of the current tour.
00:52Looking at the XML structure, it's a linear layout that contains an image view.
00:58There's a default graphic called map_various.
01:01It's just a simple picture of the map of California.
01:05Then next to it, there's another linear layout, this time with an orientation of
01:09vertical containing two text views, one for the title and one for the price, and
01:14they each have a different text size.
01:17So, it's a fairly simple layout, and it's designed to be used as the layout for an item in the list.
01:24Next, I'll show you the graphics that I've added.
01:26They're in the drawable-hdpi folder. I've added a whole bunch of map graphics.
01:33They start off with map_ and then a name, and these match values that are in the
01:39database table in the image column of the tours table.
01:42For example, there's a bigsur tour where the image value is set to map_bigsur without the .png extension.
01:52I haven't provided graphics for every single tour in the data set, but you'll have enough to get started.
01:58Next, I'll show the Java class that's going to use this list item tour layout.
02:03It's called TourListAdapter.java.
02:08This is an adapter class that extends the ArrayAdapter class.
02:14Going back to the MainActivity and going down to the refreshDisplay method,
02:18we're currently using a generic layout, one that's included in the Android SDK
02:24and it's designed to show text only. My goal is to do a richer display.
02:29And so this class extends the ArrayAdapter class.
02:33Just like in the original code, this ArrayAdapter has items data typed as my tour class.
02:39It has a constructor method that lets me pass in the current context and the current data.
02:46Both are then saved in fields within the class.
02:48As the list is rendered, the getView method will be called for each item in the list.
02:55For each item, we're inflating the layout, listitem_tour right here and that's
03:00that layout that's in the resources folder.
03:03Then I'm getting a reference to the current tour object by calling the tours
03:07list's get method and passing in the current position which is passed in here by the Android SDK.
03:15Next, I'm populating the TextView objects with data.
03:19This is the code that's setting the title.
03:21I'm getting a reference to the text element and then setting it with a value
03:25from the tour object, and I'm doing the same thing here for the price but
03:30formatting it using a NumberFormat object.
03:33Here's perhaps the most interesting code, and you might not have seen this before.
03:38The layout has an ImageView object, and first, I'm getting a reference to the ImageView object by its ID.
03:46Then I'm dynamically getting an imageResource integer by calling
03:51context.getResources().getIdentifier. This method receives three values:
03:58first a string, which would be the name of the resource. I'm passing in the
04:02value of the tour object's image property.
04:06Then I'm indicating the subfolder of the resources area.
04:10That's going to be drawable.
04:12I'm not specifying a pixel density and so the same image will be used for all
04:17pixel densities, regardless of device.
04:20And finally, I'm passing in the current package name using the expression context.getPackageName.
04:26If an image is found that matches these three values, its integer resource
04:32number which is in the generated resource class will be returned.
04:37Otherwise, the value of zero will be returned.
04:40And then in some conditional code, I'm asking, did you find a resource?
04:45If so, set the ImageView with that resource, and if not, leave the default image alone.
04:52So, all of this code together, again, is called for each and every item in the list.
04:57We're getting the data, we're setting the TextView objects, we're setting the
05:01ImageView object and returning the new layout object.
05:05There's one more bit of code to add, and that code is going to look at the
05:09current settings for the application.
05:12Going back a couple of chapters when I was working on shared preferences,
05:17I added a preference to this application called View Images.
05:21Here will be the logic.
05:22If that preference has a value of true, I'll use this new RichDisplay, and if it
05:27has a value of false, I'll use the simple text display that I've been using.
05:33I'll go to the MainActivity class and let's code up the behavior.
05:37I'll go to the refreshDisplay method in MainActivity.
05:42Right now this method always shows the simple list display.
05:46I'll make some extra space here.
05:49I'll find out what the value of the preference is for viewing images.
05:53I'll create a new variable called viewImages, and I'll call the settings object's getBoolean method.
06:00Remember, the settings object is declared and created in the On Create method,
06:05and it's pointing to the Preferences Set that's handled by my preference activity.
06:10Now for the key, I'll type in my constant VIEWIMAGE, and for the default value, I'll pass in false.
06:18So, I'm saying if you can't find this preference, assume that it's false and
06:23that will mean we'll see the simple text display and not the new rich display.
06:29Now I'll evaluate that variable, VIEWIMAGES, I'll type if and press Ctrl+Spacebar and choose if else.
06:38The viewImages variable is added as the condition, and that's exactly what I want.
06:43I'll go down here and take this existing code and cut and paste it into the else block.
06:49So, now I'm saying that if the viewImages variable is false, then I'll display
06:53my tours with simple text, but if it's true, I'll use my new RichDisplay.
06:59I'll move the cursor into the if clause, and just as in the other code, I'll
07:04declare an ArrayAdapter with a data type of tour and I'll name it adapter.
07:10But this time I'll use my custom adapter class that I showed you with the syntax new TourListAdapter.
07:18I'll pass in this as the context and my list of tours as the data.
07:24Then just as with the other code, I'll call setListAdapter and pass in the adapter object.
07:31And that's all the custom code I need to do. Everything else here is already done.
07:36I've added an item to my onOptionsItemSelected method.
07:41This is the method that's called when a user selects a menu item, and I already
07:45have a settings menu choice in my menu.
07:48So when that item is selected from the menu, I'll create a new Intent.
07:53It'll set up my SettingsActivity class and start the activity.
07:58And I already have code in the onCreate method that sets up a listener for the
08:02settings, so that when the settings are changed, I'll automatically call that refreshDisplay method.
08:07So, let's give it a test. I'll run the application in the emulator.
08:14When it first opens, it shows the old display, the text only display where the font sizes are all the same.
08:21Now I'll go to the menu and I'll choose Settings.
08:26That opens my preference activity.
08:28I'll check the checkbox to View images and I'll return.
08:33And when I come back to the MainActivity, I'm now seeing the RichDisplay that's
08:38using my custom layout and my custom Java class.
08:42Also, as I scroll through, I see the custom graphics are being correctly
08:46assigned where they match the data in the database table.
08:50The Week of Wine gets Wine Country, Avila Beach Hot Springs gets the Avila
08:54Beach graphic, Big Sur gets Big Sur, and so on and so forth, and wherever there isn't a match
09:00I'm seeing the generic graphic, the map of California.
09:04So you can use this coding model to combine the use of shared preferences with
09:09information that you're storing in your database.
09:12You can make your visual display much more interesting and fun to look at, and
09:17you can give the user a choice, letting them go into the preferences and select
09:24their preferred display with rich graphics or with plain text.
Collapse this transcript
Passing user-selected data to a detail activity
00:00My sample application so far is using data that's imported from XML and then
00:05stored persistently in an SQLite database, and it's now capable of being
00:10displayed in either a rich display as we see here or in a pure text display.
00:16I'm going to add some new functionality now, and in fact, in the new version of
00:20the project--which is named DetailView-- the application now has a detail view.
00:26When the user touches or clicks on an item, that results in opening another activity, and right now
00:32that activity is populated with dummy data.
00:35I'll show you how I created that detail activity but then go on to the most
00:39important part, how to take a data object such as my tour object and prepare it
00:45so that it can be passed from one activity to another.
00:49First, let's take a look at the elements of this new functionality.
00:53In this version of the application, I've added a new layout file called tour_detail.xml.
01:00You can find it in the layout folder underneath the resources.
01:05This file uses nested LinearLayouts.
01:08There's an ImageView control that's displaying the map and then a nested
01:12LinearLayout with an orientation of vertical that contains two text views for the title and the price.
01:19Then underneath all that, there's another text view nested within a ScrollView.
01:24This is for the description, a long bit of text on a smaller device, the
01:29ScrollView will allow the TextView to scroll up and down, so the user can see all of the text.
01:35To go along with that layout file, I have a new java class called TourDetailActivity.
01:41This is where I've defined that dummy data.
01:44This class extends activity, and it's registered in the manifest file as a new activity.
01:50In the onCreate method, I'm populating the tour object with this dummy data and
01:55then calling the refreshDisplay method which contains all the code to display
02:00the data with the TextViews and the ImageView.
02:04This code is very similar to the code that I used to display data in the list items in the rich display.
02:11Finally, in the main activity class, I've added a new method called onListItemClick.
02:17This method is called automatically from the list activity when the user selects an item.
02:22I'm creating a new intent object, populating it with the TourDetailActivity
02:26class and then starting the activity.
02:29So now my job is to take the selected data, the actual tour that the user selected,
02:35and pass it to the new detail activity.
02:38The first step is to get a reference to the selected tour.
02:42I can do that by using this position argument that's passed into onListItemClick.
02:47That will tell me the position of the tour that was selected in the data collection.
02:52So above the Intent, I'll create a new tour object that I'll name tour, and I'll
02:58get its reference by calling tours.get and I'll pass in the position argument.
03:04So now I know what data I want to pass in.
03:07One way of passing the data would be to call the putExtra method of the Intent
03:12object a whole bunch of times.
03:14For example, after I create the Intent, I might call intent.putExtra, and then I
03:22would pass in a string as the key to the value, and then the actual value and I might call tour.getId,
03:28and I would call that method five times here, and then in the
03:35TourDetailActivity, I would call the getExtra method five times and put the
03:40tour object back together again.
03:42That approach will definitely work, but a cleaner and more elegant approach is
03:47to do some work in the Tour class itself so that you can pass the entire object
03:52as a single object, and the right way to do this is to use an interface in Android called Parcelable.
03:59So let's go to the tour object that I've already defined.
04:03I'll shrink down my editor, and in my Package Explorer, I'll go to the model
04:08package and I'll open the class Tour.java.
04:13Right now this class is following a classic data transfer object or JavaBean pattern.
04:19It has private properties and then getters and setters for each of those properties.
04:24And in addition, there's a two-string method that's being used in the pure text
04:29presentation to display the tour information.
04:32In order to make this tour object something that you can pass from one activity
04:36to the next, you need to implement the Parcelable interface.
04:42I'll go back up to the class declaration.
04:43I'll add the implements keyword and then type par, press Ctrl+Spacebar, and select Parcelable.
04:53When I add that to the Tour declaration, I see an error on the right.
04:58I'll click on it and it tells me that I have to add unimplemented methods.
05:03I'll select that quick fix, then I'll scroll down to the bottom of the class
05:08and I see that two new methods have been created called describeContents and writeToParcel.
05:15The describeContents method will be called by Android whenever it needs to find
05:20out what kind of special objects might be a part of this class.
05:24My class doesn't have any special objects. It's limited to primitive values and strings.
05:30I can return a value of zero and that basically says never mind.
05:35The writeToParcel method is a different story.
05:38Its job is to describe the different data elements and pass them to what's
05:42called the parcel destination and it will be called automatically whenever
05:47Android needs to get a flattened version of this object, something it can save
05:52while one activity is going away and another activity is coming to the screen.
05:57We're getting to a point now, though, where there's going to be some significant typing.
06:01So I've added a typinghelp file to the project.
06:05I'll open that up and I'll expand it to Full Screen, and let's take a look at all of the code.
06:13First of all, there's a new default no arguments constructor method.
06:18The Tour class in its current state didn't have one of these and it's going to
06:22need it because there's going to be another version of the constructor method
06:26that receives a parcel argument.
06:29This class is going to be called automatically whenever a tour object is being passed into an activity.
06:36The class will be instantiated and then values will be passed in, in something called the parcel.
06:41We then take those values and pass them to the private properties of the current instance of this class.
06:48Notice the order in which I'm calling these methods.
06:51That will become important in a moment.
06:54Here's that describeContents method again, and I'm only returning zero, which
06:58basically means I don't have anything other than simple values.
07:03Here's a full implementation of the writeToParcel method.
07:06This method will be called when I'm passing data out of the activity.
07:10Remember I said to remember the order in which I was passing data in?
07:14You have to pass data in and data out in exactly the same order.
07:20So I have my data in the order of ID, title, description, price, and image here,
07:25and I'm using the same order in the writeToParcel method.
07:28That is absolutely critical.
07:31Finally, each class which implements the Parcelable interface must have a static field called CREATOR,
07:39the name of the field must be all uppercase, and it has to be of a datatype of Parcelable.Creator.
07:46The data type of the generic declaration should match your data object.
07:51I'm using Tour in the generic declaration here and here and also when the
07:57methods create from parcel and newArray, both in the data that's being passed
08:01back and in the instantiation code within the methods.
08:05So all this code taken together will make my tour class something that can be
08:10passed from one activity to the next.
08:13As I mentioned, it's a lot of code, so I'm just going to copy it and paste it.
08:18I'll select and copy. I'll come back to the Tour class.
08:22I'll remove the Override methods that were just created and instead paste in that code.
08:29When I paste in the code, I'll see I need to apply a few quick fixes to add import statements.
08:35So I'll move to each line that's displaying an error and press Command+1 on Mac
08:40or Ctrl+1 on Windows and select the Import quick fixes.
08:44I'll scan and make sure I don't have any other errors then I'll save that class.
08:52Now I'll come back to the MainActivity class.
08:55I'll still use the putExtra method to pass the data into the Intent object, but
09:00now instead of an arbitrary key for the value, I must use the full package name
09:06and class name of the data object. Just as with any key value that you pass into
09:11putExtra, this will be a string.
09:14You can either pass in the entire package name or you can start with the
09:19sub-package underneath the default package of the project, and that's what I'll do.
09:24I'll start with .model and then .tour and then I'll pass in the tour object that
09:31I just got a reference to up here. That's all I need to do.
09:35All of the difficult code is now in the Tour class, and all I have to do is get
09:40an object and pass it in and say what class it's an instance of.
09:45I'll save those changes, then I'll come to the TourDetailActivity class.
09:51I'm going to comment out the code that's creating the dummy tour object, and I'll
09:56replace it with the following code. First, I'll create a bundle object.
10:01I'll press Ctrl+Spacebar just to make sure I've added the import for the class,
10:05and I'll name the object B and then I'll call getIntent().getExtras.
10:15Then to get the data from the bundle, I'll say tour = and then I'll call a
10:21method of the bundle object called getParcelable and I'll pass in the same
10:26string key, .model.tour, and that's it.
10:32Once again, the code in the receiving activity is incredibly simple, all the
10:36hard stuff is in the Tour class and I'm ready to test.
10:41I'll run the application in the emulator again, and I'll click into an item and
10:48this time I see the actual data of the selected item.
10:52Remember, I mentioned that I'd be able to scroll up and down on a smaller device
10:56so I could see all of the text in the description.
11:00I'll go back a level to the My List Activity.
11:03I'll scroll a bit and I'll select an item with a custom map, the
11:07Channel Islands Excursion, and I see that my Detail Activity correctly selects the right image as well.
11:13And again, I'll scroll up and down and show that it's all working correctly.
11:17So the most important new lesson in this exercise is how to take a class that's
11:23designed to contain data and turn it into something that's Parcelable.
11:27That is something that can be passed from one activity to the next to make it
11:32easy to move data around your Android application.
Collapse this transcript
Working with multiple database tables
00:00My sample application so far uses a single database table to store tours data.
00:06I'm now going to add some new functionality to the application.
00:09I'm going to let the user add a tour to a list that we'll call mytours--a custom list--
00:16and then I'll let them show a filtered view that only shows the tours that they've selected.
00:21I'll need a second database table to manage this, and I'll define it first in my database open helper class.
00:28Now this entire task will take quite a bit of code, so as I have in the past,
00:33I've provided a typing help file called typinghelp.txt that's a part of the Multiple Tables project.
00:40I'll start with the first snippet where I'm defining the table mytours.
00:45There are two declarations here, one for the new table name, and one for an SQL create statement that will create
00:52the table in the database.
00:54I'll copy that code to the clipboard, and I'll go to the class ToursDBOpenHelper.java.
01:00In the class I'll place the cursor after all of the existing constants and then
01:07I'll paste this new code into place.
01:10So now I have constants to define two tables, one called tours and one called mytours.
01:17This new table has only a single column, an INTEGER PRIMARY KEY.
01:22It's not auto-incrementing because I'm going to add explicit values to this table.
01:27Then I'll be joining them together at runtime to show the list that the user has selected.
01:33Now I need to use these constants.
01:35I'll scroll down toward the bottom of the class and I'll go to the on create method.
01:41I have an existing line of code that's executing the create statement for the tours table.
01:46I'll duplicate that code, and I'll change the constant that I'm calling from
01:51TABLE_CREATE to TABLE_MYTOURS_CREATE.
01:56Then I'll also add code to drop that table when I'm upgrading the database.
02:01I'll go to the onUpgrade method, and I'll duplicate the existing drop statement
02:05and I'll change this one from TABLE_TOURS to TABLE_MYTOURS, my new table name constant.
02:14Finally, to trigger the database upgrade, I'll increment the database version.
02:21I'll scroll up to the top of the class and locate my database version constant
02:26and I'll increment it form 1 to 2.
02:29Remember, you can only increment your database version.
02:32You can't decrement it and it must be a whole number.
02:36I'll save those changes and I'll test them.
02:39Before I test, I'm going to open my LogCat console.
02:44If you have any existing messages in the LogCat console, clear them and then
02:49make sure that you're filtering on the EXPLORECA tag.
02:52Now I'll run the application in the emulator.
02:56As the application comes to the screen, Android detects the difference between
03:00the old version and the new version of the database and the code is correctly executed to upgrade it.
03:07So now my database has two tables, TOURS and MYTOURS, and I'm ready to add some SQL statements that can add data
03:15to the table and retrieve it.
03:18Next, I'll go to my DataSource class, ToursDataSource.java.
03:23As I described in a previous video in this course, you can either have multiple
03:28data sources, one for each table, or if your database structure is simple
03:32enough, you can manage the whole thing with a single data source, and that's what I'm going to do.
03:38I'm going to be adding a couple of methods.
03:40One to insert a new row into the new mytours table and one to join the mytours
03:46and the tours table together to get back a filtered list of only the tours that the user has selected.
03:53Once again, I'll go over to the typinghelp file. Here is my next to code snippet.
03:58This is the code that will add a new row to mytours.
04:02I'll select and copy it, then go back to the DataSource class, I'll go down to
04:08the bottom of the DataSource class and I'll paste in the new method after the
04:13existing method, cursor to list. Let's take a look at the method.
04:17Just as with the previous insert method, I'm creating an instance of the ContentValues class.
04:23I'm adding a single value, the COLUMN_ID of a tour object that's been passed in.
04:29Then I'm inserting that into the MYTOURS table and getting back a value.
04:34Then I'm testing whether the result is -1. Here's why.
04:39If the user tries to insert an item that's already in the mytours table, that will cause an index conflict,
04:46because remember, I set that column as a primary key.
04:49When that happens, the insert method returns a value of -1.
04:54So I'm returning an expression that says result not equal to -1.
05:00If that expression resolves to true, that means that the insert succeeded.
05:04I've added the new the row.
05:06And if returns false, that means that the item was already in the database table.
05:12Now I'll add code to the application to call this method.
05:16I'll do this from the TourDetailActivity class.
05:19In this activity, I've added a new menu item that's labeled add to mytours and
05:24I've added some code to this class that will handle that menu item.
05:29The ID of the menu item is menu_add and here is the code that I'll place in this position.
05:35I'll create an if/else statement.
05:39I'll type if and press Ctrl+Space and then choose if else.
05:43I'll set the condition to datasource.addToMyTours, and I'll pass in the current tour object
05:50which was created when this activity came to the screen.
05:55If the call to this method returns true, that means a new item was added to the mytours list.
06:01And for now, all I'm going to do is call the log class' I method, and I'll pass
06:08in the LOGTAG constant and a message of Tour added.
06:14And if it fails, I'll put a message of Tour not added.
06:18I'll duplicate that line of code and move it down and I'll change the output.
06:25Notice I'm getting an error on the LOGTAG constant.
06:29That's because it was initially commented out in this version of the activity.
06:33I'll remove the comment and scroll back down and all of the errors have been cleared.
06:39I'll save my code in the Activity class and also in the DataSource class.
06:43I'll make sure I don't have any errors, and now I'll run the application with this new code in place.
06:53When the application comes to the screen, I'll click in to an item in the list
06:58and then I'll click the menu item ADD TO MY TOURS.
07:01Nothing happens in the application yet, but I'll go back to Eclipse and once
07:06again go to my LogCat console and I see the log message Tour added.
07:12Now I'll go back to the application and try to add the same tour again, and this
07:17time I get the message Tour not added.
07:20I'll go back to the application and click the back button to go back to the tour list.
07:25I'll click into another item.
07:28I'll click ADD TO MY TOURS and I get that Tour added and by now I should have
07:34two items in the mytours table. But I'm not quite done yet.
07:38Now I'm going to add code that retrieves that data and displays just the selected tours.
07:46I'll go back to my ToursDataSource class.
07:49I've already added this method to add new a row to my tours.
07:53Now I'll add a method that joins this table with the tours table and returns just the selected tours.
08:00Once again, I have some help in the typinghelp file.
08:05I'll scroll down to the bottom and I'll select this method, findMyTours.
08:11I'll copy it to the clipboard, go back to the DataSource class, and paste it into place.
08:17And let's take a look at the code.
08:19There is a query variable which is a select statement that joins the two
08:24tables together--tours and mytours--on the primary key columns, both of which are named tourId.
08:31I'm then using a method called rawQuery.
08:34When you call the rawQuery method, the first argument is a string representing
08:38the SQL statement and the second argument can be an array of parameter values.
08:44This SQL statement doesn't have any parameters, so I'm just passing in null.
08:49Then there is a Log output telling me how many rows I got back.
08:52I'm processing the cursor using my existing method cursorToList and returning that data.
08:59I'll save the changes to the DataSource, and then I'll go to my MainActivity
09:04class and add some code to call the new method.
09:07My MainActivity has a new menu choice labeled mytours, and when the user selects
09:13that menu choice, they'll trigger a call to the onOptionsItemSelected method and
09:18I'll add the code to call the new method here. The code will tours = datasource.findMyTours.
09:28And then I'll call the refreshDisplay method, and the screen will update with just the selected tours.
09:34I'll save and run the application.
09:39Now I'm re-launching the application, but I'm not going to go add a new item to mytours.
09:45I'm just going to do the filtering right now to show that the data is persisting
09:49in storage even between application launches.
09:53I'll click MY TOURS and there are the tours that I previously selected.
09:59I'll click on ALL and then I go back to all tours.
10:02I'll drag the list up a bit and I'll choose another tour.
10:06I'll click on ADD TO MY TOURS.
10:09I'll then return to the main display and filter again, and I'll see that it's been correctly added.
10:15So my two tables are now a part of my database structure, and I've given the user
10:20an interface that lets them add items to that second table and use the second table to create a filtered view.
Collapse this transcript
Deleting data from database tables
00:00At this point, my tour finder application uses two tables, one for the primary
00:06tour data, and one for a mytours table that tracks tours that the user has
00:11selected and I can look at a filtered view showing just those tours.
00:15I'd like to add functionality to the application that lets the user remove
00:20an item from that custom list.
00:22I've added some code to the application so that when the user goes into an item
00:27from the mytours list, they now see a menu choice, REMOVE FROM MY TOURS .
00:32I'll show you how I did that.
00:35First, I'll go the XML file, tour_detail.xml that you can find in the resource area's menu folder.
00:43I've added a second item to the menu.
00:45Its ID is menu_delete, and this string menu_delete equates to the string you see
00:51on the screen, Remove from My Tours. Then I added logic to TourDetailActivity.java.
00:59In the CreateOptionsMenu method shown here, I now have code that filters which menu item is selected.
01:07If we came from the My Tours view, then we are going to show the delete item.
01:12And if we didn't come from My Tours, then we'll show the Add Item.
01:15And here is how we know whether we came from My Tours.
01:19In the MainActivity class, I've added a boolean field called isMyTours, and each
01:24time I'm presenting data I'm setting that value to true or false.
01:29In the on create method, when I first present the data, I'm setting it to false.
01:35And when a user selects an item from the list, for all but the filter from
01:39MyTours I'm also setting it to false, but here I'm setting it to true.
01:45And finally, I'm passing that value into the new activity by adding it as
01:50an extra right here in the onListItemClick method.
01:54So the entire chain of logic is, when the user clicks on a filter, I determine
01:59whether they're watching my tours or not.
02:02Then when they open the detail activity, I pass that value in and the detail
02:07activity evaluates it and decides what menu choices to present.
02:11So that's just the management of the user interface.
02:15Now we come to the important point for this course, how to actually remove data from the database.
02:21As with all code that deals directly with the database, I recommend that you put this into the DataSource class.
02:28I'll go to my ToursDataSource.java file and open it to full screen, and I'll go
02:34down to the bottom of the class.
02:36I'm going to add a new method that I will call removeFromMyTours, and I'll place
02:42it right here after the addToMyTours method.
02:45I'll make it a public boolean method, and again, it will be called
02:50removeFromMyTours, and just like addToMyTours, it will receive a single argument
02:55data type as the tour class. Next, I'll create a selection string.
03:01This will be a simple value starting with the name of the primary key column,
03:05then an equals operator, and then the ID of the tour object that was passed in as an argument.
03:12I'll create a String named where and I'll start it with the name of the column,
03:17which I'll get from a constant of my OpenHelper class, ToursDBOpenHelper.column_ID.
03:26Then I'll append an equals operator and I'll append to that the ID of the
03:31current tour, which I'll get from tour.getId.
03:36So now I have a string that I can use to filter a delete operation.
03:40Make sure you've typed this part correctly, because if you get it a little bit
03:44wrong you can actually remove all the data from the table.
03:48So next, I'll call the delete method of the database object.
03:53This is similar to the insert method.
03:55It's going to create an SQL statement for me that deletes the row from the table
04:00filtered on the ID that I pass in. The code will look like this.
04:05First, I'll declare a variable named result, data typed as an integer.
04:09Then I'll get that value from database.delete.
04:14Notice that the delete method returns an int, not a long value like the insert method.
04:20I'll pass in the name of the table that I want to delete a row from, again, I'll
04:23use a constant, ToursDBOpenHelper.TABLE_MYTOURS.
04:30Next, I'll pass in the where clause, and that's the string that I just created.
04:35And finally for the last argument, I'll pass in a value of null.
04:39I don't need to pass in any arguments because I've included everything I need in the where clause.
04:45Finally, I'll do a return and I'll evaluate the result.
04:49When you call the delete method, the result would be a numeric value indicating how many rows were affected.
04:56The same thing is true of the update method of the database object.
05:00So if I get back a value of 1, that means that I successfully deleted one row from the database table.
05:07So I'll pass back this expression, result == 1, and if it's any other value I'll pass back false.
05:16So that's all the work I need to do in the DataSource class.
05:19Now I'll go to the DetailActivity class where I'll make a call to this method.
05:24I've added code to the onOptionsItemSelected method to handle the new menu delete menu choice.
05:33I'll use this code to delete the selected tour from the database table.
05:37If datasource.removeFromMyTours then I'll pass in the current tour object and
05:46if I get back a value of true, that means the data was successfully removed from the table.
05:52I'm going to set a result from this activity that can be read from the launching activity.
05:58I'll call the setResult method and pass in a value of -1.
06:03This value can be anything I want.
06:05It's simply a flag that will be read by the sending activity to determine what happened.
06:11My logic says I removed an item from the database table, so it's a -1.
06:16I have one less item. Then I'll call a method called finish.
06:21When you launch an activity from another activity, calling the finish method
06:26removes the current activity and goes back to the previous one.
06:30So my logic is if I successfully removed the row from the database table, then
06:36tell the sending activity that it happened and close the current activity.
06:40I'll save those changes and I have one more code change to make before I test the application.
06:47I'll go to the MainActivity class.
06:49Notice in this version of the application I launched the DetailActivity class
06:54with a method called startActivitytForResult, and then I passed in a value known as a request code.
07:02I'm using a constant, TOUR_DETAIL_ACTIVITY.
07:05It doesn't matter what the value of this constant is,
07:08I just need to use it to find out when I come back to this activity where I came from.
07:15So now I'll add one more method to this class.
07:19When I've started this second activity and then I come back, that will trigger
07:23a method called onActivityResult.
07:26It's a method that's a part of the super class, so to create it all I need to do
07:31is type the beginning of the method name, press Ctrl+Space and then choose a method from the list.
07:37OnActivityResult receives three arguments, requestCode, resultCode, and Intent.
07:45I'll use conditional logic to find out whether I came from the DetailActivity
07:50and whether something was removed from the database table.
07:54So I'll use an if clause for that.
07:56I'll type if, I'll press Ctrl+Space, and choose an if statement.
08:00And here is my condition.
08:04If request code matches the constant TOUR_DETAIL_ACTIVITY and the resultCode has
08:12a value of -1--remember that's the value I set in the detail activity--then that
08:19means that something has changed in the MyTours list, and I'll refresh the list.
08:25Within the conditional block, I'll make sure that my DataSource is open.
08:29I'll call datasource.open.
08:33I'll refresh the list by saying tours = datasource.findMyTours.
08:41And as I have in the past, I'll call my refreshDisplay method.
08:45So now when I remove an item from the list, from the DetailActivity and then
08:50return to the screen, I'll detect that that has happened, and I'll re-query the
08:55data and show the refreshed list.
08:58And finally, I'll still be on the MyTours list so I'll say isMyTours = true,
09:04and that's all of the code.
09:06Now I'm ready to test. I'll run the application in the emulator.
09:11When the application comes to the screen, I'll click on MyTours.
09:16That shows me the items that I've previously added to the MyTours table.
09:19I'll click into an item, and because I came from that view, I see the REMOVE
09:24FROM MY TOURS menu choice, I click it, it's removed from the table, and
09:29I immediately come back to the MainActivity where my data is refreshed.
09:34I'll click into another item, I'll remove it, I come back and the data is refreshed.
09:41So now you have a complete working example of an application that uses multiple
09:45tables in a database and can insert and delete data as needed.
09:50Remember at all times that the user is always capable of removing this sort of
09:55private data from their application.
09:57Local data that you put on the device is never permanent.
10:01The only way to make it permanent is to save it up to a server.
10:05Working with server-hosted data is outside the scope of this course, but now
10:10that you have a good sense of the various options that are available for
10:13local data storage with preferences, internal and external files, and SQLite hosted data,
10:21you should be able to manage the data that's on your device in all of these different forms.
Collapse this transcript
Conclusion
Where to go from here
00:00Thanks for sitting with me through this video series, Android SDK: Local Data Storage.
00:06In this series I described how to work with locally-stored data in the form of
00:10preferences, files, and the SQLite Relational Database Engine.
00:16I described strategies for creating, changing, and displaying data, and showed how
00:21to apply best practices in Android Database Programming.
00:25To learn more about programming in Java, check out the course Java Advanced Training,
00:29and for more about object-oriented programming in general, I recommend
00:34watching Foundations of Programming Object-Oriented Design.
00:38I hope this course has helped you get ready to build your own data-centric Android apps.
00:45Thanks again for watching, and happy programming.
Collapse this transcript


Suggested courses to watch next:

MySQL Essential Training (2h 46m)
Bill Weinman


Java Essential Training (7h 17m)
David Gassner


Are you sure you want to delete this bookmark?

cancel

Bookmark this Tutorial

Name

Description

{0} characters left

Tags

Separate tags with a space. Use quotes around multi-word tags. Suggested Tags:
loading
cancel

bookmark this course

{0} characters left Separate tags with a space. Use quotes around multi-word tags. Suggested Tags:
loading

Error:

go to playlists »

Create new playlist

name:
description:
save cancel

You must be a lynda.com member to watch this video.

Every course in the lynda.com library contains free videos that let you assess the quality of our tutorials before you subscribe—just click on the blue links to watch them. Become a member to access all 100,877 instructional videos.

start free trial learn more

If you are already an active lynda.com member, please log in to access the lynda.com library.

Get access to all lynda.com videos

You are currently signed into your admin account, which doesn't let you view lynda.com videos. For full access to the lynda.com library, log in through iplogin.lynda.com, or sign in through your organization's portal. You may also request a user account by calling 1 1 (888) 335-9632 or emailing us at cs@lynda.com.

Get access to all lynda.com videos

You are currently signed into your admin account, which doesn't let you view lynda.com videos. For full access to the lynda.com library, log in through iplogin.lynda.com, or sign in through your organization's portal. You may also request a user account by calling 1 1 (888) 335-9632 or emailing us at cs@lynda.com.

Access to lynda.com videos

Your organization has a limited access membership to the lynda.com library that allows access to only a specific, limited selection of courses.

You don't have access to this video.

You're logged in as an account administrator, but your membership is not active.

Contact a Training Solutions Advisor at 1 (888) 335-9632.

How to access this video.

If this course is one of your five classes, then your class currently isn't in session.

If you want to watch this video and it is not part of your class, upgrade your membership for unlimited access to the full library of 1,934 courses anytime, anywhere.

learn more upgrade

You can always watch the free content included in every course.

Questions? Call Customer Service at 1 1 (888) 335-9632 or email cs@lynda.com.

You don't have access to this video.

You're logged in as an account administrator, but your membership is no longer active. You can still access reports and account information.

To reactivate your account, contact a Training Solutions Advisor at 1 1 (888) 335-9632.

Need help accessing this video?

You can't access this video from your master administrator account.

Call Customer Service at 1 1 (888) 335-9632 or email cs@lynda.com for help accessing this video.


site feedback

Thanks for signing up.

We’ll send you a confirmation email shortly.


By signing up, you’ll receive about four emails per month, including

We’ll only use your email address to send you these mailings.

Here’s our privacy policy with more details about how we handle your information.

Keep up with news, tips, and latest courses with emails from lynda.com.

By signing up, you’ll receive about four emails per month, including

We’ll only use your email address to send you these mailings.

Here’s our privacy policy with more details about how we handle your information.

   
submit Lightbox submit clicked