LiveData not updating after inserting into Room database - android

My project basically allows the user to create a list of products, where different products can be added. So in my case, the relation existing between my entities is many to many: I have a table for all the products that are added when the app is installed, I have a table with the lists the user creates and finally I have a table that records when the user add a product to a list.
The problem I´m getting, is that after a user add a product to a list, the LiveData that is being observed in the activity does not update the list and I cannot figure out why.
The activity (the product code is introduced by the user in another activity started for result):
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_list_of_products)
// Get the listname from the bundle
listName = intent.extras.getString(resources.getString(R.string.INTENT_EXTRA_LISTNAME))
// Set up the ViewModel
viewModel = ViewModelProviders.of(this, ListOfProductsViewModelFactory(application, listName)).get(ListOfProductsViewModel::class.java)
// RecyclerView setup
val recyclerView = findViewById<RecyclerView>(R.id.productRecyclerView)
val mAdapter = ProductAdapter(this)
recyclerView.adapter = mAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
viewModel!!.getProductsInProductList().observe(this, Observer {
products -> mAdapter.setProducts(products!!)
})
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if(requestCode == NEW_PRODUCT_ACTIVITY_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
val code = data!!.extras.getString(resources.getString(R.string.ADD_MANUALLY_ACTIVITY_REPLY))
val resultOfInsertion = viewModel!!.insertProductInProductList(code)
if(resultOfInsertion) {
Toast.makeText(applicationContext, "${code} successful added",
Toast.LENGTH_LONG).show()
} else {
Toast.makeText(applicationContext, "${code} was not added",
Toast.LENGTH_LONG).show()
}
}
else {
Toast.makeText(applicationContext, "Insertion cancelled",
Toast.LENGTH_LONG).show()
}
}
The ViewModel:
private var mRepo = ProductsInProductListRepository(application, listName)
private val productsInProductList = mRepo.getProductsInProductList()
fun getProductsInProductList() : LiveData<List<Product>> {
return productsInProductList
}
fun insertProductInProductList(code: String) : Boolean {
return mRepo.insertProductInProductList(code)
}
The repository:
private var productsInProductListDao : ProductsInProductListDao
private var productsInProductList : LiveData<List<Product>>
private val listName : String
constructor(application : Application, listName: String) {
val db = ProductDatabase.getProductDatabase(application)
this.productsInProductListDao = db!!.productsInProductListDao()
this.listName = listName
this.productsInProductList = productsInProductListDao.getProducstForProductList(listName)
}
fun getProductsInProductList() : LiveData<List<Product>> {
return productsInProductList
}
fun insertProductInProductList(productCode : String) : Boolean {
if(isProductAlreadyAdded(productCode)) {
return false
}
InsertProductInProductListAsync(productsInProductListDao, listName).execute(productCode)
return true
}
private fun isProductAlreadyAdded(productCode : String): Boolean {
return productsInProductListDao.getProductAddedToCertainList(listName, productCode).isNotEmpty()
}
The DAO:
#Dao
interface ProductsInProductListDao {
#Insert(onConflict = OnConflictStrategy.FAIL)
fun insertProductInProductList(productInProductList: ProductsInProductList)
#Query("SELECT code, model, pvpr, qtr, segmentation FROM product_table INNER JOIN products_in_productlist_table ON code=productCode WHERE listName=:listName")
fun getProducstForProductList(listName : String) : LiveData<List<Product>>
#Query("SELECT code, model, pvpr, qtr, segmentation FROM product_table INNER JOIN products_in_productlist_table ON code=productCode WHERE listName=:listName and code=:productCode")
fun getProductAddedToCertainList(listName : String, productCode: String) : List<Product>
}
The Entity:
#Entity(
indices = [Index("productCode")],
tableName = "products_in_productlist_table",
primaryKeys = ["listName", "productCode"],
foreignKeys = [
ForeignKey( onDelete = ForeignKey.CASCADE,
entity = ProductList::class,
parentColumns = ["name"],
childColumns = ["listName"]),
ForeignKey( entity = Product::class,
parentColumns = ["code"],
childColumns = ["productCode"])
]
)
class ProductsInProductList {
#NonNull
#ColumnInfo(name = "listName")
val listName : String
#NonNull
#ColumnInfo(name = "productCode")
val productCode : String
constructor(listName: String, productCode: String) {
this.listName = listName
this.productCode = productCode
}
}
Product adapter:
private val mInflater = LayoutInflater.from(context)
private val context = context
private var mProducts : List<Product>? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder {
val itemView = mInflater.inflate(R.layout.product_item, parent, false)
return ProductViewHolder(itemView)
}
fun setProducts(products : List<Product>) {
this.mProducts = products
}
override fun getItemCount(): Int {
if(mProducts != null)
return mProducts!!.size
return 0
}
override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
if(mProducts != null) {
val current = mProducts!!.get(position)
holder.setText(current.code)
} else {
holder.setText(context.resources.getString(R.string.lbl_no_list_created))
}
}
fun getProductAtPosition(position: Int) : Product {
return mProducts!!.get(position)
}
ProductViewHolder:
private var productItemView : TextView = itemView.findViewById(R.id.productItemRecyclerView)
fun setText(current: String) {
productItemView.text = current
}
Any idea why when creating a new row in the products_in_productlist_table table, the LiveData is not being updated?

Add the notifyDataSetChange() in your method in your AdapterClass :
fun setProducts(products : List<Product>) {
this.mProducts = products
notifyDataSetChange()
}

Related

How to sort items in RecyclerView by value from its item TextView

