Beginning Android Programming with Android Studio
Beginning Android Programming with Android Studio
By
Roger Deutsch
Copyright © 2016
http://raddev.us/LYAA
Table of Contents
Introduction...............................................................................1
Chapter 1...................................................................................9
Getting Started With Android Studio....................................9
Chapter 2.................................................................................47
Generate Your First Project.................................................47
Chapter 3.................................................................................75
Run Device Emulator & Debug Code.................................75
Chapter 4...............................................................................111
Stenotes App Part 1...........................................................111
Chapter 5...............................................................................163
Stenotes App Part 2...........................................................163
Chapter 6...............................................................................221
Stenotes : Add Some OOP................................................221
Chapter 7...............................................................................289
Building QuoteCap(ture) App: Learning About Intents....289
Chapter 8...............................................................................335
QuoteCap: Altering the Layout, .......................................335
Chapter 9...............................................................................399
QuoteCap : Finishing the App – Finding Bugs In APIs.....399
Chapter 10.............................................................................437
TxtFwd : Building the App - Retrieve Your Text Messages
From Anywhere................................................................437
ii This page intentionally left blank.
iii
Introduction
So the writer who breeds
2. For personal pleasure - hopefully you are interested in seeing your own
creations become real. You can write your own apps, run them on your phone
and give them to all your Android-friends.
3. To create your own apps. Maybe you want to create a solution with an app
that you can’t find -- at least a reasonably good version that you trust.
However, if you are a beginner and need more explanation, read the details
that explain the more difficult parts so that you understand what the code
does before moving on.
I will also do some organization work which will break the content into
chapters that cover specific topics so that if a person understands the material
in the chapter she can skip it and move directly to the material which better
meets her needs.
Write Challenge
Can you type the code that will compile? Do you understand the syntax of the
commands? Do you know how to instantiate an object in Java? Do you know
how to access the library which contains the functionality you need?
Build Challenge
Does the code compile? Is the code valid? If not, what do you do? How do
you understand what Android Studio is telling you to change? One of the
most powerful abilities in the programming world is the ability to understand
the errors you are seeing and knowing how to get the errors to go away. You
will learn to do this here. Gradle (Java build system) is a huge part of
Android Studio and may drive you crazy. We’ll learn how to control it and
what to do when it fights us.
Run Challenge
Can you deploy the app somewhere (emulator or device) to get it running.
Android apps cannot run on your windows or linux box. You have to deploy
them.
Does the program crash before you even see the main Activity (form)? Does
it crash immediately after you click the only button on the form? Does your
program crash for some unknown reason when you attempt to get data from
the Internet? These are logic errors within your code that are probably based
upon your lack of understanding about how the components and functions
you have built upon work.
The point of all these three challenges is that you must write, build and run
code to learn how the entire Android App ecosystem works. The more you go
through those challenges the more you will become familiar with the errors
that may occur. The more you become familiar with the errors that may occur
the more likely it is that when you get stuck in an odd situation you will be
able to figure out how to get your app written, building and running again.
Complete Apps
We will also finish at least 5 fully developed applications and deploy them to
the Google Play store.
This will take you through the complete development cycle of an Android
app and provide you with validation that if you finish the book, you will be a
professional app developer.
Blast Off!
I’m interested in writing code, building and running it so I’m not going to tell
you the history of Android or Android Studio or anything else. Instead, I’m
going to walk you through how to get Android Studio (Version 1.5.1 as of
this writing), install it and generate your first program.
Let’s go!
Chapter 1
Getting Started With Android Studio
However, the knowledge you gain even while simply installing Android
Studio and the supporting tools is all part of your learning. In other words,
simply installing the development environment is valuable to you as a
developer. Keep that in mind as we move through these steps and I’ll try to
reveal some extras that you may not have noticed that will become important
to you later when you’re developing your apps.
There are some hardware and Operating System requirements. You have run
it on a Mac, Windows or Linux system.
Since Android Studio consumes quite a few resources (memory and CPU) on
your machine, the faster the machine you have and the more memory you
have the better.
As of 2015-02-05 here are the basic requirements (straight from the Google
site) so you will know what you need:
• Microsoft® Windows® 8/7/Vista (32- or 64-bit)
• 2 GB RAM minimum, 4 GB RAM recommended
• 400 MB hard disk space
• At least 1 GB for Android SDK, emulator system images, and
caches
• 1280 x 800 minimum screen resolution
• Java Development Kit (JDK) 7
• Optional for accelerated emulator: Intel® processor with
support for Intel® VT-x, Intel® EM64T (Intel® 64), and
Execute Disable (XD) Bit functionality
When you click it, Windows will help you so you can save the JDK
download on your file system.
Once you open the installer UAC (User Account Control) is going to verify
that you really want to allow the executable to run.
And very
quickly after that, you'll see:
Click the [Next] button to start the installation and you'll see:
I'm just leaving everything to install in the default location. You can see it
will go to c:\Program files\ which is the x64 Windows default.
Click the icon and the installation screen should come back up.
It says, we can just click the [Next] button (since we've installed the JDK) so
let's try that.
You will then see the following:
Take time to notice a couple of things on this dialog.
First of all it will intall three things on your computer:
1. Android Studio
2. Android SDK (software development kit) -- these
represent the various versions of Android that have been released.
3. Android Virtual Device (AVD) -- this is the emulator and Android OS files
for different versions of the Android OS so you can run the emulator as if
your are running any version of Android on your computer. This is how we
test our app on multiple Android devices.
The following splash screen showed up and that thin green line is actually a
progress bar.
When the progress bar finally got to the far right of the splash screen a new
window popped up.
Click the [Next] button to validate that your installation is correct.
Click the [Next] button to accept the default settings for Android
Studio.
Since you are a new user you will need to learn more about the
system before you have a good idea about how to customize
Android Studio.
The next window provides an overview of the additional tools and SDKs it is
going to download and install and the path where it will install them.
Click the [Finish] button to allow the installation to continue.
If you clicked the [Show Details] button, when it finally completes
you will see an entire list of everything it installed and the [Finish] button
will be enabled so you can click it.
At the bottom you can see that informs you that the "Android SDK is up to
date."
Click the [Finish] button.
Finally, Android Studio is actually ready to run.
You will now be able to choose to "Start a new Android Studio project".
Now we can go ahead and start a basic test project to make sure it all works.
However, since this chapter has been extremely long with so many
screenshots I’m going to move us to chapter two so that our first
project is created separately to make it easier to skip this chapter on
installation for those who’ve already done all that.
In chapter two, we’ll start off, right where we stopped here. See you there.
Chapter 2
Generate Your First Project
Click the “Start a new Android Studio project" choice.
Go ahead and type the word test, since that is what we are going to call this
first app.
As soon as you type that word you will see that Android Studio
gives you a hint about naming your app. (See red text in the next
screenshot).
Go ahead and change the name to start with an uppercase T and tab to the
next field (Company Domain).
I type the name of my web domain in that box (raddev.us) but you should not
use my domain.
You should use your web site domain or if you don’t have one, use your first
name or first and last name or some other identifier.
Company Domain Value
For now it doesn’t matter much, but when you get ready to deploy your real
app to the Google Play store you’ll want something that
identifies you more distinctly as the creator of your app.
Package Name
As you can see, the package name is generated by using the value you type in
the Company Domain text box. Of course it also takes the value, flips the
order and prepends it to the name of your app.
The package name is a simple way to provide your code with a namespace so
that if others create similar Java classes then the two can be distinguished
from each other. Again, for now, none of that may make sense to you and
that is okay. We will explain it further in later chapters. For now, it is
important to not let this small detail bother you. The value is not permanent
and is not important at this time.
The first thing you’ll notice is that the template automatically targets phone
and tablet devices which are running API 15 (or newer). This is also known
as the API level (API Level 15).
Every major version (1.x, 2.x, etc) of the Android OS is referred to by a code
name which is the name of a dessert (or sugary snack). They did this in an
attempt to make it easier for users to refer to the major version of the OS they
are running. Users can say, “I’m
running Ice Cream Sandwich.” “I’m running Jelly Bean.”
If you target a newer platform, like Android 6.0 (Marshmallow, API Level
23) you will be targeting a smaller number of users because not as many
devices have released that are running that version of the OS yet.
Statistical Data for OS Usage
You can see the current statistics for number of users running each version of
the OS at:
http://developer.android.com/about/dashboards/index.html
Examining that data can help you choose the best version to target the
greatest number of users so that your app will have a larger market.
Other Platform Types You can also see that the project template allows you
to create an app which runs on wearables (watches, etc), TV and even
Android Auto and Android Glass.
For now, we are going to accept the default and run as a phone or tablet app
so go ahead and click the [Next] button.
When I clicked that button another installation occurred which looked like
the following:
Android Studio determined that I needed some build tools so I could build the
app.
Finally the [Next] button was enabled so go ahead and click [Next] now and
you will see the following:
Android Studio is prompting us to choose a Layout type for our application’s
Activity.
An Android Activity is the User Interface which allows the user to interact
with your application. Each Activity should focus on allowing the user to do
one thing.
For now, we are going to select the default Activity called Blank Activity.
Notice that it is not the Empty Activity -- which is an Activity with nothing
on it. Instead this just provides an Activity which will have a few UI elements
on it, but which are not filled out in any way. It will make more sense once
we build the application.
Click the [Next] button.
Activity Name
Android Studio wants you to name your Activity so you can refer to it in
code.
It suggests the name of MainActivity since this will be the Activity the user
sees when the application starts. That is a good name and the name I always
like to use in my projects as a developer
convention to remind me which Activity is the first one the user sees.
Layout Name
The Layout Name is generated according to the name you type in the Activity
name edit box. The layout name is the name of the XML (eXtensible Markup
Language) file which is generated by Android Studio. The layout file
represents all of the graphic elements that the user sees on the Activity
(buttons, edit boxes, etc). We will be learning how to edit and create layout
files so that our apps provide a way for users to enter text and run commands
by clicking buttons.
Title
The Title edit box allows you to enter a Title that will be displayed at the top
of the Activity.
Go ahead and change that value to “First App” just so you can see that when
we build and run the app.
It’ll look like the following:
Fragment Checkbox
For now, we will not use a layout Fragment, but we will learn about using
them later. Layout fragments are a powerful way to reuse Activities simply
by reloading a fragment of the UI into the currently loaded Activity. We will
learn how to create and control these powerful elements but that is for later.
For now, leave the checkbox unchecked.
Go ahead and click the [Finish] button and Android Studio will create the
project.
You will see something like the following with the floating progress bar.
Android Studio is getting everything ready so you can begin to develop your
application.
The build system known as Gradle is doing a lot of work preparing your app
for development.
Finally, if you wait long enough, then Android Studio will open a few files
for you and it will look similar to the following.
You can see a bit behind the tips dialog.
I’ll click the [Close] button on the tips and show you the main Android
Studio window again.
You can see the main Activity as it will appear on an Android phone. If you
look closely enough you will see that in the top left corner of the Activity, the
words, “Hello, world” show up.
Project Navigator
First of all, take a look at the far left side of Android Studio which looks like
the following:
Project TreeView Similar To File Explorer
This is similar to a File Explorer type of treeview, but a bit different. Notice
that the blue highlight indicates the file that is currently being viewed on the
right side.
The blue highlight is showing us that the file named
content_main.xml, which is found under the layout folder is currently open.
That’s the XML layout file I mentioned when we were creating the project.
The important thing to note for now is that Project is currently selected. If
you click one of the others your view will change. For now, the others won’t
do much of anything except slightly change the view in this navigation bar.
But later they will have some meaning. The important thing to note for now
is that you are on the Project choice and it displays the treeview which allows
you to examine the files in the project.
1. activity_main.xml
2. content_main.xml
That is simply because the project has split these two XML files up to help
you better manage the User Interface elements. We will see more about this
in a moment.
Project Structure
The next thing to help you get familiar with the files in the project is to take a
look at the folders that the files are contained in. Again, notice that these
XML files which represent the UI are under the folder named layout.
And, then notice that the layout folder is inside a folder named res.
Conventional Organization
res is an abbreviation for resource. All of the files found under this folder are
resources that you project will use. Things like images, icons and other user
interface elements. You can see there are other folders such as menu, mipmap
and values also under the res folder. We will learn more about those later.
This is all a conventional way of keeping things organized. You could change
the way things are organized, but the Android Studio developers have
provided you with a structure and understanding it will help you understand
what you are doing as you develop your app.
For now, though let’s take a look at what is in the java folder. If you’ll click
the triangle-arrow next to the java folder you will see that the folder will
expand.
Then, if you’ll expand the one that says us.raddev.test that one will expand
also and you’ll see an item with an icon and the label MainActivity.
Note, you will also see a folder named us.raddev.test(androidTest) but that
code represents some code that we can use later to test our application. For
now, we’ll ignore that.
1. The title bar (light blue) has changed to show you the
complete path to the file you are viewing on the right.
2. The project navigation tree on the left shows MainActivity highlighted.
3. The tab above the content on the right displays the name of the file
MainActivity.java.
Here’s the first example at the top of MainActivity.java, where the imports
section is located:
Click the [+]
button to expand the code and you will see:
A little further down there is some more collapsed code which looks like the
following (I highlighted the section)
See the small arrow within the code (after the word (view))?
That is a collapsed section of code. To see all of the code, click the [+] button
on the left again.
It’ll look like:
You can see that the code is actually doing something with the Snackbar
which shows some kind of message like, “Replace with your own action.”
Now that you know how to create a new project and how to
navigate around in Android Studio a bit, we are ready to go ahead and build
and run the app.
Building the app is quite easy. It’s just a simple matter of executing a
command in Android Studio. However, running the app contains a few
challenges since we will have to run the app on an Android emulator.
Emulator Challenge
If all goes well, the emulator will start up and run perfectly on your
computer. There are quite a few steps to successfully get through to get it
running though.
Again, since this chapter has had a lot of basics in it and because building and
running the app is going to take quite a lot of screenshots and explanation we
will do that work in chapter 3.
Chapter 3
Run Device Emulator & Debug Code
At the top of Android Studio in the main menu, we want to click and expand
the Run menu
When it expands, we want to choose the Run app choice. When you click that
menu item a dialog box will appear which looks like the following:
The bottom portion of that dialog is the part that is important to us. You can
see that Android Studio has auto-selected the Launch emulator radio button
for us, because it knows that there is no emulator running yet.
You can also see that the Android virtual device (AVD) that it is suggesting
we run is named Nexus4. Go ahead and click the [OK] button now.
######################################################
###################################################### Note:
When I attempted to start the Virtual Device nothing happened and I went
through a number of steps to attempt to figure it out. I’ll add all of the things
I ended up trying as a sidebar in case you have issues. Otherwise, this chapter
will proceed as if you had no problems starting the emulator.
######################################################
######################################################
At this point the app looks fairly close to what we saw in the preview within
Android Studio.
However, for some reason the preview doesn’t show the word, Test, in the
title bar as the emulator version does.
The Freebies
You get a few items for free in the application, simply because we chose the
layout template that was provided by Android Studio.
First of all, click the vertical ellipsis (menu at top right of app) and you’ll see
that it displays one menu item : Settings.
However,
clicking the Settings menu which appears will not do anything, because we
haven’t written any code for it yet.
Now, let’s go ahead and click the round pink button with the envelope on it.
Clicking it will not do much, but it will active the “snackbar”.
Try it now.
Before we begin to delve into the code, let’s make sure we know how to use
our tools fairly well. In the long run it will pay off. We’ll look at the
following list of items to finish out this chapter and then next chapter we will
start writing some code and actually doing some things to alter the app.
Of course, if you are confident with skipping these items because you already
understand them, then feel free to do so.
After we work through these items you will be quite familiar with Android
Studio and it will be much easier to move around Studio and easier to
understand the code we are working on.
We will learn
more about this later, but when you touch that button in an Android app, it
actually suspends and closes the app.
Once you click that, you’ll see the Device Chooser window.
That window will appear every time even though your emulator is still
running. However, you can check the “User same device for future launches”
choice so it will always use your running device so it won’t bother you with
this window any more. Don’t worry, there is still a way to switch it using
another menu option later if something happens.
When you do, keep your eye on the Android Monitor logcat window, because
a lot of messages are going to be written there as the application starts.
I copied out the text that was written and it is more than 4,600 lines. At 50
lines per page that would be over 92 pages if you printed it out.
Here are a few of the interesting lines from the output with notes (marked
with note) and bold is my emphasis:
02-08 16:01:48.361 37-37/? D/dalvikvm: GC_EXPLICIT freed 13K, 1% free 12554K/12611K, paused 2ms+4ms
*************************************************
Note : dalvikvm is the Virtual Machine that all Android programs ran in under Android versions 4.4
(KitKat) and before. This is the Java Run Time which runs the applications on all Android devices
(version 4.4 and before) not just within the emulator.
*************************************************
02-08 16:01:48.361 37-37/? W/Zygote: Preloaded drawable resource #0x1080475 (res/drawable
xhdpi/quickcontact_badge_overlay_normal_light.9.png) that varies with configuration!!
02-08 16:01:49.070 86-100/? I/SystemServer: Entropy Service
02-08 16:01:49.130 86-100/? I/SystemServer: Power Manager 02-08 16:01:49.141 86-100/? I/SystemServer: Activity Manager
02-08 16:01:49.170 86-101/? I/ActivityManager: Memory class: 64
02-08 16:01:49.291 86-101/? A/BatteryStatsImpl: problem reading network stats
java.lang.IllegalStateException: problem parsing idx 1 at
com.android.internal.net.NetworkStatsFactory.readNetworkSt
atsDetail(NetworkStatsFactory.java:300)
at
com.android.internal.net.NetworkStatsFactory.readNetworkSt atsDetail(NetworkStatsFactory.java:250)
Note: You can see that exceptions (errors) occur within the system that are unrelated to our application.
*************************************************
02-08 16:08:29.517 86-93/? I/dalvikvm: Jit: resizing JitTable from 4096 to 8192
02-08 16:08:29.737 86-104/? I/PackageManager: Removing non-system package:us.raddev.test
02-08 16:08:29.737 86-101/? I/ActivityManager: Force
*************************************************
02-08 16:08:37.737 86-100/? D/BackupManagerService: Received broadcast Intent
{ act=android.intent.action.PACKAGE_ADDED
dat=package:us.raddev.test flg=0x10000010 (has extras) } Note: Here the newly built version of our app is being
deployed to the device.
*************************************************
2-08 16:08:42.007 622-622/? D/dalvikvm: Not late-enabling CheckJNI (already on)
02-08 16:08:42.037 86-233/? I/ActivityManager: Start proc
us.raddev.test for activity us.raddev.test/.MainActivity: pid=622 uid=10040 gids={}
Note: Here the app is being started.
Hopefully, that provides you with a bit of insight into the logging and that
you can actually get some information about what your app is doing even
when it hasn’t yet been drawn on the screen.
However, that is way too much information to dig through. That’s why you
can add some code to the application and turn on a filter so only the
information you want to see is shown in the logcat window.
Alter MainActivity.java
Let’s go ahead and make some changes to our Java code to add our logging
functionality.
We’ll make the application log when the user clicks the Settings menu item.
We are going to type some code in the if statement shown, right after the
opening curly brace.
That code currently looks like:
if (id == R.id.action_settings) {
return true;
}
Now, let’s change it (add the bolded line shown in the following code
snippet.
if (id == R.id.action_settings) {
Log.d(“test”,”User clicked the Settings menu item.”); return true;
}
When you get as far as the d in that line Android Studio is going to offer
some help.
It’s just trying to let you know that there is a function it knows about named
Log.d and there are a couple of function overloads (function takes varying
number and types of parameters).
We are going to use the first one shown, but you can just type an opening
parenthesis (.
When you do, Android Studio will automatically type the closing
parenthesis and will offer you more help:
It is telling you that the function you are looking for exists in a specific
package (library - android.util.Log) which you haven’t included a reference
to yet. To add the package simply press Alt and the Enter key combination.
When you do that Android Studio adds the following line at the top of
MainActivity.java:
import android.util.Log;
That causes the Java compiler to include the package when it builds the code.
That makes the Log.d function, which was written by the original Android
Devs, available to you for calling.
Now, however, we still need to add the two strings to the Log.d function or
the code will not compile.
Go ahead and make sure you line looks complete now.
Log.d(“test”,”User clicked the Settings menu item.”); When it is correct, it will look like
the following:
Notice that Android Studio code editor colorizes the strings to green for some
contrast.
If you want to investigate more of the functions you can allow Android
Studio to help you by opening up another line where we typed the first line of
code and typing:
Log. (that’s Log with a period following). When you do that the built in
Android Studio help with offer suggestions of function and property names of
the Log class.
Our Function Call
In our function call, we create a filter named “test” and we are writing a log
entry line which will say, “User clicked the Settings menu item.”
You can see there are so many suggestions that there is even a scrollbar
provided so you can see them all.
Switch over to your emulator which should be displaying your test app.
Click the vertical ellipsis menu in the upper right corner.
Next, click the Settings menu item which appears and keep an eye on the
Android Monitor Logcat window.
Of course, we will use Logging all through the book and you will use it all
through your Android development so we will continue to see much more of
this as we go. Now, let’s move on to the other smaller items I promised to
cover.
Run Window
When you click the Run button at the bottom of Android Studio you can see a
bit more information about your running program.
The first line gives you the target device. This can be important if
you have more than one device attached. At times you may have an emulator
running and a physical device attached so it helps to know where Studio
deployed the app.
You can see it renames the file and places our app in a directory named
/data/local/tmp.
Next, Studio runs a command to install the APK onto the emulator. DEVICE
SHELL COMMAND: pm install -r
"/data/local/tmp/us.raddev.test"
Finally, you can see where the app is launched and the command that Studio
fires to do that:
Launching application:
us.raddev.test/us.raddev.test.MainActivity. DEVICE SHELL COMMAND: am start -n
"us.raddev.test/us.raddev.test.MainActivity" -a android.intent.action.MAIN -c
android.intent.category.LAUNCHER
The additionally interesting thing is that you can run those commands
yourself from a command line to manually do this work. We will see how
this works later, but it is good to know what Android Studio is doing on your
behalf. Knowing these things are what will make you excel as an Android
Developer.
Let’s wrap this chapter up so that (next chapter) we can start writing our first
app.
Auto-Collapse Of Messages
When you do that Studio automatically collapses the Messages window, so
make sure you click the Messages button again so you can see what gets
output there.
When the build finishes (fails) you will see something like the following:
Slow Build?
Keep in mind that if at any time you perceive that your builds are
slow, you will want to alter the Compiler Command-line options and remove
those two strings we added. They generate a lot of output.
Making the change still doesn’t provide a lot of help about our error. That’s
why developers have to look at the messages we do receive very closely and
also be very familiar with valid syntax in our code. Remove the Bad Code
Make sure you remove the problem character and get a good build again,
before moving on.
Before closing out the chapter let’s take a look at one more tool, the ADB
(Android Debug Bridge).
You can enable it within Android Studio on the Tools menu.
Once you do that you can run a Debug version of your code. Go to the Run
menu and select the Debug app option.
When the app is started a new window will appear at the bottom of Android
Studio.
It is letting you know it is connected to your emulator and is ready. Move to
your emulator and you should see your app running
normally. Click the two different action items available in your app. You will
not see any difference at this point.
Set A Breakpoint
Move back to Android Studio and click to the left of the if statement we’ve
been examining.
If you do that in the little tray next to the editor then a red dot will appear.
That is a breakpoint.
Now, when you run the code that hits that line, the execution will break at
this location and you will be able to control the execution. Go to the Run
menu and choose Debug app.
The app will start in
debug mode.
Go back to your app running in your emulator and once again, click the
ellipsis menu and then the Settings menu item. When you do
that Android Studio will jump to the top window again and the code will stop
and highlight the line it stopped on in blue.
You can see a small check on top of the breakpoint now. The
execution has stopped on that line.
Press your F8 button and the code will advance one line, into the the first
statement within the
You can also float over variables with your cursor to find out what value they
are currently set to. Try this with the id value, even
though it isn’t meaningful to us yet -- we’ll learn about it in later
chapters.
You can see that the id is equal to 2131492991.
You can also see values of variables at the bottom of Android Studio.
Again, you can see the value of the id variable.
You can inspect objects in the window also. For example our
Activity object which we named MainActivity is the first item
showing in the list. Click the down arrow next to it to expand it and you can
see all of it’s member variables and more.
At this point we don’t know what all of that means, but it is important to
know how you can inspect items at run time. We often need to know the
value of a variable to debug our code and this is how we can do that.
Go ahead and stop the debugging so we can end ;the chapter. Go to the Run
menu again and click the Stop menu item.
Once you do that the debug connection will stop.
However, your app will still be running in the emulator. Warning and
Crashes
Very often switching between debug and running a normal copy of the app
crashes the system running in the emulator. If this happens the app will
become unresponsive and then you’ll probably see the boot up screen again
in the emulator. You’ll just have to wait for the OS (Operating System) to
start again.
Chapter Summary
This chapter has brought you a long way toward building a solid foundation
for you as a professional Android app developer. You built an application
that is based upon a template. That may not feel like much, but you are much
further along because you’ve conquered one of the most difficult barriers to
Android development: getting the emulator running.
We’ve not only got your app running and deployed to the emulator, but
we’ve also successfully altered a small bit of code and learned various ways
to know what is going on via logging and debugging.
These points of knowledge will serve you well over your Android
development career as they grow more solid.
1. build a complete app which will allow you to write and save notes on your
device.
2. run it on the emulator
3. show you how to sideload the app to a real device -sideloading allows you
to deploy to a device without deploying it to the Google Play store.
1. design a User Interface (UI) using XML and learning about layouts.
2. write Java code to save files, display note lists and more
3. learn a bit about the app manifest (AndroidManifest.xml) and app
permissions
4. learn more about how apps are structured.
Chapter 4
Stenotes App Part 1
Now that we’ve been all through using Android Studio to create a new
project, I can tell you very quickly what to do and you will know how to do
it.
That is fitting since this will be a somewhat thin app. It’s simply going to
allow users to create short notes that they can save to their device. It will also
provide a way to view the list of notes that they’ve saved so they can open
them for viewing and editing at a later time. We’ll also have to provide a way
to delete notes.
Finally, click the [Finish] button and Android Studio will generate the basic
app project framework for you.
At this point, Android Studio is attempting to display the basic User Interface
(UI) that it has created for your project. However, it cannot do so yet, because
it needs to compile (build) the Layout XML (eXtensible Markup Language)
to turn it into the UI you will see in the preview device.
As you can see at the bottom of the previous screenshot, Gradle is working
on doing this, but hasn’t completed. This process can take a while depending
upon your hardware, but eventually when Gradle completes the build, you
should see a preview of the basic UI.
These are the types of things that new Android devs see and they can be
confusing so I try to mention them.
2. Adding a new message means the user needs a place to type his note. To
do that, we’ll add a new Activity (Window or Form) that will hold the text
(we’ll call it NoteActivity) that is typed into the note.
3. The MainActivity will be a list of all saved notes with some kind of
indication of what is in the note. We’ll change MainActivity to look that way.
4. This means that when a user touches (taps) one of the items, it should open
the NoteActivity and load the message from the device storage.
5. We need to provide a way to delete any specific note and we’ll do that by
activating some functionality upon long-hold of any item in the list displayed
on the MainActivity. Longhold is when the user touches and holds a UI item
rather than just taps it.
Under the app folder you’ll find a java folder. Expand that one. Finally,
you’ll find the us.raddev.stenotes folder. Expand that one too and notice that
the MainActivity class is listed in that one. That’s what the little blue ‘c’
means : class. The MainActivity class is found in the MainActivity.java file
so if you double-click the MainActivity class on in the project explorer then
the file will open in the editor on the right.
Add The New Activity
What we want to do is add a new Activity. To do that, right-click the
us.raddev.stenotes folder in project explorer and a menu will
appear.
When that menu appears, hover your mouse over the New menu item and it
will expand.
Keep hovering down over the Activity menu item and the menu will expand
again.
Finally, hover down over the Empty menu item and click it.
It will look like the following:
Keep in mind, this is not the Blank Activity template we used for the
MainActivity. This is the Empty Activity.
When you click that menu item, Android Studio will present you with a
configuration window so you can set up the new Activity.
Studio offers a default name, but we want to name ours NoteActivity so type
that in your Activity Name text box.
When you do, Studio will automatically update the values in Layout Name
and title.
By typing the Activity name you are indicating the name of the
Activity class for the programmable object in your code. And, you are also
indicating that the java file will be named NoteActivity.java.
Notice that the file is highlighted in the project explorer on the left and it is
displayed in the text editor on the right. You can see the file name at the top
of the tab.
Notice also, that Studio has also created a new file in the re\layout\ directory
named activity_note.xml.
Android Studio has also taken the liberty of opening that file already too. If
you look closely on the right side you will see a tab with the file name
(activity_note.xml) at the top.
When I clicked that button I saw the following: It looks like some class
cannot be instantiated and I guess the previewer needs it to do its work.
My guess is that the layout is depending upon something in the Activity class
(NoteActivity.java) and that class hasn’t been compiled yet.
Go to the main menu Build...Rebuild Project… and click it to rebuild.
Go ahead and collapse the preview window again, by clicking the vertical
Preview button on the far right. Now we can focus on the Layout XML so we
can alter it to allow the user to type her note.
</RelativeLayout>
Since we’ve stated that, anything that parses it will try to let us
know when things we add don’t look like valid or well-formed XML. This
includes the Android Studio editor.
Quick XML Overview
XML is simply a data structure described by text. Each structure or tag in a
an XML document is called an element. An element will take the form of a
pair of one opening tag and one closing tag (in most cases).
One XML element will look like the following:
<elementName></elementName> Generally, data for the tag is typed
between the two tags something like the following: <elementName>sample
data</elementName>
Each element may also contain one or more attributes which describe the
element.
<elementName attributeName1=value1
attributeName2=value2>sample data</elementName>
Each attribute is what is called a name-value pair. That means each one has a
name and a value.
As you can see, the name comes first then an equal sign (=) and then the
value. It’s that simple.
Name-value pairs are used everywhere in the programming world.
Now that you understand these basics. Examine the Layout xml in
activity_note.xml again and I believe you’ll notice that there is actually only
one element in the entire document: <RelativeLayout> That is the root
element.
However, this one element contains numerous attributes:
xmlns:android="http://schemas.android.com/apk/res/and roid"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:paddingBottom=
"@dimen/activity_vertical_margin" android:paddingLeft=
"@dimen/activity_horizontal_margin" android:paddingRight=
"@dimen/activity_horizontal_margin" android:paddingTop=
"@dimen/activity_vertical_margin" tools:context=
"us.raddev.stenotes.NoteActivity"
Let’s take a look at each one so you can understand exactly what the XML
code is doing.
Again, understanding these things will make your life far easier as an
Android developer and will help you understand problems you run into when
something renders improperly because you will understand where the code
comes from.
XML Namespaces
We touched upon namespaces with Java code and packages in an earlier
chapter.
What is the purpose of a namespace?
It is simply to keep things separated.
It is possible that some other library you use could use a variable or function
with the same name. That would cause the system to fail to build and not run
since it cannot differentiate between them. Namespaces provide a simple way
to provide more qualifiers to the name of your things.
It’s like placing everything in a box with your label on it. That way the
compiler can know which box to go to when it needs a particular variable.
This keeps code from clashing with each other.
I know that was a long explanation, but now it becomes important while we
look at the layout XML.
Now that you know what that first part is, you can basically ignore it. The last
RelativeLayout attribute has the tools namespace and looks like:
Name-Value Pairs
Now you can see if you ignore the namespace portion that each attribute is a
name-value pair just as we previously learned. Now this mess of code is
beginning to make a bit more sense.
The name-value pairs are the attributes and they are the things that are going
to change the style of our elements. The first two we see are:
layout_width="match_parent"
layout_height="match_parent"
These two attributes insure that the RelativeLayout element width and height
are going to match the parent (container). Since the RelativeLayout is the
outermost element, what would the parent container be?
In this case, it is the basic window frame that is automatically added. That
means these values set the RelativeLayout to expand to the entire window
height and width.
some help.
It requires you set the layout_width so it immediately allows you to choose a
value for this new control.
As soon as you choose (or type) the match_parent value for
layout_width then Android Studio will jump to the
android:layout_height=”” value and suggest a value for that one too.
That code is actually inside a function named onClick(). That is the function
that is called when the FloatingActionBar is clicked. That FloatingActionBar
only contains one element which is the circular button with the envelope on
it.
We are going to use that button to load and show our Note Activity, since the
button is there for us to use.
We want to type the following two lines of code and then comment out the
lines of code which are already in the onClick() function since we don’t want
those lines to run any more.
Intent i = new Intent(MainActivity.this, NoteActivity.class);
startActivity(i);
Code Comments
Java (and most programming languages) allow developers to add comments
to the source code so developers can place notes and information about the
code inline with the code. This can help us remember something important
about the code later.
In Java, the way to make a line a comment is by placing two slash characters
before the text you want to be commented.
// this is a comment
This causes the Java compiler to simply ignore the lines so they are not
compiled. Since, in most cases the notes would not be valid Java code, this
allows the notes to be in the source code fiile but not affect the program..
We can also comment out lines in an effort to keep those lines from being
recognized by the compiler and built into our program. That is what I am
doing with the original lines of code found in the onClick method. That way I
don’t lose those lines, in case I want them later. However, since the compiler
will ignore them now, those lines will not be built into our final program.
You can see that the Android Studio editor grays out commented lines to
indicate that they are not compiled into the program.
Once you build and run you’ll see the MainActivity and it’ll display the
round pink button with the envelope on it. Once that displays, go ahead and
click it. When you do, the NoteActivity will be loaded and displayed. There’s
not much to it, but you can type some text in the EditText control.
Here’s a snapshot of it after I typed some text.
At this point we have loaded the new NoteActivity and it displays the
EditText for us, but otherwise the app doesn’t do much. The text isn’t saved
and the EditText box takes up the entire screen and centers the text vertically
which seems a little odd.
We will fix all of these things of course, but first let’s talk about an Activity’s
lifetime.
Activity Lifetime
Each Activity has a certain lifecycle and specific events associated with the
lifecycle.
Understanding the lifecycle and the associated events helps us to understand
what we can do while working within the Activity.
To learn more about the lifecycle of the Activity we are going to use our
knowledge of logging to help us look at how the NoteActivity object is
constructed and determine when each lifecycle event fires.
Let’s look more closely at the code we added to our onClick event and talk
about exactly what they do.
The first line:
Intent i = new Intent(MainActivity.this, NoteActivity.class);
That line creates a new Intent object. An Intent is simply a description of the
functionality that you want Android to start for you. In this case we want
Android to load a new Activity for us. This constructor for the Intent object
takes two parameters. The first parameter is a Context which the new
Activity will be loaded within. In other words, what is the context in which
the NoteActivity is loaded? The answer is the context of the MainActivity.
It’s as if you are telling the Intent constructor who owns the NoteActivity. In
our case it is the activity which is loading the new NoteActivity.
After we instantiate the new Intent object, we make a call to the startActivity
method which takes the Intent object we just created as a parameter. That call
looks like:
startActivity(i);
That method is a part of our MainActivity. We have gotten this method for
free since we derived MainActivity from AppCompatActivity.
If you look at the definition of our MainActivity you can see where it is
derived from that class:
public class MainActivity extends AppCompatActivity {
What Is AppCompatActivity?
That leads us to the next obvious question: what is AppCompatActivity?
It is a built-in class that the android libraries make available. If you look at
the top of the file in the import statements you will see a line which looks like
the following:
import android.support.v7.app.AppCompatActivity;
You can see the project template went ahead and imported that library and
derived our main Activity from it. That’s because there have been some
changes to the Activity class and those have been moved into a specific
library to make them compatible on newer devices. That way when you
create your app you can use code that makes your app look the same on
various devices running different versions of Android.
The reason we examined each of these items was simply to determine where
the startActivity() method came from and now we know that it is part of the
base Activity class (and the
AppCompatActivity class). Now that we know where it came from, let’s
continue our talk about our code.
Add Logging : Watch Event Order
First of all, let’s alter our onClick() method in MainActivity.java and add two
lines which will log some information we can watch as the app runs.
You’ll need to add the lines which are bolded in the code snippet which
follows. It’s the three lines which call the Log.d() method. Note, I also
deleted the lines that we had previously commented out so you can do that
also, if you like.
public void onClick(View view) {
Log.d("MainActivity", "in onClick..."); Intent i = new Intent(MainActivity.this, NoteActivity.class);
Log.d("MainActivity", "after new Intent()..."); startActivity(i);
Log.d("MainActivity", "after startActivity"); }
});
With that code you’ll see some output when you click the action button. We
also want to alter the NoteActivity class and add some logging to it so we
will see the order of all the events.
Alter the NoteActivity.java class so it now contains the following code. The
bolded lines are the ones you’ll need to add. You can see that we’ve added a
constructor method. That is the method that is named exactly the same as the
class (public NoteActivity()). That method will run when the NoteActivity
class is instantiated. We do that so we can log the moment when the
NoteActivity class is loaded into memory.
public class NoteActivity extends AppCompatActivity {
public NoteActivity ()
{
Log.d("NoteActivity", "Inside NoteActivity constructor..." );
}
@Override
protected void onCreate(Bundle savedInstanceState) { Log.d("NoteActivity", "In NoteActivity.onCreate()...");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_note);
setTitle("Note - new");
}
}
Next, we’ve added a Log.d() call in the onCreate so we can see when that
code actually runs.
Finally, you can see that I’ve also added a new method call on the last line of
the onCreate() method which is called setTitle(). This method simply allows
me to put a new string into the title area at the top of our NoteActivity. This
is a nice way to indicate to the user that our new NoteActivity has loaded
because the text at the top will change from Stenotes to “Note - new”.
Set Up Logcat
Before we run the code, let’s set up logcat so it’ll only show us the lines from
the Log.d() method.
To do that, you :
That’s the name of the two tags that we are using in our Log.d() statements as
the first parameter.
They are separated by the pipe character | -- shift and backslash keys get a
pipe.
Once everything looks like that, go ahead and click the [OK] button. Once
you click the [OK] button, your logcat window should get
much cleaner if you’ve already ran the app.
When you run the app, you won’t see anything in the logcat window. But
when you click the envelope button the app will write five lines to the
window.
The first three lines come from our onClick method.
The first one is right after the onClick() is called.
Then, the new Intent() object is created and finally, the
startActivity() method is called.
What this shows us, is that the startActivity actually creates our NoteActivity
object , because after startActivity is called, you see the first line from
NoteActivity where the constructor is called. Once the constructor is finished,
the object is loaded in memory and ready for use by the program.
@Override
public void onPause()
{
super.onPause();
Log.d("NoteActivity", "onPause()..."); }
@Override
public void onResume()
{
super.onResume();
Log.d("NoteActivity", "onResume()..."); }
@Override
public void onStop()
{
super.onStop();
Log.d("NoteActivity", "onStop()..."); }
@Override
public void onDestroy()
{
super.onDestroy();
Log.d("NoteActivity", "onDestroy()..."); }
You want to add that code right after the onCreate() function.
We want to learn when those events are fired, because we need to manage the
data in our app.
Make the previous code changes or download this version of the code, build
and run it.
Once the app is running go ahead and click the Envelope button and you’ll
see some additional Log messages in Logcat.
Now we can see that some other events are fired in the app also, when you
click the Envelope button.
Now, you can see that note only is the onCreate() event called but the
onStart() and onResume() events are also called (last two lines in the logcat
output above).
Those events were called even when we weren’t overriding them, but since
we didn’t override them and implement some functionality we weren’t aware
of them running.
Activity LifeCycle
So, when a new Activity is instantiated then the constructor runs, then
onCreate(), onStart() and onResume() runs.
Now, we know that we can be alerted within our code when those run and we
can run our custom code to do something.
We can see that by watching the series of LifeCycle events that are fired
when we rotate the app.
That’s because the application knows that you unloaded the NoteActivity and
decided it wasn’t important to keep that information around.
We’ve learned quite a lot about the app’s lifecycle so let’s finish this app up
and move to our next one.
It should display any notes upon opening the app. We need to be able to save
or cancel the NoteActivity changes we make.
Chapter 5
Stenotes App Part 2
The Android Studio template simply broke up the MainActivity XML (layout
files) into two so we could handle the two sections of the view more easily.
If you look closely at the activity_main.xml you will see that it contains the
shell of the view and a reference to the main content of the Activity (the large
white-space in the middle).
< android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_main" />
</android.support.design.widget.CoordinatorLayout>
However, you can see in the bolded line that there is an included layout file
named content_main.
This is a reference to the broken out content_main.xml file which represents
the main content or view area of the Activity. This is the part that is currently
represented by a RelativeLayout and which we are going to convert to a
ListView.
Ignore activity_main.xml For Now
The point is that you can ignore activity_main.xml for now. It contains the
outer menu framework and we don’t want to change those right now.
</ListView>
New Resource ID
The second line adds a new resource id which can be used to reference this
ListView from your code. We will see how you can do that when we alter the
MainActivity.java file.
The Android build system will create an integer which is unique throughout
your app, which it will then use to reference this specific ListView. It’s as if
the system is naming it after a unique integer value. To make it easier for you
to remember the ListView’s name for use in your code, we have added a text
name (mainListView). Again, we will see how this is referenced in code in
the java file.
Now that you’ve made that change, if you were to switch over to Design
mode you will see that the main content now has a preview which looks like
a list of items.
However, if you run the application right now, you won’t see any list items
yet, since the application isn’t adding any.
That adds the ListView object, but we still can’t use it, since we haven’t tied
it to anything.
To tie that object to our screen element, we call a helper method provided to
us by the Activity named findViewById().
The id that we will use is the one we created for the ListView. Again, you can
look back at the content_main.xml file and examine the bold line.
Next, we’ll add the line which calls findViewById() and loads our ListView
object reference.
Again, it’s just one line of code which looks like:
Once, the listView object reference is set to our screen element we can use
the object to make things happen in our app.
However, there is a specific way of making a ListView work and it requires
the creation of an ArrayAdapter which we’ll attach it to our ListView.
So now, let’s add two more member variables like the following, after our
ListView.
private ArrayList<String> listViewItems = new ArrayList<String>();
private ArrayAdapter<String> adapter;
The first new variable we added is simply a list of strings which will
represent the items we will add to the list.
The second item is our reference to the ArrayAdapter which we will use to
actually add the items to the ArrayList.
We still need to initialize the adapter however so let’s add that code in the
onCreate() after the initialization of the listView object.
We want it to look like the following:
adapter = new ArrayAdapter<String>(
this,android.R.layout.simple_list_item_1, listViewItems);
The following image shows all of the code we are going to add now, but I
will continue explaining each line as we go.
After we initialize the adapter to be associated with the proper context, style
and list of items, we want to associate it with our listView object. It just takes
one line of code.
listView.setAdapter(adapter);
The ListView provides this function named setAdapter() since the original
developers know we are going to want to connect the adapter with our
ListView.
This now means that changes we make to our associated adapter (add or
remove items) will be displayed in our ListView. Let’s go ahead and add a
few items to our ListView, which we do by adding the items to the adapter.
The lines to add three items look like the following: adapter.add("thing 1");
adapter.add("thing 2");
adapter.add("thing 3");
Those three items add three new list items to our ListView and the associated
strings will appear in our ListView when it renders on the screen.
However, there is one more function call we have to make to insure the
adapter is updated and forces the ListView to redraw itself so the items
actually show up on the screen. adapter.notifyDataSetChanged();
Now, we have all the code which will allow us to run the app and see some
test items in our MainActivity.
Go ahead and build and run the app.
If you haven’t typed in the code you can get it with the changes up to this
point at:
Stenotes_v4.zip
Each of the items in the list is clickable. If you click any of the items right
now, you will see each of them flash to a different style when clicked, but the
click won’t start any other action.
We’ll add that code in a moment. For now, let’s create a first step in adding a
new item to the list.
Go ahead and move back to the Android Studio editor and make sure you
have MainActivity.java open again. If it’s not already open, remember you
just go to the project navigator and double-click the MainActivity.java and
it’ll open again.
Move down in the file to the place where the onClick() function is
implemented.
Let’s go ahead and comment out all of the lines in there except the first one.
That way we’ll still see in the Logcat that we are in the onClick. We can
quickly comment those lines by highlighting them all and then choosing the
main Code...menu (at the top of Android Studio) and then choosing the
Comment with Line Comment menu item.
Note : Instead of using the menu item you can highlight the lines and then
type Ctrl+Slash and the lines will switch to comments. If you do Ctrl+Slash
on any commented line, Android Studio will uncomment the line. The
command simply toggles between the two.
Once you make that choice those four lines will include the // comment
marks and will no longer compile into the code.
Once you comment out those lines, move to the line right past those
commented lines so we can begin adding our code to add new ListView
items. You can see the bar cursor in the previous image which shows you
where we’ll type our code.
When we add that line we’ve simply added a formatter that will make our
time string look a little better. You can see that we are telling it to use a 4-
digit year followed by 2-digit month and 2-digit day, then an underscore and
2-digits for each of the remaining elements (hour, minute, second).
When you add that line, Android Studio is going to try to help you add the
appropriate package which contains the function and you’ll need to hit
Alt+Enter to do so.
Next we’ll add one line of code which will get the current DateTime from the
system and use the formatter to create a string.
String outItem =
sdf.format(Calendar.getInstance().getTime());
Again, Android Studio will offer to add the import upon pressing Alt+Enter.
Go ahead and make sure you do that.
We then store the what is returned from the format() method in our string
variable we have named outputItem.
Now, all we need to do is add the item to our adapter and notify the adapter
that we have updated the data. This code is just like the code where we add
the “thing 1”, “thing 2” & “thing 3” items.
adapter.add(outItem);
adapter.notifyDataSetChanged();
Finally, our complete code which is ready to run looks like the following:
Now when you build and run this code, every time you click the Envelope
button a new item with the current date/time will be added to the list.
Go ahead and run the app. If you haven’t followed all the changes you can
get them at:
Stenotes_v5.zip
When you run the app, click the Envelope button numerous times and each
time it will add a new list item.
You can see that I added so many that they no longer all fit on one screen.
The nice thing is that you can now swipe up or down and the list will scroll.
You can see the scroll indicator bar on the right side in the previous snapshot.
Envelope Icon
Let’s change the Envelope icon now, since it isn’t a good indicator of what
the button does for us.
The envelope icon is a resource provided by Android development
environment and there are other icons too, so let’s attempt to choose a better
onw.
Google It
I Googled the resource name and found a link to the Android Developers site
which describes R.drawable and I found the item listed at:
http://developer.android.com/reference/android/R.drawable.html
Icon Name Prefix
Apparently the prefix of IC_ is an attempt to let us know it is an
icon.
So, I’m guessing that the other IC_ items are icons also.
I browsed through them and found one named ic_menu_add. Let’s change
the value to that one and see if we get a better icon.
android:src="@android:drawable/ic_menu_add"
Go ahead and rebuild and run and let’s see what it looks like. You can get the
code at:
Stenotes_v6.zip
Now, it’s a plus button. That’s more appropriate since the + sign better
indicates that it adds something. And, we’ve also learned a bit about some
built-in resources that are available to us.
List Of Notes
Now, we need to make our list show our list of notes.
However, we haven’t stored a list of notes. We haven’t even saved one note
yet.
We need to allow the user to create new notes and we need to make sure we
display the list of notes in our ListView.
If we didn’t store this information in a separate file then we’d have to search
the file system for our note files each time and open them and get the titles
out each time the user starts the app. Instead of doing that, we’ll just store the
information in one file which will list each note. That means we need to add
the title and file name to the file which contains the list each time the user
adds a new note.
That part is easy enough using the Java provided file functionality. However,
we also need to allow the user to delete any note and that may prove to be a
bit more difficult, since we’d have to find the item in our list file and then
delete the file from the File system also.
Open the activity_note.xml and make the following changes. <?xml version="1.0"
encoding="utf-8"?> <LinearLayout
xmlns: android="http://schemas.android.com/apk/res/android"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin" xmlns:tools="http://schemas.android.com/tools"
tools:context="us.raddev.stenotes.NoteActivity" android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/linearLayout">
< EditText
android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Note title"
android:id="@+id/titleText" />
< EditText
android:layout_width="match_parent" android:layout_height="0dp"
android:layout_weight="1"
android:id="@+id/noteText"
android:hint="Note text..."
android:gravity="top"
android:layout_below="@+id/titleText" />
< LinearLayout
android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="wrap_content"
android:gravity="right">
<Button
android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Cancel"
android:id="@+id/cancelButton"
/>
< Button
android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Save"
android:id="@+id/saveButton" /> </LinearLayout>
</LinearLayout>
Our outermost LinearLayout is set to place the controls vertically with the
attribute :
android:orientation="vertical"
There are basically three vertical groups on the NoteActivity.
The last vertical group is further broken up into two horizontal groups by
adding a nested LinearLayout which has it’s orientation set to horizontal.
This one contains two buttons (one for Cancel and one for Save).
If you make the changes to activity_note.xml and then switch to design view
you’ll see what the final layout looks like:
Also, notice that on the right side you can see the Component Tree which
shows you that the outermost element is a LinearLayout
which contains the two EditText controls and the nested
LinearLayout. Further, you can see that the two buttons are
contained in the inner LinearLayout and this LinearLayout is setup up for
horizontal orientation.
Element Weight
The real magic of this layout come from one line which we’ve
placed in the EditText control noteText.
android:layout_weight="1"
This tells the LinearLayout that this item has a maximum height (1 is max)
and should have the maximum height possible while still allowing each of the
other layout groups to appear on the this screen.
You can see that we actually set the height of this control to 0:
android:layout_height="0dp"
That tells the layout to draw the item’s height based upon its weight. The
weight is relative to the other controls which are contained within the same
outer element. In our case the outer element is the LinearLayout.
You can see a lot more about LinearLayout and the example I drew from at
the Google Android Developer site at:
http://developer.android.com/guide/topics/ui/layout/linear.html
What Is Dp?
It stands for Density-independent Pixels. It is a way to measure the dots that
make up a device’s screen. Knowing their size is important to keeping things
drawn in the right size on the screen. There are different units which can be
used for these sizes such as (px [pixels], pt [point] 1/72 of inch, mm
[millimeter], etc).
Landscape Mode
Now, when the device is rotated into Landscape mode, the layout will
automatically calculate the height of the items, provide the maximum height
possible for the note EditText while still displaying the title and the row of
buttons at the bottom. You can test your Landscape layout versus your
portrait layout very easily in Android Studio.
We’ll go over more about how to manipulate layouts to keep them sized right
as we develop our other apps in this book.
Now, let’s switch back to our code and work on saving our note data to a file.
The first thing to do is the easiest thing: add click handlers to our
two buttons on our NoteActivity.
Now, move to the last line of the onCreate() method. You can see that line in
the previous screenshot. It’s the line where we had previously set the title of
our NoteActivity.
First we need to make sure our buttons reference our onscreen elements.
Add the following code:
cancelButton = (Button)findViewById(R.id.cancelButton); saveButton = (Button)findViewById(R.id.saveButton);
After we set them up for both buttons the code will look like:
Of course, clicking the buttons still doesn’t do anything because we haven’t
added any code to either onClick() function yet.
The Cancel button should check a value to determine if the note has been
altered.
If the text has not been altered there is no reason to save it again, so we will
simply close the NoteActivity to take the user back out to the MainActivity.
That’s the easiest part of the code to write so let’s do that work now.
The entire code for the cancel button’s onclick will look like the following:
cancelButton .setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//view.getContext().getApplicationContext()
new AlertDialog.Builder(view.getContext())
.setTitle("Lose changes?")
.setMessage("Are you sure you want to lose your changes to this note?")
.setPositiveButton(R.string.yes_button, new
DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) { na.finish();
}
})
.setNegativeButton(R.string.no_button, new
DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) { // do nothing
}
})
.setIcon(android.R.drawable.ic_dialog_alert)
.show();
}
});
You can see that when the cancelButton is clicked we create a new
AlertDialog box.
We set the title, the message the user sees and then we set up the positive
button and the negative button. Normally these would have the text OK and
Cancel. However, I believe it makes it a little more readable and easier to
understand by using Yes and No instead.
You can see that I created two new strings named yes_button and no_button
and set their string values appropriately.
This may not seem that important now, but if you have error messages and
more detailed items it is an important programming practice to follow. This
also makes it much easier if you decide to create a multilingual version of
your app. You can load the appropriate string resource file for the user’s
location / language.
Notice that we’ve set up both the Yes button and the No button to have
OnClickListeners also, so that when the user clicks them the button will do
something.
.setPositiveButton(R.string.yes_button, new
DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) { na.finish();
}
})
.setNegativeButton(R.string.no_button, new
DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) { // do nothing
}
})
In our case we are using an object named na to make that call. That’s because
of the way we’ve constructed the cancelButton’s onClickListener() method.
We will see other ways of constructing that method later.
But for now, we had to have a reference to our NoteActivity object which is
currently displayed on the screen.
To capture that reference I simply added a final variable to the top of the
NoteActivity class which stores the this variable. Again, remember that the
“this” variable contains a reference to the context to the current object you
are working with.
If you look at the top of the file I’ve created and initialized this temporary na
object in our NoteActivity class. I’ve initialized it to the this variable and
made it final.
Next, click the (+) plus button and the NoteActivity will be displayed.
Click the
CANCEL button and the dialog box we created will appear.
First, click the [No] button. The dialog box will disappear and you will be
back on your NoteActivity. In this case the user would be able to continue
editing her note without loss of data.
Now, click the [CANCEL] button again to display the dialog box. However,
this time, click the [Yes] button.
When you do that, you will see two things:
Next Steps
Now, we need to implement the Save button so users can save the notes they
create.
However, as we look at the Note in our problem domain (more about this in
the next chapter) we see that it is actually a thing which has some properties
and some abilities. That means we should design a class for the note item
because that will allow us to organize our code. Organizing our code allows
us to develop it and do maintenance on it much easier.
We’ll see more about how it can help in the next chapter, where we will
finish our Stenotes application.
Chapter 6
Stenotes : Add Some OOP
Now we need to start thinking about our note object so we can separate it
from our View (layout) code. Keeping our data separated into logical units
(object) helps us keep our code organized.
What Is A Note?
We’ve described our note has having some text and a title. We also know that
our note will be stored in a file so those three things end up defining our note
object.
We might use a bit of UML (Universal Modeling Language) to draw our
Note object something like the following:
UML is a good quick way to communicate the properties and methods your
domain object has in a quick small diagram. You can see that this is the Note
object and it has Text, Title and a FileName where it is saved.
It also has two methods so it can Save its data and Delete itself from the
system when necessary.
This is just a guess at how this will work, but it’s a good start.
A dialog box will appear which is trying to guide you to create the Class in
the correct folder.
Make sure you have chose ..\app\src\main\java as shown in the previous
picture and click the [OK] button.
A dialog will appear and you can type the name of our class in : Note
Click the [OK] button and Android Studio will create the empty class for you
and open it in the editor.
First, we’ll add our private properties: private String text;
private String title;
private String fileName;
After you type those in, go ahead and right-click on the first one so we can let
Android Studio help us add a Getter and Setter for them. When you right-
click the item a menu will appear.
Choose the Generate.... menu item and another menu will appear.
Choose the Getter and Setter menu item.
This will display another dialog box with all of your private variables.
Go ahead and hold the ALT key and click each of the items and they will
highlight.
Then, click the [OK] button.
Your source code will be updated.
So far, we’ve simply added our three private properties and then made them
available using Getter and Setter methods. If you would like more
information on creating Java classes please see the following at my web site:
Now, let’s generate a Constructor for the class. Again, you simply right-click
in the code and choose the Generate… menu item. Then you choose
Constructor from the Generate menu.
When you do that you’ll see another dialog which asks you which fields
you’d like to initialize when you construct the object:
Go ahead and choose all three (hold the ALT key to multi-select) and click
the [OK] button.
Android Studio will add the following code for you:
public Note(String text, String fileName, String title) { this.text = text;
this.fileName = fileName;
this.title = title;
}
Now, we can construct one of these objects by passing in values for text,
filename and title and it will automatically set the object’s values to the
values you send in.
Let’s add a Note object to our NoteActivity so that when the user clicks the
(+) FloatingActionBar button a new Note will be created also.
We will make the Note object a member variable of the NoteActivity class.
When you go and begin to add the new private member variable of your type
Note you will see that Android Studio will attempt to help you find the class.
Package Problem
The problem is that none of those objects that Android Studio guesses is
actually the one we want. We want our Note class which we just created in
our project. Android Studio cannot find it though because we didn’t place it
in a good namespace (package) and Studio gets confused.
We need to add our Note to a package so Studio can differentiate our Note
from other items with the word Note in them.
Adding A Package
We can easily add a package to our project. Go to solution explorer and right-
click the Java folder and a menu will appear. Go down to the New… menu
and then select the Package menu item which
appears.
It will prompt you to select a folder you want the package to be created in.
We will choose our main Java folder.
I’ve uncollapsed the us.raddev.stenotes package so you can still see that our
two Activity classes (both are .java files) are in that package. There are no
classes in our new us.raddev.domain package yet though.
Let’s add our Note.java file to our new pacakge.
You can simply click on the Note.java file in solution explorer and drag it to
the domain package and drop it.
You’ll see some red box highlights appear around the target
packages as you roll over them.
When you finally drop it, you will be prompted to make sure you really want
to take this action.
Since Android Studio knows that now the reference to the Note class will be
different for anything that is already using it, it asks you if you want to search
for places where it is currently used so it can refactor them. It’s attempting to
help you so any references that may already be using the Note class won’t get
all fouled up.
Leave the settings as they are and allow Studio to help you and click the
[Refactor] button.
You will see that the file is moved in solution explorer and if you
scroll to the top of Note.java you will see that Studio has added a new
package statement.
Now the Note.java class is packaged properly and will be reusable in other
projects if we should like to use it and it’ll show up in NoteActivity when we
add our member variable.
Open up NoteActivity and let’s try adding the Note member variable again.
You can now see that it is one of the choices that Studio offers now.
If you choose (double-click) our Note class from the list and allow Studio to
add the item, then you will see that Studio also adds the import statement at
the top of the file for you.
We still need to add the name of our variable. Remember, the token
following the word private is indicating the type that we are creating. In this
case we are creating a type of us.raddev.domain.Note. However, we need to
provide a variable name which we will use to reference this instance of our
object.
Notice that I had you name the variable with a leading underscore _. That is
because a good naming convention is to name member variables with leading
underscores to indicate to code readers lately that this is a private member
variable of the class we are viewing.
This concludes the declaration of our member variable but we still haven’t
instantiated a Note object or used one. Let’s do that now.
I’m adding the instantiation of the object to the current code right after line
where we call setTitle().
So far, It looks like the following:
Note Refactoring
Android Studio warns me (red squiggly underline) that something is wrong
with my Note constructor call.
I remember that the constructor we created before, wants all three parameters
(title, text and filename). However, now that I’m using the Note class I can
see that when I first construct one, I don’t have a couple of those parameters
(title or text) since the user hasn’t had the chance to fill them out yet.
I could pass in the location where I want the note file to be saved too, but
now that I think about it I’d rather have the Note class do that for me. So, I’m
going to alter the Note constructor so it takes zero parameters and generates a
new filename for the file where it will store the text and title.
You can see we now have a constructor which takes no parameters and does
nothing in the body of the method.
I want to add the code that generates a file name for our Note text. I’m going
to add a private method which will do that work and I’m going to call that
method from the constructor. The method will be private because it will be
called only from within this class (from the constructor). For now I’m going
to “stub” out the code and then I can write the real code to do the work after.
Android Storage
There are rules for storing files on Android devices in order to keep things
organized and even more importantly to keep users secure from malicious
apps which might try to read another app’s data.
Context.getFilesDir()
We will learn more about advanced storage later, but for now we can use the
simplest methods to store our data, because the data will only be used by our
app.
That also means we do not have to add any additional permissions to our
AndroidManifest.xml (more about the manifest and permissions later).
The method we can use to get the storage location for our app is the
Context.getFilesDir().
######################################################
SIDEBAR: AndroidManifest.xml
###################################################### I came
back and decided to add this sidebar about the AndroidManifest.xml here
because I discovered that some versions of Android do seem to require us to
include an item in the manifest to let the user know that we will be reading
and writing to the sandboxed storage which only our
app can write to.
I’ve added the value in the manifest in the code downloads so the app will
work on all of your devices, but I wanted to let you know a bit more about
the manifest does when I first mention it here in this chapter.
If you’ll go to solution explorer and examine the folders you’ll see the
manifests folder at the same level as the java folder. This manifest file
describes settings for your entire application.
Note : When I opened my project it took a moment while Gradle built and
Studio attempted to draw solution explorer before the manifests folder
appeared.
You can see that the manifest is another XML file. If you look about half way
down the file you’ll see an XML node named <activity>
That describes main activity which launches when our app starts. We’ll talk
more about that in later chapters, but for know just know that is how the app
knows which Activity to use to start when the app starts.
We are interested in the line near the top which is before the <application>
node.
<uses-permission
That is the item we had to
android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
add which gives the app permission to write to storage.
When these permissions (perms) are added it forces the app to warn the user
that the app has the permission. This is to protect users from installing apps
which have abilities they don’t know about.
If you do not add this permission to the manifest then on newer versions of
the OS (operating system) the app will crash. That’s because it will throw an
exception because it doesn’t want to allow a malicious app to do things to the
user’s system without the user knowing.
That means we are unable to call the Context.getFilesDir() from within our
Note object directly. Since we cannot call that method from within our object,
we will call the getFilesDir() and send the result into our Note activity’s
constructor. So, once again we will alter our Note constructor to take a
String. We will also have to add a new member variable to our Note class to
store the String.
When I’m done typing my line is going to look like the following:
Notice that the getFilesDir() has a red squiggly underline and there is a
lightbulb to the left. Android Studio is attempting to warn that
our Note constructor doesn’t take a parameter of this type.
That’s okay, because the other hint Studio gave us reminded me
that getFilesDir() returns and File object and I don’t want to send in the File
object to the constructor either. I only want the full path to the Files Directory
as a String. Fortunately, there is a method I can call on the returned File
object which will return the entire path
called the absolute path.
If I go back into the editor and type a dot (. same as period) after
the closing parenthesis Android Studio will attempt to show you available
methods again. This time it shows me the one I want.
You can see that I’m choosing the getAbsolutePath() method and that (far
right) it returns a String (representing that path).
That is exactly what I want to send in to my constructor so that later I can
store my Note file at that location.
Here’s the final code for the Note constructor which is in our
NoteActivity.
_note = new Note(getFilesDir().getAbsolutePath());
You can see that we are creating a new Note by constructing it with a String
which we get back after calling the getFilesDir() method which returns (in-
place) a File object, which then calls the
getAbsolutePath() method to return the String. Then that String is sent into
our Note Constructor.
Now we just need to make sure we alter our Note constructor to handle this.
Switch over to your Note.java file in the editor and make the following
changes.
public Note(String filePath) { this._fileRootPath = filePath; generateFileName();
}
private String _fileRootPath;
You can see our constructor now takes a String.
I’ve then added a new member variable named _fileRootPath. After that, we
take the value sent into the constructor and store it in our member variable so
we can use it later when the user saves her note text.
1. store the values from the View’s title and text elements into our Note
object.
2. Have our Note object save itself to a file in our app storage.
Next, we’ll initialize them so we can use these objects to get the
values which the user has typed into them on the Activity.
_titleText = (EditText)findViewById(R.id.titleText);
_noteText = (EditText)findViewById(R.id.noteText);
First of all, notice that I added some logging so we can watch what the
function does and make sure it works. But since I added this
logging with a tag of “Note.java” you need to add the new tag to
your filter.
Remember how to do this? You Edit the filter configuration.
When you choose that you’ll see the dialog where you can add the | Note.java
to the filter.
Click the [OK] button and logcat will display only the messages we want to
see from our app.
The next thing you see in the code is where we create the
OutputStreamWriter object and initialize it s we can write to our note file. To
create the OutputStreamWriter we have to create a new FileOutputStream
and we initialize the FileOutputStream with our _fileRootPath which will
include our fileName.
OutputStreamWriter outputStreamWriter =
new OutputStreamWriter(
new FileOutputStream(
new File(_fileRootPath + "/"+fileName)));
After we write the file we have to make sure we call the close() method to
make sure the file is closed properly.
This work of generating a unique file name is done when we construct our
Note object. In our constructor we call the generateFileName() method.
You can see where the code is called from the constructor and what the actual
method does in the following code listing:
The core
code which generates the file name for us is:
this .fileName =
File.createTempFile("note",
".sten",
null).getName();
You can see that the result of calling the createTempFile is stored in our
member variable named fileName.
When we call the File.createTempFile() method we have to supply three
arguments but the documentation says the last one can be null if not used.
That is the path to the file and we don’t need it so we do pass in null.
The first argument is a prefix for the file so that every file will be prefixed
with the word “note” so we can determine easily that these are our Note files.
The first thing we do is get the rootPath to our apps file storage. This is that
same call to the getFilesDir() that we did in the Note class.
Next, you can see that we add the rootPath value to the adapter just so we can
see it for reference in the list. Later we will remove this code. It’s just for
testing.
Get Files In Directory
Next we new up a File object using our rootPath. The File object provides an
easy way to get the files in a directory by calling its listfiles() method which
returns an array of File objects. The array will contain one File for each file
found in the target directory.
Next we run a for loop based upon the number of objects in the file[] array.
If there were no files this for loop will not run at all.
However, if there is one or more file in the target directory then we get the
name of the file using the file object and an index value named i :
file[i].getName()
We pass that value to the adapter.add() method to insert the file name into our
listview.
Finally, after we iterate through all the files we call the
adapter.notifyDataSetChanged() method so the listview will update on the
screen.
I ran the app and added two notes and here is what it looks like so far:
You can run the app now too, by getting the code at: stenotes_v8.zip
Keep in mind that you can create new notes, but you cannot edit them or
reload them yet, since we haven’t added the onclick functionality for
handling the situation when a note is clicked from the listview.
Let’s do that work now.
Go back to the MainActivity.java file in the Studio editor and add the
following code after our fab.onClickListener:
listView .setOnItemClickListener(new
AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Log.d("MainActivity", "item clicked");
}
});
As you can see, the listView’s onClickListener is set up quite a bit differently
than the way we have to set up a button click listener.
That’s because we don’t want to know when the ListView itself is clicked,
but we want to know when the item within the list is clicked and that is a bit
different.
At this point, when you run the program and click an item all you get is an
“item clicked” message in your Logcat. We want to reload the Note and
display it on our NoteActivity so there is more work to do. We will revisit
this item onClickListener further onward in this chapter. But for now, let’s
see what we need to do to the Note class.
However, in this case we want to pass in a filename and have the Note object
load our existing data.
Method Overloading
There is a feature of Java classes called Method overloading which allows
you to call the same method (in this case our constructor) with different
parameter types or a different number of parameters so that the code will do
different things.
Method overloading will suit us fine, however, you cannot have two methods
with the same signature.
Even though we are changing the name of the parameter, Java can only
differentiate if the types or number of arguments is different
We could create another constructor which takes two strings: public Note(String
filePath, String fileName)
Or, we could create a new constructor which takes a String and an integer
public Note(String filePath, int fileCount)
Exception Handling
The first thing you’ll notice is that the entire method is wrapped in a
Try...Catch… block.
That’s because creating the FileInputStream can throw a FileNotFound
exception and Java forces you to write safe code that handles that situation.
StringBuilder
Once we build the BufferedReader up we are ready to read data from the file,
but for our purposes we also build up a StringBuilder which allows us to
create large strings very quickly.
I create a String variable named line to hold each line which will be read in. I
also add a lineCounter simply because I want the first line to be loaded into
the title variable of our Note object.
This line calls the readLine() method of the BufferedReader ( r ). Each time it
reads a line it checks to see if the return from readLine() was null. If it is then
it means the end of the file has been reached and there is no more data to
read.
Finally, right before the loadNote() method returns, it sets the Note text field
to the value that has been built up in the StringBuilder so that the Note text is
completely loaded from the file and ready to be displayed on screen.
The code makes one check just to insure that the StringBuilder isn’t just an
empty string. Then it sets the text value of the Note. if (inString.toString() != "")
{
text = inString.toString();
}
That’s all there is to it in the Note class, but now we need to do some work to
make sure the loaded Note displays in the NoteActivity properly.
Switch back over to the NoteActivity class file and take a look at the
onCreate() method.
Right now you can see (on last line of previous image) that every time
onCreate is called (any time the NoteActivity is launched) then we load up a
new Note and initialize it with a new file name. However, now we only want
to do that work if the (+) add note button is clicked on MainActivity.
Android Bundles
Next, we create a new Bundle object.
This is one of the standard ways to pass values from one Activity to another.
In our case we want to pass the value of the file name from the MainActivity
to the NoteActivity.
After we new up the Bundle we call a method it provides called putString()
which allows us to store a String in the Bundle which we can later retrieve on
the other Activity.
bundle.putString("noteFileName", fileName);
Next, we create our new NoteActivity Intent just as we do when the user
clicks the (+) button.
Intent i = new Intent(MainActivity.this,
NoteActivity.class);
After that, we add our new Bundle to the Intent object by calling the
putExtras() method.
i.putExtras(bundle);
Then, we start the activity just as we did when there was no Bundle.
startActivity(i);
Each time a user clicks an item in the list it will send the fileName across to
the NoteActivity. However, that means we need to do something with the
value. For that, we need to switch back to our NoteActivity and change our
onCreate().
However, if the bundle object exists then we pull the string off the bundle
using its name (noteFileName) and the getString() method of the Bundle
object.
String fileName = bundle.getString("noteFileName"); _note = new Note(new File(getFilesDir() + "/" +fileName));
After we get the String from the Bundle we can then call the new Note
constructor which takes a File object by newing up a File object which will
point to our existing file in storage.
When we do that the Note fields Title and Text will be loaded up with the
appropriate data.
The only thing left to do is display the values on the screen by loading the
values into the titleText and noteText EditText objects.
You can see that now we check to insure the _note object isn’t null. If it is
null then it has no values and we don’t need to do any work. Then, if the
_note object is valid we go ahead and call the setText() methods on each of
the EditText boxes with the values which are in the Note object. That
displays the values which are in the Note file.
This creates complete functionality with the ability to create, read and update
Notes from a note list.
You can run the code by getting the code:
Stenotes_v10.zip
More For Another Time
You have a working app which will allow you to create and edit notes.
However, there is much more work to be done on this for it to be ready for
production release.
Additional Features
Some of the features would be just nice to have but others are probably
strong requirements to make it more useful and easy to use.
Here’s the list of things that I can think of that need to be added for sure:
We have done a Linux ls (list files) with parameters telling it to show all files
in long form in the target directory. These are the files which contain the
notes we have created in our program.
Summary
This has been a huge chapter and you’ve followed along you’ve
grown serious Android Dev chops.
You know a bit about:
◦◦
Android Storage
Writing and Reading files from Android Storage ◦
Bundles and how you can pass data across Activities. ◦
The Activity lifecycle.
Most importantly, you know how a real Android app is built. Even if you
stopped going forward in this book at this point you have enough knowledge
to build many of your own apps. But don’t stop there’s way more to learn and
advance your skills.
What’s Next?
Android Intents are powerful and there are built-in Intents which you can
implement your code on top of to get text from SMS messages, use pictures
from the camera and even do things when the phone rings. We’ll look into
some of the more interesting Intents and learn how to build our own Intent to
grab text from any screen we are reading so we can store quotes and text in a
database for research purposes. That way, if you’re reading a book you can
save quotes from the book in your own list / database and call up those quotes
later when you want to reference them.
Chapter 7
Building QuoteCap(ture) App: Learning About Intents
We are going to build an app in this chapter which will allow a user to
capture text as she is reading and save it to a sqlite database on her device. To
do this, we will have to add an Intent which can be launched when the user
selects text she is reading from any other app (web browser, Kindle app, etc).
I’m going to call the app QuoteCap (Quote Capture). The app will allow the
user to:
1. set up categories so he can organize his quotes
2. add a note which will be saved along with the quotes in case there is
additional information he wants to remember
3. allow user to add a heading to each quote
4. All quotes and notes will be saved in a sqlite database on the device,
however, since we want to allow the user to use this information further, we
are going to provide a way to print by posting the data to a web API,
5. The app will also provide a way to get the data out of the device for use in
other documents like MS-Word or LibreOffice.
Creating QuoteCap
Go to AndroidStudio and create a new project named QuoteCap. When you
create the project and you are choosing the Activity template, go ahead and
choose [Empty Activity]. We don’t need the menu and the buttons since our
Activity will be fairly simplistic with only one main Activity for configuring
the app.
You can download this project if you don’t want to create it:
quoteCap_v01.zip
1. display, add, delete a list of categories which will be provided to her when
she uses QuoteCap to save a quote.
2. A way to page through the quotes the user has saved.
That’s it for the most part, because most of our functionality will be
presented on a dialog box which the user will interact with.
App Walk-through
Let’s take a walk through of the completed app so you can see what I mean.
This app will be a service to other apps since it is more about collecting that
quotes.
################################################
This tells the system that our application allows text to be sent to it. Since,
we’ve nested the intent-filter under our MainActivity, when the text is sent to
our app then the MainActivity will be brought to the front. That will allow
the user to do something with the text in our app.
Go ahead and add the new intent so our AndroidManifest.xml looks like the
following now:
Once you build app, go ahead and run it so it’ll get installed onto your device
emulator. However, for the test that we are going to do now, the app does not
need to be running.
As a matter of fact, once the app starts, go ahead and click the back arrow and
the app will be suspended and probably stop.
After that, start up the Android Browser on your emulator. After the browser
comes up, go to a web site that has some text. I went to
http://en.wikipedia.org (english version of wikipedia). You can go to any site
you like. I just know that wikipedia will load relatively fast and will have
some text for me to select. Click on the text somewhere in the article and
hold.
If you hit only text when you click, then the two selector tags will appear and
you can move them to select more or less text.
However, if you happen to hit one of the links, you’ll first be presented with a
menu:
If that happens, choose the last menu item [Select text] and then the two
selector tags will appear.
Just make sure you select some text. In either case, when you select text then
at the top of the browser app you’ll see some buttons appear which allow you
to do something with your selection.
The first choice is the checkmark icon which simply indicates that you are
done.
The second choice is [Select all] which allows you to select all the text
currently displayed.
The third choice is the paper over paper icon which indicates that you can
copy the text.
The fourth and final choice is the additional menu button. The fourth one is
the one we want to select.
Click that one and additional menu choices will appear.
Share is the menu item we want. Click the Share menu item now and you will
see a list of apps that are registered on the device which accept text.
Look at that! QuoteCap is available.
Go ahead and click it.
When you do, it isn’t quite fantastic, yet.
As a matter of fact, the Browser crashed on my emulator when I clicked. But,
that’s not normal and it isn’t related to our app.
Once I clicked the [OK] button the QuoteCap MainActivity came to the front,
but of course we haven’t written any code to handle the incoming text yet so
nothing happens.
Let’s add the code to get the incoming text now. We’ll display it on the
MainActivity just so we can see it does something for now.
Go to Android Studio and open up the activity_main.xml.
You can see that it is currently a RelativeLayout. Highlight the top tag and
type LinearLayout and Studio will change the bottom one for you so we will
now have a LinearLayout.
Finally, we see some of the code that works with the incoming Intent.
I snagged this code right out of the Google Android development
documentation at :
http://developer.android.com/training/sharing/receive.html
The first line gets the incoming Intent. Intent intent = getIntent();
Once we get the Intent we need to grab the Intent Action and Type from it to
make sure we handle it properly.
String action = intent.getAction(); String type = intent.getType();
After we do that, we can decide what to do based upon the action being a
SEND action and the type being “text/plain”. We need to know the type
because obviously we can’t display picture data in our TextView.
Once, we’ve determined the type, we can do whatever we want with the data,
it’s nice to split that work up by calling a method. In our case we call the
handleReceivedText() method and send it the Intent.
That’s the code which will do the work of retrieving the actual text and
displaying it in our TextView.
You can see, it is very easy to get the text. We just call the intent’s built-in
method called getStringExtra and we pass it a predefined static String
(Intent.EXTRA_TEXT). That item is defined by the Android libraries.
As a programmer you will find that it is these little things that are cause for a
lot of extra (and unexpected) code that you have to write. It’s another reason
that there are “no simple apps”.
Of course, this program doesn’t do much for us because we aren’t saving the
data anywhere. This time, we have more information we want to save than
we did with Stenotes. What I mean is that we want to be able to save a few
different kinds of information this time.
I’m thinking we want to save:
If we capture all this information about each quote then later, it’ll be much
easier to remember where it came from. We will also create a way to page
through these quotes in our app so we can review them more easily and these
fields will help us remember the context of why the quote was important to
us in the first place.
We will allow any of the fields to be empty (except the captured text field) so
the user will be able to capture text very quickly without much intrusion from
our app if they don’t want to save all the other info.
Since all of this information creates a structure which we might refer to as a
Quote which has properties like text, title, source, notes and category, you
can easily see that this Quote thing is a good candidate for a Domain object.
It is also a good candidate to be a table in a database. That’s where we’re
heading with this app. We are going to save our data in a local Sqlite
(pronounced SequelLight or Sequel-ite) database.
What Is Sqlite?
Here’s a quote from http://sqlite.org
The good news is that Android has made it very easy to use Sqlite. Let’s get
started on creating our database and writing code to save our data to it.
For now, our table will end up being something like the following: ID :
unique id for row, will be auto-generated (more later) Title: 100 bytes
Text: 5000 bytes (this is the actual quote text we’re copying from the other
source) Average size of English word is 5 bytes, this means you get about
1000 words.
Source: 500 bytes trying to allow for title, link, author name, etc. Notes:
5000 bytes - if you’re a researcher like I am, you’ll want some space to type
what you were thinking about when you grabbed the quote
Category: 500 bytes - this should be relatively short so it can show up easily.
Created: Date - I like to know a history of what I was reading so we’ll add
this field too so that when we create an entry we grab the date. This way there
is a bit of history to the research you are doing.
That will give you a rough idea of what we will be creating in code.
A dialog box will appear asking you what you want to name your class.
Let’s call it QuoteDatabaseHelper.
After you type the name, click the [OK] button.
When you do,
Android Studio will generate the
QuoteDatabaseHelper.java file and open it in the editor for you.
Notice that Android Studio placed our new class in our app package since we
had right-clicked that package when we created the class. It has also added
the correct package name at the top of our new file. This makes sense
because this class will be specific to code we are writing for this app and it
being in the same package
namespace is good.
Begin typing the extends keyword after the class name and as you type the
name of the class we want to extend (SqliteOpenHelper) you will see that
Android offers it as a choice.
When you do that Android Studio will probably kind of flash and
redraw the editor window and you’ll see a red lightbulb appear on the left
side of the editor window. Studio has looked into the code you are extending
and it knows there are methods that need to be implemented.
If you click the down arrow which appears next to the red lightbulb you will
see that Studio offers to help.
Click the [Implement methods] item.
When you do, a dialog box will appear requesting you pick the methods you
want to implement and override.
This is Studio’s best guess about something that seems to be going wrong.
It wants us to implement a constructor in our derived class.
Let’s try to get Studio to help us to generate the constructor for us. Right-
click somewhere in the editor and a context menu should appear.
Choose the [Generate…] menu item.
As we
have seen in the past another menu titled, Generate, will appear. We
obviously want to choose the [Constructor] menu item.
Now Studio will be satisfied with your code and no more mean red
squigglies.
Now that we have our QuoteDatabaseHelper stubbed out we need to switch
gears a bit and create our Quote object so we can begin to assign it the correct
properties and methods to keep our
development work separated properly.
Here’s a quick class diagram to show what our Quote class should look like:
Most of those fields probably look like what you expect. However, the
CategoryId may have thrown you. You may have expected a Category which
was a String so we could add a String which represents a category. However,
in the case of a Category the best way to implement a solution is a separate
table which contains a list of all possible Categories.
The table would look something like the following. It’s very simple and only
requires two columns (one for ID and one for the category String)
We will provide the user with some way of managing these categories so they
can add and delete them.
One of values will then be used in the data rows when the user saves a new
Quote.
That data will look like what is shown in the following table. Notice that I’ve
cut out some fields that would actually be in the Quote table because I want it
to fit on the page/screen nicely. Also, please imagine that the Text field has
much data in it as represented by “data…”.
Now, imagine what I have to do if I decide that I want to change the text for
the Category with the ID = 1. I only have to edit that in one place in the
database. I would go to the Category table and change it from None to
“Miscellanous” and then I’d be done.
Now, I’d have to update all three rows (and anywhere else that the Category
“None” is used.
It’s also more of a pain because you have to search where the Category =
“None” for the change.
This is a form of normalizing your database and though we’ve only touched
on it slightly you will hear more about it if you continue development work.
Normalizing your database is done so you don’t have redundant data that
you’d have to edit in several tables if you change one piece of data. It’s
another form of code organization.
Let’s go add our Quote class and then we’ll see how and where we will put
our code that will create our Sqlite tables for us.
Adding The Quote Class
We want to add the Quote class to our app package so go and right-click the
package in solution explorer just like you did when we added the
QuoteDatabaseHelper class.
Name our new class Quote and click the [OK] button.
Next add the getters and setters for each of those. Studio will do the work for
you if you right-click the editor and choose Generate...
After that you can choose [Getter and Setter]
When you do, Studio will prompt you for which properties you want to
generate them for.
Choose all of them and click the [OK] button.
I’ll do the work too and if you want you can get the code up to this point at:
QuoteCap_v04.zip
Let’s add that constructor now and then we’ll also add UI elements to our
MainActivity so the user can set the other elements of the Quote object
manually -- by typing those values in.
After the user sets all the values she wants to on the form, she should be able
to press a save button to save the data to the database so we’ll add a Save
button to MainActivity also. Once the user successfully saves her data we
should close our Activity, since our
Since, it could be that the user decides not to Save the value, we’ll also add a
Cancel button which will close the form so the app he was sharing from is in
the front again.
The rest of the elements in our Quote object can all be displayed as EditText
elements and the Created (Date) element can simply be a TextView element
since we don’t want to allow the user to be able to edit that item.
Of course, we also need the two buttons (Save & Cancel) which will allow
the user to Save or Cancel her changes.
Chapter 8
QuoteCap: Altering the Layout,
Layouts can be somewhat difficult to work with until you learn all the
attributes you can use. We can’t go over every attribute in this book or we’d
never get our apps written. (I’m planning on writing a followup book which
will touch upon advanced features and more focus on layouts next.)
First let’s take a look at what the final UI looks like and then I’ll show you
the layout XML and talk about it a little.
Initially there is plenty of space for everything to fit on this screen. However,
if the user puts a large amount of text in one or more of those fields, the
buttons will disappear off the bottom of the screen. Adding the root
ScrollView layout XML which acts as a container to the rest of the layout
solves this problem.
Here’s an example of what I’m talking about.
The buttons at the bottom are gone.
If you didn’t have a root node of ScrollView, then the user wouldn’t be able
to scroll the buttons back into view. You can get the code up to this point at:
QuoteCap_v05.zip
Here’s what the layout XML looks like
340
First of all, please notice that it was difficult to get the entire layout structure
into one manageable snapshot so I collapse the EditText items which were all
similar. Just know that each of those EditText elements represent the
following fields :
1. Text
2. Source
3. Notes
They are all defined the same way except for their hint and id values.
Hint Attribute
The hint attribute is a String value that allows you to set a message the user
can see when the EditText control is empty. The text that appears is slightly
lighter than text that has been entered and it serves to provide the user with an
idea of what is supposed to go in the EditText field.
You can see those values in the original snapshot of the QuoteCap.
It’s a simple way to setup our layout and now that we have it, we can move
back to working with our Quote object.
Let’s move back to our MainActivity.java file and add a Quote object as a
member variable.
You can see that we added the _quote variable at the top of the MainActivity
class.
Then, we instantiate our new Quote object down in the handleReceivedText()
method (highlighted in the following example).
We used our constructor which takes a String (the passed in text which we
grabbed from the Intent).
Now, our object is in memory. However, with this code change we are no
longer displaying the quote text on the screen. That’s not very useful so let’s
add the code to do so.
Databinding : Objects & Views
Very often when we have an object in memory we allow a user to type values
into screen elements to alter the values. However, keep in mind that the
screen elements EditText, Spinners, etc. are not tied (or bound) to the
properties in the object.
This poses a challenge that when the user changes the value on screen we
need to do something to change the value in the object. This software
challenge is generally referred to as databinding.
We are going to add the official way to bind our Quote object to our view
(layout) by following the very well documented guide at:
http://developer.android.com/tools/data-binding/guide.html
We need to tell the project that we want to build the Android binding code
into our project and to do that we add a script directive which looks like:
dataBinding {
enabled = true
}
Go ahead and add it into the script at the to and it’ll look like the following:
You can see the portion we added has its beginning ending curly
braces highlighted in light blue.
Go ahead and build your app just to make sure it builds okay.
You can get the app with the changes up to this point at:
QuoteCap_v06.zip
Open up activity_main.xml (layout file) in your editor again and make the
changes so it looks like the following:
Notice that I added the <layout> (notice the lowercase L) tag to wrap the
entire existing layout.
I also ripped the original namespace definition from the ScrollView and
pasted it up to the <layout> tag.
It’s that easy. Of course, we add these to each layout element where
appropriate and we set the element’s text property. A full example will look
like the following:
android:text=”@{quote._title}”
or
android:text=”@{quote._source}”
Double-quotes
Take note that these directives are surrounded by double-quotes. That’s very
important.
Once they are all mapped up, the layout will look like the following:
Go ahead and build again to insure the code compiles properly. You can get
the code up to this point at:
QuoteCap_v07.zip
Now, there’s just one more simple thing to do and we can run it and try it out.
You can see (next image) the import for the class is added at the top of our
MainActivity.java file.
However, you cannot see that generated class in the solution explorer.
After that you can call the generated method setQuote() on the binding
object. It will look like the following:
binding.setQuote( quoteObject). The setQuote() method is also generated as
the compiler inspects your code and knows the type that you are binding. If
your target object had been named Book then this method would be
generated as setBook(). Quite amazing and helpful.
binding.setQuote(_quote);
Try It Out
Run the app, copy some text, share it with QuoteCap and you’ll see that the
Text EditText element will be automatically filled with the value and ready to
be saved.
In this last snapshot of the QuoteCap screen you can see that the text we
copied from the web browser is displayed in the Text layout field. All this
work is done for us by the binding. If the user changes the text, the _quote
object is automatically updated since the layout element is bound to the
object.
Now, we can focus on the work to save our Quote object our Sqlite database.
Alter QuoteDatabaseHelper
The first thing we want to do is slightly alter our
QuoteDatabaseHelper class to make a few things more clear. Go ahead and
open the QuoteDatabaseHelper.java in the Studio editor and let’s make those
changes.
Now, when you see variables within code that are formatted like that, you
will know they are constants that have been defined for your use. That makes
it easier to know what is going on in code.
Here are our first two constants that we want to add to our
QuoteDatabaseHelper:
public static final int DB_VERSION = 1;
public static final String DB_NAME = "quote.db";
Notice also that these two constants are also marked as public (we can get to
them outside the class, but of course only for reading since they cannot have
their value changed). However, we have also marked these two constants as
static, which means they are global values to the package they are in. These
are at times also referred to as Class Variables, because you can get to them
via the class (not the instance of an object). That means we can get to these
values by typing the following code:
String myString = QuoteDatabaseHelper.DB_NAME; Normally, we would
have to instantiate a class and then use the object to get to the value, but since
these are static variables we get to them via the class.
When you add that code Android Studio is going to complain at you because
the class and those two constants
(SQL_CREATE_QUOTE and SQL_CREATE_CATEGORY) do not exist
and Studio is trying to warn you that it cannot build properly. That’s fine for
now. Let’s go create the class which will contain these items.
Once you add the final modifier the class will look like the following: package
us.raddev.quotecap;
public final class QuoteDatabaseContract {
}
These two classes are inner classes to our Contract class and are both derived
from the BaseColumns class. This class will add an extra field named _ID to
our table definitions that we can use as a primary key on our tables.
Once we add the inner classes which define our tables the class will look like
the following:
Next, we need to add our constants which will define the action that occurs
when the db.execSQL() methods will take when they are called with the
constants from the QuoteDatabaseHelper.onCreate() method.
First I will show you the code and then I will show you what that code
expands to as a string so you’ll better understand what is happening.
You can see we’ve added five new static final strings to our
QuoteDatabaseContract class.
Three of those are private so they are only used inside the
QuoteDatabaseContract. The last two are public and we use them outside the
class in our QuoteDatabaseHelper in the onCreate() method.
First Three Private Strings
The first three private strings are simply chunks of strings we will use
repetitively while building the two larger strings.
You can see that we reference these strings in when creating the other two
strings (SQL_CREATE_QUOTE and
SQL_CREATE_CATEGORY). Keep in mind that the two public strings are
used in our onCreate() method on the database where we execute some sql
using db.execSQL().
Take a close look at each of the two public strings and notice that they each
start out with the same text which is: “CREATE TABLE “
Our database is now defined with two tables (Quote & Category) but we still
haven’t added the code which will actually create the quote.db file and the
tables within it yet.
Let’s do that now and then let you run the app.
ApplicationContext
We send in the Application Context so that the database is bound to the
ApplicationContext which is a Singleton class (only one of them exists in
app).
Let’s run the app to insure it works. You can get the code up to this point at:
QuoteCap_v09.zip
However, once you run the app, it still will not create the database. Just
calling the QuoteDatabaseHelper constructor doesn’t actually generate the
database file (quote.db).
To create the database we need to call the method
_quoteDBHelper.getWriteableDatabase().
However, we want to do that work in the Quote class in our Save() method.
Let’s add that code just to make it create our database file. Open up the
Quote.java file and add the following
public void save(QuoteDatabaseHelper qoh){ Log.d("MainActivity", "in Quote.Save()..."); SQLiteDatabase db =
qoh.getWritableDatabase();
Now we need to add the onClick handler to our button in the MainActivity.
Switch over to the MainActivity.java in the editor and we’ll add that code.
This is just like the work we did in the Stenotes app to get a button working.
}
});
_binding .setQuote(_quote); }
_quote.save(_quoteDBHelper); }
});
Notice that we check to see if our member _quote is null. If the user started
up the application without copying text to share, then the _quote has not yet
been initialized and the binding has not been set either, so we have to do that
here. After that we simply call the save() method of our Quote object.
We Need an Empty Constructor
This code is almost complete, however, Studio will warn you that you do not
have an implementation for a Quote constructor that takes 0 parameters. In
the case when the user starts the app directly she hasn’t copied in any text so
we don’t have any text to use to initialize the Quote.Text parameter. Let’s go
add the empty constructor to the Quote class and then run the app.
Run the code and then click the [Save] button. When you do, the app will
determine if the database has been created on the device (more in a moment).
The database hasn’t been created so it will attempt to create it on your device
at a sandboxed* location in your storage under your program’s app
installation directory.
*Sandboxed location means protected area that the Android OS only allows
the registered app to read from. No other app can steal the data by reading
from this location.
Errors and Crashes When Attempting to Run
I want to show you what happened the first time I ran the code and hit the
Save button.
Note: If you’ve obtained the code from QuoteCap_v10.zip you will not
encounter this problem because I fixed it before zipping up the project for
you. However, if you’ve followed along and made the changes your app will
crash.
Do you see the problem? It’s quite subtle. There is a trailing comma
-- a comma after the last field. I’m sure that is what is choking it.
When you start the app and click the [Save] button you have no
indication that the database has been created. Since your app
doesn’t crash you may assume it has been created, but we are
developers and developers have to know exactly what happens
when code runs. Let’s take a look at how we can see the database file
(quote.db) which was created for us.
Run ADB
When you finally get to that directory you can run the adb.exe tool which can
help you connect to a device or emulator which is running and connected to
the computer.
You can use ADB to run a Linux ls (list) command on the device. This
allows you to see the files which are on the device. Once you get to the target
(platformtools) directory type the following command and hit <ENTER>
adb shell ls -al data/data/us.raddev.quotecap/ <ENTER>
Notice that these are forward slashes (Linux uses forward slashes as
separators).
When you run that command you will see something like the following if
you’ve already run QuoteCap and pressed the [Save] button:
If you look to the far left, you’ll see some letters (drwxrwx). The first letter
lets you know the item is a Directory. You can see there is a databases
directory. Let’s add that to our path that we send to the ls command and try
again.
After running that command you see that there are two files in the directory.
One is the expected quote.db and the other is a Sqlite system file named
quote.db-journal. According to the Sqlite
documentation the extra -journal file holds information related to database
transactions (inserts, deletes, etc) in order to allow them to be rolled back.
You can read more about that at http://sqlite.org.
Let’s go back to the application now and continue our work saving our data
in the database.
Let’s go ahead and add the code to save our Quote object to the database
now.
However, we aren’t going to be able to know it’s saved yet, because nothing
particularly special happens on the screen. However, I’ll show you how to
use the ADB again to query the database from the command line.
Saving A Record to the Database
Open up the Quote.java file in the editor again and move down to the save()
method. We’ll add some code which will write the _quote object to the
database.
######################################################
########### SIDEBAR : Things That Should Work ############
###################################################### At this
point I actually went on a diversion because I discovered that two-way
binding on Android is not free.
1. when you change the value in the object, the UI element is automatically
updated.
2. When you change the value in the UI (a user types a value into an EditText
box) then the value in the object is updated.
I have also worked extensively with AngularJS and its two-way binding
works as smooth as butter so I am spoiled and annoyed that we have to do
more work to handle this thing that should be handled well in the subsystem
(Android libraries) we are building on.
First of all, the bug that you’ll see is that when you set the value in the object
and the onscreen element is supposed to update, then the EditText box will
leave the cursor at the beginning of the EditText box, even though it should
relocate itself to the end of the text that is entered into the EditText. There are
ways around this and there are numerous StackOverflow.com answers but
many of them only work sometimes.
1. First I’m going to give you the code with just the Quote.Text field
implemented with two-way binding.
2. I’ll let you run the app and save some records to the database.
3. I’ll show you the command line ADB to query the database Then I’ll
implement two-way binding for the rest of the fields and give you the
updated code but I won’t show you all of that code since it is exactly the
same as the rest.
######################################################
######################################################
Let’s do a quick review of how the code is set up now. We’ll go over the
changes to MainActivity and the Quote class and then see a couple changes
in the layout file.
In MainActivity, the first thing of importance is that when
MainActivity is started (onCreate()) we check if there is an Intent of the text
type. If there is we can initialize our Quote object using the Intent text.
However, if there is no Intent, the user has started the app directly and there
is no text to use to initialize the _quote object. In that case we new up an
empty object and set up the binding.
After that we log the information about the Tables we are going to create and
then we initialize our _quoteDBHelper so that when we finally call
getWriteableDatabase() we will be able to obtain a reference to the database.
Of course, if the database hasn’t been created yet then it will be at that time.
Continuing down through the MainActivity.java file you can see that we’ve
implemented some code for both the [Save] button click and the [Cancel]
button click.
Of course the [Cancel] button isn’t really doing the work that it will do in the
end. Instead we are using it to show how that when you change the bound
object that it will change the EditText element automatically.
Over in the Quote class I’ve made two getter/setter accessors for the _text
property bindable by adding the @Bindable decorator. This attribute added to
those properties is part of hooking up the binding from the layout to our
Quote class. Again, this tells the compiler to add in some additional
functionality into our class.
Along with those changes I’ve also added a TextWatcher object named
textChanged which is called when the screen element (EditText) is updated.
This is how we keep the object in sync with the value that the user types on
the screen.
For this to work it has to be wired up on the layout so we’ve
changed the EditText element for the Quote._text item so that it has a new
attribute.
We’ve added the following line to our EditText which represents the text
field of the Quote object onscreen:
bind:addTextChangedListener="@{quote.textChanged}"
Go ahead and run the app, type some text in and click the save button.
When you do that we can then query the database to verify that the data was
saved.
Back at our command prompt for ADB we want to run the following
command:
adb shell sqlite3
data/data/us.raddev.quotecap/databases/quote.db 'select * from quote'
<ENTER>
It’ll look like the following:
You can see that we are running a command named sqlite3. That is name of
the sqlite executable which is installed on the device. It is the actual sqlite
program which does all the work of creating and updating your database.
When you hit the <ENTER> button you will see your data:
You can see the “Here’s some test text to save to the database” is what I
typed in QuoteCap.
Each field is separated by a pipe symbol | .
If you haven’t pressed the [Save] button or it fails to create the database for
some reason you may see an error like the one in the next screen shot that
states: “unable to open database file”. That’s because the file doesn’t exist for
some reason.
Now, we have a working copy of our app which creates the
database and binds the one field but there is a lot more work to do. Right
now, if you don’t change the text and you keep pressing the [Save] button
then it will simply insert a new row into the database every time you hit the
button.
That’s not what we really want. Instead we would want to update
the last inserted record. That will take more work however.
Of course none of the other fields are saved in the database either so we need
to fix that.
I’m going to set up the rest of the databinding so all fields will be
updated both ways and they will be saved to the database. You can get that
code at:
QuoteCap_v12.zip
We’ve covered plenty for this chapter. However, we need to complete this
app and we will in the next chapter. I want to add a few things like allowing
the user to view the quotes that have been saved and save any updates the
user has made to fields. There are a few things to work out like how to insure
you update a record which has already been saved in the database instead of
saving a new record. It’s all related to using the rowId that comes back from
the sql insert. We also need to see how to deal with the Create Date / Time
value in Sqlite.
Chapter 9
QuoteCap : Finishing the App – Finding Bugs In APIs
A lot has happened in the small space between the last chapter and this one.
In my world, where I am writing this book I stumbled upon a problem with
the QuoteCap application related to the way we are sharing text from other
apps.
A few people have chimed in, but no one else has really talked about this
issue.
Unfortunately, the best answer out there which is supposed to work (override
onNewIntent() ) does not work at all.
Behaves Differently On Different API Levels
Also, you will see that this feature works entirely differently on different API
Levels (Android versions). It seems that running Android 5.0 (API Level 21)
and above solves the problem and it works as expected. However, we are
running API Level 15 (Android 4.0.4) in our dev environment and targeting
that version since it targets the greatest number of users. I myself have a
Galaxy Core Prime phone running Android 4.4 and it behaves the improperly
on that version also.
Here’s what the app looks like with a sample entry added:
As you can see, most of the functionality need for the complete app has been
added.
The user can save, edit and delete entries and can cancel an entry
- which will throw away any added data and return her to the last entry in the
list.
Of course, when a user decides to delete an entry she is warned with a dialog
box which requires confirmation before the entry is deleted.
The code to do that was taken directly from our Stenotes app.
Images on Buttons
I’ve also implemented the simple and available icons from the Android dev
libraries to display icons on the buttons where appropriate. This is as simple
as adding a new android attribute to our buttons such as in the case of our
Save button like the following:
android:drawableLeft="@android:drawable/ic_menu_save"
This is a nice feature which draws the icon to the left of the Button’s text.
Let’s dig down through the details of all the code now so you can see how
this app works.
Let’s go through our MainActivity class and walk through the functionality
found in each of the Button clicks.
When the user clicks the Save button the first thing we need to do is
determine if the Quote object is null (hasn’t been set yet). If it is we simply
new one up and bind it to the View so the values will be set properly.
You can see that I have some logging statements in here so I can follow the
code a bit more easily while it is running. However, the
first line of code that is more interesting is where we use the
QuoteDatabaseHelper.
getWriteableDatabase
You can see that we use the QuoteDatabaseHelper because it provides a built-
in method named getWriteableDatabase(). That method will return a
SqliteDatabase to us which we can use to do our insert and update work.
SQLiteDatabase db = qoh.getWritableDatabase();
If we were only wanting to read records from the database we would’ve
called the getReadableDatabase(). We’ll see more about this later when we
examine the [Previous] and [Next] button code.
SQLiteDatabase Methods
The entire reason we get a SQLiteDatabase instance is because it provides
helper methods we can use to work with our database.
The first thing we do is initialize our isInsert boolean value (which we will
return to the caller) so that it is false. In other words, if this is an update then
the isInsert will stay false otherwise we’ll set it to true.
The next thing you see is that we set up a new variable of type
ContentValues.
ContentValues values = new ContentValues();
values.put(QuoteDatabaseContract.QuoteTableDef.COLUMN_NAME _TEXT, _text);
values.put(QuoteDatabaseContract.QuoteTableDef.COLUMN_NAME _TITLE, _title);
values.put(QuoteDatabaseContract.QuoteTableDef.COLUMN_NAME _SOURCE, _source);
You can see that after we instantiate a new ContentValues container we then
call the put() method multiple times. The put() method takes two parameters:
1. A String representing the name of the value (in our case, it’s our column
name)
2. The actual value of the item (in this case it is the matching property in our
Quote class)
This creates a simple name-value pair collection which then can be used in
our call to the SqliteDatabase insert() or update() method.
Now that we’ve set up the values which the new Quote row will be updated
with or the previously created Quote row will be set to we can simply call the
appropriate method (insert or update) to do our work.
However, to determine if it should be an insert or update, we simply check
the value of our Quote._id field. If it is greater than 0 then we know the quote
object has previously been saved to the database and we call update(),
otherwise we call insert().
The update() method takes three parameters, however, we don’t need to use
the last one so we can send in a null* value.
*If you are a newer developer you can read more about what null means at
my website where I’ll be adding numerous articles on introductory
development topics.
The first parameter to the update() method is simply a String which contains
the name of the table you want to update. Since we created constants for all
of these values earlier, now we can use them again:
QuoteDatabaseContract.QuoteTableDef.TABLE_NAME
That’s the QuoteTableDef table name which is a String containing the value
“quote”.
The second parameter is the values object we just instantiated and initialized
with values from our quote object.
Finally, the third parameter is a String representing a SQL Where clause.
I have created a String by adding two strings together. We want the row to be
updated to have a Where clause which follows a pattern like the following:
“_id = <number>”
I use the Quote._id value to create the String and since the _id value is a long
(integer) value I have to convert the value to the String and I use the static
method String.valueOf() to do that for me.
This creates a dynamic String which creates a good Where clause so that only
the record we want to update gets its values changed. If we did not add a
Where clause and we ran the update, then all the records in the database
would be updated with the same values.
After everything is set up the update() method will set all the column values
properly. It’s actually all quite easy.
The code which runs when we insert a new quote record is only slightly
different.
long newRowId = db.insert(
QuoteDatabaseContract.QuoteTableDef.TABLE_NAME, null,
values);
this._id = newRowId;
isInsert = true;
You can see that the call to insert() has a slight different order of parameters.
Again, the first parameter is our constant String representing the name of our
table.
In the case of our insert() the second parameter represents the
nullColumnHack value.
That is an odd thing which can help you add a completely empty row to a
Sqlite table.
In our case, we never want to add a completely empty row so I set the value
to null since we don’t want to use the parameter.
The last parameter is the same values object we previously instantiated and
initialized. This is the nice thing about this code is that it uses the exact same
values object so we just pass it into whichever method (update or insert) we
end up calling.
Also, notice that the insert() method also returns the IDENTITY value
(generated _ID) that Sqlite has just created to insert our new row. We need to
save that value so we can store it in our current quote object. You can see we
save it in the newRowId variable and then store it in our object by setting the
this._id = newRowid. This insures that if the user clicks the [Save] button
twice, then the second time, the update() method will be called instead, since
the quote object’s _id value will then be greater than zero.
Finally, since this is an insert and we have added a new record we set the
isInsert boolean to true so we can return it to our calling method.
Back in the MainActivity onClick of the [Save] button you will see that we
use the boolean to determine whether or not we need to update our
lastRecordId value.
if ( _quote.save(_quoteDBHelper)) {
lastRecordId = _quote.get_id();
You can see if the _quote.save() method returns true then we set the
lastRecordId value.
We do that as a simple way to know the lastRecordId as a reference point for
paging through records in the quote database. A bit more about that later.
}
});
The first thing we do is unbind the current object from the view because we
are going to destroy the current object. Next, we call a method we wrote
called loadCurrentQuote(). That method loads a quote from the database so
we have something to display on the screen to the user.
In our case, we load the quote from the database with the largest _id value.
This is an arbitrary decision. I figure that is the most recently added entry and
so it is a good one to display to the user.
No Warning Dialog
In this case I have not added a warning dialog that you will lose the data that
has just been copied in. You can implement that feature later if you like.
Next we set up a Cursor object that we can use to iterate through the records
in the database.
To get the record we want (the one with the largest _id value) we call a helper
method on the Sqlite database named rawQuery(). That method allows us to
pass in a String representing a SQL statement which the database will run for
us and return the resultset (matching rows).
Because the subquery is a normal valid query itself, you could run it on the
command line against your database like the following: adb shell sqlite3
data/data/us.raddev.quotecap/databases/quote.db ‘select max(_id) from
quote’ <ENTER>
You can see now via replacement that our original query would then look like
the following:
select * from quote where _id = 112
That will return one record to us (the last one which was added to the
database).
The next line uses the Cursor object to moveToLast() (since we know only
one row was going to be returned we could’ve used moveToFirst() and there
would be no difference) to read the record.
After that you see where we start getting the values from the record to
instantiate quote object which contains the values from the row. In the
following code we use the Cursor’s getString() method to get a string value
that is stored in the text column. We store it in a local variable named text
and then we use that text to construct our new Quote object.
String text = c.getString(c.getColumnIndex("text")); Log.d("MainActivity", "text from query : " + text); _quote = new
Quote(text);
Next, we get the _id value out of the column using the Cursor again. Then we
set our newly instantiated Quote object’s _id value to that same value.
_quote .set_id(c.getInt(c.getColumnIndex("_id"))); Log.d("MainActivity", "_id = : " +
String.valueOf(_quote.get_id()));
lastRecordId = _quote.get_id();
Finally, we set the lastRecordId (since we’ve just queried for that value) to
the value we stored in the Quote oibject.
Then we initialize the rest of the Quote properties from the values in the row
using the Cursor.
_quote.set_source(c.getString(c.getColumnIndex("source"))) ;
_quote.set_title(c.getString(c.getColumnIndex("title"))); _quote.set_note(c.getString(c.getColumnIndex(QuoteDatabase
Contract.QuoteTableDef.COLUMN_NAME_NOTES)));
c.close();
Before that highlighted line runs, keep in mind that the binding has been
removed. That means if we do not bind the newly instantiated and initialized
_quote object then the View would not display the values of the object we
just initialized. That’s why we call
_binding.setQuote(_quote) here.
When we do the UI updates and you will see the values from the record with
the largest _id value.
The next and previous button both have some interesting code in them
because of the way we have to keep track of the current record we are
displaying.
If we are not at the end of the list then we call a method we wrote named
DisplayRecord(). When we call it we pass in the currentId value and a
boolean representing whether we are moving up through the list or down. In
this case we pass true which means we are moving up.
Here’s how the DisplayRecord() method works. This method is very similar
to what we saw in the Quote.save() method so a lot of it will look familiar.
The DisplayRecord() method is called from both the [Next] button and the
[Previous] button. This is a good reuse of code since the code result is so
similar.
Howevever, you can see that the way we create the SQL query is interesting.
Ternary Operator
One of the first things I do is determine if we are moving up through the list
or down. That determines whether or not we want a record with a higher or
lower _id than what we have right now. I use a ternary operator to set the
directionSign (less than or greater than).
The ternary operator is made up of a colon ( : ) and a question mark ( ?).
It’s nice for this type of one line if statements.
It takes of form of :
In our case we check the isUp boolean variable. If it is true then we want our
select statement to get an _id that is greater than the _id we are currently on
and so our ternary statement returns a > (greater than sign). If isUp is false
we return the second value which is a < (less than sign).
"select * from quote where _ID " + directionSign + " " + id + ";";
This retrieves all rows where the _ID is greater than 5 (our current _ID) so
that we can make sure we get the next row (which may not necessarily be
_ID = 6 since rows may have been deleted).
Once that is set up you can see that the rest of the code is the same as the
Quote.save() method where we run a rawQuery() and then read the values out
of the columns to initialize our _query object.
That’s all there is too it. However, the challenge of keeping our current object
in sync with the items in the database should be obvious. There are multiple
rows in the database and we have to know where we are as we display them
to the user so the user can move through the entire set of records.
The Problem
However, as I was testing the app on the emulator running API Level 15 I
found that the text shared with QuoteCap worked the first time, but every
time after that when I attempted to share text the text value sent in to
QuoteCap was the same. That meant we didn’t have a way to allow
QuoteCap to keep running and easily add new quotes numerous times. That
would’ve destroyed the purpose of the app so I had to find a workaround. I
did create a workaround, but it took me a few days to get it to all working
exactly as a user would expect.
However, that meant that if the user pop up a dialog box or switch the
orientation of his phone or a received an incoming call then the app would be
destroyed and possibly lose current unsaved data.
Workaround Explanation
My idea was to create a pass-through Activity which would accept the Intent
Extras and then place the Extras in a Bundle which it then passed to my
MainActivity. The pass-through Activity would then use startActivity to start
the MainActivity which would guarantee that the pass-through Activity
would go to the onPause() method. Then, in that onPause() method I would
call the finish() which would destroy the pass-through Activity. The user
would never see the pass-through activity and the problem would be solved
because the pass-through Activity would be destroyed each time and receive
new Intent Extras every time it was instantiated.
This works fairly well, but I learned to do this I also had to configure my
pass-through Activity a bit differently in the AndroidManifest.xml.
You can see that I’ve added the pass-through Activity named
QuoteCapGrabber. I’ve also added a launchMode attribute with a setting of
singleInstance. That setting insures that there is only one of these Activities
in memory ever. That helps insure that the
system will destroy the Activity properly so that I always get the
newly shared text.
After including this in the manfiest, it’s a simple matter of adding the code in
our QuoteCapGrabber.java.
You’ve learned a lot with the the QuoteCap app. It’s nice little app which can
really help if you want to save text and quotes while you are reading on a
device.
Now let’s move on to our final app. This app will help you see how you can
1. share data over an Internet connection
2. Grab text (SMS) messages as they come into a device.
3. A bit about multithreading and why you must not do a lot of work on your
app’s main thread.
We’ll learn a lot more too as we write an app which will allow you to grab
text messages as they come in and forward them to an account where you can
pick them up via a web page and reply to them from the web page. Then,
when you reply to the text message from the web page, the app will pick
them up and send them from your registered device so that others who
receive the text messages will get them from your phone even though you
were able to type them on a web page.
I believe you’ll find this app quite interesting. Let’s go create it.
Chapter 10
TxtFwd : Building the App - Retrieve Your Text Messages
From Anywhere
Building our app which grabs text messages and forwards them to you so you
can edit them using a web page and keyboard will challenge our app building
skills from many angles.
There’s a lot to do so let’s talk about what we want our app to do and how we
might do it.
The Idea In A Nutshell
Here’s the idea. I want to be able to start the app up and set a checkbox that
tells the app to grab each text message (SMS) that comes to my phone and
forward it to a location where I can read it from a web page and then reply
using my computer and that same web page. This will allow me to type text
messages I want to send using my normal computer keyboard without having
to purchase a separate bluetooth keyboard.
Of course, this app also allows us a way to investigate the various Android
APIs which allow us to do this work.
######################################################
Disclaimer
This application will use your phone’s SMS service to send and receive text
messages. Your normal charges will apply just as if you are sending and
receiving the messages yourself. If you do not turn off the forwarding feature
you may be sending and / or receiving an extra text message for everyone
you send or receive and that could drive up your messaging costs. The author
cannot be held responsible for any extra expense to you when using the app.
Your use of the app indicates your understanding and acceptance of this
disclaimer.
######################################################
Where Do We Forward The Message?
The next thing to consider is where we can forward the text message. First of
all, we certainly don’t want our text messages to be exposed on the Internet
no matter how benign they may be. Also, how would you store those text
messages so they may be displayed on a web page? How could all of this be
secure? There is a one word answer to that question: Firebase.
What’s Firebase?
http://firebase.com
It’s a highly-available, Internet, JSON (JavaScript Object Notation), NoSQL
data storage which is easily accessible via HTTPS (Encrypted / Secure).
Firebase provides a great, easy-to-use API which is accessible via Android
apps and provides free developer accounts so you can get started for free.
There are a few parts to this app so I will break them down and
show you a flow for each state in an attempt to communicate
exactly how the app will work.
At this point, the user has viewed one or more messages and replied to a
message. When the user replies using the web app then the message and
details are written to an outbound location in the Firebase store.
We have to do this so the user is warned when installing the app that it can
read (RECEIVE_SMS) messages. A nefarious app could gather a lot of
information by reading all the user’s text messages so we have to give the
app the explicit permission to be able to do so.
If we fail to add the permission to the manifest then the app will crash when
it runs the SMS receiving functionality.
Let’s go ahead and add a new class named MsgReceiver to our project and
add it into our us.raddev.txtfwd package. Next go ahead and make sure
MsgReceiver extends BroadcastReceiver and finally go ahead and implement
the @Override of the onReceive() method.
Once you do all that, your class will look like the following:
Now we just need to implement the code for onReceive() which will get the
information we want out of the text message.
Add the following code to the onReceive() method and when Studio attempts
to help you add the imports make sure you add the
android.telephony.SmsMessage.
Also you are going to see that we check the API Level in our code to
determine how we handle the SMS message. That’s because we had to do
that a different way in APIs before Level 19 and we are supporting API
Levels all the way back to 15 (Android version 4.0.4
- Ice Cream Sandwich). This is an arbitrary decision and we could just
change our minimum API Level to 19 and forget about the old code if we
wanted to.
You can easily see all the API Levels and their corresponding Android
version names and code names at :
https://source.android.com/source/build-numbers.html
Let’s build the app and deploy it and I’ll show you how to test it on your
emulator.
You can get the code at:
TxtFwd_v01.zip
Once you run the app, move back to Android Studio so we can start the
Android Device Monitor (ADM).
You can get to the ADM from the menu bar at the top of Studio.
You
can also get to it from the Tools menu:
Once you start up the ADM it will look something like the following:
Take a close look at the menu buttons in the top right and you’ll see that the
DDMS tab is selected and you will also see that at the far left the current
running emulator is chosen.
This is the DDMS ( Dalvik Debug Monitor Server). This tool is somewhat
poorly named because the Dalvik Virtual Machine is no longer used on the
newer versions of Android (5.0 & up).
Incoming Number
Using it is a bit odd, because you cannot do anything until you enter a
number in the [incoming number] edit box. That is the number which will be
reported to the app as the sender’s number.
Go ahead and type a number like 5551234 in the edit box and then you will
be able to choose the SMS radio button indicating that you want to send a
text message. If you enter anything besides numbers in that edit box you will
see that the radio buttons go back to disabled and you won’t be able to send a
text.
After you type a number in, select the SMS radio button and then the
Message edit box will be enabled so you can type a message to send. Type a
message in the edit box. Next, move your Android emulator so you can see it
when you click the [Send] button.
I took a snapshot right after clicking the button and the toast message popped
up in the TxtFwd app.
Create A Message Class
Now that we’re all set up for receiving messages, let’s create a better way to
handle the text message info by wrapping it all up in a class. This will help is
later when we write the data to the Firebase data storage anyways.
Go ahead and add the new Message class to our main package and we’ll add
all of the properties above. Then you can generate the getters and setters for
all of the properties.
Finally, you can add an constructor which takes all four values as parameters.
The most important part of this code is how we receive the SMS
message itself. As we said earlier, the Android APIs have changed a bit so
that if the user is running API Level 19 or newer you can use the simpler
method of pulling the message directly off of the incoming Intent with the
call to getMessagesFromIntent(). This is slightly more direct way of getting
the SMS info than the older version where you have to grab the extras Bundle
off the Intent and then loading the “pdus” from the Bundle. PDU stands for
Protocol Data Unit -- basically meaning a data type based upon a protocol
(SMS being the protocol).
SmsMessage : Allows Us To Get Message Data
After you’ve retrieved the SmsMessage using either the new or old method
then the code is the same. We simply grab the data that we are interested in
using the built-in methods getTimeMillis() to get the Date-Time info the
message was received.
getOriginatingAddress() allows us to get the number we will use to reply and
getMessageBody() allows us to retrieve the actual message which was sent.
Firebase Account
To do the next work you should get your own Firebase account, but you don’t
have to, because the TxtFwd app will use the Firebase location that I’ve
generated.
If you have a Google account it is extremely easy to sign up for Firebase. All
you have to do is login using your Google account and Firebase will create an
account for you and create an initial database. If you do that, it will look like
the following when you first sign up.
I’m going to go ahead and rename my default Firebase database
to : TxtFwd
Now it has a better URL also:
Now we need to add the Firebase SDK (which we get as a JAR file from the
Firebase web site).
The information on how to do this work (slightly different) is at the official
Firebase site at:
https://www.firebase.com/docs/android/quickstart.html
However, I found a bit simpler method to do this and I take you through it
step by step as this chapter continues.
When you go through this there may be a newer version so check the
quickstart page to be sure you have the latest.
This jar file extends the libraries which we are building our app on. If you
examined the core Android libraries you’d find numerous jars where the
functionality we find in packages is found. This jar file is provided by
Firebase which provides this new package with functionality that allows us to
do work specifically related to implementing Firebase.
When you click the [Project] item, the solution explorer view will change.
The item we are interested in is the [libs] folder.
That is where we are going to add our new jar file so that it will be built into
our solution and the Firebase API methods will become available to us for
use.
Drag And Drop The Jar
To do this we can simply go to the location (using File Explorer) where you
originally saved the jar file when you downloaded it and drag it and drop it.
It’ll look like the following:
When you drop the file into the libs folder Android Studio will warn you
about what you are doing and ask you to confirm the action.
Click the [OK] button and Android Studio will warn you with the
following (somewhat confusing dialog):
It’s trying to tell you that you are changing your project. Make sure the [I
want to edit these files anyway] radio button is selected and then click the
[OK] button.
Now if you expand the [libs] folder you will see that the Firebase jar has been
added.
Now, we need to explicitly add the library to Android Studio. Rightclick on
the jar file in solution explorer and then choose the [Add as library…] menu
item.
When you choose [Add As LIbrary…] then Studio will prompt you with a
dialog.
You can click the [OK] button to add the library.
When you complete that step, some information is added to the Gradle build
file automatically for you. If you were to look at that file you’d see it at the
bottom:
Adding
the library added that last line.
We are still in Project view and the Android view is much cleaner so let’s
drop that original menu and choose Android again so we are back to our
normal solution explorer view.
After I added that I went ahead and built the project again. If it doesn’t seem
to build try cleaning the project and then building again. Sometimes Studio
and Gradle get out of sync.
At this point we have the Firebase library added and ready to be referenced,
but we are not using it yet. It is necessary to initialize Firebase before using it
and we’ll see how to do that. First we need to add a permission to our
manifest though, because Firebase obviously saves our data at a remote
location using the Internet as the transport. That means we have to add the
Internet permission to our app.
Initialize Firebase
To get the Firebase storage to work for us we have to call a one time
initialization when our app starts. We can do that work in our MainActivity
onCreate() since it starts when the app starts.
It’s just one line of code to add and it looks like the following:
Firebase.setAndroidContext(this);
When you add that line, Studio will prompt you to add the Firebase import.
The code in the MainAcitivty class will look like the following:
Once you add that line, Firebase is ready to work for you. It’s really very
easy to use Firebase so lets add a few lines in the
MsgReceiver class to write a record out to the Firebase store and then I’ll
give you the code you can build and test out.
First of all here’s what the code that I’m adding looks like the in the
MsgReceiver class.
The lines which write to the Firebase storage are the two lines near the
bottom which I’ve highlighted. Before we look more closely at those let’s
take a look at the first lines in the onRecieve() method. The first thing I do is
new up a Random object. The Random object allows me to generate random
values. In this case I am generating a random Long value (8 byte value --
which is an extremely large number). I do this to generate a fake userId each
time the the app receives a message. This is just for our testing purposes.
This means that every time the application gets a new message it will
generate a new fake userId and then add the dateTime and body (message
text) values underneath.
If you build and run that code you will see there is no longer a toast message
that appears, but instead the values are written to logcat.
You can see the from (I changed the number to 5551230) and the
receivedDate where you can see that I am up after midnight writing this code
and chapter of the book. :)
You can also see that I was running on an emulator that is running API Level
19 (Android 4.4) so it logs that I’m on the NEWER API LEVEL.
However, the code not only writes to the logcat, it also writes to the Firebase
store. Here’s what that looks like:
In this case I’ve stacked all the windows so you can see a few things.
1. On the far right I’ve sent a message (This is a super
message) using the DDMS.
2. To the left you can see that the message was written at
txtfwd\demo\3750760521007434539\body
3. You can also see at the bottom left that logcat also has the message written
to it.
Now, any time the TxtFwd app is running on your device and you are
connected to the Internet, your incoming text messages will be forwarded to
this location.
###############################
I will also be adding any fixes to this book there and I will add other
supplementary materials. That is also where you can download all of the code
for your use.
I've greatly enjoyed writing this book and I've learned a lot in the process
myself. I hope you too have enjoyed it and learned much. Keep on learning,
keep on coding.
~Roger Deutsch 03/22/2016