84
Android Academy TLV 14/12/2015 Idan Felix Content Providing End 2 End 4B

Session #4 b content providers

Embed Size (px)

Citation preview

Android Academy TLV14/12/2015

Idan Felix

Content ProvidingEnd 2 End

4B

First,

Yonatan Levin

Android Evangelist Gett

Idan Felix

Senior Android & RedheadVaronis

Jonathan Yarkoni

Android Developer & Advocate

Ironsource

Android Academy TLV

Britt Barak

The coolest and fairest of us all

Real

After this lecture, you get 2 weeks off

Next Meeting:5/1/2016

Next Meeting:5/1/2016

Winner take it all

Last Week, J’ Talked about Lifecycle

Lesson #4: A bit of context

This lesson is made of 3 parts:

a. Activities’ Lifecycle

b. Content Providers

c. Loaders

But this lesson is about one major subject:

Providing Data Correctly

Map of today: Seamless Data Integration

Database (SQLite)

Content Provider

Next Week: Hook ‘em up w/ Loaders!

These will enable us to connect your data to your activities,

seamlessly.

Database

Database:A structured set of data, accessible in various ways.

Relational database:A Database which uses tables and relations to organize the data,

Usually uses SQL for operations

Relational Database management system:A program (or less) that implements a Relational Database.

Common vendors:PostgreSQL, Oracle, MySQL, Microsoft SQL Server

When to use a Database

When…

- Dealing with large datasets

- Caching of structured data

(Same data downloading over and over again)

- Preloaded-data

Better loading times, Better battery utilization

Our Database

SQLite is a mini-RDBMS.

Unlike most:

- Serverless (Runs in your process, not on its own)

- Zero-Configuration

- Most widely deployed database

Get to know it better: https://www.sqlite.org/

Our Database

Most widely deployed database:Every Android device

Every iPhone and iOS device

Every Mac

Every Windows 10 machine

Every Firefox, Chrome, and Safari web browser

Every instance of Skype

Every instance of iTunes

Every Dropbox client

Most television sets and set-top cable boxes

Most automotive multimedia systems

Countless millions of other applications

Source: https://www.sqlite.org/mostdeployed.html

Structured Query Language, or SQL, is the language used to

communicate with database.

It’s been around since the 70’s, and uses 3VL.

From our standpoint, it has 2 types of statements: DDL and DML.

Ess Kiu What?

Read More: https://en.wikipedia.org/wiki/SQL; https://www.sqlite.org/lang.html

Imagine this table

Data taken from: http://www.meetup.com/TLV-Android-Academy/events/☺

* Not really how dates are represented

_ID Date* Title Lecturer Floor

1 3/11/2015 Intro And Basics Yonatan 34

2 10/11/2015 Basics and ListViews Felix 29

3 17/11/2015 Off threading Jonatan 29

4 1/12/2015 New Activities and Intents Yonatan 34

5 8/12/2015 Lifecycles Jonatan 34

6 15/12/2015 Databases and stuff Unknown Unknown

7 5/1/2016 Loaders Unknown Unknown

Getting Data: Select

The Select statement is used to select parts of a table.

SELECT ( * | [column, column])

FROM (table)

WHERE (condition)

ORDER BY (column) (ASC | DESC)

This is uber-simplified. Read more: https://www.sqlite.org/lang_select.html

Getting Data: Select

SELECT *

FROM Sessions

WHERE floor=’34’

_ID Date* Title Lecturer Floor

1 3/11/2015 Intro And Basics Yonatan 34

4 1/12/2015 New Activities and Intents Yonatan 34

5 8/12/2015 Lifecycles Jonatan 34

Getting Data: Select

SELECT Date, Title

FROM Sessions

WHERE floor=’34’Date Title

3/11/2015 Intro And Basics

1/12/2015 New Activities and Intents

8/12/2015 Lifecycles

Getting Data: Select

SELECT Date, Title

FROM Sessions

WHERE floor=’34’

ORDER BY date DESC

Date Title

8/12/2015 Lifecycles

1/12/2015 New Activities and Intents

3/11/2015 Intro And Basics

Getting Data: Select

SELECT *

FROM Sessions

WHERE title LIKE ’% and %’

_ID Date Title Lecturer Floor

1 3/11/2015 Intro And Basics Yonatan 34

2 10/11/2015 Basics and ListViews Felix 29

4 1/12/2015 New Activities and Intents Yonatan 34

6 15/12/2015 Databases and stuff Unknown Unknown

Getting Data: Select

SELECT *