In my HomeFragment's RecyclerView I have Occurrence with name that has calculated last recorded DateTime(Occurrence class has list of DateTime's).
What I want is to have sorted items by those red, orange and green values:
-4h 30m: should be first,
0h -13m: should be second,
22h 16m: third,
13d 17h 36m: fourth,
20d 17h 37m: fifth,
and so on.
screenshot from HomeFragment
I decided that I want to sort this list not through DAO query but through value from item’s textView because there will be a lot of queries to DB if I want to save all calculated seconds to all items, Am I right?
Here are my files:
Occurence
`#Entity
data class Occurence(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name="occurence_id")
val occurenceId: Int = 0,
#ColumnInfo(name = "occurence_name")
val occurenceName: String,
#ColumnInfo(name = "create_date")
val createDate: String,
#ColumnInfo(name ="occur_more")
val occurMore: Boolean,
#ColumnInfo(name = "category")
val category: String,
#ColumnInfo(name = "description_id")
val descriptionId: Int = 0,
#ColumnInfo(name = "interval_frequency")
val intervalFrequency: String = "hours"
)`
DateTime
There are secondsFromLast and secondsToNext because i was trying to store those calculated times by UPDATE and then query Occurrence by secondsToNext ascending order and populate recycler view with this query. But with this approach there will be enormous inserts to DateTime with fresh calculated seconds
` #Entity
data class DateTime(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name="date_time_id")
val dateTimeId: Int = 0,
#ColumnInfo(name="occurence_owner_id")
val occurenceOwnerId: Int =0,
#ColumnInfo(name="full_date")
val fullDate: String,
#ColumnInfo(name= "time_spend")
val timeSpend: String?,
#ColumnInfo(name="seconds_from_last")
val secondsFromLast: Long?,
#ColumnInfo(name="seconds_to_next")
val secondsToNext: Long?,
)
`
OccurrenceWithDescripion.kt
`
data class OccurrenceWithDescripion (
#Embedded
val occurence: Occurence,
#Relation(
parentColumn = "occurence_id",
entityColumn = "occurence_owner_id"
)
val description: List<Description>
){}`
TimeCounter
Here im calculating time between last DateTime and selected by the user interval. On screenshot, second item on list called “Kwiatek” have interval 2 weeks, i did activity (class DateTime) in this occurrence Kwiatek 6h 23m ago, and next one should be in 13days 17hours 36min
`class TimeCounter(occurence: Occurence, dateTime: DateTime) {
private val occurrence = occurence
private val lastDateTime = dateTime.fullDate
fun getSecondsTo(): Long {
val interval = occurrence.intervalFrequency
val intervalValue = interval.split(" ")[0].toLong()
val intervalFrequency = interval.split(" ")[1]
var toSecondsTo: Long = 0
when (intervalFrequency) {
Constants.MINUTES -> {
toSecondsTo = 60 * intervalValue
}
Constants.HOURS -> {
toSecondsTo = 3600 * intervalValue
}
Constants.DAYS -> {
toSecondsTo = 86400 * intervalValue
}
Constants.WEEKS -> {
toSecondsTo = 604800 * intervalValue
}
Constants.MONTHS -> {
toSecondsTo = 2629800 * intervalValue
}
}
return toSecondsTo
}
fun calculateSecondsTo(secondsTo: Long): Long {
val timeFrom = lastDateTime
val timeTo = secondsTo
val pattern = "HH:mm:ss dd.MM.yyyy"
val formatter = DateTimeFormatter.ofPattern(pattern)
val lastDate = LocalDateTime.parse(timeFrom, formatter)
val calculatedToDay = lastDate.plusSeconds(timeTo)
val secondsTo = ChronoUnit.SECONDS.between(
LocalDateTime.now(),
calculatedToDay,
)
return secondsTo
}
fun getSecondsPassed(): Long {
val today = LocalDateTime.now()
val pattern = "HH:mm:ss dd.MM.yyyy"
val formatter = DateTimeFormatter.ofPattern(pattern)
val lastDate = LocalDateTime.parse(lastDateTime, formatter)
val secondsPassed = ChronoUnit.SECONDS.between(
lastDate,
today
)
return secondsPassed
}
fun secondsToComponents(secondsPassed: Long): String {
secondsPassed.seconds.toComponents{days, hours, minutes, seconds, nanoseconds->
var calculated = ""
when (days) {
0L -> calculated = "${hours}h ${minutes}m"
else -> calculated = "${days}d ${hours}h ${minutes}m"
}
}
return calculated
}
}`
OccurrenceWithDateTimeAdapter
`class OccurrenceWithDateTimeAdapter(private val onItemClicked: (OccurrenceWithDatesTimes) -> Unit):
ListAdapter<OccurrenceWithDatesTimes, OccurrenceWithDateTimeAdapter.OccurrenceViewHolder> (DiffCallback)
{
class OccurrenceViewHolder(private val binding: OccurenceHomeItemBinding):
RecyclerView.ViewHolder(binding.root) {
fun bind(occ: OccurrenceWithDatesTimes){
binding.apply{
occurenceName.text= occ.occurence.occurenceName
if(occ.occurrenceDatesTimes.isNotEmpty()){
val time = TimeCounter(occ.occurence,occ.occurrenceDatesTimes[0])
val secondsFrom = time.getSecondsPassed()
val secondsTo = time.getSecondsTo()
val calculatedSecondsTo = time.calculateSecondsTo(secondsTo)
timeToNext.text= time.secondsToComponents(calculatedSecondsTo)
timeFromLast.text= time.secondsToComponents(secondsFrom)
applyRedTimeColor(time.secondsToComponents(calculatedSecondsTo))
applyOrangeTimeColor(time.secondsToComponents(calculatedSecondsTo))
} else {
timeFromLast.text= "-"
timeToNext.text= "-"
}
}
}
private fun applyOrangeTimeColor(timeString: String) {
val context = binding.timeToNext.context
if(
!timeString.contains("-") &&
timeString.contains("1h") ||
timeString.contains("0h")
){
binding.timeToNext.setTextColor(
ContextCompat.getColor(
context,
R.color.orange
)
)
}
}
private fun applyRedTimeColor(timeString: CharSequence){
val context = binding.timeToNext.context
if (timeString.contains("-")) {
binding.timeToNext.setTextColor(
ContextCompat.getColor(
context,
R.color.red_700
)
)
} else {
binding.timeToNext.setTextColor(
ContextCompat.getColor(
context,
R.color.green
)
)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OccurrenceViewHolder {
return OccurrenceViewHolder(
OccurenceHomeItemBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
)
}
override fun onBindViewHolder(holder: OccurrenceViewHolder, position: Int) {
val current = getItem(position)
holder.itemView.setOnClickListener{
onItemClicked(current)
}
holder.bind(current)
}
companion object {
private val DiffCallback = object : DiffUtil.ItemCallback<OccurrenceWithDatesTimes>() {
override fun areItemsTheSame(oldItem: OccurrenceWithDatesTimes, newItem: OccurrenceWithDatesTimes): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: OccurrenceWithDatesTimes, newItem: OccurrenceWithDatesTimes): Boolean {
return oldItem.occurence.occurenceName == newItem.occurence.occurenceName
}
}
}
}`
HomeFragment
`class CounterHomeFragment : Fragment() {
private val viewModel: CounterViewModel byviewModels{
DateTimeViewModelFactory(
(activity?.applicationas CounterApplication).database.occurenceDao(),
(activity?.applicationas CounterApplication).database.dateTimeDao(),
(activity?.applicationas CounterApplication).database.descriptionDao()
)
}
private var selectedChip = "Relacje"
private var _binding: FragmentCounterHomeBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentCounterHomeBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val adapter = OccurrenceWithDateTimeAdapter{
val action =
CounterHomeFragmentDirections.actionCounterHomeFragmentToOccurenceFragment(it.occurence.occurenceId,it.occurence.occurenceName)
this.findNavController().navigate(action)
}
binding.occurenciesRecyclerView.adapter= adapter
viewModel.allOccurences.observe(this.viewLifecycleOwner) { items ->
items.let {
it as MutableList
adapter.submitList(it)
}
}
viewModel.retrieveOccurenceWithCategory(selectedChip).observe(this.viewLifecycleOwner){items->
items.let{
adapter.submitList(it)
}
}
binding.occurenciesRecyclerView.layoutManager= LinearLayoutManager(this.context)
binding.newOccurency.setOnClickListener{
val action = CounterHomeFragmentDirections.actionCounterHomeFragmentToNewFragment("Create new occurence")
this.findNavController().navigate(action)
}
}
}`
OccurenceDao
`#Dao
interface OccurenceDao {
#Query("SELECT * from occurence ORDER BY create_date ASC")
fun getOccurencies(): Flow<List<Occurence>>
#Query("SELECT * from occurence WHERE occurence_id = :id")
fun getOccurence(id: Int): Flow<Occurence>
#Query("SELECT * from occurence WHERE occurence_id = :id")
fun getOccurenceId(id: Int): Int
#Transaction
#Query("SELECT * from occurence")
fun getOccurrencesWithDatesTimes(): Flow<List<OccurrenceWithDatesTimes>>
#Transaction
#Query("SELECT * from occurence WHERE category = :category")
fun getOccurrencesWithCategory(category: String): Flow<List<OccurrenceWithDatesTimes>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertOccurence(occurence: Occurence)
#Update
suspend fun update(occurence: Occurence)
#Delete
suspend fun delete(occurence: Occurence)
}`
DateTimeDao
`#Dao
interface DateTimeDao {
#Transaction
#Query("SELECT * from dateTime WHERE occurence_owner_id = :id ORDER BY full_date DESC")
fun getOccurenceWithDatesTimes(id:Int): Flow<List<DateTime>>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertDateTime(dateTime: DateTime)
#Update
suspend fun update(dateTime: DateTime)
#Delete
suspend fun delete(dateTime: DateTime)
}`
In my ViewModel im just making inserts, deletes, updates and queries.
`class CounterViewModel(
private val occurenceDao: OccurenceDao,
private val dateTimeDao: DateTimeDao,
private val descriptionDao: DescriptionDao
) : ViewModel() {
val allOccurences: LiveData<List<OccurrenceWithDatesTimes>> =
occurenceDao.getOccurrencesWithDatesTimes().asLiveData()
fun retrieveOccurence(id: Int): LiveData<Occurence> {
return occurenceDao.getOccurence(id).asLiveData()
}
fun retrieveOccurenceWithCategory(category: String): LiveData<List<OccurrenceWithDatesTimes>>{
return occurenceDao.getOccurrencesWithCategory(category).asLiveData()
}
fun retrieveDatesTimes(id: Int): LiveData<List<DateTime>> {
return dateTimeDao.getOccurenceWithDatesTimes(id).asLiveData()
}
fun getOccurrenceWithDatesTimes(): LiveData<List<OccurrenceWithDatesTimes>> {
return occurenceDao.getOccurrencesWithDatesTimes().asLiveData()
}
fun retrieveDescriptions(id: Int): LiveData<List<Description>> {
return occurenceDao.getOccurrencesWithDescriptions(id).asLiveData()
}
private fun insertOccurence(occurence: Occurence) {
viewModelScope.launch {
occurenceDao.insertOccurence(occurence)
}
}
fun deleteOccurence(occurence: Occurence) {
viewModelScope.launch {
occurenceDao.delete(occurence)
}
}
fun updateOccurence(occurence: Occurence) {
viewModelScope.launch {
occurenceDao.update(occurence)
}
}
private fun getNewOccurenceEntry(
occurenceName: String,
createDate: String,
occurMore: Boolean,
category: String,
intervalFrequency: String
): Occurence {
return Occurence(
occurenceName = occurenceName,
createDate = createDate,
occurMore = occurMore,
category = category,
intervalFrequency = intervalFrequency
)
}
// fn to acquire data from newfragment
fun addNewOccurence(
occurenceName: String,
createDate: String,
occurMore: Boolean,
category: String,
intervalFrequency: String
) {
val newOccurence =
getNewOccurenceEntry(occurenceName, createDate, occurMore, category, intervalFrequency)
insertOccurence(newOccurence)
}
fun updateOccurence(
occurenceId: Int,
occurenceName: String,
createDate: String,
occurMore: Boolean,
category: String,
intervalFrequency: String
) {
val updatedOccurence = getUpdatedOccurenceEntry(
occurenceId = occurenceId,
occurenceName = occurenceName,
createDate = createDate,
occurMore = occurMore,
category = category,
intervalFrequency = intervalFrequency
)
updateOccurence(updatedOccurence)
}
private fun getUpdatedOccurenceEntry(
occurenceId: Int,
occurenceName: String,
createDate: String,
occurMore: Boolean,
category: String,
intervalFrequency: String
): Occurence {
return Occurence(
occurenceId = occurenceId,
occurenceName = occurenceName,
createDate = createDate,
occurMore = occurMore,
category = category,
intervalFrequency = intervalFrequency
)
}
fun isEntryValid(occurenceName: String): Boolean {
if (occurenceName.isBlank()) {
return false
}
return true
}
// DATES TIMES BLOCK
private fun insertDateTime(dateTime: DateTime) {
viewModelScope.launch {
dateTimeDao.insertDateTime(dateTime)
}
}
fun deleteDateTime(dateTime: DateTime) {
viewModelScope.launch {
dateTimeDao.delete(dateTime)
}
}
private fun getNewDateTimeEntry(
occurenceOwnerId: Int,
fullDate: String,
timeStart: String,
secondsFromLast: Long,
secondsToNext: Long,
): DateTime {
return DateTime(
occurenceOwnerId = occurenceOwnerId,
fullDate = fullDate,
timeSpend = timeStart,
secondsFromLast = secondsFromLast,
secondsToNext = secondsToNext
)
}
fun addNewDateTime(
occurenceOwnerId: Int,
fullDate: String,
timeStart: String,
secondsFromLast: Long,
secondsToNext: Long,
) {
val newDateTime = getNewDateTimeEntry(
occurenceOwnerId, fullDate, timeStart,
secondsFromLast,
secondsToNext
)
insertDateTime(newDateTime)
}
fun getDate(): String {
val currentDateTime = LocalDateTime.now()
return currentDateTime.format(DateTimeFormatter.ofPattern("HH:mm:ss dd.MM.yyyy"))
}
fun getHour(): String {
val currentTime = LocalDateTime.now()
return currentTime.format(DateTimeFormatter.ofPattern("HH:mm:ss"))
}
/**
* Description block
*/
private fun insertDescription(description: Description) {
viewModelScope.launch {
descriptionDao.insertDescription(description)
}
}
fun deleteDescription(description: Description) {
viewModelScope.launch {
descriptionDao.deleteDescription(description)
}
}
private fun getNewDescriptionEntry(
descriptionNote: String,
occurenceOwnerId: Int
): Description {
return Description(
descriptionDate = LocalDateTime.now().toString(),
descriptionNote = descriptionNote,
occurenceOwnerId = occurenceOwnerId
)
}
fun addNewDescription(
descriptionNote: String,
occurenceOwnerId: Int
) {
val newDescription = getNewDescriptionEntry(descriptionNote, occurenceOwnerId)
insertDescription(newDescription)
}
}
/**
* Factory class to instantiate the [ViewModel] instance.
*/
class DateTimeViewModelFactory(
private val occurenceDao: OccurenceDao,
private val dateTimeDao: DateTimeDao,
private val descriptionDao: DescriptionDao
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(CounterViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return CounterViewModel(occurenceDao, dateTimeDao, descriptionDao) as T
}
throw IllegalArgumentException("Unknow view model classs////////////")
}`
So basically my question is how to sort list of OccurrenceWithDatesTimes in CounterHomeFragment’s recycler view not by Room query
I know there are types wit ‘ocurrence’ im sory for that. And also sorry for the readability of the code.
Here is githup repository: https://github.com/Daniel4913/Counter
Have a nice sunday!
I tried to save calculated seconds to database but also have problem with Room queries/inserts/updates to do that with OccurrenceWithDateTime class

