113
Android Academy TLV 4/12/2016 Britt Barak WIFI: pUp3EkaP Content Providing #5

Session #5 content providers

Embed Size (px)

Citation preview

Page 1: Session #5  content providers

Android Academy TLV4/12/2016Britt Barak

WIFI: pUp3EkaP

Content Providing#5

Page 2: Session #5  content providers

First,

Page 3: Session #5  content providers

Britt BarakBritt Barak

Figure 8

Android AcademyWomen Techmakers

Page 4: Session #5  content providers

Jonathan Yarkoni

Android Developer & Advocate Ironsource

Android Academy Staff

Yonatan LevinGoogle Developer

Expert & Android @ Gett

Britt BarakAndroid Lead

Figure8

Yossi SegevAndroid Developer

Crave

Page 6: Session #5  content providers

What Do We Do?

●Android Fundamentals

●Android UI / UX

●Community Hackathon

●Android Performance

●Mentors Program●Active community

Page 7: Session #5  content providers

Community Mentors

Erez Pickman

Page 8: Session #5  content providers

Data In Apps

Page 9: Session #5  content providers
Page 10: Session #5  content providers

Application

Content Provider

Database (SQLite)

Road Map

Page 11: Session #5  content providers

Road Map

Last time:Activity Life Cycle

Today: Data Integration

Next time:Load Data To Activity

Page 12: Session #5  content providers

Database

Page 13: Session #5  content providers

Database

Database: A structured set of data

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

Page 14: Session #5  content providers

When to use a Database

- Large datasets- Structured data- Cache / Preload data

Better loading times, Better battery utilization

Page 15: Session #5  content providers

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/

Page 16: Session #5  content providers

Our Database

Most widely deployed database:Every Android deviceEvery iPhone and iOS deviceEvery MacEvery Windows 10 machineEvery Firefox, Chrome, and Safari web browserEvery instance of SkypeEvery instance of iTunesEvery Dropbox clientMost television sets and set-top cable boxesMost automotive multimedia systemsCountless millions of other applicationsSource: https://www.sqlite.org/mostdeployed.html

Page 17: Session #5  content providers

SQL - Structured Query Language

the language to communicate with

database.

Page 18: Session #5  content providers

Consider 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 1/11 Intro And Basics Yonatan 29

2 8/11 Basics and ListViews Britt 29

3 13/11 Off threading Yarkoni 34

4 20/11 New Activities and Intents Yonatan 34

5 27/11 Lifecycles Yarkoni 34

6 4/12 Databases and stuff Britt Unknown

7 11/12 Loaders Yossi Unknown

Page 19: Session #5  content providers

Actions:

- Select - gets part of the table- Update - modifies values in existing records- Insert - Adds records to tables.- Delete - Removes records from tables.

Page 20: Session #5  content providers

Select - gets part of the 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

Page 21: Session #5  content providers

Select

SELECT * FROM Sessions WHERE floor=’34’

_ID Date* Title Lecturer Floor

3 13/11 Off threading Yarkoni 34

4 20/11 New Activities and Intents Yonatan 34

5 27/11 Lifecycles Yarkoni 34

Page 22: Session #5  content providers

Select

SELECT Date, Title FROM Sessions WHERE floor=’34’

Date* Title

13/11 Off threading

20/11 New Activities and Intents

27/11 Lifecycles

Page 23: Session #5  content providers

Select

SELECT Date, Title FROM Sessions WHERE floor=’34’ORDER BY date DESC

Date* Title

27/11 Lifecycles

20/11 New Activities and Intents

13/11 Off threading

Page 24: Session #5  content providers

Select

SELECT * FROM Sessions WHERE title LIKE ’% and %’

_ID Date* Title Lecturer Floor

1 1/11 Intro and Basics Yonatan 29

2 8/11 Basics and ListViews Britt 29

4 20/11 New Activities and Intents Yonatan 34

6 4/12 Databases and stuff Britt Unknown

Page 25: Session #5  content providers

Select

SELECT * FROM Sessions WHERE lecturer LIKE ’Yo%’

_ID Date* Title Lecturer Floor

1 1/11 Intro And Basics Yonatan 29

4 20/11 New Activities and Intents Yonatan 34

7 11/12 Loaders Yossi Unknown

Page 26: Session #5  content providers

Select

SELECT * FROM Sessions WHERE lecturer LIKE ’Yo%’AND floor = ‘34’

_ID Date* Title Lecturer Floor

4 20/11 New Activities and Intents Yonatan 34

Page 27: Session #5  content providers

Select

SELECT * FROM Sessions_ID Date* Title Lecturer Floor

1 1/11 Intro And Basics Yonatan 29

2 8/11 Basics and ListViews Britt 29

3 13/11 Off threading Yarkoni 34

