Upload
vitali-pekelis
View
126
Download
1
Embed Size (px)
Citation preview
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
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
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
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
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
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
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.
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.
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