Insider's Guide To Udacity Android Developer Nanodegree Part 2
Written by Nikos Vaggalis   
Monday, 24 April 2017
Article Index
Insider's Guide To Udacity Android Developer Nanodegree Part 2
Lifecycle issues
Uploading the Project and Feedback


The next chapter on Preferences is about interacting with the UI and its state by making choices typically by clicking on a range of checkboxes, and persisting those choices to disk.Android has encapsulated this common functionality into the Preference Fragment, available to any application and easily tweaked to adapt to any requirements at hand.Nevertheless, we'll be exclusively using SQLite for our data persistence needs, as such this chapter can be safely left off.



SQLite is designed to cater for more complex requirements, and we use it for manipulating the list which contains our Favourite movies.A movie gets tagged as favourite by clicking on the Star present in each movie's details screen, which depending on the state it currently occupies (on or off) either saves the movie to the database as a favourite or removes it from it if it's already in.

In order to use a SQLite database we first have to implement the Contract, a clause that defines the tables and the colons for each table that is included in the database.

In this case we just have a single table "favouritemovies" with a single column "moviedbid" that stores the uniquely identifying movie ID, retrieved from the initital call to the MovieDB API URL.

public static final class TableEntry
implements BaseColumns { public static final Uri CONTENT_URI = BASE_CONTENT_URI.buildUpon().
appendPath(PATH_FAVOURITES).build(); public static final String TABLE_NAME
= "favouritemovies"; public static final String COLUMN_MOVIEDBID
= "moviedbid"; }


The rest of the chapter goes through performing SQL DDL and DML, which are hidden underneath the DBhelper class, but the focus quickly shifts from direct SQL manipulation to the indirect use of Content Providers, which in essense are Data Mappers that sit in between the application and the backend database.

"The Data Mapper is a layer of software that separates the in-memory objects from the database. Its responsibility is to transfer data between the two and also to isolate them from each other. With Data Mapper the in-memory objects needn't know even that there's a database present; they need no SQL interface code, and certainly no knowledge of the database schema. (The database schema is always ignorant of the objects that use it.) Since it's a form of Mapper (473), Data Mapper itself is even unknown to the domain layer."   


The question is then, why opt for another layer of indirection than go for straight forward and hassle free direct SQL ?

The answer is that Content Providers allow developers to change the underlying data source without needing to change any code in the applications that access the content provider.Then, some important classes such as Loaders and Cursor Adapters use them exclusively, therefore if you want to use a Loader you have to make your data accessible through a content provider instance.



Still, the main reason of their existence is to open up the data source to other app developers so they can access, use and modify it. For example, let's say that we have an app that needs to retrieve the contacts stored in your phone.Since the default Contacts app is using a content provider, it exposes its data source to all other applications, therefore everyone can easily hook into it.

The steps for building a Content Provider are broken down to:

  1. Get permission by adding a request to the Manifest

  2. Get the ContentResolver, the class that correctly identifies which content provider to call, since in a typical device there's going to be afew providers set up by various applications.

  3. Pick one of four basic actions: query, insert, delete, update,  and pass them to the Query object.

  4. Use a URI to identify whether we're looking for data reading or manipulating.This is typically implemented with a URIMatcher object. 

At this point, in order to make the application more flexible and efficient, I've started using Lambdas to wrap the SQL operations in:

Function<Uri, Integer> delete =
(uri) -> getContentResolver().delete(uri, null, null); Function<Uri, Cursor> select =
(uri) -> getContentResolver().query(
uri, null, null, null, null); BiConsumer<Uri, ContentValues> insert =
(a, b) -> getContentResolver().insert(a, b); @Override public Boolean loadInBackground() { Uri myUri = Uri.parse(args.getString(getResources().
getString(R.string.put_extra_uri))); try { if (args.getString(getResources().
== "select")
{ return (select.apply(myUri)).
getCount() > 0 ? true : false; }
else if (args.getString(
== "delete")
{ delete.apply(myUri); }
else if (args.getString(getString(
== "insert")
{ int myId = args.getInt(getResources().
ContentValues contentValues =
new ContentValues();
insert.accept(myUri, contentValues); } }
catch (Exception e) { ex = e; } return null;

Lambdas, although not discussed in the course, are elements of Java version 8 that must be enabled by upgrading the build using the Jack toolchain:

jackOptions { enabled true } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 }

Once that is done, Android Studio correctly identifies and works with Lambdas. The downside is that for it to work it requires a minSDK version of 24, something that  rules out many older devices

Including a Loader

Since everything database-related goes through the Content Provider, we also need a Loader and specifically a CursorLoader to wrap all database access in, in order to  retrieve our Favourite movies list upon clicking on the corresponding menu option of the MainActivity. 

The last requirement to fulfil is making a movie's list of trailers and reviews accessible through its Details screen (ChildActivity).For this we need to make a call to the MovieDB API to retrieve the Trailers

{ id: 127380, results: [ { id: "579511269251412c2b000dc0", iso_639_1: "en", iso_3166_1: "US", key: "eVSAZv4oqW8", name: "Official Sneak Peek", site: "YouTube", size: 720, type: "Teaser" }, { id: "57975a57925141399400405a", iso_639_1: "en", iso_3166_1: "US", key: "NQu-153MnGQ", name: "Official Trailer 2", site: "YouTube", size: 1080, type: "Trailer" }, { id: "571cdda9c3a3684e98001850", iso_639_1: "en", iso_3166_1: "US", key: "iG0P6bjyUNI", name: "Official US Teaser Trailer", site: "YouTube", size: 1080, type: "Trailer" }, { id: "571cdd7cc3a3684e9800184b", iso_639_1: "en", iso_3166_1: "US", key: "JhvrQeY3doI", name: "Official US Trailer", site: "YouTube", size: 1080, type: "Trailer" } ] }

and another one to retrieve the Reviews

 id: 127380,
 page: 1,
 results: [
    id: "5835dce5c3a3682fad019c4a",
    author: "Reno",
   content: "**Before Nemo, a long ago another child 
got lost in the open ocean...** Whoa! 200 million
dollar film, 1 billion box office collections worldwide.
The 27th film to do that so and the 5th animation film.
This sequel was made after 13 years, surely a long
gab, but 'Finding Nemo' was one of my favourite
animations _9/10_",
   url: "
 total_pages: 1,
 total_results: 1

So we need to add two extra objects ReviewData and TrailerData to hold the reviews and the trailers instances, two extra Adapters, ReviewsAdapter and TrailersAdapter, and two RecyclerViews to host those Adapters.


Last Updated ( Monday, 20 November 2017 )