4 20/11 New Activities and Intents Yonatan 34

5 27/11 Lifecycles Yarkoni 34

6 4/12 Databases and stuff Britt Unknown

7 11/12 Loaders Yossi Unknown

Page 28: Session #5  content providers

UPDATE (table) SET (column)=(expr) [, (column)=(expr) [,...]WHERE (condition)

Update - Modifies values in existing records.

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

Page 29: Session #5  content providers

UPDATE Sessions SET lecturer=’Britt’, floor=29WHERE _id = 6

Update

_ID Date* Title Lecturer Floor

6 4/12 Databases and stuff NULL NULL

_ID Date* Title Lecturer Floor

6 4/12 Databases and stuff Britt 29

Page 30: Session #5  content providers

INSERT INTO (table) ((column) [, (column) [,...]])VALUES

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

Insert - Adds records to tables

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

Page 31: Session #5  content providers

INSERT INTO Sessions (date, title, lecturer, floor)

VALUES (‘18/12’, ‘Rich and responsive layout’, null, null)

Inserting Data: Insert

Page 32: Session #5  content providers

Inserting Data: Insert

_ID Date* Title Lecturer Floor

1 1/11 Intro And Basics Yonatan 29

2 8/11 Basics and ListViews Britt 29

3 13/11 Off threading Yarkoni 34

4 20/11 New Activities and Intents Yonatan 34

5 27/11 Lifecycles Yarkoni 34

6 4/12 Databases and stuff Britt 29

7 11/12 Loaders Yossi Unknown

8 18/12 Rich and responsive layouts null null

Page 33: Session #5  content providers

Delete - Removes records from tables.

DELETE FROM (table) WHERE (condition)

Page 34: Session #5  content providers

Deleting Data: Delete

DELETE FROM Sessions WHERE floor = 29_ID Date* Title Lecturer Floor

1 1/11 Intro And Basics Yonatan 29

2 8/11 Basics and ListViews Britt 29

3 13/11 Off threading Yarkoni 34

4 20/11 New Activities and Intents Yonatan 34

5 27/11 Lifecycles Yarkoni 34

6 4/12 Databases and stuff Britt 34

7 11/12 Loaders Yossi Unknown

8 18/12 Rich and responsive layouts null null

Page 35: Session #5  content providers

Be CRUD!

Basic operations of persisting data

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

Page 36: Session #5  content providers

Be CRUD!

What’s important for us to know after each operation?Create - ID of the new recordRead - the record-setUpdate - # changed recordsDelete - # deleted records

Page 37: Session #5  content providers

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

Page 38: Session #5  content providers

Delete a table.

DROP TABLE IF EXISTS (table)

Page 39: Session #5  content providers

Things I didn’t talk about

Using multiple tables, defining relationsDesigning databases (3NF, 5NF, Inheritance modeling)

Using indexing to improve performanceThread-safety, reader/writers, locking, ...Triggers, ViewsTransactions

Page 40: Session #5  content providers

Major Challenges (For us)Avoid SQL Injection

Upgrade when needed

Being up-to-date

Page 41: Session #5  content providers

SQL Injection

Page 42: Session #5  content providers

What is SQL Injection?

Users:Username Password

guest 1234

admin VeryHardPassword

Britt Dre@mB!g

Yonatan I3>starWars

Yossi @ndr0id

Yarkoni BBB4Ever!

Page 43: Session #5  content providers

How to log in?

Page 44: Session #5  content providers

Try : Strings concatenation

“SELECT * “ + “FROM users “ +“WHERE (username=’” + username + ”’) and (password=’” + password + “‘)“

Page 45: Session #5  content providers

Try 1: Strings concatenation

For username = “guest” and password = “1234”:

“SELECT * “ + “FROM users “ +“WHERE (username=’” + username + ”’) and (password=’” + password + “‘)“

Page 46: Session #5  content providers

Try 1: Strings concatenation

For username = “guest” and password = “1234”:

SELECT * FROM usersWHERE (username=’guest’) and (password=’1234‘)

Page 47: Session #5  content providers

Try 2: Strings concatenation

For username = “admin” and password = “a or (1=1)) --”:

“SELECT * “ + “FROM users “ +“WHERE (username=’” + username + ”’) and (password=’” + password + “‘)“

Page 48: Session #5  content providers

Try 2: Strings concatenation

For username = “admin” and password = “a or (1=1)) --”:

SELECT * FROM usersWHERE (username=’admin’) and (password=’a or (1=1)) -- ‘)Read more: https://en.wikipedia.org/wiki/SQL_injection

Page 49: Session #5  content providers

Solution: Use Query Parameters

Use this string: “SELECT * “ + “FROM users “ +“WHERE (username=?) and (password=?)“

and pass the input as Query Parameters.

Page 50: Session #5  content providers