FROM Sessions

WHERE lecturer LIKE ’%onatan’

_ID Date Title Lecturer Floor

1 3/11/2015 Intro And Basics Yonatan 34

3 17/11/2015 Off threading Jonatan 29

4 1/12/2015 New Activities and Intents Yonatan 34

5 8/12/2015 Lifecycles Jonatan 34

Getting Data: Select

SELECT *

FROM Sessions

WHERE lecturer LIKE ’%onatan’

AND floor = ‘29’

_ID Date Title Lecturer Floor

3 17/11/2015 Off threading Jonatan 29

Getting Data: Select

SELECT *

FROM Sessions

WHERE _id = ‘3’

_ID Date Title Lecturer Floor

3 17/11/2015 Off threading Jonatan 29

Getting Data: Select

SELECT * FROM Sessions_ID Date Title Lecturer Floor

1 3/11/2015 Intro And Basics Yonatan 34

2 10/11/2015 Basics and ListViews Felix 29

3 17/11/2015 Off threading Jonatan 29

4 1/12/2015 New Activities and Intents Yonatan 34

5 8/12/2015 Lifecycles Jonatan 34

6 15/12/2015 Databases and stuff NULL NULL

7 5/1/2016 Loaders NULL NULL

Use the Update statement to modify values in existing records.

UPDATE (table)

SET (column)=(expr) [, (column)=(expr) [,...]

WHERE (condition)

Updating Data: Update

Again, Super simplified. Read more: https://www.sqlite.org/lang_update.html

UPDATE Sessions

SET lecturer=’Felix’, floor=’34’

WHERE _id = 6

Updating Data: Update

_ID Date Title Lecturer Floor

6 15/12/2015 Databases and stuff NULL NULL

_ID Date Title Lecturer Floor

6 15/12/2015 Databases and stuff Felix 34

Use Insert statement to add records to tables.

INSERT INTO (table)

((column) [, (column) [,...]])

VALUES ( expr [, expr [,...]])

Inserting Data: Insert

Read more: https://www.sqlite.org/lang_insert.html

INSERT INTO Sessions

(date, title, lecturer, floor)

VALUES

(‘12/1/16’, ‘Rich and responsive layout’,

null, null)

Inserting Data: Insert

Inserting Data: Insert

_ID Date Title Lecturer Floor

1 3/11/2015 Intro And Basics Yonatan 34

2 10/11/2015 Basics and ListViews Felix 29

3 17/11/2015 Off threading Jonatan 29

4 1/12/2015 New Activities and Intents Yonatan 34

5 8/12/2015 Lifecycles Jonatan 34

6 15/12/2015 Databases and stuff Felix 34

7 5/1/2016 Loaders NULL NULL

8 12/1/2016 Rich and Responsive Layout NULL NULL

Deleting Data: Delete

Use the Delete statement to remove records from tables.

DELETE FROM (table)

WHERE (condition)

Deleting Data: Delete

DELETE FROM Sessions

WHERE floor = 29_ID Date Title Lecturer Floor

1 3/11/2015 Intro And Basics Yonatan 34

2 10/11/2015 Basics and ListViews Felix 29

3 17/11/2015 Off threading Jonatan 29

4 1/12/2015 New Activities and Intents Yonatan 34

5 8/12/2015 Lifecycles Jonatan 34

6 15/12/2015 Databases and stuff Felix 34

7 5/1/2016 Loaders NULL NULL

8 12/1/2016 Rich and Responsive Layout NULL NULL

Be CRUD!

These 4 actions (Create, Read, Update and Delete) are the 4 basic

operations of persisting data.

Read more: https://en.wikipedia.org/wiki/Create,_read,_update_and_delete

Be CRUD!

What’s important for us to know after each operation?

Create - we’ll want to grab the ID of the new record

Read - we’ll want to have the record-set

Update - we’ll want to know how many records have changed.

Delete - “ “ “ “ “ “ “ were deleted.

Creating our table

CREATE TABLE Sessions (

_id INTEGER PRIMARY KEY,

date TEXT,

title TEXT,

lecturer TEXT,

floor INTEGER

)

Read More: https://www.sqlite.org/lang_createtable.html

Drop it like it’s hot

Last, we’ll need to know how to delete a table.

DROP TABLE IF EXISTS (table)

Things I didn’t talk about

Using multiple tables, defining relations

Designing databases (3NF, 5NF, Inheritance modeling)

Using indexing to improve performance

Thread-safety, reader/writers, locking, ...

Triggers, Views

Transactions

Major Challenges (For us)

Avoid SQL Injection

Upgrade when needed

Being up-to-date

What is SQL Injection?

Many times, we use Strings to send commands to the database.

You might think it’s a good idea to connect strings to create a query,

like so:

“SELECT * “ +

“FROM users “ +

“WHERE (username=’” + username +

”’) and (password=’” + password + “‘)“

What is SQL Injection?

So, if the input is “guest” and “1234”, you’ll get:

SELECT *

FROM users

WHERE (username=’guest’)

and (password=’1234‘)

What is SQL Injection?

but what if the input is “admin” and “a‘ or (1=1)) --”, you’ll get:

SELECT *

FROM users

WHERE (username=’admin’)

and (password=’’ or (1=1)) -- ‘)

Read more: https://en.wikipedia.org/wiki/SQL_injection

Solution: Use Query Parameters

Use this string:

“SELECT * “ +

“FROM users “ +

“WHERE (username=?) and (password=?)“

and pass the input as Query Parameters.

Demo Time: SQL Injection

We’ll see how proper credentials (“GUEST” / “ADMIN” and “1234”)

succeeds, With or without Query Parameters.

Then, we’ll see how evil credentials succeeds too -

Unless we’re passing the credentials as part of the query parameters,

and not as part of the query.

What’s hard about Upgrades?

- You need to keep track of your schema’s version.

- You need to perform upgrade steps for each schema version.

- You need to detect when to do it

- You need to think about how to upgrade the schema,

what to do with existing data, setting default values, etc.

A helper class to manage database creation and version

management.

Subclass by implementing onCreate(SQLiteDatabase),

and onUpgrade(SQLiteDatabase, int,

int).

This class makes it easy for ContentProvider implementations to

defer opening and upgrading the database until first use, to avoid

blocking application startup with long-running database upgrades.

SQLiteOpenHelper to the rescue!

Reference: http://developer.android.com/reference/android/database/sqlite/SQLiteOpenHelper.html

You have 2 methods that you use to get a SQLiteDatabase:

getReadableDatabase() and getWritableDatabase()

If the database does not exist, it will create it.

If it needs upgrading, you’ll get upgraded.

SQLiteOpenHelper to the rescue!

But When?

From the SQLiteDatabase object

- Cursor query(String table,

String[] columns,

String selection,

String[] selectionArgs,

String groupBy,

String having,

String orderBy)

- long insert(String table,

String nullColumnHack,

ContentValues values)

SQLiteOpenHelper to the rescue!

you get, you can:

- int update(String table,

ContentValues values,

String whereClause,

String[] whereArgs)

- int delete(String table,

String whereClause,

String[] whereArgs)

Don’t hard-code Strings

Don’t hard-code Strings

Since we’ll need to use most of the strings also in the next section,

it’s common to extract them to a Contact class.

The contract class contains:

- Table names and column names

- URI Related stuff, such as

- Content Authorities

- Build methods

- Parse methods

Developer Responsibilities

1.extend SQLiteOpenHelper

2.Implement onCreate and onUpdate

3.Create a constructor,

Call the super’s constructor with:

a. A Context

b. Database name

c. An optional Cursor Factory (which we won’t use, so we’ll pass null)

d. A Version

SQLiteOpenHelper: Step 1

Create a class that extends SQLiteOpenHelper

public class AndroidAcademyDatabaseHelper extends SQLiteOpenHelper {

}

SQLiteOpenHelper: Step 2

Implement the OnCreate and OnUpgrade methods

public class AndroidAcademyDatabaseHelper extends SQLiteOpenHelper {

@Override

public void onCreate(SQLiteDatabase db) {

}

@Override

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

}

}

SQLiteOpenHelper: Step 2

Implement the OnCreate and OnUpgrade methodspublic class AndroidAcademyDatabaseHelper extends SQLiteOpenHelper {

String sqlCreation =

"CREATE TABLE Sessions ( " +

"_id INTEGER PRIMARY KEY, " +

"date TEXT, " +

"title TEXT, " +

"lecturer TEXT, " +

"floor INTEGER " +

")";

@Override

public void onCreate(SQLiteDatabase db) {

db.execSQL(sqlCreation);

createInitialData(db);

}

}

SQLiteOpenHelper: Step 2

Implement the OnCreate and OnUpgrade methodspublic class AndroidAcademyDatabaseHelper extends SQLiteOpenHelper {

@Override

public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

db.execSQL("DROP TABLE IF EXISTS Sessions");

onCreate(db);

}

}

