I'm new with Room Database and I would like to extract from one database an array of int.
I think that the problem is in the code of the fragment, when I would like to use the function "stationlist" to obtain an array of the codes of stations present in database. The output "StationList" seems to be Unit instead of Array: does anyone know the solution?
Thank you
This is my entity and data class:
#Parcelize
#Entity(tableName = "user_table")
data class User(
#PrimaryKey(autoGenerate = true)
val id: Int,
val Name: String,
val StationCode: Int
): Parcelable
This is my Dao:
#Dao
interface UserDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun addUser(user: User)
#Query("SELECT intArrayOf(StationCode) FROM user_table")
suspend fun stationlist() : Array
}
This is my database:
#Database(entities = [User::class], version = 1, exportSchema = false)
abstract class UserDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
#Volatile
private var INSTANCE: UserDatabase? = null
fun getDatabase(context: Context): UserDatabase{
val tempInstance = INSTANCE
if(tempInstance != null){
return tempInstance
}
synchronized(this){
val instance = Room.databaseBuilder(
context.applicationContext,
UserDatabase::class.java,
"user_database"
).build()
INSTANCE = instance
return instance
}
}
}
}
This is my Repository:
class UserRepository(private val userDao: UserDao) {
val readAllData: LiveData<List<User>> = userDao.readAllData()
suspend fun addUser(user: User){
userDao.addUser(user)
}
suspend fun stationlist(){
userDao.stationlist()
}
}
and this is my Viewmodel:
class UserViewModel(application: Application): AndroidViewModel(application) {
val readAllData: LiveData<List<User>>
private val repository: UserRepository
init {
val userDao = UserDatabase.getDatabase(
application
).userDao()
repository = UserRepository(userDao)
readAllData = repository.readAllData
}
fun addUser(user: User){
viewModelScope.launch(Dispatchers.IO) {
repository.addUser(user)
}
}
fun stationlist(){
viewModelScope.launch(Dispatchers.IO) {
repository.stationlist()
}
}
}
At last this is a part of the fragment:
class AddFragment : Fragment(), OnMapReadyCallback {
private lateinit var mUserViewModel: UserViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// here is all the part regarding displaying the layout and collecting data
mUserViewModel = ViewModelProvider(this).get(UserViewModel::class.java)
view.add_btn.setOnClickListener {
insertDataToDatabase()
}
return view
}
private fun insertDataToDatabase() {
if(inputCheck(name, stationcode)){
val user = User(
0,
name,
stationcode)
mUserViewModel.addUser(user)
// Here is the problem:
val StationList= mUserViewModel.stationlist()
}
The problem is the dao method. Change it with this:
#Query("SELECT StationCode FROM user_table")
suspend fun stationlist() : Array<Int>
EDIT
There are some other problems with coroutines.
In fragment you call this:
val StationList= mUserViewModel.stationlist()
But this method does not return immediately.
fun stationlist(){
viewModelScope.launch(Dispatchers.IO) {
repository.stationlist()
}
}
You should implement a live data array, which is updated when async db query returns. In this way:
val stationList: MutableLiveData<Array<Int>>() = MutableLiveData()
fun stationlist(){
viewModelScope.launch(Dispatchers.IO) {
stationList.value = repository.stationlist()
}
}
And in fragment you have to observe the list:
mUserViewModel.stationList.observe(viewLifecycleOwner) { array ->
// do stuff
}
You also have to change repository stationlist() return type:
suspend fun stationlist(): Array<Int> {
return userDao.stationlist()
}
These are useful links to understand coroutines, room and livedata:
https://developer.android.com/topic/libraries/architecture/livedata
https://developer.android.com/codelabs/kotlin-android-training-coroutines-and-room#0
Related
I'm trying to make an Android application using Kotlin, Room Database and both view model and view model factory.
The issue is that when I try to create the object of the entity I am trying to insert, I just get this warning (which i think in this case makes no sense):
Dialog with warning "unexpected tokens (Use ';' to separate expressions on the same line)"
Anyway, here are my Fragment, FragmentViewModel, FragmentViewModelFactory, Entity, Dao and Database files (or relevant parts):
Fragment:
class RegisterFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = DataBindingUtil.inflate<RegisterFragmentBinding>(
inflater,
R.layout.register_fragment,
container,
false
)
val application = requireNotNull(this.activity).application
val dataSource = NbaCafeDB.getInstance(application).usuariDao
val viewModelFactory = RegisterViewModelFactory(dataSource, application)
val registerViewModel =
ViewModelProvider(this, viewModelFactory).get(RegisterViewModel::class.java)
binding.setLifecycleOwner(this)
...
binding.endavantButton.setOnClickListener { View ->
val email = binding.email.text.toString()
val username = binding.registerUser.text.toString()
val password = binding.registerPassword.text.toString()
val confPassword = binding.confirmPassword.text.toString()
if (email != "" && username != "") {
if (registerViewModel.userExists(username)) {
Toast.makeText(context, "Aquest nom d'usuari ja existeix", Toast.LENGTH_LONG)
.show()
} else if (password == confPassword) {
registerViewModel.insert(username, email, password)
} else {
Toast.makeText(context, "Les contrassenyes no coincideixen", Toast.LENGTH_LONG)
.show()
}
}
}
FragmentViewModel:
class RegisterViewModel(
private val dataSource: UsuariDao, application: Application
) : AndroidViewModel(application) {
fun insert(nomUsuari: String, emailUsuari: String, passUsuari: String) {
val usuari: Usuari(nomUsuari, emailUsuari, passUsuari)
viewModelScope.launch {
dataSource.insert(usuari)
}
}
fun userExists(usuariNom: String): Boolean {
return dataSource.userExists(usuariNom)
}
}
FragmentViewModelFactory:
class RegisterViewModelFactory(
private val dataSource: UsuariDao,
private val application: Application
) : ViewModelProvider.Factory {
#Suppress("Unchecked_cast")
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(RegisterViewModel::class.java)) {
return RegisterViewModel(dataSource, application) as T
}
throw IllegalArgumentException("Unknown View Model Class")
}
}
Entity:
#Entity
data class Usuari(
#PrimaryKey
#ColumnInfo(name = "nom_usuari")
var nomUsuari: String,
#ColumnInfo(name = "email_usuari")
var emailUsuari: String,
#ColumnInfo(name = "password_usuari")
var passwordUsuari: String
)
DAO:
#Dao
interface UsuariDao {
#Insert
suspend fun insert(usuari: Usuari)
#Query ("SELECT EXISTS(SELECT * FROM Usuari WHERE nomUsuari = :usuariNom)")
fun userExists(usuariNom: String): Boolean
}
And finally, Database:
#Database(
entities =
[Beguda::class,
Comanda::class,
Postre::class,
Sandwich::class,
Usuari::class],
version = 3,
exportSchema = false
)
abstract class NbaCafeDB : RoomDatabase() {
abstract val begudaDao: BegudaDao
abstract val comandaDao: ComandaDao
abstract val postreDao: PostreDao
abstract val sandwichDao: SandwichDao
abstract val usuariDao: UsuariDao
companion object {
#Volatile
private var INSTANCE: NbaCafeDB? = null
fun getInstance(context: Context): NbaCafeDB {
synchronized(this) {
var instance = INSTANCE
if (instance == null) {
instance = Room.databaseBuilder(
context.applicationContext,
NbaCafeDB::class.java,
"nba_cafe_database"
)
.fallbackToDestructiveMigration()
.allowMainThreadQueries()
.build()
INSTANCE = instance
}
return instance
}
}
}
}
I hope someone has an answer for this, thanks in advance!!
I'm new to android and room. I'm trying to make a local db but I'm struggling at this point where the database is not empty as the image below proves. But when I try to select any data it returns an empty list or null values. Note that the insert query works fine.
Code:
Entity:
#Entity(tableName = "product_table")
#Parcelize
data class Product(
#PrimaryKey
#SerializedName("id")
val id : String,
#SerializedName("title")
val title : String,
#SerializedName("price")
val price : String,
#SerializedName("category")
val category : String,
#SerializedName("description")
val description : String,
#SerializedName("image")
val image : String,
val color : String,
val size : String
): Parcelable
Dao:
#Dao
interface CartDao {
#Query("SELECT * FROM product_table")
fun get_all_carts(): LiveData<List<Product>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert_item_to_cart(product: Product) : Long
#Delete
suspend fun delete_item_from_cart(product: Product)
#Query("Delete FROM product_table")
fun delete_all_cart()
}
Database:
#Database(entities = [Product::class], version = 1)
abstract class ProductDatabase : RoomDatabase() {
abstract fun productDao(): CartDao
companion object{
#Volatile
private var INSTANCE: ProductDatabase? = null
fun getDataseClient(context: Context) : ProductDatabase {
if (INSTANCE != null) return INSTANCE!!
synchronized(this) {
INSTANCE = Room
.databaseBuilder(context, ProductDatabase::class.java, "product_database")
.fallbackToDestructiveMigration()
.build()
return INSTANCE!!
}
}
}
}
Repository:
class CartRepository {
companion object {
var productDatabase: ProductDatabase? = null
var product: LiveData<Product>? = null
fun initializeDB(context: Context) : ProductDatabase {
return ProductDatabase.getDataseClient(context)
}
fun get_all_data(context: Context) : List<Product> {
productDatabase = initializeDB(context)
var temp_list = emptyList<Product>()
CoroutineScope(Dispatchers.IO).launch {
temp_list = productDatabase!!.productDao().get_all_carts()
}
return temp_list
}
fun get_first_item(context: Context, input_id : String) : Product {
productDatabase = initializeDB(context)
var temp_item : Product = Product("","","","","","","","")
CoroutineScope(Dispatchers.IO).launch {
temp_item = productDatabase!!.productDao().get_item(input_id)
}
return temp_item
}
}
}
View Model:
#HiltViewModel
class CartFragmentViewModel #Inject constructor(
private val productDao : CartDao
) : ViewModel() {
var products_list : MutableLiveData<List<Product>>
init {
products_list = MutableLiveData()
}
fun get_all_products(context: Context) : List<Product>{
return CartRepository.get_all_data(context)
}
fun get_first_item(context: Context, input_id : String) : Product{
return CartRepository.get_first_item(context, input_id)
}
}
Fragment:
class CartFragment #Inject constructor(
) : Fragment(R.layout.cart_fragment_layout) {
lateinit var cart_list : List<Product>
val cart_adapter = CartRecyclerViewAdapter()
val viewModel by activityViewModels<CartFragmentViewModel>()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
rv_cart.adapter = cart_adapter
rv_cart.layoutManager = LinearLayoutManager(
activity?.applicationContext,
LinearLayoutManager.VERTICAL,
false
)
rv_cart.setHasFixedSize(true)
/*cart_list = viewModel.get_all_products(activity?.applicationContext!!)
cart_adapter.submitList(
cart_list
)
cart_adapter.notifyDataSetChanged()
Log.d(TAG, "Fetched Data: {${cart_list.get(1).title}}")*/
var p = viewModel.get_first_item(activity?.applicationContext!!, "1")
cart_adapter.submitList(
listOf(
p
)
)
cart_adapter.notifyDataSetChanged()
var s = p.title
Log.d(TAG, "Fetched Data: {$s}")
/*viewModel.get_first_item(activity?.applicationContext!!).observe(viewLifecycleOwner, Observer {
cart_adapter.submitList(listOf(it))
cart_adapter.notifyDataSetChanged()
})*/
//viewModel.get_first_item(activity?.applicationContext!!)
}
}
There are many comments and logs in the Fragment Class for the sake of trying to figure what the problem is. I can't really know what is happening and when I use LiveData as return type of dao get functions the app crashes. Hope someone can help me out and thanks for your attention.
The problem with your CartRepository methods.
fun get_first_item(context: Context, input_id : String) : Product {
productDatabase = initializeDB(context)
var temp_item : Product = Product("","","","","","","","")
CoroutineScope(Dispatchers.IO).launch {
temp_item = productDatabase!!.productDao().get_item(input_id)
}
return temp_item
}
In the above method, you are fetching an item in a background thread which means it goes into another thread and allows the return temp_item to always returns immediately, without blocking the code to wait for a result so that's why you are getting null and or empty list.
Solution is :
Make all database operation methods in CartRepository as suspended, see below:
Note: I use an object instead of class
object CartRepository {
var productDatabase: ProductDatabase? = null
fun initializeDB(context: Context) : ProductDatabase {
return ProductDatabase.getDataseClient(context)
}
suspend fun get_all_data(context: Context) : List<Product> {
productDatabase = initializeDB(context)
return productDatabase!!.productDao().get_all_carts()
}
suspend fun get_first_item(context: Context, input_id : String) : Product {
productDatabase = initializeDB(context)
return productDatabase!!.productDao().get_item(input_id)
}
}
And in your viewModel call this suspend function in viewModelScope like below:
#HiltViewModel
class CartFragmentViewModel #Inject constructor(
private val productDao : CartDao
) : ViewModel() {
.....
var productData = MutableLiveData<Product>()
fun get_first_item(context: Context, input_id: String) {
viewModelScope.lauch(Dispatchers.IO){
val data = CartRepository.get_first_item(context, input_id)
withContext(Dispatchers.Main){
productData.value = data
}
}
}
....
In your fragment call get_first_item first then observe you data productData and you can do the same things for other database operations also by following all steps.
I hope this will help you if you dont understand any code just let me know in the comments and please ignore the typos
To use a LiveData with room, firstly, you should attach an observer to the live data.
So instead of returning the value of LiveData from the Repository methods, you should return the Live Data object itself,
And then observe that Livedata in your Viewmodel class.
If you need the code sample, you can ask me in the comment section
Happy Coding :D
After inserting data into RoomDB when I fetch it using mindValleyDao.getCategories().value It returns null
DatabaseClass
#Database(entities = arrayOf(CategoryBO::class), version = 1, exportSchema = false)
abstract class MindValleyDatabase : RoomDatabase(){
abstract fun mindValleyDao(): MindValleyDao
companion object {
// Singleton prevents multiple instances of database opening at the
// same time.
#Volatile
private var INSTANCE: MindValleyDatabase? = null
fun getDatabase(context: Context): MindValleyDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
MindValleyDatabase::class.java,
"mindvalley_database"
).allowMainThreadQueries()
.fallbackToDestructiveMigration().build()
INSTANCE = instance
return instance
}
}
}
}
CategoryBO.kt
#Entity(tableName = "CategoryEntity")
data class CategoryBO( #PrimaryKey(autoGenerate = true) val id:Int, val name:String)
Doa
#Dao
interface MindValleyDao {
#Query("SELECT * from CategoryEntity ORDER BY id ASC")
fun getCategories(): LiveData<List<CategoryBO>>
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(categoryBO: CategoryBO)
//suspend fun insert(categoryBO: CategoryBO)
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(categoryBOList: List<CategoryBO>)
}
I am testing it by inserting Category and fetching list of categories like
class MindValleyViewModelNew #Inject constructor() : BaseViewModel() {
var categoryList: MutableLiveData<List<CategoryBO>> = MutableLiveData()
private lateinit var mindValleyDao:MindValleyDao
fun loadDatabase(mContext:Context){
mindValleyDao = MindValleyDatabase.getDatabase(mContext).mindValleyDao()
GlobalScope.launch(Dispatchers.IO) {
mindValleyDao.insert(CategoryBO(0,"first item"))
val cats = mindValleyDao.getCategories().value
categoryList.postValue(cats)
}
}
}
mindValleyDao.getCategories() has return type is LiveData, that's why it query value async, you shouldn't call .value
LiveData type in Room should only use for observe,
If you want to get value, modify your code to fun getCategories(): List<CategoryBO> instead
i am implementing Room with a vIewModel, my structure is the following
#DAO,#Entity,#Database,#Repository
#Entity(tableName="dx_table")
class dx_table(
#ColumnInfo(name = "name")
val naxme: String,
#PrimaryKey
#ColumnInfo(name = "phone")
val phone: String,
#ColumnInfo(name = "passx")
val passx: String,
#ColumnInfo(name = "login_fx")
val login_fx: String
)
#Dao
interface dx_dao{
#Query("SELECT * FROM dx_table")
fun get_all():LiveData<List<dx_table>>
#Insert
suspend fun insertTrx(dx_table:dx_table)
#Query("UPDATE dx_table SET login_fx =:login_fx where phone=:phonex")
suspend fun insertFx(login_fx: String,phonex: String)
#Query("SELECT * from dx_table where phone=:phonex")
suspend fun get_name_px(phonex: String):List<dx_table>
#Query("Delete from dx_table")
suspend fun deleteAll()
#Query("Select * from dx_table where login_fx=1")
suspend fun selectFx():List<dx_table>
}
#Database(entities = arrayOf(dx_table::class), version = 1, exportSchema = false)
public abstract class DxDatabase : RoomDatabase() {
abstract fun dxDao(): dx_dao
companion object {
// Singleton prevents multiple instances of database opening at the
// same time.
#Volatile
private var INSTANCE: DxDatabase? = null
fun getDatabase(context: Context): DxDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
DxDatabase::class.java,
"dx_database"
).build()
INSTANCE = instance
return instance
}
}
}
}
class dxRepository(private val dxDao: dx_dao ){
val k_d:LiveData<List<dx_table>> = dxDao.get_all()
suspend fun insert_trx(dx_table: dx_table){
dxDao.insertTrx(dx_table)
}
suspend fun insert_fx(login_fx: String,phonex: String) {
dxDao.insertFx(login_fx,phonex)
}
suspend fun select_fx() {
dxDao.selectFx()
}
suspend fun get_name_px(phonex: String) :List<dx_table> {
return dxDao.get_name_px(phonex) as List<dx_table>
}
}
The viewmodel is
class DxViewModel (application: Application) : AndroidViewModel(application) {
var repository: dxRepository
var k_d: LiveData<List<dx_table>>
init {
// Gets reference to WordDao from WordRoomDatabase to construct
// the correct WordRepository.
val dxDao = DxDatabase.getDatabase(application).dxDao()
repository = dxRepository(dxDao)
k_d = repository.k_d
}
fun insert_trx(dxTable: dx_table) = viewModelScope.launch {
repository.insert_trx(dxTable)
}
fun insert_fx(login_fx: String, phonex: String) = viewModelScope.launch {
repository.insert_fx(login_fx, phonex)
}
fun select_fx() = viewModelScope.launch {
repository.select_fx()
}
fun get_name_px(phonex: String) = viewModelScope.launch {
repository.get_name_px(phonex)
}
}
I can track the live data using observe,its not an issue, the problem i am facing is with the get_name_px(phone)
var mView = ViewModelProviders.of(this).get(DxViewModel::class.java)
var lm = mView.get_name_px(phone)
here lm seems to be job type , i need the return List , how do i get it .
In your viewModel function select_fx() return a job, because launch does not return result, so you have to either:
1) Use async and await
fun get_name_px(phonex: String) = viewModelScope.async {
repository.get_name_px(phonex)
}.await()
2) Not use launch viewModel, use it in Activity/Fragment
suspend fun get_name_px(phonex: String) = repository.get_name_px(phonex)
class CardFragment : Fragment() {
fun get() {
// launch in Dispatchers.Main
lifecycleScope.launch {
var lm = mView.get_name_px(phone)
}
// launch in background thread
lifecycleScope.launch(Dispatchers.Default) {
var lm = mView.get_name_px(phone)
}
}
}
I am trying, without success, to solve a problem for days. I would like to update my recyclerView whenever the records of a particular model change in the Database (DB Room). I use ViewModel to handle the model data and the list of records are stored in LiveData.
Database
#Database(entities = arrayOf(Additive::class), version = ElementDatabase.DB_VERSION, exportSchema = false)
abstract class ElementDatabase() : RoomDatabase() {
companion object {
const val DB_NAME : String = "element_db"
const val DB_VERSION : Int = 1
fun get(appContext : Context) : ElementDatabase {
return Room.databaseBuilder(appContext, ElementDatabase::class.java, DB_NAME).build()
}
}
abstract fun additivesModels() : AdditiveDao
}
Model
#Entity
class Additive {
#PrimaryKey #ColumnInfo(name = "id")
var number : String = ""
var dangerousness : Int = 0
var description : String = ""
var names : String = ""
var notes : String = ""
var risks : String = ""
var advice : String = ""
}
Dao
#Dao
interface AdditiveDao {
#Query("SELECT * FROM Additive")
fun getAllAdditives() : LiveData<List<Additive>>
#Query("SELECT * FROM Additive WHERE id = :arg0")
fun getAdditiveById(id : String) : Additive
#Query("DELETE FROM Additive")
fun deleteAll()
#Insert(onConflict = REPLACE)
fun insert(additive: Additive)
#Update
fun update(additive: Additive)
#Delete
fun delete(additive: Additive)
}
ViewModel
class AdditiveViewModel(application: Application) : AndroidViewModel(application) {
private var elementDatabase : ElementDatabase
private val additivesModels : LiveData<List<Additive>>
init {
this.elementDatabase = ElementDatabase.get(appContext = getApplication())
this.additivesModels = this.elementDatabase.additivesModels().getAllAdditives()
}
fun getAdditivesList() : LiveData<List<Additive>> {
return this.additivesModels
}
fun deleteItem(additive : Additive) {
DeleteAsyncTask(this.elementDatabase).execute(additive)
}
private class DeleteAsyncTask internal constructor(private val db: ElementDatabase) : AsyncTask<Additive, Void, Void>() {
override fun doInBackground(vararg params: Additive): Void? {
db.additivesModels().delete(params[0])
return null
}
}
}
Fragment
class AdditivesFragment : LifecycleFragment() {
private var viewModel : AdditiveViewModel? = null
private var adapter : AdditivesAdapter? = null
companion object {
fun newInstance() : AdditivesFragment {
val f = AdditivesFragment()
val args = Bundle()
f.arguments = args
return f
}
}
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater?.inflate(R.layout.fragment_additives, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
this.adapter = AdditivesAdapter(ArrayList<Additive>())
this.additives_list.layoutManager = GridLayoutManager(this.context, 2, GridLayoutManager.VERTICAL, false)
this.additives_list.adapter = this.adapter
this.viewModel = ViewModelProviders.of(this).get(AdditiveViewModel::class.java)
this.viewModel?.getAdditivesList()?.observe(this, Observer<List<Additive>> { additivesList ->
if(additivesList != null) {
this.adapter?.addItems(additivesList)
}
})
super.onActivityCreated(savedInstanceState)
}
}
Now, my question is why is the observer called only once (at the start of the fragment) and then is not called back again? How can I keep the observer constantly listening to the changes in the DB (insert, update, delete) so that my recyclerView instantly can be updated? Thanks a lot for any suggestion.
This is where you made a mistake:
this.viewModel = ViewModelProviders.of(this).get(AdditiveViewModel::class.java)
you are passing this while you are inside the fragment which is pretty disturbing for some people cause it is not a syntax error but logical. You have to pass activity!! instead, it will be like this:
this.viewModel = ViewModelProviders.of(activity!!).get(AdditiveViewModel::class.java)
UPDATE:
Pass viewLifecycleOwner while being inside fragment while observing the Data
mainViewModel.data(viewLifecycleOwner, Observer{})
If you're using fragmentKtx, you can init viewModel this way:
private val viewModel by viewModels<MainViewModel>()
If You've viewModelFactory:
private val viewModel by viewModels<MainViewModel>{
viewModelFactory
}
with this approach you don't need to call:
// you can omit this statement completely
viewModel = ViewModelProviders.of(this).get(AdditiveViewModel::class.java)
You can simply just start observing the data..