Solution: Use Query Parameters

For username = “admin” and password = “a or (1=1)) --”:

“SELECT * “ + “FROM users “ +“WHERE

(username=“admin”) and (password=“a or (1=1)) --”)“

Page 51: Session #5  content providers

Try 2: Strings concatenation

For username = “admin” and password = “a or (1=1)) --”:

SELECT * FROM usersWHERE (username=’admin’) and (password=’a or (1=1)) -- ‘)Read more: https://en.wikipedia.org/wiki/SQL_injection

Page 52: Session #5  content providers

Questions ?

Page 53: Session #5  content providers

Upgrade

Page 54: Session #5  content providers

Application

Content Provider

DataBase (SQLite)

SQLiteOpenHelper

Page 55: Session #5  content providers

What’s hard about Upgrades?

- keep track of schema’s version.- perform upgrade steps for each schema version.- 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.

Page 56: Session #5  content providers

Open DB if it exists

Create DB if doesn’t

Upgrade if necessary.

SQLiteOpenHelper for the rescue!

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

Page 57: Session #5  content providers

Subclass by implementing:- onCreate(SQLiteDatabase)- onUpgrade(SQLiteDatabase, int, int).

SQLiteOpenHelper for the rescue!

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

Page 58: Session #5  content providers

Easier for ContentProvidersto defer opening and upgrading the database until first use,to avoid blocking application startup with long-running database upgrades.

SQLiteOpenHelper for the rescue!

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

Page 59: Session #5  content providers

You have 2 methods that you use to get a SQLiteDatabase:getReadableDatabase() and getWritableDatabase()

SQLiteOpenHelper for the rescue!

Page 60: Session #5  content providers

Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy)

long insert(String table, String nullColumnHack, ContentValues values)

What To Do With SQLiteDatabase ?

int update(String table, ContentValues values, String whereClause, String[] whereArgs)

int delete(String table, String whereClause, String[] whereArgs)

Page 61: Session #5  content providers

Don’t hard-code Strings

Page 62: Session #5  content providers

Don’t hard-code Strings

- Most Strings will be reused.- Access same resource from different objects

Good practice: extract them into a single Contract class.

Page 63: Session #5  content providers

Don’t hard-code Strings

The contract class contains:- Table and column names- URI Related stuff, such as

- Content Authorities

- Build methods

- Parse methods

Page 64: Session #5  content providers

Developer Responsibilities

Page 65: Session #5  content providers

Developer Responsibilities

1.extend SQLiteOpenHelper2.Implement onCreate and onUpdate3.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

4.Extract a contract (Optionally)

Page 66: Session #5  content providers

1.Extend SQLiteOpenHelper

public class AndroidAcademyDatabaseHelper extends SQLiteOpenHelper {

}

Page 67: Session #5  content providers

2.onCreate and onUpgradepublic class AndroidAcademyDatabaseHelper extends SQLiteOpenHelper {

@Override public void onCreate(SQLiteDatabase db) {

}

@Override public void onUpgrade(SQLiteDatabase db, int oldVersion,

int newVersion) {

}}

Page 68: Session #5  content providers

2.onCreate and onUpgradepublic 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); }}

Page 69: Session #5  content providers

2.onCreate and onUpdatepublic class AndroidAcademyDatabaseHelper extends SQLiteOpenHelper {

@Override public void onUpgrade(SQLiteDatabase db, int oldVersion,

int newVersion) { db.execSQL("DROP TABLE IF EXISTS Sessions"); onCreate(db); }}

Page 70: Session #5  content providers

3.Constructorpublic class AndroidAcademyDatabaseHelper extends SQLiteOpenHelper {

public AndroidAcademyDatabaseHelper(Context context){ super(context, "androidacademy.db", null, 1); }

}

Page 71: Session #5  content providers

4.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"; }}

Page 72: Session #5  content providers

4.Optional: Extract Contract

then, use Contract in the helper. 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 " + ")";

Page 73: Session #5  content providers

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.

Page 74: Session #5  content providers

5.(Bonus!) implement CRUD on the Helper

Making the SQL Helper 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

Page 75: Session #5  content providers

Application

Content Provider

DataBase (SQLite)

SQLiteOpenHelper Contract

Page 76: Session #5  content providers

Any questions?

Page 77: Session #5  content providers

The Relationship Between Apps And Data

Page 78: Session #5  content providers

Application

Content Provider

DataBase (SQLite)

SQLiteOpenHelper

InsertQueryUpdateDelete

Contract

Page 79: Session #5  content providers

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

Application

Content Provider

Database

InsertQueryUpdateDelete

Data Layer:

Page 80: Session #5  content providers

- encapsulate the data- manage access to db- mechanisms for data security.

Content Provider - Role

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

Application

Content Provider

Database

InsertQueryUpdateDelete