RecycleViewer not showing anything in the list after update in Firebase database

I got a problem and I can't see an error in my code, maybe You'll help me. I've got an application in Android Studio that uses connection with Firebase. I add products to the database by a button and in the activity that holds RecycleViever, it lists that product there. The problem is that RecycleViewer shows nothing, even though that my button adds product to the Firebase. That RecycleViewer should list products from Firebase. I add product with It's structure:
data class Product(var name: String, var price: Double, var quantity: Long, var bought: Boolean) {
var id: String = ""
companion object {
fun fromContentValues(values: ContentValues?): Product {
values?.let{
return Product(
values.getAsString("name"),
values.getAsDouble("price"),
values.getAsLong("amount"),
values.getAsBoolean("bought"))
} ?: throw IllegalArgumentException()
}
}
#Exclude
fun toMap(): Map<String, Any?> {
return mapOf(
"id" to id,
"name" to name,
"price" to price,
"quantity" to quantity,
"bought" to bought
)
}
}
Product Repository:
class ProductRepository(private val dbRef: DatabaseReference) {
fun insert(product: Product) {
val key = dbRef.push().key
if (key == null) {
Log.w("error", "Couldn't get push key for posts")
return
}
product.id = key
val productValues = product.toMap()
val childUpdates = hashMapOf<String, Any>(
key to productValues
)
dbRef.updateChildren(childUpdates)
}
fun update(product: Product){
val productValues = product.toMap()
val childUpdates = hashMapOf<String, Any>(
product.id to productValues
)
dbRef.updateChildren(childUpdates)
}
fun delete(key: String){
dbRef.child(key).removeValue()
}
}
Product ViewModel:
class ProductViewModel(app: Application) : AndroidViewModel(app) {
private val repo: ProductRepository
val allProducts: MutableList<Product>
init {
allProducts = arrayListOf()
val database = FirebaseDatabase.getInstance()
val reference : DatabaseReference = database.getReference("database/products")
var name: String
var price: Double
var amount: Long
var bought: Boolean
reference.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
for (product in snapshot.children) {
name = product.child("name").value as String
price = product.child("price").value as Double
amount = product.child("amount").value as Long
bought = product.child("bought").value as Boolean
val newProduct = Product(name, price, amount, bought)
allProducts.add(newProduct)
}
}
override fun onCancelled(error: DatabaseError) {
Log.w("error", "loadPost:onCancelled", error.toException())
}
})
repo = ProductRepository(reference)
}
fun insert(product: Product) {
repo.insert(product)
}
fun update(product: Product) {
repo.update(product)
}
fun delete(product: Product) {
repo.delete(product.id)
}
}
My Adapter:
class ProductsAdapter(val productViewModel: ProductViewModel) : RecyclerView.Adapter<ProductsAdapter.ViewHolder>() {
private var products = mutableListOf<Product>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductsAdapter.ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val view = ListElementBinding.inflate(inflater)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ProductsAdapter.ViewHolder, position: Int) {
val currentProduct = products[position]
holder.binding.rvTv1.text = currentProduct.name
holder.binding.rvTv2.text = currentProduct.price.toString()
holder.binding.rvTv3.text = currentProduct.amount.toString()
holder.binding.rvCb1.isChecked = currentProduct.bought
holder.binding.bted.setOnClickListener {
currentProduct.name = holder.binding.rvTv1.text.toString()
currentProduct.price = holder.binding.rvTv2.text.toString().toDouble()
currentProduct.amount = holder.binding.rvTv3.text.toString().toLong()
currentProduct.bought = holder.binding.rvCb1.isChecked
productViewModel.update(currentProduct)
}
holder.binding.btdel.setOnClickListener {
productViewModel.delete(currentProduct)
products.remove(currentProduct)
notifyDataSetChanged()
}
}
override fun getItemCount(): Int = products.size
inner class ViewHolder(val binding: ListElementBinding) : RecyclerView.ViewHolder(binding.root)
fun setProducts() {
this.products = productViewModel.allProducts
notifyDataSetChanged()
}
fun addProduct(product: Product) {
productViewModel.insert(product)
notifyDataSetChanged()
}
}
And finally activity that Lists it all:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = ActivityListBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.rv1.layoutManager = LinearLayoutManager(baseContext)
binding.rv1.addItemDecoration(DividerItemDecoration(baseContext, DividerItemDecoration.VERTICAL))
val productViewModel = ProductViewModel(this.application)
binding.rv1.adapter = ProductsAdapter(productViewModel)
(binding.rv1.adapter as ProductsAdapter).setProducts()
binding.bt5.setOnClickListener() {
val name = et1.text.toString()
val price = et3.text.toString().toDouble()
val amount = et2.text.toString().toLong()
val bought = false
val product = Product(name, price, amount, bought)
(binding.rv1.adapter as ProductsAdapter).addProduct(product)
et1.text.clear()
et2.text.clear()
et3.text.clear()
}
binding.rv1.adapter = ProductsAdapter(productViewModel)
}
}
I literally have no idea, why it's not showing anything. Maybe You can help me.