SQLiteOpenHelper: Step 3

Implement a constructorpublic class AndroidAcademyDatabaseHelper extends SQLiteOpenHelper {

public AndroidAcademyDatabaseHelper(Context context){

super(context, "androidacademy.db", null, 1);

}

}

Optional: Extract Contract

public class AndroidAcademyContract {

public static class SessionEntry implements BaseColumns {

public static final String TABLE_NAME = "Sessions";

public static final String COLUMN_DATE = "date";

public static final String COLUMN_TITLE = "title";

public static final String COLUMN_LECTURER = "lecturer";

public static final String COLUMN_FLOOR = "floor";

}

}

Optional: Extract Contract

then, change the code in the helper to use the contract.

For example:

private static final int DATABASE_VERSION = 1;

private static final String DATABASE_NAME = "androidacademy.db";

private static final String SQL_CREATION = "CREATE TABLE " +

AndroidAcademyContract.SessionEntry.TABLE_NAME + " ( " +

AndroidAcademyContract.SessionEntry._ID + " INTEGER PRIMARY KEY, " +

AndroidAcademyContract.SessionEntry.COLUMN_DATE + " TEXT, " +

AndroidAcademyContract.SessionEntry.COLUMN_TITLE + " TEXT, " +

AndroidAcademyContract.SessionEntry.COLUMN_LECTURER + " TEXT, " +

AndroidAcademyContract.SessionEntry.COLUMN_FLOOR + " INTEGER " +

")";

Demo Time

1.in onCreate we set the ListView with a cursor adapter,

but with no cursor.

We also create the DB helper there.

2.when we refresh, or do something,

that’s when we hit the DB for the first time -

and if it doesn’t exist, we create it.

3.There’s no Observing mechanism when using the dbHelper

directly.

1 Step Forward

If you want, you can implement CRUD methods on the SQL Helper,

making it a full blown Data-Access-Object (or DAO).

public void insertSession(SQLiteDatabase db,

String date, String title, String lecturer, Integer floor) {

ContentValues values = new ContentValues();

values.put(AndroidAcademyContract.SessionEntry.COLUMN_DATE, date);

values.put(AndroidAcademyContract.SessionEntry.COLUMN_TITLE, title);

values.put(AndroidAcademyContract.SessionEntry.COLUMN_LECTURER, lecturer);

values.put(AndroidAcademyContract.SessionEntry.COLUMN_FLOOR, floor);

db.insert(AndroidAcademyContract.SessionEntry.TABLE_NAME, null, values);

}

Read more: https://en.wikipedia.org/wiki/Data_access_object

Any questions?

Map of today: Seamless Data Integration

Database (SQLite)

Content Provider

Content providers manage access to a structured

set of data.

They encapsulate the data,

and provide mechanisms for defining data

security.

Content providers are the standard interface that

connects data in one process with code running

in another process.

Content Provider

API Guide: http://developer.android.com/guide/topics/providers/content-providers.html

Why would you need a content provider?

If any of the following reasons apply:

- You want to offer complex data or files to other applications

- You want to allow users to copy complex data from your app into other apps

- You want to provide custom search suggestions (using the search framework)

- You want to use a Sync Adapter

- You want another level of abstraction over direct SQL access

- You want to get updates (ContentObserver and Loaders)List based on: http://developer.android.com/guide/topics/providers/content-provider-creating.html

1.Create a class that extends ContentProvider

2.Add a manifest entry for your content provider

3.Use your content provider with a Content Resolver

Content Provider Recipe

The abstract ContentProvider

You’ll need to implement these methods:

- query

- delete

- update

- insert

- getType

- onCreatehttp://developer.android.com/guide/topics/providers/content-provider-creating.html#RequiredAccess

Each implementation should be about 3 things:

1. What’s the URI? What does the user want to do?

2. Use the datasource (if applicable)

3. Notify the change to everyone (if applicable)

query()

The Query method returns a cursor.

public Cursor query(Uri uri,

String[] projection,

String selection,

String[] selectionArgs,

String sortOrder)

This is super-easy to implement with the SQLiteOpenHelper.

Also: cursor.setNotificationUri(getContext().getContentResolver(), uri);

Read More: http://developer.android.com/guide/topics/providers/content-provider-creating.html#Query

The insert method should return the uri to the newly created record.

public Uri insert(Uri uri, ContentValues values);

To create this URI, you can use the URI.BuildUpon.

Also: getContext().getContentResolver().notifyChange(uri, null);

insert()

update()

