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