How to fetch only the String from the Room Persistence Library?

Today I started learning how to use Room for my simple test project. My current issue is retrieving the saved data as a String, and not the entire entity. I included an image down below:
I just want the title for each entity. Any ideas?
MainActivity:
class MainActivity : AppCompatActivity() {
lateinit var mAdapter: MyAdapter
lateinit var db: NotesDatabase
var itemsList = mutableListOf<String>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
loadApp()
}
private fun loadApp() {
GlobalScope.launch {
dataBaseSetup()
withContext(Dispatchers.Main) {
setUpRecycler()
}
}
}
//Database
private fun dataBaseSetup() {
db = Room.databaseBuilder(
applicationContext, NotesDatabase::class.java, "notes-list.db"
).build()
//Database
GlobalScope.launch {
var dataBaseList = db.notesDao().getAllNotes() as MutableList
Log.d("Main", "$dataBaseList")
for (i in 0 until dataBaseList.size) {
itemsList.add("${dataBaseList[i]}")
}
}
}
private fun setUpRecycler() {
mAdapter =
MyAdapter(itemsList)
val mList: DragDropSwipeRecyclerView = findViewById(R.id.list)
mList.layoutManager = LinearLayoutManager(this)
mList.adapter = mAdapter
mList.orientation =
DragDropSwipeRecyclerView.ListOrientation.VERTICAL_LIST_WITH_VERTICAL_DRAGGING
mList.disableSwipeDirection(DragDropSwipeRecyclerView.ListOrientation.DirectionFlag.RIGHT)
val onItemSwipeListener = object : OnItemSwipeListener<String> {
override fun onItemSwiped(
position: Int,
direction: OnItemSwipeListener.SwipeDirection,
item: String
): Boolean {
Log.d("Main", "Position = $position, Direction = $direction, Item = $item")
when (direction) {
OnItemSwipeListener.SwipeDirection.RIGHT_TO_LEFT -> {
Toast.makeText(
applicationContext,
"$item deleted",
Toast.LENGTH_SHORT
).show()
//todo: add deleted code here
//Database
GlobalScope.launch(Dispatchers.Default) {
db.notesDao().delete(NotesEntity("$item"))
}
}
OnItemSwipeListener.SwipeDirection.LEFT_TO_RIGHT -> {
Toast.makeText(
applicationContext,
"$item archived",
Toast.LENGTH_SHORT
).show()
//todo: add archived code here
}
else -> return false
}
return false
}
}
mList.swipeListener = onItemSwipeListener
// button
fabAddItem()
}
private fun fabAddItem() {
fab_add.setOnClickListener {
Log.d("Main", "Button pressed")
val builder = AlertDialog.Builder(this)
val inflater = layoutInflater
val dialogLayout = inflater.inflate(R.layout.edit_text_layout, null)
val editText = dialogLayout.findViewById<EditText>(R.id.et_editText)
with(builder) {
setTitle("Enter some text!")
setPositiveButton("OK") { dialog, which ->
mAdapter.updateItem(editText.text.toString())
//Database
GlobalScope.launch(Dispatchers.Default) {
db.notesDao().insertAll(NotesEntity(editText.text.toString()))
}
Toast.makeText(
applicationContext,
"Text added successfully",
Toast.LENGTH_SHORT
).show()
}
setNegativeButton("Cancel") { dialog, which ->
Log.d("Main", "Negative button clicked")
}
setView(dialogLayout)
show()
}
}
}
}
NotesEntity Class:
#Entity(tableName = "notes_items")
data class NotesEntity(
#PrimaryKey var title: String
)
NotesDao:
#Dao
interface NotesDao {
#Query("SELECT * FROM notes_items")
fun getAllNotes(): List<NotesEntity>
#Query("SELECT * FROM notes_items WHERE title LIKE :title")
fun getTitle(title: String): NotesEntity
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(vararg todo: NotesEntity)
#Delete
fun delete(title: NotesEntity)
#Update
fun updateNotes(vararg title: NotesEntity)
}
NotesDatabase:
#Database(entities = [NotesEntity::class], version = 1)
abstract class NotesDatabase : RoomDatabase() {
abstract fun notesDao(): NotesDao
companion object {
#Volatile private var instance: NotesDatabase? = null
private val LOCK = Any()
operator fun invoke(context: Context)= instance ?: synchronized(LOCK){
instance ?: buildDatabase(context).also { instance = it}
}
private fun buildDatabase(context: Context) = Room.databaseBuilder(context,
NotesDatabase::class.java, "todo-list.db")
.build()
}
}
"${dataBaseList[i]}" should be "${dataBaseList[i].title}", the only field of your entity
In the SQL query, when you type Select *, the * here stands for ALL. So your query will return the object, in order to just return the string, you have to replace the * with the column name (attribute) you want to fetch as:
Select title from notes_items

