Android Data Persistence

42
Android: data persistence Romain Rochegude 2016.09.30 1

Transcript of Android Data Persistence

Page 1: Android Data Persistence

Android: data persistence

Romain Rochegude2016.09.30

1

Page 2: Android Data Persistence

Introduction

2

Page 3: Android Data Persistence

Introduction

• POO basics: modeling the domain• Modeling domain objects and their interactions• Data bound with a remote API• Need of a local database

3

Page 4: Android Data Persistence

The native way

4

Page 5: Android Data Persistence

The “raw” way

private static final StringSQL_CREATE_ENTRIES =

"CREATE TABLE REPO (" +"_ID INTEGER PRIMARY KEY," +"NAME TEXT)";

private static final StringSQL_DELETE_ENTRIES =

"DROP TABLE IF EXISTS REPO ";

5

Page 6: Android Data Persistence

• Subclass SQLiteOpenHelper

public class ReposDbHelper extendsSQLiteOpenHelper {

public static final intDATABASE_VERSION = 1;

public static final StringDATABASE_NAME = "repos.db";

public ReposDbHelper(Contextcontext) {

super(context, DATABASE_NAME,null, DATABASE_VERSION);

}//...

6

Page 7: Android Data Persistence

//...public void onCreate(SQLiteDatabase

db) {db.execSQL(SQL_CREATE_ENTRIES);

}

public void onUpgrade(SQLiteDatabasedb, int oldVersion, intnewVersion) {

db.execSQL(SQL_DELETE_ENTRIES);onCreate(db);

}}

7

Page 8: Android Data Persistence

• Get an instance of SQLiteOpenHelper

ReposDbHelper dbHelper =new ReposDbHelper(getContext());

8

Page 9: Android Data Persistence

• Put Information into a Database

SQLiteDatabase db =dbHelper.getWritableDatabase();

ContentValues values = newContentValues();

values.put("name", "a sample name");

long newRowId = db.insert("REPO", null,values);

9

Page 10: Android Data Persistence

• Read Information from a Database

SQLiteDatabase db =dbHelper.getReadableDatabase();

String[] projection = { "_id", "name" };