The Update method should return the number of updated records.

public int update(Uri uri,

ContentValues values,

String selection,

String[] selectionArgs)

Also: getContext().getContentResolver().notifyChange(uri, null);

delete()

The Delete method should return the number of deleted records.

public int delete(Uri uri, String selection, String[] selectionArgs)

Also: getContext().getContentResolver().notifyChange(uri, null);

Here’s when things begin to get tricky.

You’re expected to return a string, that identifies your data structure.

This is the agreed format:

vnd.

getType()

Read more: http://developer.android.com/guide/topics/providers/content-provider-creating.html#TableMIMETypes

android.cursor.item/

android.cursor.dir/

vnd.<name>.<type>

For single Items

For sets

Usually, Your app namespace

Depends on the URI

onCreate

This method returns true if the creation was a success.

it should be quick, (it usually runs on the UI thread)

so it’s not the right place to do a database update.

Lucky for us, SQLiteOpenHelper is ...

Read More: http://developer.android.com/guide/topics/providers/content-provider-creating.html#OnCreate

Manifest Entry

In order for Android to find your Content Provider (given a URI),

You must register it in the App’s Manifest, as such:

<provider

android:authorities="com.example.felixidan.session4b.nosql.provider"

android:name=".NoSQLContentProvider" />

<provider

android:authorities="com.example.felixidan.session4b.sql.provider"

android:name=".SQLContentProvider" />

Using it (Part 1)

You NEVER use a ContentProvider directly.

Instead, you ask a ContentResolver to do the work for you.

Consider this “refresh” method:

public void refresh(View view) {

Cursor cursor = getContentResolver()

.query(SQLProviderContract.NumberEntries.CONTENT_URI,

null,

null,

null,

null);

adapter.swapCursor(cursor);

}

Using it (Part 1)

The ContentResolver has all CRUD methods you’ll need.

Consider this Insert

public void insertARandomNumber(View view) {

Random random = new Random();

int randomNumber = random.nextInt(100);

ContentValues newNumberValues = new ContentValues();

newNumberValues.put(SQLProviderContract.NumberEntries.COLUMN_NUMBER, randomNumber);

getContentResolver()

.insert(SQLProviderContract.NumberEntries.CONTENT_URI,

newNumberValues);

}

Using it (Part 2): Only in 3 weeks

In Session #4C,

We’ll see how to properly use a content provider,

to keep your activities synced with the data,

performing loading on a background thread,

and keeping it safe with the Activity Lifecycle.

Any questions?

Demo Time

2 Content Providers: Both keep track of numbers.

The first uses an ArrayList<Integer> as a backing store,

The other uses a SQL database.

Interesting: Cursor Observation on the content provider.

So why UDACITY’s code is so complicated?

3 things to spice-up your Provider

URI Matching

Getting information from the URI / Building the URI

SQLiteQueryBuilder

URI Matching

Since their Content-Provider uses 2 tables,

And they support all kinds of queries (Weather with location, location and dates, etc)

Then they support a few kinds of URIs.

For example: content://com.example.android.sunshine.app/location

content://com.example.android.sunshine.app/weather/

content://com.example.android.sunshine.app/weather/London

content://com.example.android.sunshine.app/weather/London/15-DEC-2015

URI Matching

The URIMatcher class accepts URI templates, and IDs,

matcher.addURI(authority, WeatherContract.PATH_WEATHER, WEATHER);

matcher.addURI(authority, WeatherContract.PATH_WEATHER + "/*", WEATHER_WITH_LOCATION);

matcher.addURI(authority, WeatherContract.PATH_WEATHER + "/*/#",

WEATHER_WITH_LOCATION_AND_DATE);

and when you give it a URI, it tells you which template it is (by ID).

final int match = sUriMatcher.match(uri);

SRC: https://github.com/udacity/Sunshine-Version-

2/blob/sunshine_master/app/src/main/java/com/example/android/sunshine/app/data/WeatherProvider.java

URI Matching

Think of the impact this has on these Content Provider methods:

- query

- delete

- update

- insert

- getType

Each would have some kind of “switch case” statement, according to the Match results.

Getting Parameters from the URIs

This is another pro-tip:

URIs have methods so we can access their parts.

Useful examples:

uri.getPathSegments().get(1)

→ content://authority/words/party

uri.getQueryParameter(“q”)

→ content://google.com/search?q=Party

Any questions?

The End

Drive Home Safely,

Or Join us for a coffee in the Open Space outside.

See you Next Year (in 3 weeks: 5/1/2016)