One-to-many in Room with Kotlin

The task is to open an activity with notes attached to this diary when you select a single diary.
(one-to-many)
This is how entities in the database look like:
#Entity(tableName = "word_table")
data class Word(#ColumnInfo(name = "word") val word: String,
#ColumnInfo(name = "description") val description : String
)
{
#ColumnInfo(name = "id")
#PrimaryKey(autoGenerate = true)
var id : Long = 0
}
#Entity(tableName = "note_table")
data class Note(#ColumnInfo(name = "note_name") val note : String,
#ColumnInfo(name = "text") val text : String,
#ColumnInfo(name = "diaryId") val diaryId : Long
){
#PrimaryKey(autoGenerate = true)
var idNote : Long = 0
}
Using a data class in NoteRepository.kt
data class NotesAndWords (#Embedded val word : Word,
#Relation(parentColumn = "id", entityColumn = "diaryId")
val notes : List<Note>)
And a Query in WordDao.kt
#Transaction
#Query("SELECT * from word_table ")
fun getSomeNotes() : LiveData<List<NotesAndWords>>
I get the data and save it in the NoteRepository class:
class NoteRepository (private val wordDao : WordDao) {
var allNotes : LiveData<List<NotesAndWords>> = wordDao.getSomeNotes()
suspend fun insertNote(note : Note)
{
wordDao.insertNote(note)
}
}
Then via NoteViewModel.kt passing data to NoteActivity.kt:
class NoteViewModel(application: Application) : AndroidViewModel(application) {
private val repository: NoteRepository
val allNotes: LiveData<List<NotesAndWords>>
init {
val wordsDao = WordRoomDatabase.getDatabase(application, viewModelScope).wordDao()
repository = NoteRepository(wordsDao)
allNotes = repository.allNotes
}
fun insertNote(note: Note) = viewModelScope.launch {
repository.insertNote(note)
}
}
(NoteActivity.kt)
class NoteActivity : AppCompatActivity() {
private val newWordActivityRequestCode = 1
private lateinit var noteViewModel: NoteViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_note)
val adapter = NoteListAdapter(this, intent.getLongExtra("tag", -1) ){
val intent = Intent(this, ClickedActivity::class.java)
intent.putExtra("tag", it.note)
startActivity(intent)
}
recyclerview1.adapter = adapter
recyclerview1.layoutManager = LinearLayoutManager(this)
noteViewModel = ViewModelProvider(this).get(NoteViewModel::class.java)
noteViewModel.allNotes.observe(this, Observer {
adapter.setNotes(it)
})
val fab = findViewById<FloatingActionButton>(R.id.fab)
fab.setOnClickListener {
val intent = Intent(this, NewWordActivity::class.java)
startActivityForResult(intent, newWordActivityRequestCode)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == newWordActivityRequestCode && resultCode == Activity.RESULT_OK)
{
data?.getStringArrayListExtra(NewWordActivity.EXTRA_REPLY)?.let {
val note = Note(it[0], it[1], intent.getLongExtra("tag", -1))
noteViewModel.insertNote(note)
}
}
else
{
Toast.makeText(applicationContext, R.string.empty_not_saved,
Toast.LENGTH_LONG).show()
}
}
Then, in the adapter, I use MutableMap to transform the list so that the key is the name id and the value is the notes selected on request (attached to a specific diary)
NoteListAdapter.kt:
class NoteListAdapter internal constructor(
context: Context,
val wordId: Long,
private val listener : (Note) -> Unit
) : RecyclerView.Adapter<NoteListAdapter.NoteViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
//private val mContext = context
private var notes = emptyList<NotesAndWords>() // Cached copy of words
private var notesMapped = mutableMapOf<Long, List<Note>>()
inner class NoteViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val noteItemView: TextView = itemView.findViewById(R.id.textView1)
private val noteDescriptionView: TextView = itemView.findViewById(R.id.textView)
fun bindView(note: Note, listener : (Note) -> Unit) {
noteItemView.text = note.diaryId.toString()
noteDescriptionView.text = note.text
itemView.setOnClickListener {
listener(note)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteViewHolder {
val itemView = inflater.inflate(R.layout.recyclerview_layout, parent,
false)
return NoteViewHolder(itemView)
}
override fun onBindViewHolder(holder: NoteViewHolder, position: Int) {
holder.bindView(notesMapped[wordId]!![position], listener)
}
internal fun setNotes(notes: List<NotesAndWords>) {
this.notes = notes
for (i in this.notes) {
notesMapped[i.word.id] = i.notes
}
notifyDataSetChanged()
}
override fun getItemCount() = notesMapped[wordId]!!.size
}
Database:
#Database(entities = [Word::class, Note::class], version = 2, exportSchema = false)
abstract class WordRoomDatabase : RoomDatabase() {
abstract fun wordDao(): WordDao
private class WordDatabaseCallback(private val scope: CoroutineScope) : RoomDatabase.Callback()
{
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
INSTANCE?.let { database ->
scope.launch {
populateDatabase(database.wordDao())
}
}
}
suspend fun populateDatabase(wordDao: WordDao) {
//wordDao.deleteAll()
//wordDao.deleteAllNotes()
}
}
companion object {
#Volatile
private var INSTANCE: WordRoomDatabase? = null
fun getDatabase(context: Context, scope:CoroutineScope): WordRoomDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
val instance = Room.databaseBuilder(context.applicationContext,
WordRoomDatabase::class.java, "word_database")
.addCallback(WordDatabaseCallback(scope))
//.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
return instance
}
}
}
I've created several diaries and one note in each of them, using the buttons to create new diaries and notes. Now, if you select several diaries in turn, then on some attempt to select a diary, a NullPointerException is issued in the adapter, in this line:
override fun getItemCount() = notesMapped[wordId]!!.size
Why is this exception thrown if notesMapped always has the wordId key?
NoteActivity is called from another activity and the diary id is passed to it
This repository on GitHub: https://github.com/Lomank123/RoomDatabase
Edit:
noteViewModel.allNotes.observe(this, Observer {
var getList = emptyList<Note>()
for(i in it)
{
if(i.word.id == wordId)
{
getList = i.notes
break
}
}
adapter.setNotes(getList)
})
I've changed the Observer in NoteActivity and changed setNotes() method in adapter, but now it returns nothing. With for() I get the right notes and give them to adapter.setNotes(). If it doesn't work, how can I get the correct list of notes?
Hi initially the map is empty and the map is returning a null value and you are checking size on a null object.
Also as a good practice do not use a map instead use a list of notes only and pass the list directly.

