better data persistence on android
TRANSCRIPT
![Page 1: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/1.jpg)
Better Data Persistence on Android
Eric MaxwellCredible Software
![Page 2: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/2.jpg)
What to Expect
• Android Storage Options
• Focus on SQLite / Database Storage
• Make it better using the Realm Mobile Database
![Page 3: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/3.jpg)
Android Storage Options
• Shared Preferences
![Page 4: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/4.jpg)
Android Storage Options
• Shared Preferences
• Disk Storage • Internal • External
![Page 5: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/5.jpg)
Android Storage Options
• Shared Preferences
• Disk Storage • Internal • External
• Relational Database w/ SQLite
![Page 6: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/6.jpg)
SQLite
• Relational Database on the device • android.database.sqlite.SQLiteDatabase
• Perform insert, query, update, delete, begin/end transaction
• android.database.sqlite.SQLiteOpenHelper • Handle Create, Upgrade events
![Page 7: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/7.jpg)
Nae Nae Assistant App
• List of Nae Nae Lyrics
• Add New
• Delete
• Persistent across launches
![Page 8: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/8.jpg)
View Layout<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" > <GridLayout android:id="@+id/lyricButtons" android:layout_width="fill_parent" android:layout_height="wrap_content" android:padding="10dp"> <Button android:id="@+id/watch_me" android:text="@string/lyric_watch_me" android:onClick="add"/> <Button android:id="@+id/whip" android:text="@string/lyric_whip" android:onClick="add"/> <Button android:id="@+id/nea" android:text="@string/lyric_nea" android:onClick="add"/> <Button android:id="@+id/bop" android:text="@string/lyric_bop" android:onClick="add"/> </GridLayout> <ListView android:id="@android:id/list" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/hello_world" /></LinearLayout>
![Page 9: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/9.jpg)
Simple Data Objectpublic class Lyric { private long id; private String lyricText;
// Getters / Setters...
// Will be used by the ArrayAdapter in the ListView @Override public String toString() { return lyricText; } }
![Page 10: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/10.jpg)
Activity - Lifecycle Eventspublic class NaeNaeActivity extends ListActivity { private LyricDataSource datasource; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_naenae); datasource = new LyricDataSource(this); datasource.open(); List<Lyric> values = datasource.getAllLyrics(); // Use the SimpleCursorAdapter to show the // elements in a ListView ArrayAdapter<Lyric> adapter = new ArrayAdapter<Lyric>(this, android.R.layout.simple_list_item_1, values); setListAdapter(adapter); } @Override protected void onResume() { super.onResume(); datasource.open(); } @Override protected void onPause() { super.onPause(); datasource.close(); } ... }
The datasource gets initialized and opened on create and is then used to
get all of the lyrics and placed into an array
adapter.
The datasource is opened and closed on resume and
stop.
![Page 11: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/11.jpg)
Activity - Events Add/Removepublic void add(View view) { String lyricText = ((Button) view).getText().toString(); if (!TextUtils.isEmpty(lyricText)) { ArrayAdapter<Lyric> adapter = getListAdapter(); Lyric lyric = null; lyric = datasource.createLyric(lyricText); adapter.add(lyric); }} public void delete (int position) { if (getListAdapter().getCount() > 0) { ArrayAdapter<Lyric> adapter = getListAdapter(); Lyric lyric = adapter.getItem(position); datasource.deleteLyric(lyric); adapter.remove(lyric); }} @Override@SuppressWarnings("unchecked") public ArrayAdapter<Lyric> getListAdapter() { return (ArrayAdapter<Lyric>) super.getListAdapter();}
Add
Remove
![Page 12: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/12.jpg)
Custom SQLiteOpenHelperpublic class MySQLiteHelper extends SQLiteOpenHelper { public static final String TABLE_LYRICS = "lyrics"; public static final String COLUMN_ID = "_id"; public static final String COLUMN_LYRIC = "lyric"; private static final String DATABASE_NAME = "lyric.db"; private static final int DATABASE_VERSION = 1; // Database creation sql statement private static final String DATABASE_CREATE = "create table " + TABLE_LYRICS + "( " + COLUMN_ID + " integer primary key autoincrement, " + COLUMN_LYRIC + " text not null);"; public MySQLiteHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase database) { database.execSQL(DATABASE_CREATE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.w(MySQLiteHelper.class.getName(), "Upgrading database from version " + oldVersion + " to " + newVersion + ", which will destroy all old data"); db.execSQL("DROP TABLE IF EXISTS " + TABLE_LYRICS); onCreate(db); } }
![Page 13: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/13.jpg)
Lyric Datasourcepublic class LyricDataSource { // Database fields private SQLiteDatabase database; private MySQLiteHelper dbHelper; private String[] allColumns = { MySQLiteHelper.COLUMN_ID, MySQLiteHelper.COLUMN_LYRIC}; public LyricDataSource(Context context) { dbHelper = new MySQLiteHelper(context); } public void open() throws SQLException { database = dbHelper.getWritableDatabase(); } public void close() { dbHelper.close(); }
public List<Lyric> getAllLyrics() { List<Lyric> lyrics = new ArrayList<Lyric>(); Cursor cursor = database.query(MySQLiteHelper.TABLE_LYRICS, allColumns, null, null, null, null, MySQLiteHelper.COLUMN_ID); cursor.moveToFirst(); while (!cursor.isAfterLast()) { Lyric lyric = cursorToLyric(cursor); lyrics.add(lyric); cursor.moveToNext(); } // Make sure to close the cursor cursor.close(); return lyrics;}
public Lyric createLyric(String lyric) { ContentValues values = new ContentValues(); values.put(MySQLiteHelper.COLUMN_LYRIC, lyric); long insertId = database.insert(MySQLiteHelper.TABLE_LYRICS, null, values); Cursor cursor = database.query(MySQLiteHelper.TABLE_LYRICS, allColumns, MySQLiteHelper.COLUMN_ID + " = " + insertId, null, null, null, null); cursor.moveToFirst(); Lyric newLyric = cursorToLyric(cursor); cursor.close(); return newLyric;} public void deleteLyric(Lyric lyric) { long id = lyric.getId(); database.delete(MySQLiteHelper.TABLE_LYRICS, MySQLiteHelper.COLUMN_ID + " = " + id, null);} public Lyric getLyricByValue(String lyricText) { Lyric lyric = null; Cursor cursor = database.query(MySQLiteHelper.TABLE_LYRICS, allColumns, MySQLiteHelper.COLUMN_LYRIC + " = '" + lyricText + "'" , null, null, null, null); cursor.moveToFirst(); if (!cursor.isAfterLast()) { lyric = cursorToLyric(cursor); } // Make sure to close the cursor cursor.close(); return lyric;}
private Lyric cursorToLyric(Cursor cursor) { Lyric lyric = new Lyric(); lyric.setId(cursor.getLong(0)); lyric.setLyricText(cursor.getString(1)); return lyric;}
![Page 14: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/14.jpg)
Using SQLite Pros
• No additional dependencies
• Built in programatic transaction management
• Built in creation and upgrade event handling
![Page 15: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/15.jpg)
What happens when you add a couple fields?public class MySQLiteHelper extends SQLiteOpenHelper { public static final String TABLE_LYRICS = "lyrics"; public static final String COLUMN_ID = "_id"; public static final String COLUMN_LYRIC = "lyric"; private static final String DATABASE_NAME = "lyric.db"; private static final int DATABASE_VERSION = 1; // Database creation sql statement private static final String DATABASE_CREATE = "create table " + TABLE_LYRICS + "( " + COLUMN_ID + " integer primary key autoincrement, " + COLUMN_LYRIC + " text not null);"; public MySQLiteHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase database) { database.execSQL(DATABASE_CREATE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.w(MySQLiteHelper.class.getName(), "Upgrading database from version " + oldVersion + " to " + newVersion + ", which will destroy all old data"); db.execSQL("DROP TABLE IF EXISTS " + TABLE_LYRICS); onCreate(db); } }
![Page 16: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/16.jpg)
Update the data modelpublic class Lyric { private long id; private String lyricText; private String source; private boolean isChorus; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getLyricText() { return lyricText; } public void setLyricText(String lyricText) { this.lyricText = lyricText; }
// Additional Getter/Setters // Will be used by the ArrayAdapter in the ListView @Override public String toString() { return lyricText; } }
![Page 17: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/17.jpg)
Update the Datasourcepublic class LyricDataSource { // Database fields private SQLiteDatabase database; private MySQLiteHelper dbHelper; private String[] allColumns = { MySQLiteHelper.COLUMN_ID, MySQLiteHelper.COLUMN_LYRIC, MySQLiteHelper.COLUMN_SOURCE, MySQLiteHelper.COLUMN_CHORUS}; public LyricDataSource(Context context) { dbHelper = new MySQLiteHelper(context); } public void open() throws SQLException { database = dbHelper.getWritableDatabase(); } public void close() { dbHelper.close(); }
public List<Lyric> getAllLyrics() { List<Lyric> lyrics = new ArrayList<Lyric>(); Cursor cursor = database.query(MySQLiteHelper.TABLE_LYRICS, allColumns, null, null, null, null, MySQLiteHelper.COLUMN_ID); cursor.moveToFirst(); while (!cursor.isAfterLast()) { Lyric lyric = cursorToLyric(cursor); lyrics.add(lyric); cursor.moveToNext(); } // Make sure to close the cursor cursor.close(); return lyrics;}
public Lyric createLyric(String lyric) { ContentValues values = new ContentValues(); values.put(MySQLiteHelper.COLUMN_LYRIC, lyric); values.put(MySQLiteHelper.COLUMN_CHORUS, isChorus ? 1 : 0); values.put(MySQLiteHelper.COLUMN_SOURCE, source); long insertId = database.insert(MySQLiteHelper.TABLE_LYRICS, null, values); Cursor cursor = database.query(MySQLiteHelper.TABLE_LYRICS, allColumns, MySQLiteHelper.COLUMN_ID + " = " + insertId, null, null, null, null); cursor.moveToFirst(); Lyric newLyric = cursorToLyric(cursor); cursor.close(); return newLyric;} public void deleteLyric(Lyric lyric) { long id = lyric.getId(); database.delete(MySQLiteHelper.TABLE_LYRICS, MySQLiteHelper.COLUMN_ID + " = " + id, null); } public Lyric getLyricByValue(String lyricText) { Lyric lyric = null; Cursor cursor = database.query(MySQLiteHelper.TABLE_LYRICS, allColumns, MySQLiteHelper.COLUMN_LYRIC + " = '" + lyricText + "'" , null, null, null, null); cursor.moveToFirst(); if (!cursor.isAfterLast()) { lyric = cursorToLyric(cursor); } // Make sure to close the cursor cursor.close(); return lyric;}
private Lyric cursorToLyric(Cursor cursor) { Lyric lyric = new Lyric(); lyric.setId(cursor.getLong(0)); lyric.setLyricText(cursor.getString(1)); lyric.setChorus(cursor.getInt(2) == 1); lyric.setSource(cursor.getString(3)); return lyric;}
![Page 18: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/18.jpg)
Add the columns to our SQLiteOpenHelperpublic class MySQLiteHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "lyric.db"; private static final int DATABASE_VERSION = 1; public static final String COLUMN_ID = "_id"; public static final String TABLE_LYRICS = "lyrics"; public static final String COLUMN_LYRIC = "lyric"; public static final String COLUMN_SOURCE = "source"; public static final String COLUMN_CHORUS = "isChorus"; // Database creation sql statement private static final String DATABASE_CREATE = "create table " + TABLE_LYRICS + "( " + COLUMN_ID + " integer primary key autoincrement, “ + COLUMN_LYRIC + " text not null, " + COLUMN_CHORUS + " text, " + COLUMN_SOURCE + " integer, " + " ); " ; ...}
![Page 19: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/19.jpg)
How about a second tablepublic class MySQLiteHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "lyric.db"; private static final int DATABASE_VERSION = 1; public static final String COLUMN_ID = "_id"; public static final String TABLE_LYRICS = "lyrics"; public static final String COLUMN_LYRIC = "lyric"; public static final String COLUMN_SOURCE = "source"; public static final String COLUMN_CHORUS = "isChorus"; public static final String TABLE_ARTISTS = "artists"; public static final String COLUMN_ARTIST_NAME = "name"; public static final String COLUMN_ARTIST_LABEL = "label"; // Database creation sql statement private static final String DATABASE_CREATE = "create table " + TABLE_LYRICS + "( " + COLUMN_ID + " integer primary key autoincrement, " + COLUMN_LYRIC + " text not null, " + COLUMN_CHORUS + " text, " + COLUMN_SOURCE + " integer " + " ); " + "create table " + TABLE_ARTISTS + "( " + COLUMN_ID + " integer primary key autoincrement, " + COLUMN_ARTIST_NAME + " text not null, " + COLUMN_ARTIST_LABEL + " text " + " ); " ; ...}
![Page 20: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/20.jpg)
Or a many to many relationshippublic class MySQLiteHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "lyric.db"; private static final int DATABASE_VERSION = 1; public static final String COLUMN_ID = "_id"; public static final String TABLE_LYRICS = "lyrics"; public static final String COLUMN_LYRIC = "lyric"; public static final String COLUMN_SOURCE = "source"; public static final String COLUMN_CHORUS = "isChorus"; public static final String TABLE_ARTISTS = "artists"; public static final String COLUMN_ARTIST_NAME = "name"; public static final String COLUMN_ARTIST_LABEL = “label";
public static final String TABLE_LYRIC_ARTIST = "lyric_artist";public static final String COLUMN_LYRIC_ID = "lyric_id";public static final String COLUMN_ARTIST_ID = "artist_id"; // Database creation sql statement private static final String DATABASE_CREATE = "create table " + TABLE_LYRICS + "( " + COLUMN_ID + " integer primary key autoincrement, " + COLUMN_LYRIC + " text not null, " + COLUMN_CHORUS + " text, " + COLUMN_SOURCE + " integer " + " ); " + "create table " + TABLE_ARTISTS + "( " + COLUMN_ID + " integer primary key autoincrement, " + COLUMN_ARTIST_NAME + " text not null, " + COLUMN_ARTIST_LABEL + " text " + " ); “
+ "create table "+ TABLE_LYRIC_ARTIST + "( "+ COLUMN_LYRIC_ID + " integer, "+ COLUMN_ARTIST_ID + " integer, "+ " FOREIGN KEY(" + COLUMN_ARTIST_ID + ") REFERENCES " + TABLE_ARTISTS + "(" + COLUMN_ID + "), "+ " FOREIGN KEY(" + COLUMN_LYRIC_ID + ") REFERENCES " + TABLE_LYRICS + "(" + COLUMN_ID + ") "+ " ); " ;
...}
![Page 21: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/21.jpg)
Using SQLite Cons
• Queries / Updates are very tedious
• Changes become cumbersome
• Relationships between an object graph must be manually managed
![Page 22: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/22.jpg)
Realm Mobile Database Replacement
![Page 23: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/23.jpg)
About Realm
• Realm is a Mobile Database:
• A replacement for Core Data or SQLite
• Feels like an Object Relational Mapping tool
• Free & Open Source Apache 2.0
• Comparable Performance to SQLite
![Page 27: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/27.jpg)
How it works
![Page 28: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/28.jpg)
Realm Objects Look like Regular Objects…public class Dog extends RealmObject { private String name; private int age; @ignored private int dontPersistMe; // + Standard setters and getters here}
@RealmClass public class Person implements RealmModel { private String name; private RealmList<Dog> dogs; }
![Page 29: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/29.jpg)
Easy to add / relate objectstry(Realm realm = Realm.getInstance(this.getContext())) { realm.beginTransaction(); Dog dog = realm.createObject(Dog.class); dog.setName("Rex"); dog.setAge(3); Person person = realm.createObject(Person.class); person.setName("Tim"); person.getDogs().add(dog); realm.commitTransaction();}
![Page 30: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/30.jpg)
Updates must be wrapped in transactionstry(Realm realm = Realm.getInstance(this.getContext())) { realm.beginTransaction(); Dog dog = realm.createObject(Dog.class); dog.setName("Rex"); dog.setAge(3); Person person = realm.createObject(Person.class); person.setName("Tim"); person.getDogs().add(dog); realm.commitTransaction();}
![Page 31: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/31.jpg)
Updates must be wrapped in transactionstry(Realm realm = Realm.getInstance(this.getContext())) { realm.beginTransaction(); Dog dog = realm.createObject(Dog.class); dog.setName("Rex"); dog.setAge(3); Person person = realm.createObject(Person.class); person.setName("Tim"); person.getDogs().add(dog); realm.commitTransaction();}
If an exception occurs, you must catch it and rollback with realm.cancelTransaction( );
![Page 32: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/32.jpg)
1 to Manytry(Realm realm = Realm.getInstance(this.getContext())) { realm.beginTransaction(); Dog dog = realm.createObject(Dog.class); dog.setName("Rex"); dog.setAge(3); Person person = realm.createObject(Person.class); person.setName("Tim"); person.getDogs().add(dog); realm.commitTransaction();}
1 to Many
![Page 33: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/33.jpg)
Many to Manytry(Realm realm = Realm.getInstance(this.getContext())) { realm.beginTransaction(); Dog rex = realm.createObject(Dog.class); rex.setName("Rex"); rex.setAge(3); Dog spot = realm.createObject(Dog.class); spot.setName("Spot"); spot.setAge(4);
Person tim = realm.createObject(Person.class); tim.setName(“Tim"); tim.getDogs().addAll(Arrays.asList(spot, rex));
Person mary = realm.createObject(Person.class); mary.setName("Mary"); mary.getDogs().addAll(Arrays.asList(spot, rex));
rex.getOwners().addAll(Arrays.asList(tim, mary)); spot.getOwners().addAll(Arrays.asList(tim, mary)); realm.commitTransaction();}
Many to Many
![Page 34: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/34.jpg)
Querying// Queries uses Builder pattern to build up the query conditions // For this example, assume Dog has a property “Person owner”RealmResults<Dog> results = realm.where(Dog.class) .greaterThan("age", 8) .equalTo("owner.name", "Tim") .findAll();
// Queries are chainableRealmResults<Dog> allRex = results.where() .contains("name", "rex") .findAll();
// Or they can be combinedRealmResults<Dog> result2 = realm.where(Dog.class) .greaterThan("age", 8)
.equalTo(“owner.name", "Tim") .contains("name", "rex") .findAll();
![Page 35: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/35.jpg)
More Queries// Logical Query Grouping boolean checkCase = false; RealmResults<Dog> r = realm.where(Dog.class) .greaterThan("age", 8) //implicit AND .beginGroup() .equalTo("name", “CuJo”, checkCase) .or() .contains("name", "rex") .endGroup() .findAll();
// Sorting RealmResults<Dog> result = r.where(Dog.class).findAll();result.sort("age"); // Sort ascendingresult.sort("age", RealmResults.SORT_ORDER_DESCENDING);
// Result Aggregation long sum = result.sum("age").longValue();long min = result.min("age").longValue();long max = result.max("age").longValue();double average = result.average("age");long matches = result.size();
![Page 36: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/36.jpg)
Query Conditions
• between, greaterThan, lessThan, greaterThanOrEqualTo & lessThanOrEqualTo
• equalTo, notEqualTo, in, not & distinct
• contains, beginsWith & endsWith
• isNull & isNotNull
• isEmpty & isNotEmpty
![Page 37: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/37.jpg)
Realm Object Field Types
• Java primitives
• Wrapper types
• byte[] for blob
• Realm Object
• RealmList<? extends RealmObject>
![Page 38: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/38.jpg)
Realm Annotations
• @Required - Not Null
• @Ignore - Transient
• @PrimaryKey - string or integer (short, int or long)
• @Index - Implicitly set by Primary Key
![Page 39: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/39.jpg)
Integrating Realm
![Page 40: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/40.jpg)
Realm Installation
1. Add a classpath dependency to the project level build.grade filebuildscript { repositories { jcenter() } dependencies { classpath "io.realm:realm-gradle-plugin:2.1.1" } }
2. Apply the realm-android plugin at the app module level build.grade file
apply plugin: 'realm-android'
![Page 41: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/41.jpg)
Custom SQLiteOpenHelper Goes Away!public class MySQLiteHelper extends SQLiteOpenHelper { public static final String TABLE_LYRICS = "lyrics"; public static final String COLUMN_ID = "_id"; public static final String COLUMN_LYRIC = "lyric"; private static final String DATABASE_NAME = "lyric.db"; private static final int DATABASE_VERSION = 1; // Database creation sql statement private static final String DATABASE_CREATE = "create table " + TABLE_LYRICS + "( " + COLUMN_ID + " integer primary key autoincrement, " + COLUMN_LYRIC + " text not null);"; public MySQLiteHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase database) { database.execSQL(DATABASE_CREATE); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.w(MySQLiteHelper.class.getName(), "Upgrading database from version " + oldVersion + " to " + newVersion + ", which will destroy all old data"); db.execSQL("DROP TABLE IF EXISTS " + TABLE_LYRICS); onCreate(db); } }
![Page 42: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/42.jpg)
public class Lyric extends RealmObject { private int sortKey; @PrimaryKey private String id; @Required private String lyricText; // Getters / Setters...
// Will be used by the ArrayAdapter in the ListView @Override public String toString() { return lyricText; } }
Simple Data Object - Still Simple
RealmObject and
Annotations
![Page 43: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/43.jpg)
Lyric Datasourcepublic class LyricDataSource { // Database fields private SQLiteDatabase database; private MySQLiteHelper dbHelper; private String[] allColumns = { MySQLiteHelper.COLUMN_ID, MySQLiteHelper.COLUMN_LYRIC}; public LyricDataSource(Context context) { dbHelper = new MySQLiteHelper(context); } public void open() throws SQLException { database = dbHelper.getWritableDatabase(); } public void close() { dbHelper.close(); }
public List<Lyric> getAllLyrics() { List<Lyric> lyrics = new ArrayList<Lyric>(); Cursor cursor = database.query(MySQLiteHelper.TABLE_LYRICS, allColumns, null, null, null, null, MySQLiteHelper.COLUMN_ID); cursor.moveToFirst(); while (!cursor.isAfterLast()) { Lyric lyric = cursorToLyric(cursor); lyrics.add(lyric); cursor.moveToNext(); } // Make sure to close the cursor cursor.close(); return lyrics;}
public Lyric createLyric(String lyric) { ContentValues values = new ContentValues(); values.put(MySQLiteHelper.COLUMN_LYRIC, lyric); long insertId = database.insert(MySQLiteHelper.TABLE_LYRICS, null, values); Cursor cursor = database.query(MySQLiteHelper.TABLE_LYRICS, allColumns, MySQLiteHelper.COLUMN_ID + " = " + insertId, null, null, null, null); cursor.moveToFirst(); Lyric newLyric = cursorToLyric(cursor); cursor.close(); return newLyric;} public void deleteLyric(Lyric lyric) { long id = lyric.getId(); System.out.println("Lyric deleted with id: " + id); database.delete(MySQLiteHelper.TABLE_LYRICS, MySQLiteHelper.COLUMN_ID + " = " + id, null);} public Lyric getLyricByValue(String lyricText) { Lyric lyric = null; Cursor cursor = database.query(MySQLiteHelper.TABLE_LYRICS, allColumns, MySQLiteHelper.COLUMN_LYRIC + " = '" + lyricText + "'" , null, null, null, null); cursor.moveToFirst(); if (!cursor.isAfterLast()) { lyric = cursorToLyric(cursor); } // Make sure to close the cursor cursor.close(); return lyric;}
private Lyric cursorToLyric(Cursor cursor) { Lyric lyric = new Lyric(); lyric.setId(cursor.getLong(0)); lyric.setLyricText(cursor.getString(1)); return lyric;}
![Page 44: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/44.jpg)
Lyric Datasourceprivate Realm r; public LyricDataSource(Realm realm) { r = realm;}
public void deleteAllLyrics() { r.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.delete(Lyric.class); } });}
public Lyric getLyricByValue(String lyricText) { return r.where(Lyric.class) .equalTo("lyricText", lyricText) .findFirst();}
public List<Lyric> getAllLyrics() { return r.where(Lyric.class) .findAllSorted("sortKey"); }
public Lyric createLyric(String lyricString) {
final Lyric lyric = new Lyric(); lyric.setId(UUID.randomUUID().toString()); lyric.setLyricText(lyricString); lyric.setSortKey(System.currentTimeMillis()); r.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { lyric = realm.copyToRealm(lyric); } }); return lyric;}
public void deleteLyric(Lyric lyric) { r.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { realm.where(Lyric.class) .equalTo("id", lyric.getId()) .findAll() .deleteAllFromRealm(); } });}
![Page 45: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/45.jpg)
Application / Activity - Lifecycle Eventspublic class TestDatabaseActivity extends ListActivity { private LyricDataSource datasource; private Realm realm; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);
realm = Realm.getDefaultInstance(); datasource = new LyricDataSource(realm); List<Lyric> values = datasource.getAllLyrics(); // Use the SimpleCursorAdapter to show the // elements in a ListView ArrayAdapter<Lyric> adapter = new ArrayAdapter<Lyric>(this, android.R.layout.simple_list_item_1, values); setListAdapter(adapter); } @Override protected void onDestroy() { super.onDestory(); realm.close(); }
... }
public class NaeNaeApplication extends Application { @Override public void onCreate() { super.onCreate(); Realm.init(this); RealmConfiguration realmConfiguration = new RealmConfiguration.Builder() .deleteRealmIfMigrationNeeded() .build(); Realm.setDefaultConfiguration(realmConfiguration); }}
![Page 46: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/46.jpg)
Application / Activity - Lifecycle Eventspublic class TestDatabaseActivity extends ListActivity { private LyricDataSource datasource; private Realm realm; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);
realm = Realm.getDefaultInstance(); datasource = new LyricDataSource(realm); List<Lyric> values = datasource.getAllLyrics(); // Use the SimpleCursorAdapter to show the // elements in a ListView ArrayAdapter<Lyric> adapter = new ArrayAdapter<Lyric>(this, android.R.layout.simple_list_item_1, values); setListAdapter(adapter); } @Override protected void onDestroy() { super.onDestory(); realm.close(); }
... }
Keep reference
Get new instance of realm datasource
on create and pass to the datasource to
use.
1
public class NaeNaeApplication extends Application { @Override public void onCreate() { super.onCreate(); Realm.init(this); RealmConfiguration realmConfiguration = new RealmConfiguration.Builder() .deleteRealmIfMigrationNeeded() .build(); Realm.setDefaultConfiguration(realmConfiguration); }}
![Page 47: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/47.jpg)
Application / Activity - Lifecycle Eventspublic class TestDatabaseActivity extends ListActivity { private LyricDataSource datasource; private Realm realm; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);
realm = Realm.getDefaultInstance(); datasource = new LyricDataSource(realm); List<Lyric> values = datasource.getAllLyrics(); // Use the SimpleCursorAdapter to show the // elements in a ListView ArrayAdapter<Lyric> adapter = new ArrayAdapter<Lyric>(this, android.R.layout.simple_list_item_1, values); setListAdapter(adapter); } @Override protected void onDestroy() { super.onDestory(); realm.close(); }
... }
Keep reference
Get new instance of realm datasource
on create and pass to the datasource to
use.
For getDefaultInstance to work, we must
set the default configuration once
1
2
public class NaeNaeApplication extends Application { @Override public void onCreate() { super.onCreate(); Realm.init(this); RealmConfiguration realmConfiguration = new RealmConfiguration.Builder() .deleteRealmIfMigrationNeeded() .build(); Realm.setDefaultConfiguration(realmConfiguration); }}
![Page 48: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/48.jpg)
Application / Activity - Lifecycle Eventspublic class TestDatabaseActivity extends ListActivity { private LyricDataSource datasource; private Realm realm; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main);
realm = Realm.getDefaultInstance(); datasource = new LyricDataSource(realm); List<Lyric> values = datasource.getAllLyrics(); // Use the SimpleCursorAdapter to show the // elements in a ListView ArrayAdapter<Lyric> adapter = new ArrayAdapter<Lyric>(this, android.R.layout.simple_list_item_1, values); setListAdapter(adapter); } @Override protected void onDestroy() { super.onDestory(); realm.close(); }
... } Close on destroy
Keep reference
Get new instance of realm datasource
on create and pass to the datasource to
use.
For getDefaultInstance to work, we must
set the default configuration once
1
2
3
public class NaeNaeApplication extends Application { @Override public void onCreate() { super.onCreate(); Realm.init(this); RealmConfiguration realmConfiguration = new RealmConfiguration.Builder() .deleteRealmIfMigrationNeeded() .build(); Realm.setDefaultConfiguration(realmConfiguration); }}
![Page 49: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/49.jpg)
Checkpoint
• Remove SQLite DataHelper
• Made our datasource code easier to read
• Reduced Code:
SQLite RealmLyric 28 40
Activity 77 72DataSource 92 61
MySQLiteHelper 41 0Application 0 21
Total 238 194
![Page 50: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/50.jpg)
Additional Realm Features
![Page 51: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/51.jpg)
Change Listeners public class MyActivity extends Activity { private Realm realm; private RealmChangeListener<RealmResults<Lyric>> listener; private RealmResults<Lyric> lyrics; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); realm = Realm.getDefaultInstance(); lyrics = realm.where(Lyric.class).contains("lyricText", "Bop").findAll(); listener = new RealmChangeListener<RealmResults<Lyric>>() { @Override public void onChange(RealmResults<Lyric> updatedResults) { // Do something with the updated results, actually the results here // match the ones stored as a reference in this activity. } }; lyrics.addChangeListener(listener); } @Override protected void onDestroy() { super.onDestroy(); realm.removeAllChangeListeners(); realm.close(); } }
![Page 52: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/52.jpg)
Async Operations // Async with Callback private RealmChangeListener callback = new RealmChangeListener() { @Override public void onChange() { // called once the query complete and on every update // use the result } }; public void onStart() { try(Realm r = Realm.getInstance(context)) { RealmResults<Lyric> result = r.where(Lyric.class).findAllAsync(); result.addChangeListener(callback); }}
// Or you can block (be careful) while (!result.isLoaded()) { // Results are now available}
// Or to block until async results are loaded result.load()
![Page 53: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/53.jpg)
Transaction Blocks & Async Transactions // Transaction will be committed after block unless exception occurs. realm.executeTransaction(new Realm.Transaction() { @Override public void execute(Realm realm) { Lyric user = realm.createObject(Lyric.class); user.setId(UUID.randomUUID().toString()); user.setLyricText("Let's get it started!"); }});
// Asynchronous Transaction realm.executeTransactionAsync(new Realm.Transaction() { @Override public void execute(Realm bgRealm) { Lyric user = bgRealm.createObject(Lyric.class); user.setId(UUID.randomUUID().toString()); user.setLyricText("Let's get it started in here"); }}, new Realm.Transaction.OnSuccess() { @Override public void onSuccess() { // Do something on the thread you were on. } }, new Realm.Transaction.OnError() { @Override public void onError(Throwable error) { // Transaction failed and was automatically canceled. // Do something on the thread you were on. } });
![Page 54: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/54.jpg)
Insert JSON Directly // Insert from a stringr.beginTransaction();r.createObjectFromJson(Lyric.class, "{ lyricText: \"Bidi-bidi-bop\", " + "id: \"864dee8a-d9ac-4e5a-a843-e051ad2d6e8a\" }");r.commitTransaction();
// Insert multiple items using a InputStreamInputStream is = new FileInputStream(new File("path_to_file"));r.beginTransaction();try { r.createAllFromJson(Lyric.class, is); r.commitTransaction();} catch (IOException e) { r.cancelTransaction();}
![Page 55: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/55.jpg)
ListAdapter & Recycler Adapters dependencies { compile ‘io.realm:android-adapters:1.4.0' }
![Page 56: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/56.jpg)
ListAdapter & Recycler Adapters dependencies { compile ‘io.realm:android-adapters:1.4.0' }
public class MyAdapter extends RealmBaseAdapter<Lyric> implements ListAdapter { private static class ViewHolder { TextView lyricTextView; } public MyAdapter(Context context, int resId, RealmResults<Lyric> realmResults, boolean automaticUpdate) { super(context, realmResults, automaticUpdate); } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder viewHolder; if (convertView == null) { convertView = inflater.inflate(android.R.layout.simple_list_item_1, parent, false); viewHolder = new ViewHolder(); viewHolder.lyricTextView = (TextView) convertView.findViewById(android.R.id.text1); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } Lyric item = realmResults.get(position); viewHolder.lyricTextView.setText(item.getLyricText()); return convertView; } public RealmResults<Lyric> getRealmResults() { return realmResults; }}
![Page 57: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/57.jpg)
ListAdapter & Recycler Adapters dependencies { compile ‘io.realm:android-adapters:1.4.0' }
MyRecyclerViewAdapter extends RealmRecyclerViewAdapter<Promotion, MyRecyclerViewAdapter.ViewHolder> { private final MyActivity activity; public RealmRecyclerViewAdapter(@NonNull MyActivity activity, @Nullable OrderedRealmCollection<Lyric> lyrics) { super(promoListActivity, lyrics, true); this.activity = activity; } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()) .inflate(R.layout.lyric_list_item, parent, false); return new ViewHolder(view); } @Override public void onBindViewHolder(final ViewHolder holder, int position) { Lyric lyric = getData().get(position); holder.lyrcTextView.setText(lyric.getLyricText()); holder.lyricId = lyric.getId()); } public class ViewHolder extends RecyclerView.ViewHolder { public TextView titleView; public String promotionId; public ViewHolder(View view) { super(view); lyricTextView = (TextView) view.findViewById(R.id.lyric_itle); } }
![Page 58: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/58.jpg)
Encryption
RealmConfiguration config = new RealmConfiguration.Builder(context) .encryptionKey(getKey()) .build();
• The Realm file can be stored encrypted on disk by passing an encryption key (byte [])
• All data persisted to disk is transparently encrypted and decrypted with standard AES-256 encryption
![Page 59: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/59.jpg)
Migrations
RealmConfiguration config = new RealmConfiguration.Builder(context) .schemaVersion(2) // Must be bumped when the schema changes .migration(new MyMigration()) // Migration handler class .build();
• Bump the version with each change
• Point the RealmConfiguration to you custom RealmMigration Implementation
![Page 60: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/60.jpg)
MigrationsRealmConfiguration config = new RealmConfiguration.Builder(context) .schemaVersion(2) // Must be bumped when the schema changes .migration(new MyMigration()) // Migration handler class .build();
public class MyMigration implements RealmMigration {
public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { RealmSchema schema = realm.getSchema(); // DynamicRealm exposes an editable schema if (oldVersion == 0) { // Migrate to version 1: Add a new class schema.create("Person") .addField("name", String.class) .addField("age", int.class); oldVersion++; // Bump the version after each updgrade } if (oldVersion == 1) { // Migrate to version 2: Add PK + relationships schema.get("Person") .addField("id", long.class, FieldAttribute.PRIMARY_KEY) .addRealmObjectField("favoriteDog", schema.get("Dog")) .addRealmListField("dogs", schema.get("Dog")); oldVersion++; } }}
![Page 61: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/61.jpg)
Migrations
public class MyMigration implements RealmMigration {
public void migrate(DynamicRealm realm, long oldVersion, long newVersion) { RealmSchema schema = realm.getSchema(); // DynamicRealm exposes an editable schema if (oldVersion == 0) { // Migrate to version 1: Add a new class schema.create("Person") .addField("name", String.class) .addField("age", int.class); oldVersion++; // Bump the version after each updgrade } if (oldVersion == 1) { // Migrate to version 2: Add PK + relationships schema.get("Person") .addField("id", long.class, FieldAttribute.PRIMARY_KEY) .addRealmObjectField("favoriteDog", schema.get("Dog")) .addRealmListField("dogs", schema.get("Dog")); oldVersion++; } }}
![Page 62: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/62.jpg)
Realm Browser
![Page 63: Better Data Persistence on Android](https://reader036.vdocuments.us/reader036/viewer/2022062503/58f0dcb51a28abd9168b4615/html5/thumbnails/63.jpg)
Resources
• Realm - https://realm.io/ • Source Code - https://github.com/realm/realm-java
• Code Samples • Presentation Examples
https://github.com/ericmaxwell2003/roboguiceRealmNaeNae
• Credible Software - http://credible.software