Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Persistency in Android

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on. →

Persistency in Android

Persistency solutions in Android, from Shared Preferences to SQLite and to new Room.
When to use each solution?
How the magic behind Room is made?

Avatar for Daniel Szasz

Daniel Szasz

January 28, 2020

Other Decks in Programming

Transcript

  1. 4 Android Academy Staff Yonatan Levin R&D Team Lead @

    Monday.com Eden Machluf Mobile developer Refael Ozeri Founder & CEO @ Hero Apps Artyom Okun Mobile Software Engineer @ Wizdo
  2. 5 Fundamentals Squad Hadas Peled Mobile Software Engineer @ CropX

    Gil Goldzweig Android tech lead @ Takeaway.com
  3. Mentors Alexey Korolev Alex Vainshtein Ali Asadi Tiferet Cohen Hezi

    Dimri Shahar Barsheshet Berry Ventura Lev Avishay Peretz Andrew Lebedev Eugene Rozenberg David Vardi Sigal Levi Alex Cohn Nitzan Werber Orit Malki Vitaliy Abramov Shahar Ben Moshe Regev Avraham Aleksey Grach Gilad Levinson Daniel Szasz 6
  4. Mentors • Aviram Peretz • Alex Vainshtein • Andrey Oleynik

    • Alex Cohn • Daniel Szasz • Gilad Levinson • Roi Bareket • Vova Hotyakov • Maxim Waitzman • Elad Jarby • Alexander Ferents • David Vardi • Hezi Dimri • Tiferet Cohen • Nitzan Werber • Gidi Shwartzman • Berry Ventura Lev • Regev Avraham • Vitaliy Abramov • Orit Malki • Leonid Shlafman • Alexey Korolev • Ali Asadi • Aleksey Grach • Yamin Elmakis • Avishay Peretz • Shahar ben Moshe • Eugene Rozenberg • Andrew Lebedev • Dan Gladshtein • Shahar Barsheahet • Sigal Levi 7 https://forms.gle/5MDJbwtUXXCQLSYS6
  5. 8 Exercise Squad Alexey Korolev AT&T Yamin Elmakis Here Amit

    Frishberg Monday Vova Hotyakov Discount
  6. Looking for Teammates? 1. Prepare very short idea presentation in

    Google Slides (up to 3 pages): 1. Idea Explanation 2. Short self introduction (Who are you?) 3. Who you are looking for? 4. Contact details
  7. Looking for Teammates? 1. Prepare very short idea presentation in

    Google Slides (up to 3 pages): 1. Idea Explanation 2. Short self introduction (Who are you?) 3. Who you are looking for? 4. Contact details 2. Submit the Pitch Request Form - https://forms.gle/rtyV4zBpqn9umpS89
  8. Looking for Teammates? 1. Prepare very short idea presentation in

    Google Slides (up to 3 pages): 1. Idea Explanation 2. Short self introduction (Who are you?) 3. Who you are looking for? 4. Contact details 2. Submit the Pitch Request Form - https://forms.gle/rtyV4zBpqn9umpS89 3. Present it at the beginning of the lecture - 1 minute “Elevator Pitch”
  9. Android Process Hierarchy Ian Lake - Who lives and who

    dies? Process priorities on Android 21
  10. Why Should I Care - Resources ? • Release resources

    ◦ battery ◦ GPS ◦ network ◦ cpu ◦ memory 22
  11. Why Should I Care - Back to old state •

    Preserve user state - return to application after long time, classic examples: ◦ screen rotation ◦ answer to a call ◦ opening app from notification 23
  12. Why Should I Care - Stability & Quality? • Stability

    - crash free when switching apps • Google Play - Android Vitals ◦ crashes ◦ ANR 24
  13. 28

  14. Activity is in the foreground (focused) Activity override fun onCreate(savedInstanceState:

    Bundle?) override fun onStart() override fun onResume() RESUMED 31
  15. 32

  16. Activity shut down Activity DESTROYED override fun onDestroy() - finish()

    - Back button (default behaviour) - ActivityManager decides to kill - Process killed * Not always called 35
  17. 36

  18. Activity B Lifecycle Activity A Activity B startActivity(Intent(this, ActivityB::class.java)); override

    fun onCreate(savedInstanceState: Bundle?) override fun onStart() STOPPED 41 STARTED
  19. Activity B Lifecycle Activity A Activity B startActivity(Intent(this, ActivityB::class.java)); override

    fun onCreate(savedInstanceState: Bundle?) override fun onStart() STOPPED 42 RESUMED
  20. 44

  21. 46

  22. 47

  23. Screen Rotation Lifecycle onSaveInstanceState() * Always called before onStop onStop()

    onDestroy() onCreate(savedInstanceState: Bundle?) loadMovies() Running Shut down Re-Create Running 48
  24. How Much Faster? L1 cache reference ......................... 0.5 ns L2

    cache reference ........................... 7 ns Main memory reference ...................... 100 ns Compress 1K bytes with Zippy ............. 3,000 ns = 3 µs Send 2K bytes over 1 Gbps network ....... 20,000 ns = 20 µs SSD random read ........................ 150,000 ns = 150 µs Read 1 MB sequentially from memory ..... 250,000 ns = 250 µs Round trip within same datacenter ...... 500,000 ns = 0.5 ms Read 1 MB sequentially from SSD* ..... 1,000,000 ns = 1 ms Disk seek ........................... 10,000,000 ns = 10 ms Read 1 MB sequentially from disk .... 20,000,000 ns = 20 ms Send packet CA->Netherlands->CA .... 150,000,000 ns = 150 ms Number Every Programmer Must Know https://gist.github.com/hellerbarde/2843375 51
  25. 52

  26. 53

  27. Key Value XML based Storage • Small storage space •

    No structure (key-value) • Not secured • Primitive types (booleans, floats, ints, longs, and strings) • Complex objects serialize to String 55
  28. Adding Data val sharedPref: SharedPreferences! = PreferenceManager.getDefaultSharedPreferences(context) val editor: SharedPreferences.Editor!

    = sharedPref.edit() editor.putString("KEY_STRING","Android Academy is awesome!") editor.putInt("KEY_INT",1) editor.putFloat("KEY_FLOAT",1F) editor.putBoolean("KEY_BOOLEAN",true) editor.putLong("KEY_LONG",1L) editor.putStringSet("KEY_SET",new ArraySet<String>()) 58
  29. Saving Data - Commit val sharedPref: SharedPreferences! = PreferenceManager.getDefaultSharedPreferences(context) val

    editor: SharedPreferences.Editor! = sharedPref.edit() editor.putInt("KEY_INT",1) ... boolean success = editor.commit() //blocking, returns success indication 59
  30. Saving Data - Apply val sharedPref: SharedPreferences! = PreferenceManager.getDefaultSharedPreferences(context) val

    editor: SharedPreferences.Editor! = sharedPref.edit() editor.putInt("KEY_INT",1) ... editor.apply(); //background thread, no return value 60
  31. Saving Data - Commit vs Apply • commit - we

    know the result • apply - we have no idea what happened. 61
  32. Loading Data 62 sharedPref: SharedPreferences! = PreferenceManager.getDefaultSharedPreferences(context); myString: String =

    sharedPref.getString("KEY_STRING", null) myInt: int = sharedPref.getInt("KEY_INT", 0) myFloat: Float = sharedPref.getFloat("KEY_FLOAT", 0F) myBoolean: Boolean = sharedPref.getBoolean("KEY_BOOLEAN", false) myLong: Long = sharedPref.getLong("KEY_LONG", 0L) myStringSet: Set<String> = sharedPref.getStringSet("KEY_SET", null)
  33. When to use Shared Preferences • User preferences! • Global

    state - user id, user token, isLoggedIn etc. • Small, unstructured data • Unsecured - very easy to take a look inside. 65
  34. 67

  35. 68

  36. 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 70
  37. When to use a Database - requirements • Large datasets

    • Structured data • Cache / Preload data 73
  38. When to use a Database - results • Better loading

    times • Better battery utilization • Better network utilization by saving traffic 74
  39. 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/ 75
  40. Creating our table CREATE TABLE Movies ( movieId INTEGER PRIMARY

    KEY, name TEXT, imageUri TEXT, releaseDate TEXT, popularity REAL ) Read More: https://www.sqlite.org/lang_createtable.html 77
  41. CREATE TABLE statement CREATE TABLE Movies ( movieId INTEGER PRIMARY

    KEY, name TEXT, imageUri TEXT, releaseDate TEXT, popularity REAL ) Read More: https://www.sqlite.org/lang_createtable.html 78
  42. Table Name CREATE TABLE Movies ( movieId INTEGER PRIMARY KEY,

    name TEXT, imageUri TEXT, releaseDate TEXT, popularity REAL ) Read More: https://www.sqlite.org/lang_createtable.html 79
  43. Primary Key CREATE TABLE Movies ( movieId INTEGER PRIMARY KEY,

    name TEXT, imageUri TEXT, releaseDate TEXT, popularity REAL ) Read More: https://www.sqlite.org/lang_createtable.html 80
  44. Column Types CREATE TABLE Movies ( movieId INTEGER PRIMARY KEY,

    name TEXT, imageUri TEXT, releaseDate TEXT, popularity REAL ) Read More: https://www.sqlite.org/lang_createtable.html 81
  45. Column Types CREATE TABLE Movies ( movieId INTEGER PRIMARY KEY,

    name TEXT, imageUri TEXT, releaseDate TEXT, popularity REAL ) Read More: https://www.sqlite.org/lang_createtable.html 82
  46. Column Types CREATE TABLE Movies ( movieId INTEGER PRIMARY KEY,

    name TEXT, imageUri TEXT, releaseDate TEXT, popularity REAL ) Read More: https://www.sqlite.org/lang_createtable.html 83
  47. movieId name imageUri releaseDate popularity 297802 Aquaman https://image.tmdb.org/ t/p/w342/i2dF9UxOeb77CA JrOflj0RpqJRF.jpg

    2018-12-07 616.683 299536 Avengers: Infinity War https://image.tmdb.org/ t/p/w342/7WsyChQLEftFiD OVTGkv3hFpyyt.jpg 2018-04-25 128.347 324857 Spider-Man: Into the Spider-Verse https://image.tmdb.org/ t/p/w342/laMM4lpQSh5z6K IBPwWogkjzBVQ.jpg 2018-12-07 152.037 335983 Venom https://image.tmdb.org/ t/p/w342/2uNW4WbgBXL25B AbXGLnLqX71Sw.jpg 2018-10-03 227.227 338952 Fantastic Beasts: The Crimes of Grindelwald https://image.tmdb.org/ t/p/w342/kQKcbJ9uYkTQql 2R8L4jTUz7l90.jpg 2018-11-14 272.343 84
  48. 85

  49. Actions: - Insert - Adds records to tables. - Update

    - modifies values in existing records - Select - gets part of the table - Delete - Removes records from tables. 86
  50. Select - gets part of the table SELECT ( *

    | [column, column]) FROM (table) WHERE (condition) ORDER BY (column) (ASC | DESC) This is super-simplified. Read more: https://www.sqlite.org/lang_select.html 87
  51. Select SELECT * FROM Movies WHERE name = ’Aquaman’ movieId

    name imageUri releaseDate popularity 297802 Aquaman https://image.tmdb .org/t/p/w342/i2dF 9UxOeb77CAJrOflj0R pqJRF.jpg 2018-12-07 616.683 88
  52. Select SELECT * FROM Movies ORDER BY popularity ASC movieId

    name imageUri releaseDate popularity 299536 Avengers: Infinity War https://image.tmdb.org/t/p/w342/7Wsy ChQLEftFiDOVTGkv3hFpyyt.jpg 2018-04-25 128.347 324857 Spider-Man: Into the Spider-Verse https://image.tmdb.org/t/p/w342/laMM4 lpQSh5z6KIBPwWogkjzBVQ.jpg 2018-12-07 152.037 335983 Venom https://image.tmdb.org/t/p/w342/2uNW 4WbgBXL25BAbXGLnLqX71Sw.jpg 2018-10-03 227.227 338952 Fantastic Beasts: The Crimes of Grindelwald https://image.tmdb.org/t/p/w342/kQKcb J9uYkTQql2R8L4jTUz7l90.jpg 2018-11-14 272.343 297802 Aquaman https://image.tmdb.org/t/p/w342/i2dF9 UxOeb77CAJrOflj0RpqJRF.jpg 2018-12-07 616.683 89
  53. Select SELECT * FROM Movies WHERE name LIKE ’% the

    %’ movieId name imageUri releaseDate popularity 338952 Fantastic Beasts: the Crimes of Grindelwald https://image.tmd b.org/t/p/w342/k QKcbJ9uYkTQql 2R8L4jTUz7l90.j pg 2018-11-14 272.343 324857 Spider-Man: Into the Spider-Verse https://image.tmd b.org/t/p/w342/la MM4lpQSh5z6KI BPwWogkjzBVQ. jpg 2018-12-07 152.037 90
  54. Select SELECT * FROM Movies WHERE title LIKE ’% the

    %’ AND popularity > 200 movieId name imageUri releaseDate popularity 338952 Fantastic Beasts: the Crimes of Grindelwald https://image.tmd b.org/t/p/w342/k QKcbJ9uYkTQql 2R8L4jTUz7l90.j pg 2018-11-14 272.343 91
  55. 92

  56. Table Name class MoviesContract private constructor() { object MovieEntry :

    BaseColumns { const val TABLE_NAME = "MOVIE_ENTRY" } } 96
  57. Columns Names class MoviesContract private constructor() { object MovieEntry :

    BaseColumns { const val TABLE_NAME = "MOVIE_ENTRY" const val COLUMN_NAME_NAME = "name" const val COLUMN_NAME_CONTENT = "content" const val COLUMN_NAME_IMAGE_URI = "image_uri" const val COLUMN_NAME_RELEASE_DATE = "release_date" } } 97
  58. Create Table SQL private val SQL_CREATE_ENTRIES = "CREATE TABLE "

    + MovieEntry.TABLE_NAME + " (" + MovieEntry._ID + " INTEGER PRIMARY KEY," + MovieEntry.COLUMN_NAME_NAME + " TEXT," + MovieEntry.COLUMN_NAME_CONTENT + " TEXT," + MovieEntry.COLUMN_NAME_IMAGE_URI + " TEXT," + MovieEntry.COLUMN_NAME_RELEASE_DATE + " TEXT)"; private val SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + MovieEntry.TABLE_NAME; 99
  59. SQLiteOpenHelper 100 class MoviesDbHelper(context: Context?) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION)

    { companion object { private const val DATABASE_NAME = "MoviesDb.db" private const val DATABASE_VERSION = 1 } }
  60. SQLiteOpenHelper 101 class MoviesDbHelper(context: Context?) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION)

    { companion object { private const val DATABASE_NAME = "MoviesDb.db" private const val DATABASE_VERSION = 1 } }
  61. SQLiteOpenHelper 102 class MoviesDbHelper(context: Context?) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION)

    { override fun onCreate(db: SQLiteDatabase) { db.execSQL(SQL_CREATE_ENTRIES) } companion object { private const val DATABASE_NAME = "MoviesDb.db" private const val DATABASE_VERSION = 1 } }
  62. SQLiteOpenHelper 103 class MoviesDbHelper(context: Context?) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION)

    { override fun onCreate(db: SQLiteDatabase) { db.execSQL(SQL_CREATE_ENTRIES) } override fun onUpgrade(db: SQLiteDatabase, oldVersion: int, newVersion: int){ } companion object { private const val DATABASE_NAME = "MoviesDb.db" private const val DATABASE_VERSION = 1 } }
  63. Get Writable Database private fun saveData(moviesList:List<MovieModel>) { val db =

    MoviesDbHelper.getInstance(this).getWritableDatabase() db.close() } 106
  64. Filling Content Value private fun saveData(moviesList:List<MovieModel>) { val db =

    MoviesDbHelper.getInstance(this).getWritableDatabase() db.beginTransaction() try{ val values = ContentValues() for (movie in moviesList){ values.put(MovieEntry.COLUMN_NAME_NAME, movie.getName()) ... } db.setTransactionSuccessful() }finally{ db.endTransaction() } db.close() } 108
  65. Inserting Values private fun saveData(moviesList:List<MovieModel>) { val db = MoviesDbHelper.getInstance(this).getWritableDatabase()

    db.beginTransaction() try{ val values = ContentValues() for (movie in moviesList){ values.put(MovieEntry.COLUMN_NAME_NAME, movie.getName()) ... db.insert(MovieEntry.TABLE_NAME, null, values) } db.setTransactionSuccessful() }finally{ db.endTransaction() } db.close() } 109
  66. Get Readable Database private fun restoreData():List<MovieModel> { val db =

    MoviesDbHelper.getInstance(this).getReadableDatabase() } 112
  67. Projection Array private fun restoreData():List<MovieModel> { val db = MoviesDbHelper.getInstance(this).getReadableDatabase()

    val projection = arrayOf<String>(MovieEntry.COLUMN_NAME_NAME, MovieEntry.COLUMN_NAME_CONTENT, MovieEntry.COLUMN_NAME_IMAGE_URI, MovieEntry.COLUMN_NAME_RELEASE_DATE) } 113
  68. Getting column index private fun restoreData(){ val columnIndexName = cursor.getColumnIndexOrThrow(MovieEntry.COLUMN_NAME_NAME)

    val columnIndexContent = cursor.getColumnIndexOrThrow(MovieEntry.COLUMN_NAME_CONTENT) val columnIndexImageUri = cursor.getColumnIndexOrThrow(MovieEntry.COLUMN_NAME_IMAGE_URI) val columnIndexReleaseDate = cursor.getColumnIndexOrThrow(MovieEntry.COLUMN_NAME_RELEASE_DATE) } 118
  69. Getting cursor 119 private fun restoreData(){ val columnIndexName = cursor.getColumnIndexOrThrow(MovieEntry.COLUMN_NAME_NAME)

    ... val cursor = db.query(MovieEntry.TABLE_NAME,projection,null, null, null, null, null) cursor.close() }
  70. Creating result list private fun restoreData(){ val columnIndexName = cursor.getColumnIndexOrThrow(MovieEntry.COLUMN_NAME_NAME)

    ... val cursor = db.query(MovieEntry.TABLE_NAME,projection,null, null, null, null, null) val moviesList = ArrayList() cursor.close() } 120
  71. Iterating Over Cursor private fun restoreData(){ ... if (cursor.getCount() >

    0){ while (cursor.moveToNext()){ val name = cursor.getString(columnIndexName) val content = cursor.getString(columnIndexContent) val url = cursor.getString(columnIndexImageUri) val releaseDate = cursor.getString(columnIndexReleaseDate) } } ... } 121
  72. Creating MovieModel private fun restoreData(){ ... if (cursor.getCount() > 0){

    while (cursor.moveToNext()){ val name = cursor.getString(columnIndexName) val content = cursor.getString(columnIndexContent) val url = cursor.getString(columnIndexImageUri) val releaseDate = cursor.getString(columnIndexReleaseDate) moviesList.add(MovieModel(name, content, url, releaseDate)) } } ... } 122
  73. Close cursor private fun restoreData(){ ... val cursor = db.query(MovieEntry.TABLE_NAME,projection,null,

    null, null, null, null) if (cursor.getCount() > 0){ while (cursor.moveToNext()){ ... } } cursor.close() } 123
  74. 125

  75. 126

  76. 131

  77. MovieModel Entity @Parcelize @Entity data class MovieModel( @PrimaryKey val movieId:

    Int, val name: String, val imageUrl: String, val overview: String?, val backImageUrl: String, val releaseDate: String, val popularity: Double ) : Parcelable 133
  78. Entity @Parcelize @Entity data class MovieModel( @PrimaryKey val movieId: Int,

    val name: String, val imageUrl: String, val overview: String?, val backImageUrl: String, val releaseDate: String, val popularity: Double ) : Parcelable 134
  79. Primary Key @Parcelize @Entity data class MovieModel( @PrimaryKey val movieId:

    Int, val name: String, val imageUrl: String, val overview: String?, val backImageUrl: String, val releaseDate: String, val popularity: Double ) : Parcelable 135
  80. Columns @Parcelize @Entity data class MovieModel( @PrimaryKey val movieId: Int,

    val name: String, val imageUrl: String, val overview: String?, val backImageUrl: String, val releaseDate: String, val popularity: Double ) : Parcelable 136
  81. Data Access Object Database Entity Data Access Object Mapper SQL

    Query Raw Data Raw Data Entity Object 138
  82. Raw Query - Select @Dao interface MovieDao { @Query("SELECT *

    FROM MovieModel ORDER BY popularity DESC") fun getAll(): List<MovieModel> } 140
  83. Insert - Strategy ABORT @Dao interface MovieDao { @Query("SELECT *

    FROM MovieModel ORDER BY popularity DESC") fun getAll(): List<MovieModel> @Insert(onConflict = OnConflictStrategy.ABORT) fun insertAll(movies: Collection<MovieModel>) } 141
  84. Insert - Strategy FAIL @Dao interface MovieDao { @Query("SELECT *

    FROM MovieModel ORDER BY popularity DESC") fun getAll(): List<MovieModel> @Insert(onConflict = OnConflictStrategy.FAIL) fun insertAll(movies: Collection<MovieModel>) } 142
  85. Insert - Strategy IGNORE @Dao interface MovieDao { @Query("SELECT *

    FROM MovieModel ORDER BY popularity DESC") fun getAll(): List<MovieModel> @Insert(onConflict = OnConflictStrategy.IGNORE) fun insertAll(movies: Collection<MovieModel>) } 143
  86. Insert - Strategy REPLACE @Dao interface MovieDao { @Query("SELECT *

    FROM MovieModel ORDER BY popularity DESC") fun getAll(): List<MovieModel> @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertAll(movies: Collection<MovieModel>) } 144
  87. Insert - Strategy ROLLBACK @Dao interface MovieDao { @Query("SELECT *

    FROM MovieModel ORDER BY popularity DESC") fun getAll(): List<MovieModel> @Insert(onConflict = OnConflictStrategy.ROLLBACK) fun insertAll(movies: Collection<MovieModel>) } 145
  88. Raw Query - Delete @Dao interface MovieDao { @Query("SELECT *

    FROM MovieModel ORDER BY popularity DESC") fun getAll(): List<MovieModel> @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertAll(movies: Collection<MovieModel>) @Query("DELETE FROM MovieModel") fun deleteAll() } 146
  89. Raw Query - Select with LIKE @Dao public interface MovieDao

    { @Query("SELECT * FROM movieModel WHERE name LIKE :name LIMIT 1") MovieModel findByName(String name); } 147
  90. Raw Query - Select with array @Dao public interface MovieDao

    { @Query("SELECT * FROM movieModel WHERE name LIKE :name LIMIT 1") MovieModel findByName(String name); @Query("SELECT * FROM movieModel WHERE movieId IN (:movieIds)") List<MovieModel> loadAllByIds(string[] movieIds); } 148
  91. Syntax Errors Highlight @Dao interface MovieDao { @Insert(onConflict = OnConflictStrategy.REPLACE)

    fun insertAll(movies: Collection<MovieModel>) @Query("DELETE FROM MovieModel") fun deleteAll() } 149
  92. Syntax Errors Highlight @Dao interface MovieDao { @Insert(onConflict = OnConflictStrategy.REPLACE)

    fun insertAll(movies: Collection<MovieModel>) @Query("DELETE FROM MovieModel") fun deleteAll() } 150
  93. How The Magic Works? @Override public void insertAll(final Collection<MovieModel> movies)

    { __db.assertNotSuspendingTransaction(); __db.beginTransaction(); try { __insertionAdapterOfMovieModel.insert(movies); __db.setTransactionSuccessful(); } finally { __db.endTransaction(); } } 152
  94. How The Magic Works? @Override public void deleteAll() { __db.assertNotSuspendingTransaction();

    final SupportSQLiteStatement _stmt = __preparedStmtOfDeleteAll.acquire(); __db.beginTransaction(); try { _stmt.executeUpdateDelete(); __db.setTransactionSuccessful(); } finally { __db.endTransaction(); __preparedStmtOfDeleteAll.release(_stmt); } } 153
  95. How The Magic Works? @Override public List<MovieModel> getAll() { final

    String _sql = "SELECT * FROM MovieModel ORDER BY popularity DESC"; final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0); __db.assertNotSuspendingTransaction(); final Cursor _cursor = DBUtil.query(__db, _statement, false, null); try { final int _cursorIndexOfMovieId = CursorUtil.getColumnIndexOrThrow(_cursor, "movieId"); ... final List<MovieModel> _result = new ArrayList<MovieModel>(_cursor.getCount()); while(_cursor.moveToNext()) { final MovieModel _item; final int _tmpMovieId; _tmpMovieId = _cursor.getInt(_cursorIndexOfMovieId); final String _tmpName; _tmpName = _cursor.getString(_cursorIndexOfName); ... _item = new MovieModel(_tmpMovieId,_tmpName,_tmpImageUrl,_tmpOverview,_tmpBackImageUrl,_tmpReleaseDate,_tmpPopularity); _result.add(_item); } return _result; } finally { _cursor.close(); _statement.release(); } } 154
  96. 155

  97. Declaring a Database @Database(entities = [MovieModel::class, TrailerModel::class], version = 1)

    abstract class AppDatabase : RoomDatabase() { abstract fun movieDao(): MovieDao? abstract fun videoDao(): TrailerDao? companion object { private const val DATABASE_NAME = "movies" private var INSTANCE: AppDatabase? = null fun getInstance(context: Context): AppDatabase? { if (INSTANCE == null) { INSTANCE = Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, DATABASE_NAME ) .allowMainThreadQueries() .fallbackToDestructiveMigration() .build() } return INSTANCE } fun destroyInstance() { INSTANCE = null } } } 157
  98. Declaring a Database @Database(entities = [MovieModel::class], version = 1) abstract

    class AppDatabase : RoomDatabase() { abstract fun movieDao(): MovieDao? abstract fun videoDao(): TrailerDao? companion object { private const val DATABASE_NAME = "movies" private var INSTANCE: AppDatabase? = null fun getInstance(context: Context): AppDatabase? { if (INSTANCE == null) { INSTANCE = Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, DATABASE_NAME ) .allowMainThreadQueries() .fallbackToDestructiveMigration() .build() } return INSTANCE } fun destroyInstance() { INSTANCE = null } } } 158
  99. Adding DAOs @Database(entities = [MovieModel::class, TrailerModel::class], version = 1) abstract

    class AppDatabase : RoomDatabase() { abstract fun movieDao(): MovieDao? companion object { private const val DATABASE_NAME = "movies" private var INSTANCE: AppDatabase? = null fun getInstance(context: Context): AppDatabase? { if (INSTANCE == null) { INSTANCE = Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, DATABASE_NAME ) .allowMainThreadQueries() .fallbackToDestructiveMigration() .build() } return INSTANCE } fun destroyInstance() { INSTANCE = null } } } 159
  100. Creating a Database @Database(entities = [MovieModel::class, TrailerModel::class], version = 1)

    abstract class AppDatabase : RoomDatabase() { ... fun getInstance(context: Context): AppDatabase? { if (INSTANCE == null) { INSTANCE = Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, DATABASE_NAME ) .allowMainThreadQueries() .fallbackToDestructiveMigration() .build() } return INSTANCE } fun destroyInstance() { 160
  101. Creating a Database @Database(entities = [MovieModel::class, TrailerModel::class], version = 1)

    abstract class AppDatabase : RoomDatabase() { ... fun getInstance(context: Context): AppDatabase? { if (INSTANCE == null) { INSTANCE = Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, DATABASE_NAME ) .allowMainThreadQueries() .fallbackToDestructiveMigration() .build() } return INSTANCE } fun destroyInstance() { INSTANCE = null 161
  102. Using Singleton @Database(entities = [MovieModel::class, TrailerModel::class], version = 1) abstract

    class AppDatabase : RoomDatabase() { ... fun getInstance(context: Context): AppDatabase? { if (INSTANCE == null) { INSTANCE = Room.databaseBuilder( context.applicationContext, AppDatabase::class.java, DATABASE_NAME ) .allowMainThreadQueries() .fallbackToDestructiveMigration() .build() } return INSTANCE } fun destroyInstance() { INSTANCE = null } } } 162
  103. Saving Data private fun onDataReceivedFromServer(response: Response<MoviesListResult>) { main_progress.visibility = View.GONE

    response.body()?.let { val convertedList = MovieModelConverter.convertNetworkMovieToModel(it) movies.apply { clear() addAll(convertedList) } moviesList.adapter?.notifyDataSetChanged() AppDatabase.getInstance(this@MoviesActivity)?.movieDao()?.deleteAll() AppDatabase.getInstance(this@MoviesActivity)?.movieDao()?.insertAll(MoviesContent.movies) } } 164
  104. Saving Data - receving from network private fun onDataReceivedFromServer(response: Response<MoviesListResult>)

    { main_progress.visibility = View.GONE response.body()?.let { val convertedList = MovieModelConverter.convertNetworkMovieToModel(it) movies.apply { clear() addAll(convertedList) } moviesList.adapter?.notifyDataSetChanged() AppDatabase.getInstance(this@MoviesActivity)?.movieDao()?.deleteAll() AppDatabase.getInstance(this@MoviesActivity)?.movieDao()?.insertAll(MoviesContent.movies) } } 165
  105. Saving Data - updating the User private fun onDataReceivedFromServer(response: Response<MoviesListResult>)

    { main_progress.visibility = View.GONE response.body()?.let { val convertedList = MovieModelConverter.convertNetworkMovieToModel(it) movies.apply { clear() addAll(convertedList) } moviesList.adapter?.notifyDataSetChanged() AppDatabase.getInstance(this@MoviesActivity)?.movieDao()?.deleteAll() AppDatabase.getInstance(this@MoviesActivity)?.movieDao()?.insertAll(MoviesContent.movies) } } 166
  106. Saving Data - delete existing data private fun onDataReceivedFromServer(response: Response<MoviesListResult>)

    { main_progress.visibility = View.GONE response.body()?.let { val convertedList = MovieModelConverter.convertNetworkMovieToModel(it) movies.apply { clear() addAll(convertedList) } moviesList.adapter?.notifyDataSetChanged() AppDatabase.getInstance(this@MoviesActivity)?.movieDao()?.deleteAll() AppDatabase.getInstance(this@MoviesActivity)?.movieDao()?.insertAll(MoviesContent.movies) } } 167
  107. Saving Data - insert new data private fun onDataReceivedFromServer(response: Response<MoviesListResult>)

    { main_progress.visibility = View.GONE response.body()?.let { val convertedList = MovieModelConverter.convertNetworkMovieToModel(it) movies.apply { clear() addAll(convertedList) } moviesList.adapter?.notifyDataSetChanged() AppDatabase.getInstance(this@MoviesActivity)?.movieDao()?.deleteAll() AppDatabase.getInstance(this@MoviesActivity)?.movieDao()?.insertAll(MoviesContent.movies) } } 168
  108. 169

  109. Restoring Data private fun getCachedMoviesFromDataBase() { val cachedMovies: List<MovieModel>? =

    AppDatabase.getInstance(this)?.movieDao()?.getAll() cachedMovies?.let { movies.addAll(cachedMovies) moviesList.adapter?.notifyDataSetChanged() } } 171
  110. Restoring Data - load from database private fun getCachedMoviesFromDataBase() {

    val cachedMovies: List<MovieModel>? = AppDatabase.getInstance(this)?.movieDao()?.getAll() cachedMovies?.let { movies.addAll(cachedMovies) moviesList.adapter?.notifyDataSetChanged() } } 172
  111. Restoring Data - adding data to adapter private fun getCachedMoviesFromDataBase()

    { val cachedMovies: List<MovieModel>? = AppDatabase.getInstance(this)?.movieDao()?.getAll() cachedMovies?.let { movies.addAll(cachedMovies) moviesList.adapter?.notifyDataSetChanged() } } 173
  112. Restoring Data - notify to update UI private fun getCachedMoviesFromDataBase()

    { val cachedMovies: List<MovieModel>? = AppDatabase.getInstance(this)?.movieDao()?.getAll() cachedMovies?.let { movies.addAll(cachedMovies) moviesList.adapter?.notifyDataSetChanged() } } 174
  113. 175

  114. 177

  115. 178