Multiple row data not inserted using RoomDB

When I am trying to insert around 400 rows of data in DB using Room , its inserting only 90 rows of data. Please find below my code :
#Entity(tableName = "data_info")
data class DataInfo(
#PrimaryKey(autoGenerate = true) val uid: Int,
val body: String?,
val title:String?) {
}
DataInfoDao.kt
#Dao
interface DataInfoDao{
#Transaction #Insert
fun insertAll(resultModel:MutableList<DataInfo> )
#Transaction #Query("SELECT * FROM data_info ORDER BY uid COLLATE NOCASE ASC")
fun allDataByName(): DataSource.Factory<Int, DataInfo>
}
DataInfoRoomDataBase.kt:
#Database(entities = arrayOf(DataInfo::class), version = 3)
abstract class DataInfoRoomDataBase : RoomDatabase() {
abstract fun dataInfoDao(): DataInfoDao
val context:Context?=null
companion object {
private var INSTANCE: DataInfoRoomDataBase? = null
fun getDatabase(context: Context): DataInfoRoomDataBase {
val tempInstance = INSTANCE
if (tempInstance != null) {
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
DataInfoRoomDataBase::class.java,
"datainfo_page_database"
) .fallbackToDestructiveMigration()
.addCallback(SRoomDatabaseCallback())
.build()
INSTANCE = instance
return instance
}
}
}
private class SRoomDatabaseCallback : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
/*PopulateDbAsyncTask(INSTANCE)
.execute()*/
fillInDb(INSTANCE)
}
private fun fillInDb(db: DataInfoRoomDataBase?) {
// inserts in Room are executed on the current thread, so we insert in the background
ioThread {
db?.dataInfoDao()?.insertAll(prepareDummyData())
/* db?.dataInfoDao()?.insertAll(
CHEESE_DATA.map{DataInfo(uid = 0, body="Info" , title = it)}.toMutableList())*/
}
}
private fun prepareDummyData():MutableList<DataInfo>{
val dummyList = mutableListOf<DataInfo>()
for(i in 0..400){
dummyList.add(DataInfo(uid = 0, body="Info" , title = "Title $i"))
}
return dummyList
}
}
In the above code in the method "prepareDummyData()" I am prepare dummy data for 400 rows and trying to insert to DB. But only 90 records are getting inserted.
My View model is as below:
class DataInfoViewModel(application: Application) : AndroidViewModel(application) {
var allInfo: LiveData<PagedList<DataInfo>> = MutableLiveData()
lateinit var dataInfoDao:DataInfoDao
init{
dataInfoDao = DataInfoRoomDataBase.getDatabase(application).dataInfoDao()
allInfo = dataInfoDao.allDataByName().toLiveData(Config(pageSize = 30,
enablePlaceholders = true
,maxSize = 200))
}
fun remove(dataInfo: DataInfo) = ioThread {
dataInfoDao.delete(dataInfo)
}
}
Please find my View and Adapter classes as below :
class PagingRoomDataBaseFragment:Fragment() {
var fragmentView: View? = null
// private var listInfoAdapter:ListInfoAdapter?=null
private var roomDbInfoListFragmentLayoutBinding:RoomDbInfoListFragmentLayoutBinding?=null
// val dataInfoViewModel: DataInfoViewModel by lazy { ViewModelProviders.of(this).get(DataInfoViewModel::class.java) }
var mContainerID:Int = -1
private val dataInfoViewModel by viewModels<DataInfoViewModel>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
roomDbInfoListFragmentLayoutBinding = DataBindingUtil.inflate(inflater, R.layout.room_db_info_list_fragment_layout, container, false)
roomDbInfoListFragmentLayoutBinding?.lifecycleOwner = this
fragmentView = roomDbInfoListFragmentLayoutBinding?.root
container?.let{
mContainerID = container.id
}
// fragmentView?.floatingActionButton?.visibility.apply { 8 }
// initAdapter()
intRecyclerView()
setAdapter()
// initSwipeToDelete()
//listInfoAdapter?.refreshList()
return fragmentView
}
fun intRecyclerView(){
fragmentView?.dbrecyclerView?.apply {
layoutManager = LinearLayoutManager(activity)
addItemDecoration(DividerItemDecoration(activity, DividerItemDecoration.VERTICAL))
}
}
fun setAdapter(){
val listInfoAdapter =
ListInfoAdapter()
// dataInfoViewModel.allInfo.observe(this, Observer(listInfoAdapter::submitList))
fragmentView?.dbrecyclerView?.adapter = listInfoAdapter
dataInfoViewModel.allInfo.observe(this#PagingRoomDataBaseFragment.requireActivity(), Observer { data ->
// Update the cached copy of the words in the adapter.
data?.let {
//view.textView2.text = it.toString()
// Log.d("RoomDBFragment","ViewModel Observer is called")
Log.d("Fragm","it:::"+it.toString())
listInfoAdapter?.setAdapterList(it)
}
})
}
Adapter class:
class ListInfoAdapter : PagedListAdapter<DataInfo,ListInfoAdapter.ViewHolder>(diffCallback) {
private var list: List<DataInfo> = emptyList<DataInfo>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val binding: PageDbListItemBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.context), R.layout.page_db_list_item, parent, false)
return ListInfoAdapter.ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
Log.d("ADapter", "Info:::$position" + list.get(position))
holder.bind(list.get(position)/*getItem(position)*/)
}
fun setAdapterList(list: PagedList<DataInfo>) {
this.list = list
notifyDataSetChanged()
}
fun refreshList(){
notifyDataSetChanged()
}
override fun getItemCount(): Int = list.size
class ViewHolder(val binding: PageDbListItemBinding) : RecyclerView.ViewHolder(binding.root) {
var data : DataInfo? = null
fun bind(data: DataInfo?) {
this.data = data
binding.setVariable(BR.dataInfo, data) // BR - generated class; BR.item - 'item' is variable name declared in layout
binding.executePendingBindings()
}
}
companion object {
/**
* This diff callback informs the PagedListAdapter how to compute list differences when new
* PagedLists arrive.
* <p>
* When you add a Cheese with the 'Add' button, the PagedListAdapter uses diffCallback to
* detect there's only a single item difference from before, so it only needs to animate and
* rebind a single view.
*
* #see android.support.v7.util.DiffUtil
*/
private val diffCallback = object : DiffUtil.ItemCallback<DataInfo>() {
override fun areItemsTheSame(oldItem: DataInfo, newItem: DataInfo): Boolean =
oldItem.uid == newItem.uid
/**
* Note that in kotlin, == checking on data classes compares all contents, but in Java,
* typically you'll implement Object#equals, and use it to compare object contents.
*/
override fun areContentsTheSame(oldItem: DataInfo, newItem: DataInfo): Boolean =
oldItem == newItem
}
}
}

Categories

Resources