Home > Blockchain >  Databases Folder Empty In Device File Explorer-ROOM Database
Databases Folder Empty In Device File Explorer-ROOM Database

Time:07-03

When the app is ran, no errors occur however, the databases folder in device file explorer is empty. I have tried to fix the issue myself but since I'm new to android studio and kotlin I haven't been able to I just want to ensure the database is working before I carry on with the rest of the code.

Pic of databases folder

Any help would be greatly appreciated.

Code:

Todo

package com.example.todoit.data

import androidx.room.Entity
import androidx.room.PrimaryKey

@Entity(tableName = "todo_data")
data class Todo (
    @PrimaryKey val id: Int,
    val title: String,
    var isChecked: Boolean = false
)

TodoDao

package com.example.todoit.data

import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query

@Dao
interface TodoDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun addTodo(todo: Todo)

    @Query("SELECT * FROM todo_data ORDER BY id ASC")
    fun readAllData(): LiveData<List<Todo>>
}

TodoDataBase

package com.example.todoit.data

import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase

@Database(entities = [Todo::class],version = 1, exportSchema = false)
abstract class TodoDataBase: RoomDatabase() {

    abstract fun todoDao(): TodoDao

    companion object{
        @Volatile
        private var INSTANCE: TodoDataBase? = null

        fun getDataBase(context: Context):TodoDataBase{
            val tempInstance = INSTANCE
            if(tempInstance != null){
                return tempInstance
            }
            synchronized(this){
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    TodoDataBase::class.java,
                    "todo_database"
                ).build()
                INSTANCE = instance
                return instance
            }
        }


    }
}

TodoRepository

package com.example.todoit.data

import androidx.lifecycle.LiveData

class TodoRepository(private val todoDao:TodoDao) {
    val readAllData: LiveData<List<Todo>> = todoDao.readAllData()

    suspend fun addTodo(todo:Todo) {
        todoDao.addTodo(todo)
    }
}

TodoViewModel


package com.example.todoit.data

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch




class TodoViewModel(application: Application) : AndroidViewModel(application) {

    private val readAllData: LiveData<List<Todo>>
    private val repository: TodoRepository




    init {
        val todoDao = TodoDataBase.getDataBase(application).todoDao()
        repository = TodoRepository(todoDao)
        readAllData = repository.readAllData
    }


    fun addTodoToDataBase(todo: Todo) {
        viewModelScope.launch(Dispatchers.IO) {
            repository.addTodo(todo)
        }
    }
}

MainActivity

package com.example.todoit

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.todoit.data.Todo
import com.example.todoit.data.TodoViewModel
import kotlinx.android.synthetic.main.activity_main.*


class MainActivity : AppCompatActivity() {
    private lateinit var todoAdapter: TodoAdapter
    private lateinit var todoViewModel: TodoViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        todoViewModel = ViewModelProvider(this).get(TodoViewModel::class.java)
        todoAdapter = TodoAdapter(mutableListOf())
        rvTodoItems.layoutManager = LinearLayoutManager(this)
        rvTodoItems.adapter = todoAdapter

        btnAddTodo.setOnClickListener {
            val todoTitle = etTodoTitle.text.toString()
            if (todoTitle.isNotEmpty()) {
                val todo = Todo(0,todoTitle,false)
                etTodoTitle.text.clear()
                insertDataToDataBase(todo)
                todoAdapter.addTodo(todo)
        }
        btnDeleteTodo.setOnClickListener {
            todoAdapter.deleteDoneTodos()
        }
    }}

    private fun insertDataToDataBase(todo: Todo) {
        val todoTitle = etTodoTitle.text.toString()

        if(todoTitle.isNotEmpty()) {
            //Add data to database
            todoViewModel.addTodoToDataBase(todo)
        }
    }
}

TodoAdapter

package com.example.todoit

import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.todoit.data.Todo
import kotlinx.android.synthetic.main.item_todo.view.*

class TodoAdapter(
    private val todos: MutableList<Todo>,
    ) : RecyclerView.Adapter<TodoAdapter.TodoViewHolder>() {

    class TodoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {
        return TodoViewHolder(
            LayoutInflater.from(parent.context).inflate(
                R.layout.item_todo,
                parent,
                false
            )
        )
    }

    fun addTodo(todo: Todo) {
        todos.add(todo)
        notifyItemInserted(todos.size - 1)
    }

    fun deleteDoneTodos() {
        todos.removeAll { todo ->
            todo.isChecked
        }
        notifyDataSetChanged()
    }

    private fun toggleStrikeThrough(tvTodoTitle: TextView, isChecked: Boolean) {
        if (isChecked) {
            tvTodoTitle.paintFlags = tvTodoTitle.paintFlags or STRIKE_THRU_TEXT_FLAG
        } else {
            tvTodoTitle.paintFlags = tvTodoTitle.paintFlags and STRIKE_THRU_TEXT_FLAG.inv()
        }
    }

    override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
        val curTodo = todos[position]
        holder.itemView.apply {
            tvTodoTitle.text = curTodo.title
            cbDone.isChecked = curTodo.isChecked
            toggleStrikeThrough(tvTodoTitle, curTodo.isChecked)
            cbDone.setOnCheckedChangeListener { _, isChecked ->
                toggleStrikeThrough(tvTodoTitle, isChecked)
                curTodo.isChecked = !curTodo.isChecked
            }
        }
    }

    override fun getItemCount(): Int {
        return todos.size
    }
}