String selection = "NAME = ?";String[] selectionArgs = { "a sample

name" };

String sortOrder = "NAME DESC";

10

Page 11: Android Data Persistence

Cursor cursor = db.query("REPO",projection,selection,selectionArgs,null,null,sortOrder);

cursor.moveToFirst();

long itemId = cursor.getLong(cursor.getColumnIndexOrThrow("_ID")

);

• Recommendation to setup a “contract” class11

Page 12: Android Data Persistence

The ContentProvider way

• Provide a ContentProvider subclassdedicated to the application

public class RepoProvider extendsContentProvider {

}

12

Page 13: Android Data Persistence

• Define a specific UriMatcher and configureavailable URI

public class RepoProvider extendsContentProvider {

private static final UriMatchersUriMatcher =

newUriMatcher(UriMatcher.NO_MATCH);

}

13

Page 14: Android Data Persistence

static {sUriMatcher.addURI("fr.test.app.provider",

"repo", 1);sUriMatcher.addURI("fr.test.app.provider",

"repo/#", 2);}

14

Page 15: Android Data Persistence

• Override various CRUD methods

public Cursor query(Uri uri,String[] projection,String selection,String[] selectionArgs,String sortOrder) {//...

15

Page 16: Android Data Persistence

//...switch (sUriMatcher.match(uri)) {

case 2:selection = selection + "_ID = "

+ uri.getLastPathSegment();break;

default://...

}

16

Page 17: Android Data Persistence

• Use it through a ContentResolver instance

mCursor = getContentResolver().query("fr.test.app.provider/repo/2",mProjection,mSelectionClause,mSelectionArgs,mSortOrder);

17

Page 19: Android Data Persistence

Async management

• Perform CRUD operations outside of the mainthread

19

Page 20: Android Data Persistence

Query data using Loader

• To query the ContentProvider in anActivity, make it implementingLoaderManager.LoaderCallbacks<Cursor>

public class ReposListActivityextends FragmentActivityimplements

LoaderManager.LoaderCallbacks<Cursor>{

}

20

Page 21: Android Data Persistence

• Start loading data with a loader identifier

getLoaderManager().initLoader(LOADER_REPOS, null,

this);

• Implement LoaderCallbacks. . .

21

Page 22: Android Data Persistence

• . . . to create the CursorLoader

@Overridepublic Loader<Cursor> onCreateLoader(

int id, Bundle bundle) {if(id == LOADER_REPOS) {

return new CursorLoader(this,"fr.test.app.provider/repo",mProjection,null, null, null);

}//...

} 22

Page 23: Android Data Persistence

• . . . and deal with result

@Overridepublic void

onLoadFinished(Loader<Cursor> loader,Cursor cursor) {

if(loader.getId() == LOADER_REPOS){// ...

}}

23

Page 25: Android Data Persistence

The ORM way

25

Page 26: Android Data Persistence

The well-known: ORMLite

• Declare your model using ORMLite annotations@DatabaseTable(tableName = "REPO")public class RepoEntity {

@DatabaseField(columnName = "_ID",generatedId = true)

public long _id;

@DatabaseFieldpublic String name;

}26

Page 27: Android Data Persistence

• declare the corresponding DAO

public class DAORepo extendsBaseDaoImpl<RepoEntity, Long> {

public DAORepo(ConnectionSource cs)throws SQLException {this(cs, RepoEntity.class);

}

//...}

27

Page 28: Android Data Persistence

• subclass OrmLiteSqliteOpenHelper

public class DatabaseHelperTestextends OrmLiteSqliteOpenHelper {

private static final StringDATABASE_NAME = "test.db";

private static final intDATABASE_VERSION = 1;

//...

28

Page 29: Android Data Persistence

//...public DatabaseHelperTest(

Context context) {

super(context,DATABASE_NAME,null,DATABASE_VERSION,R.raw.ormlite_config);

}//...

29

Page 30: Android Data Persistence

//...@Overridepublic void onCreate(SQLiteDatabase db,

ConnectionSource cs) {TableUtils.createTable(cs,

RepoEntity.class);}

@Overridepublic void onUpgrade(SQLiteDatabase db,

ConnectionSource cs, int oldVersion,int newVersion) {

//...}

30

Page 31: Android Data Persistence

• get the requested DAODatabaseHelperTest helper = //...

ConnectionSource cs =helper.getConnectionSource();

DatabaseTableConfig<RepoEntity>tableConfig =

DatabaseTableConfigUtil.fromClass(cs,RepoEntity.class);

DAORepo dao = new DAORepo(cs,tableConfig); 31

Page 32: Android Data Persistence

• perform CRUD operations

// create

RepoEntity repo =new RepoEntity("a sample name");

dao.create(repo);

32

Page 33: Android Data Persistence

// read

List<RepoEntity> repos =dao.queryBuilder()

.where()

.eq("name", "a sample name")

.query();

33

Page 34: Android Data Persistence

• Performance: orm-gap gradle pluginbuildscript {

repositories {mavenCentral()

}dependencies {

classpath'com.github.stephanenicolas.ormgap'

+ ':ormgap-plugin:1.0.0-SNAPSHOT'}

}

apply plugin: 'ormgap' 34

Page 35: Android Data Persistence

• generate an ORMLite configuration file thatboosts DAOs creations

• to use this file

public RepoDatabaseHelper(Contextcontext) {

super(context,DATABASE_NAME,null,DATABASE_VERSION,R.raw.ormlite_config);

}

35

Page 36: Android Data Persistence

The attractive way: requery

• Object mapping• SQL generator• RxJava and Java 8 support• No reflection, compile-time processing and

generation• Relationships support• Callback method (@PostLoad)• Custom type converters

36

Page 37: Android Data Persistence

• Define object mapping

@Entityabstract class Repo {

@Key @Generatedint id;

String name;}

37

Page 38: Android Data Persistence

• Easy to perform SQL queries

Result<Repo> repos = data.select(Repo.class).where(Repo.NAME.lower().like("%sample%")).orderBy(Repo.ID.desc()).get();

38

Page 39: Android Data Persistence

Async management: RxJava

• Get a specific instance of SingleEntityStore

DatabaseSource dbSource =new DatabaseSource(context,

Models.DEFAULT, "test.db", 1);

Configuration conf =dbSource.getConfiguration();

SingleEntityStore<Persistable> data =RxSupport.toReactiveStore(new

EntityDataStore<>(conf));39

Page 40: Android Data Persistence

• and use it the RX way

data.select(RepoEntity.class).get().subscribeOn(Schedulers.newThread()).subscribe(/*...*/)

40

Page 41: Android Data Persistence

Conclusion

41

Page 42: Android Data Persistence

Conclusion

• Personal assessments of each way

ContentProvider ORMLite requery

setup - + +

performance + - +

readability - + +

maintainability - + +

42