Data Layer:

Page 81: Session #5  content providers

When to use it?

- Share data between apps:- offer complex data or files to other applications

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

- use android dbs: contacts, calendar, sms...- Android Framework:

- widgets, search, sync adapter, cursor loader

- Abstraction - Of data layer, over direct SQL access

Page 82: Session #5  content providers

1.extend ContentProvider

2.Register in Manifest

3.Use your content provider with a Content Resolver

Content Provider Recipe

Page 83: Session #5  content providers

1.extend ContentProvider

implement these methods:- query- delete- update- insert- getType- onCreate

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

Page 84: Session #5  content providers

1.extend ContentProvider

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)

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

Page 85: Session #5  content providers

query()

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);

Page 86: Session #5  content providers

public Uri insert(Uri uri, ContentValues values);

To create this URI, you can use the URI.BuildUpon.Also: getContext().getContentResolver().notifyChange(uri, null);

insert()

Page 87: Session #5  content providers

update()

public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)

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

Page 88: Session #5  content providers

delete()

public int delete(Uri uri, String selection,

String[] selectionArgs)

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

Page 89: Session #5  content providers

Returns a string, that identifies your data structure.

Format:

getType()

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

itemdir /vnd.<name>.<type>

For single Items

For sets

Usually, Your app namespace

Depends on the URI

vnd.android.cursor.

Page 90: Session #5  content providers

onCreate

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

Page 91: Session #5  content providers

1.extend ContentProvider

2.Register in Manifest

3.Use your content provider with a Content Resolver

Content Provider Recipe

Page 92: Session #5  content providers

2.Register in Manifest

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.androidacademy.session5.nosql.provider" android:name=".NoSQLContentProvider" />

<provider android:authorities="com.example.androidacademy.session5.sql.provider" android:name=".SQLContentProvider" />

Page 93: Session #5  content providers

1.extend ContentProvider

2.Register in Manifest

3.Use your content provider with a Content Resolver

Content Provider Recipe

Page 94: Session #5  content providers

3.Using it (Part 1)

NEVER use a ContentProvider directly!

Ask a ContentResolver to do the work for you,which has all CRUD methods you’ll need.

Page 95: Session #5  content providers

Application

ContentResolver

Content Provider

DataBase (SQLite)

SQLiteOpenHelper

InsertQueryUpdateDelete

Contract

Page 96: Session #5  content providers

3.Using it (Part 1)

public void refresh(View view) { Cursor cursor = getContentResolver() .query(SQLProviderContract.NumberEntries.CONTENT_URI, null, null, null, null); adapter.swapCursor(cursor);}

Page 97: Session #5  content providers

3.Using it (Part 1)

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);}

Page 98: Session #5  content providers

3.Using it (Part 2) - Next Session

Next timeWe’ll see how to properly use a content provider to:

keep your activities synced with the dataperform loading on a background thread keep it safe with the Activity Lifecycle.

Page 99: Session #5  content providers

Any questions?

Page 100: Session #5  content providers

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.

Page 101: Session #5  content providers

So why UDACITY’s code

is so complicated?

Page 102: Session #5  content providers

3 things to spice-up your Provider

URI MatchingHandling URIsSQLiteQueryBuilder

Page 103: Session #5  content providers

URI Matching

Supporting a few kinds of URIs

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

Each is treated differently

Page 104: Session #5  content providers

URI Matching

Each URI identifies with an IDTo choose the treatment

Page 105: Session #5  content providers

URI Matcher

URI URI IDURIMatcher

Page 106: Session #5  content providers

But There Can Be Countless URIs!content://com.example.android.sunshine.app/weather/

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

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

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

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

Page 107: Session #5  content providers

URI Matcher - Set Up

matcher.addURI(authority, URI_TEMPLATE, ID);

URI Template ID

WeatherContract.PATH_WEATHER WEATHER

WeatherContract.PATH_WEATHER + "/*"

WEATHER_WITH_LOCATION

WeatherContract.PATH_WEATHER + "/*/#"

WEATHER_WITH_LOCATION_AND_DATE

Page 108: Session #5  content providers

URI Matcher - Useage

final int match = sUriMatcher.match(uri);

URI URI IDURIMatcher

Page 109: Session #5  content providers

URI Matching

What does it mean for each Content Provider method?- query- delete- update- insert- getType

Page 110: Session #5  content providers

Application

ContentResolver

Content Provider

DataBase (SQLite)

SQLiteOpenHelperContract

URIMatcherInsertQueryUpdateDelete

Page 111: Session #5  content providers

Pro Tip: Get Parameters from URI

uri.getPathSegments().get(1) → content://authority/words/party

uri.getQueryParameter(“q”)

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

Page 112: Session #5  content providers

Any questions?

Page 113: Session #5  content providers

Thank you!!