From 2d7020fd2140b337f870bee8d17999f199d569a5 Mon Sep 17 00:00:00 2001 From: Sanimys <36693490+Sanimys@users.noreply.github.com> Date: Thu, 12 Mar 2020 23:27:40 +0100 Subject: [PATCH] Added a database working in LRU (#43) Co-authored-by: Joachim Dunant --- app/build.gradle | 6 +- .../java/com/h/pixeldroid/AppDatabaseTest.kt | 69 +++++++++++++++++++ .../java/com/h/pixeldroid/MainActivity.kt | 10 +-- .../java/com/h/pixeldroid/db/AppDatabase.kt | 41 +++++++++++ .../java/com/h/pixeldroid/db/Converters.kt | 16 +++++ .../main/java/com/h/pixeldroid/db/PostDao.kt | 36 ++++++++++ .../java/com/h/pixeldroid/db/PostEntity.kt | 17 +++++ .../com/h/pixeldroid/utils/DatabaseUtils.kt | 45 ++++++++++++ .../main/res/drawable/ic_add_black_24dp.xml | 9 +++ app/src/main/res/values/styles.xml | 8 +++ 10 files changed, 252 insertions(+), 5 deletions(-) create mode 100644 app/src/androidTest/java/com/h/pixeldroid/AppDatabaseTest.kt create mode 100644 app/src/main/java/com/h/pixeldroid/db/AppDatabase.kt create mode 100644 app/src/main/java/com/h/pixeldroid/db/Converters.kt create mode 100644 app/src/main/java/com/h/pixeldroid/db/PostDao.kt create mode 100644 app/src/main/java/com/h/pixeldroid/db/PostEntity.kt create mode 100644 app/src/main/java/com/h/pixeldroid/utils/DatabaseUtils.kt create mode 100644 app/src/main/res/drawable/ic_add_black_24dp.xml diff --git a/app/build.gradle b/app/build.gradle index 81322ef9..6c840d00 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -44,7 +44,7 @@ android { } - + apply plugin: 'kotlin-kapt' } dependencies { @@ -65,6 +65,10 @@ dependencies { implementation "androidx.browser:browser:1.2.0" implementation 'com.google.android.material:material:1.1.0' + def room_version = "2.2.4" + implementation "androidx.room:room-runtime:$room_version" + kapt "androidx.room:room-compiler:$room_version" + implementation "androidx.room:room-ktx:$room_version" implementation("com.github.bumptech.glide:glide:4.11.0") { exclude group: "com.android.support" diff --git a/app/src/androidTest/java/com/h/pixeldroid/AppDatabaseTest.kt b/app/src/androidTest/java/com/h/pixeldroid/AppDatabaseTest.kt new file mode 100644 index 00000000..fcfd86c6 --- /dev/null +++ b/app/src/androidTest/java/com/h/pixeldroid/AppDatabaseTest.kt @@ -0,0 +1,69 @@ +package com.h.pixeldroid + +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.h.pixeldroid.db.AppDatabase +import com.h.pixeldroid.db.PostDao +import com.h.pixeldroid.db.PostEntity +import com.h.pixeldroid.utils.* +import org.junit.After +import org.junit.Assert +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import java.util.Calendar + +@RunWith(AndroidJUnit4::class) +class AppDatabaseTest { + private var postDao: PostDao? = null + private var db: AppDatabase? = null + private var postTest = PostEntity(1, "test", date= Calendar.getInstance().time) + + @Before + fun setup() { + AppDatabase.TEST_MODE = true + db = AppDatabase.getDatabase(ApplicationProvider.getApplicationContext()) + postDao = db?.postDao() + postDao?.insertAll(postTest) + } + + @After + fun tearDown() { + + } + + @Test + fun testInsertPostItem() { + Assert.assertEquals(postTest.domain, postDao?.getById(postTest.uid)!!.domain) + } + + @Test + fun testDeleteAll(){ + postDao?.deleteAll() + Assert.assertEquals(postDao?.getPostsCount(), 0) + } + + @Test + fun testUtilsInsertAll() { + val postTest2 = PostEntity(2, "test", date= Calendar.getInstance().time) + DatabaseUtils.insertAllPosts(db!!, postTest, postTest2) + + Assert.assertEquals(postTest.domain, postDao?.getById(postTest.uid)!!.domain) + Assert.assertEquals(postTest2.domain, postDao?.getById(postTest2.uid)!!.domain) + } + + @Test + fun testUtilsLRU() { + for(i in 1..db!!.MAX_NUMBER_OF_POSTS) { + DatabaseUtils.insertAllPosts(db!!, PostEntity(i, i.toString(), date= Calendar.getInstance().time)) + } + + Assert.assertEquals("1", postDao?.getById(1)!!.domain) + Assert.assertEquals(db?.MAX_NUMBER_OF_POSTS, postDao?.getPostsCount()) + + DatabaseUtils.insertAllPosts(db!!, PostEntity(0, "0", date= Calendar.getInstance().time)) + Assert.assertEquals(db?.MAX_NUMBER_OF_POSTS, postDao?.getPostsCount()) + val eldestPost = postDao?.getById(1) + Assert.assertEquals(null, eldestPost) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/h/pixeldroid/MainActivity.kt b/app/src/main/java/com/h/pixeldroid/MainActivity.kt index 5a13ee92..d0ddfa6a 100644 --- a/app/src/main/java/com/h/pixeldroid/MainActivity.kt +++ b/app/src/main/java/com/h/pixeldroid/MainActivity.kt @@ -1,5 +1,6 @@ package com.h.pixeldroid +import android.app.Activity import android.content.Intent import android.os.Bundle import android.view.MenuItem @@ -19,6 +20,7 @@ import com.h.pixeldroid.motions.OnSwipeListener class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelectedListener { private lateinit var drawerLayout: DrawerLayout + private val newPostsActivityRequestCode = Activity.RESULT_OK override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -69,7 +71,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte supportFragmentManager.beginTransaction().replace(R.id.fragment_container, fragment).commit() } - /* + /** When clicking in the drawer menu, go to the corresponding activity */ override fun onNavigationItemSelected(@NonNull item: MenuItem): Boolean { @@ -83,7 +85,7 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte return true } - /* + /** Launches the given activity and put it as the current one */ private fun launchActivity(activity: AppCompatActivity) { @@ -91,8 +93,8 @@ class MainActivity : AppCompatActivity(), NavigationView.OnNavigationItemSelecte startActivity(intent) } - /* - Closes the drawer if we are clicking behind it + /** + Closes the drawer if it is open, when we press the back button */ override fun onBackPressed() { if(drawerLayout.isDrawerOpen(GravityCompat.START)){ diff --git a/app/src/main/java/com/h/pixeldroid/db/AppDatabase.kt b/app/src/main/java/com/h/pixeldroid/db/AppDatabase.kt new file mode 100644 index 00000000..ae428f7f --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/db/AppDatabase.kt @@ -0,0 +1,41 @@ +package com.h.pixeldroid.db + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverters + +@Database(entities = [PostEntity::class], version = 1) +@TypeConverters(Converters::class) +abstract class AppDatabase : RoomDatabase() { + abstract fun postDao(): PostDao + val MAX_NUMBER_OF_POSTS = 100 + + companion object { + // Singleton prevents multiple instances of database opening at the + // same time. + @Volatile + private var INSTANCE: AppDatabase? = null + var TEST_MODE = false + + fun getDatabase(context: Context): AppDatabase { + + return INSTANCE ?: synchronized(this) { + var instance: AppDatabase? = null + + // To be able to create a temporary database that flushes when tests are over + instance = if (TEST_MODE) { + Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java).allowMainThreadQueries().build() + } else { + Room.databaseBuilder( + context.applicationContext, AppDatabase::class.java, "posts_database" + ).build() + } + + INSTANCE = instance + return instance + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/h/pixeldroid/db/Converters.kt b/app/src/main/java/com/h/pixeldroid/db/Converters.kt new file mode 100644 index 00000000..468dfeec --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/db/Converters.kt @@ -0,0 +1,16 @@ +package com.h.pixeldroid.db + +import androidx.room.TypeConverter +import java.util.Date + +class Converters { + @TypeConverter + fun fromTimestamp(value: Long?): Date? { + return value?.let { Date(it) } + } + + @TypeConverter + fun dateToTimestamp(date: Date?): Long? { + return date?.time?.toLong() + } +} diff --git a/app/src/main/java/com/h/pixeldroid/db/PostDao.kt b/app/src/main/java/com/h/pixeldroid/db/PostDao.kt new file mode 100644 index 00000000..3b29d393 --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/db/PostDao.kt @@ -0,0 +1,36 @@ +package com.h.pixeldroid.db + +import androidx.lifecycle.LiveData +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy.REPLACE +import androidx.room.Query +import java.util.Date + +@Dao +interface PostDao { + @Query("SELECT * FROM posts") + fun getAll(): LiveData> + + @Query("SELECT * FROM posts WHERE uid = :postId") + fun getById(postId: Int): PostEntity + + @Query("SELECT count(*) FROM posts") + fun getPostsCount(): Int + + @Query("UPDATE posts SET date = :date WHERE uid = :postId") + fun addDateToPost(postId: Int, date: Date) + + @Query("DELETE FROM posts") + fun deleteAll() + + @Query("DELETE FROM posts WHERE date IN (SELECT min(date) FROM posts) ") + fun deleteOldestPost(): Int + + @Insert(onConflict = REPLACE) + fun insertAll(vararg posts: PostEntity) + + @Delete + fun delete(post: PostEntity) +} diff --git a/app/src/main/java/com/h/pixeldroid/db/PostEntity.kt b/app/src/main/java/com/h/pixeldroid/db/PostEntity.kt new file mode 100644 index 00000000..3c761083 --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/db/PostEntity.kt @@ -0,0 +1,17 @@ +package com.h.pixeldroid.db + +import androidx.room.ColumnInfo; +import androidx.room.Entity; +import androidx.room.PrimaryKey; +import java.util.Date + +@Entity(tableName= "posts") +data class PostEntity( + @PrimaryKey val uid: Int, + @ColumnInfo(name = "domain") val domain: String? = "", + @ColumnInfo(name = "username") val username: String? = "", + @ColumnInfo(name = "display name") val displayName: String? = "", + @ColumnInfo(name = "accountID") val accountID: Int? = -1, + @ColumnInfo(name = "image url") val ImageURL: String? = "", + @ColumnInfo(name = "date") val date: Date? +) \ No newline at end of file diff --git a/app/src/main/java/com/h/pixeldroid/utils/DatabaseUtils.kt b/app/src/main/java/com/h/pixeldroid/utils/DatabaseUtils.kt new file mode 100644 index 00000000..6048d457 --- /dev/null +++ b/app/src/main/java/com/h/pixeldroid/utils/DatabaseUtils.kt @@ -0,0 +1,45 @@ +package com.h.pixeldroid.utils + +import com.h.pixeldroid.db.AppDatabase +import com.h.pixeldroid.db.PostEntity +import java.util.Calendar + +class DatabaseUtils { + companion object { + /** + * Inserts one post into the specified database, + * after it has checked the LRU + */ + fun insertPost(db: AppDatabase, post: PostEntity) { + if (!IsInsertable(db)) { + removeEldestPost(db) + } + + db.postDao().addDateToPost(post.uid, Calendar.getInstance().time) + db.postDao().insertAll(post) + } + + /** + * Inserts multiple posts into the specified database + */ + fun insertAllPosts(db: AppDatabase, vararg posts: PostEntity) { + posts.forEach { insertPost(db, it) } + } + + /** + * Checks if we can add one post into the database + * or if it is full + */ + private fun IsInsertable(db: AppDatabase): Boolean { + return db.postDao().getPostsCount() + 1 <= db.MAX_NUMBER_OF_POSTS + } + + /** + * Removes the eldest post from the database + */ + private fun removeEldestPost(db: AppDatabase) { + db.postDao().deleteOldestPost() + } + } + +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_add_black_24dp.xml b/app/src/main/res/drawable/ic_add_black_24dp.xml new file mode 100644 index 00000000..0258249c --- /dev/null +++ b/app/src/main/res/drawable/ic_add_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 545b9c6d..eae59d78 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -17,4 +17,12 @@ +