Gradle(Module)

plugins {
    id 'com.android.application'
    id 'org.jetbrains.kotlin.android'
    id "kotlin-android-extensions"
}
apply plugin: 'kotlin-kapt'

android {
    compileSdk 32

    defaultConfig {
        applicationId "com.example.todoit"
        minSdk 21
        targetSdk 32
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}



dependencies {
    //ROOM
    def roomVersion = "2.4.2"
    implementation "androidx.room:room-ktx:$roomVersion"
    kapt "androidx.room:room-compiler:$roomVersion"

    // Navigation Component
    implementation 'androidx.navigation:navigation-fragment-ktx:2.2.2'
    implementation 'androidx.navigation:navigation-ui-ktx:2.2.2'


    implementation 'androidx.core:core-ktx:1.7.0'
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.6.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

    // Lifecycle components
    implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
    implementation "androidx.lifecycle:lifecycle-common-java8:2.2.0"
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"

    // Kotlin components
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72"
    api "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.5"
    api "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5"
}

Gradle(Project)

// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
    id 'com.android.application' version '7.2.0' apply false
    id 'com.android.library' version '7.2.0' apply false
    id 'org.jetbrains.kotlin.android' version '1.5.30' apply false
}



task clean(type: Delete) {
    delete rootProject.buildDir
}

CodePudding user response:

I just want to ensure the database is working before I carry on with the rest of the code.

Then I would suggest testing that in isolation (there are some issues).

I would suggest temporarily adding .allowMainThreadQueries to the databaseBuilder in the TodoDataBase class e.g.

        synchronized(this){
            val instance = Room.databaseBuilder(
                context.applicationContext,
                TodoDataBase::class.java,
                "todo_database"
            )
                .allowMainThreadQueries() // ADDED <<<<<<<<<<
                .build()
            INSTANCE = instance
            return instance
        }

You can now circumvent having to run on another thread.

You can then add another function to the TodoDao e.g.

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun altInsert(todo: Todo): Long

You can then, again temporarily, add/amend MainActivity to invoke the insert on the main thread by:-

a) Adding 2 lateinits for vars to hold a TodoDataBase and a TodoDao instance e.g. :-

lateinit var db: TodoDataBase
lateinit var dao: TodoDao

b) Instantiating the new lateinits (suggest immediately after the setContentView) :-

    db = TodoDataBase.getDataBase(this)
    dao = db.todoDao()

c) In the Add Button's on click listener replace the insertDataToDatabase with invocation of altInsert, perhaps with checking (Toast as an example) e.g. :-

            /* ADDED */
            //insertDataToDataBase(todo)
            if (dao.altInsert(todo) > 0) {
                Toast.makeText(this,"Added",Toast.LENGTH_SHORT).show()
            } else {
                Toast.makeText(this,"Oooops",Toast.LENGTH_SHORT).show()
            }
            /* END OF ADDED */

Now you can concentrate on the database aspect by running the App and then testing, say by adding 3 Todo's (e.g. Test001, Test002 and Test003) and:-

So when the App is running enter Test001 in the title and then click the ADD button:-

enter image description here

  • Now the Toast says Oops. However, using App Inspection (much better then Device Explorer) :-

enter image description here

So now on to Test002

enter image description here

Obviously there is an issue as Test001 has disappeared.

This is a twofold issue as such, the Todo's id column is defined using @PrimaryKey val id: Int, and your insert sets the id to 0. Thus a conflict occurs because an id of 0 already exists and thus Test001 is replaced by Test002, when I believe that you would want 2 Todos Test001 and Test002.

I would suggest changing to use @PrimaryKey val id: Long?=null,. This allows no value/null to be supplied and thus SQLite will generate a unique id (typically 1 then 2 then 3 ....). The id can be a 64 bit signed integer, thus Long rather than Int is more apt (it takes no more storage as far as SQLite is concerned as it will store the value in as few bytes as it can).

  • An alternative would be to code @PrimaryKey(autoGenerate = true) when 0 is converted to no value/null by Room. However, this is at the expense of the inclusion of the AUTOINCREMENT keyword, which is very unlikely to be necessary and that is inefficient.

    • The enter image description here

      However, the App itself does not reflect this, it shows:-

      enter image description here

      This is an issue within the TodoAdapter, which would be another issue/question.

      And of course using Device Explorer, then :-

      enter image description here

  • Related