Android
What to Do
- After watching Lessons 1, 2, and 3, submit Pokédex.
- After watching Lesson 4, submit Fiftygram.
- After watching Lesson 5, submit Notes.
- For Pokédex, Fiftygram, and Notes, record a screencast (about 2 minutes in length each) with voiceover in which you demonstrate your mobile apps and their features. Upload your videos to YouTube as “public” or “unlisted” and take note of their URLs.
- Submit this form.
When to Do It
By 11:59pm on Fri 11/22.
How to Do It
Introduction
- We’ll learn to write mobile apps for Android with a new language, Java, and build three apps: one that loads data and displays it; one that applies filters to images; one that lets you take notes and save them.
Lesson 1
- We’ll use Android Studio, an IDE provided by Google to help us write Android apps. We’ll download and open it, and start a new project. We’ll select the Empty Activity template for our app, and use
JavaExample
for our app name. A convention for the package name is the domain name in reverse, plus the app name, likeedu.harvard.cs50.javaexample
. We’ll use Java and support Android 5.0 or above, so most devices can use our app. - [2:20] Inside Android Studio we’ll see a lot of files that have been generated for us. We’ll want to first create an AVD, or Android Virtual Device, so we can run our app on our laptop instead of a separate device.
- [4:00] We’ll take a look at the syntax for Java, which is similar to C and has familiar data types. We can initialize and change variables, have conditions, arrays, for loops, Lists (like a dynamically-sized array),
- We can create a List object with
List<String> values = new ArrayList<>()
, specifying that the type of data it will hold isString
. Java supports many different types of Lists, but we’ll just useArrayList
. - We can also iterate over the elements in our List with
for (String value : values) {
.
- We can create a List object with
- [9:40] Java has the concept of generics, a way for a type like List to understand the type inside, in this case String. Java also has maps, also called dictionaries in Python or objects in JavaScript, which stores key-value pairs.
- [12:45] Lists and Maps are examples of classes in Java, which we can think of as structs with functions attached to them. And we call functions inside classes methods.
- [15:25] We can add a method to our
Person
class, and use a variable in our method that the constructor in the class already saved. - [17:10] We can also have static methods, or methods we can call without an instance (one that we constructed) of our class.
- [17:50] Another feature of classes is inheritance, where we can inherit fields and methods from a parent class, and optionally modify some of them.
- [20:40] Interfaces are like a list of methods that any class implementing them has to have. If a method is missing, the compiler will tell us. It turns out that declaring a list with
List<String> strings = new ArrayList<>()
is actually using an interface, whereList
is the interface, andArrayList
is the class that will implement the behaviors of a list. Withnew ArrayList<>()
, we’re creating an instance of the class. - [24:20] Java also has packages, which helps us organize and namespace our files. Like in Python, we also need to
import
certain packages we want to use. - [26:10] We’ll come back now to Android Studio and look at the project we created. Inside the
java
folder, there’s our package with aMainActivity.java
file that has one class and a method,onCreate
, that callssuper.onCreate
first (which is the parent class’ implementation), and then callssetContentView
, which we won’t worry about now. - [29:20] We can start by creating a new Java Class by right-clicking on our package folder on the left. We’ll name it Track, to represent the tracks in our course, and now we can add fields and a constructor that saves its arguments.
- [32:00] Back in our
onCreate
function, we’ll create a newList
ofTrack
s and add some tracks. We’ll create a list of strings with a static method,Arrays.asList
, to represent student names. - [35:40] Now we’ll use a map, of strings to tracks, to represent track assignments for students. We’ll iterate over every string in our list of strings and use the
Random
class to get a random track for each of them. - [39:00] To print everything, we can iterate over the map, and use the
Log
class in Android Studio to print out a log. We’ll press the play button on the top right, and the device shows the default “Hello, world”. But in Android Studio, on the bottom right we can click Logcat, inside which we can see our logs. - [43:00] We’ll add getter methods to our
Track
class to return its fields, so they can be private to the class and so other code can’t change them directly. Our getter might also have other logic.
Lesson 2
- Now we’ll add UI to our Android app. The build system is called Gradle, which helps us by downloading libraries and compiling our code.
- MVC, Model-View-Controller, is a general design pattern in which we separate our concerns, or types of code, into three categories. Models, like the Track class we created in lesson 1, stored our data. The view takes care of displaying the data when it gets it. And finally, the controller is the bridge between the model and the view, with logic deciding what data to pass to the view and when.
- An Activity in Android is like a base class for each of our screens in the app, representing a single thing we’re trying to do. For example, in a contacts app the first activity might be the list of contacts we see, and the second activity is the view of a single contact.
- Our app will also have Resources, non-Java code such as Layouts that describe how a view should look. Layouts are in a language called XML, which looks similar to HTML, with tags and attributes. For example, we can specify a
LinearLayout
with aTextView
. - [6:15] Another concept we’ll see is called Intents, that let us move from one activity to another. We’ll also have Recycler Views, which displays a list of items that we can scroll through like a feed.
- [8:15] We’ll create a new project again, an Empty Activity, to display a list of Pokemon and details about each of them. We’ll set up our project and take a look at the generated files:
AndroidManifest.xml
contains some configuration for our application, like an icon and the activities in our app.- In the
java
folder, we’ll have ourMainActivity
file, but also packages for test files. - In the
res
folder, we’ll see resources. In particular, thelayout
folder will have view for each activity, and if we openactivity_main.xml
, and we can see a UI to drag and drop component. We can also click the Text tab at the bottom to see the source XML. Important attributes includelayout-width
andlayout_height
, so we can choose to fill the entire screen or only some fraction of the parent view. - In the
values
folder, we can also add constants like strings for translation.
- [17:15] We’ll look at some Gradle scripts, which specify flags and dependencies that’s like a configuration file for the Java compiler. In the
dependencies
section, we can add more libraries as we use them. - [19:15] Now we’ll come back to
MainActivity
and add aRecyclerView
. Android’s developer documentation has a lot of details and examples that we can learn from. We’ll add the library in the Gradle file, and click Sync in Android Studio to automatically download the package. Then we’ll change ouractivity_main.xml
layout file to use aRecyclerView
instead of theTextView
. We’ll add an identifier to the view so we can reference it from our controller withandroid:id="@+id/..."
. - [23:55] We also need to define what each row looks like, so we’ll need to create a new Layout resource file in the same folder, and create a new
LinearLayout
. We’ll add aTextView
inside, and give both IDs. - [26:20] Our view is ready, so we’ll create some classes for our models. First, we’ll create a
Pokemon
class with properties and a constructor to save them. - [28:35] It turns out that a
RecyclerView
uses another class, an adapter, to control what data will be displayed, so we’ll create a new classPokemonAdapter
that extends theAdapter
class. We’ll also need what’s called a view holder so we can modify the view and layout as needed. In our adapter, we’ll define thePokedexViewHolder
, which will have the generic row view, but also theLinearLayout
andTextView
we added earlier inside each row. We’ll need a classR.id
to get the unique ID for each of those views. Then we’ll overrideonCreateViewHolder
, after our view holder is created, to create our view that represents a row. We’ll also needonBindViewHolder
to set the values of each row, given a position of the row. - [40:35] In our
MainActivity
file, we’ll add fields for our view and adapter, and also aLayoutManager
. Now we can get the view in our main layout and connect our adapter to it. We can run our project now, and see that each row takes up the whole screen, so we’ll change the layout’s width and height to bewrap_content
. - [44:00] We’ll create another activity, so we can display each Pokemon when they’re selected. We’ll create a new Activity > Empty Activity, and generate a layout file. We’ll change the layout to a simpler
LinearLayout
, and add someTextView
s inside, setting the text size and padding. - [47:45] In our
PokemonActivity
view, we’ll set the values we get from an Intent onto those views. We can callgetIntent().getStringExtra()
, built intoAppCompatActivity
, to get the variables passed into our view. We’ll need to pass along data in our adapter in theonBindViewHolder
method, withsetTag
on the view to set the currentPokemon
object to our view holder. And we’ll add an event listener,setOnClickListener
, to get ourPokemon
back from the tag and create an Intent we can pass to the next view withstartActivity.
- [55:25] We can make our view nicer in the layout XML file with some more attributes like padding, built-in animations, and centered text. And like
printf
in C,String.format
in Java can take in a format string to give our number a certain number of digits.
Lesson 3
- Now we’ll load data from the internet for our app.
- We can use an API, application programming interface, to load data from the internet in our app. An API is like a set of code that someone else has written, designed for you to use too.
- In this case, we’ll be making requests to a website and getting data back in a format called JSON, JavaScript Object Notation.
- An object in JSON might look like a dictionary of key-value pairs:
{ "course": "cs50", "tracks": ["mobile", "web", "games"], "year": 2019, }
- The values can be a string, an array, or a number as we see here, or some other data types.
- [2:10] We’ll check out PokeAPI at pokeapi.co, and see that a URL we put in will return a lot of data in the format of JSON. The documentation for the website has information about how we can get a list of data, so we try that.
- [5:25] Android has a library called Volley for making requests, and we’ll include it in our
build.gradle
file so we can use it. - [6:35] We’ll also need to use a new pattern called try, catch, where a function that might fail or have an exception, can be “caught”, or recovered from. When we catch an exception, we get an object of the
Exception
type, and we’ll be able to print out details of exactly what happened. - [8:30] In our adapter, we’ll load our
pokemon
list with a new method,loadPokemon
, and use the Volley library to make aJsonObjectRequest
. We’ll have an anonymous method that will be called once we get a response from the API, with aJSONObject
that we can parse into an array ofpokemon
. Since the response might not be what we expect, we’ll need to catch any exception we might get. - [14:40] Each result in the
JSONArray
will be aJSONObject
, and similarly we can try to get thename
andurl
from each of them and put them in ourPokemon
class. - [17:50] Once we’ve defined our request, we also need a
RequestQueue
that has theContext
of the app, so the Volley library can make requests on behalf of our app properly. We’ll add our request to the queue, so we can actually load data. - [20:25] After we load our data, we need to refresh our RecyclerView with
notifyDataSetChanged()
from our adapter, and we also need to add the permission to use the internet for our app inAndroidManifest.xml
. We’ll fix the capitalization of the name. - [24:40] We’ll use the
url
on the Pokemon data we got back to make another request when we want details about a particular Pokemon. We’ll look at the view, and add two moreTextView
s to display the types of the Pokemon. In the Activity class for the Pokemon details view, we’ll make another request, and parse the object in the response for thetypes
array. Then, we can set the values of the all theTextView
s based on the data. - [33:30] The
PokemonActivity
class will need theurl
from our adapter, so our click listener can pass that in with theIntent
object. And our activity also needs to call our newload
function, and actually make the request.
Lesson 4
- We’ll build another app now, one that allows us to apply filters to images. We’ll create a new project with an Empty Activity in Android Studio, and start by adding more views in our
activity_main.xml
layout. Since we’ll need to scroll, we’ll change the parent layout to aScrollView
, and inside have aLinearLayout
for ourImageView
and aButton
. - [4:40] In our activity, we’ll add the functionality for our button to load an image and display it. We’ll write a
choosePhoto
method, which will call the built-in Android image gallery for selecting a photo. We’ll create anIntent
and set the action and type. We’ll add arequestCode
, so we know how to handle the selected image in our app when the other activity finishes. Finally, we’ll have ourButton
call the method when it’s clicked. - [10:15] In our activity, we’ll need to override the
onActivityResult
method to actually handle the data (image file) we get back. We’ll make sure that theresultCode
is okay, and that we got data back. Then, we’ll have some steps to get an image from thedata
object, by getting the URI (like a URL), trying to open the file from it, and loading the file as a bitmap image. - [15:40] We’ll use the
BitmapFactory
to create our image object by decoding the file, and then close the file. Finally, we can set the image on ourImageView
to show what we’ve picked. - [18:05] We’ll use some third-party libraries, like
glide-transformation
, by adding them to our Gradle file by following their documentation. And we need another library for some of the filters we want, so we’ll add that too. - [20:40] We’ll add a button to our view for applying a filter, and create methods in our activity by following the documentation. We’ll use the example of loading an image into the
Glide
library, applying a transformation, and loading it into theImageView
. - [25:05] We’ll add two more in the same way, and factor out the common code, and just pass in different transformations depending on which filter we want to apply. We also have to be careful with importing the right classes from the right packages. Now we can apply different filters to our images.
Lesson 5
- We’ll build a note-taking app that can save data to the device.
- We’ll use SQLite, a simple database that saves data to a file but supports SQL queries.
- We’ll need queries like:
CREATE TABLE
INSERT INTO
SELECT ... FROM
UPDATE ... SET
- [4:25] We’ll open a new project again, with Empty Activity, and start by creating two views, one with a list of notes, and one for editing an individual note. We’ll make a
RecyclerView
as before, and create an adapter to provide data for the view. We’ll also need another view for each row,note_row
, similar to our Pokemon app. Inside our adapter we’ll create a view holder to be able to set data on the views. - [11:05] We’ll look at the documentation for Android’s persistence (data storage) library, called Room. We’ll need to add the dependencies to our Gradle file, including an
annotationProcessor
for our compiler to generate the library’s code. - [12:50] We’ll make a new model class,
Note
, with anid
andcontents
. We’ll also add some annotations, likePrimaryKey
andColumnInfo
to specify how these fields will be stored in our database by the Room library. This is essentially the definition of the table. - [14:55] In our adapter, we’ll override
onBindViewHolder
to get the contents of our note from the notes list, andgetItemCount
for the total size of the list. In our main activity, we’ll connect our view, layout, and adapter with each other. - [18:15] Now that our view is ready, we’ll write a new class, DAO, for data access object, with the Room library, so we can actually load and save note objects to our database. We’ll make a new class,
NoteDao
, which will actually be an interface that we annotate, and the Room library will generate the actual code implementing these queries. For example, we’ll add an annotation,@Query
, to thecreate()
method in our interface, without actually writing any code for it. Instead, we’ll write our SQL query in the annotation. Similarly, we can write a methodgetAllNotes
to return a list of notes. And in oursave
method’s query, we can easily use:contents
and:id
to safely substitute variables into our query, avoiding SQL injection attacks. This class is how we’ll interact with our table. - [23:50] To use our DAO, we need a database class, and we’ll call it
NoteDatabase
. This will specify the database that our DAO can use. It turns out that our database class is an abstract class, which means it has some methods that are implemented, and some methods that are not, or abstract. Again, the Room library will generate the code that implements our abstract class. - [28:55] We can use all of this code in our main activity by first connecting to this database and storing the connection as a
public static
variable so all of our activities can share it. In our adapter, we can write areload
method to access the database and use the noteDao’sgetAllNotes
method. - [32:35] And in our activity, after our view loads, we’ll run this
reload
method. We’ll also add a button to our layout with Google’s Android Material UI package, and specify some attributes of it so it looks the way we want. - [37:00] We’ll add an
onClickListener
in our main activity to the button, which should create a new note, and reload the recycler view to show it. - [38:35] Now we can create a
NoteActivity
to allow us to edit a specific note, and we’ll use aEditText
component in the layout to hold the contents of our note. In our activity, we’ll want to load the contents of the note into the text editing container, and we’ll also save the contents to the database when we go back to the recycler view in the app. We can load the note from the intent, and overrideonPause
to save the note when we leave the activity. - [42:45] In our recycler view’s adapter, we’ll set the
onClickListener
for each row’s container to create an Intent with the row’s note, and pass it to ourNoteActivity
. Finally, when we come back to this view, we’ll also want to reload the notes byonResume
. - [46:30] When we build and run our app, we see a crash, and the log tells us that our current note is
null
in the adapter, and it turns out that we have to check for it after the view has been loaded, in the click event handler, not in the constructor. - [47:55] Finally, we’ll clean up the layout by adding padding and other aesthetics.
Conclusion
- The Android documentation has lots of topics, so do use it to build even more interesting apps!
- Supersection
- Staff Solutions