Reload RecyclerView after data change with Room, ViewModel and LiveData - android

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..

Related

Can't insert into room database because of var initialization Android

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!!

Kotlin : Just got back the first parameter from roomdatabase

i have a problem with my code and i can't solve it .
let just a brief of what i'm going to do : i have a address in the ChooseFragment and there is a edit button for editting the address . ok so far good . the edit click is pass new address to the DatabaseRoom and the address text would be changed . but this is happens just for first time . the secound time the address not changed !! . i know that the insert method work and send the new data to the database room but when i want get it with my query (SELECT * FROM ... ) just show the first parameter and not replace with new value . what is wrong with my codes ?
this is my table :
#Entity (tableName = "addresstable")
data class AddressTb(
#PrimaryKey(autoGenerate = true)```
val id : Int? ,
var address: String)```
this is my database :
#Database(entities = [RoomTables::class , AddressTb::class], version = 1, exportSchema = false)
abstract class DataBaseRoom : RoomDatabase() {
abstract fun GetDao(): DaoCart
companion object {
#Volatile
private var instance: DataBaseRoom? = null
private val lock = Any()
operator fun invoke(context: Context) = instance
?: synchronized(lock) {
instance
?: makeDatabase(
context
).also {
instance = it
}
}
private fun makeDatabase(context: Context) = Room.databaseBuilder(
context.applicationContext,
DataBaseRoom::class.java,
"name"
).build()
}
}```
this is my Dao :
```//address table dao
#Query("SELECT * FROM addresstable")
fun getalladress () : LiveData<AddressTb>
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertaddress (model : AddressTb)
#Query("DELETE FROM addresstable")
suspend fun deleteaddress ()
this is my Repository :
fun getalladdress() = db.GetDao().getalladress()
suspend fun deleteaddress() = db.GetDao().deleteaddress()
suspend fun insertaddress(model : AddressTb) = db.GetDao().insertaddress(model)
this is my Viewmodel :
fun getaddress() = repository.getalladdress()
fun deleteaddres() = CoroutineScope(Dispatchers.Default).launch {
repository.deleteaddress()
}
fun insertaddress(model : AddressTb) = CoroutineScope(Dispatchers.Default).launch {
repository.insertaddress(model)
this is my fragment where i fetch the new insert :
class ChosseAddress : Fragment() {
lateinit var viewModelRoom: ViewModelRoom
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val vl = inflater.inflate(R.layout.choose_address_layout, container, false)
val database = DataBaseRoom(requireContext())
val repository = RepositoryCart(database)
val factoryRoom = FactoryRoom(repository)
viewModelRoom =
ViewModelProvider(ViewModelStoreOwner { ViewModelStore() }, factoryRoom).get(
ViewModelRoom::class.java
)
viewModelRoom.getaddress().observe(viewLifecycleOwner, Observer {
try {
vl.txt_address.text = it.address
} catch (ex: Exception) {
null
}
})
val animsec: Animation =
AnimationUtils.loadAnimation(vl.context, R.anim.anim_for_btn_zoom_out)
vl.button_back_choose_address.setOnClickListener {
it.startAnimation(animsec)
childFragmentManager.beginTransaction()
.replace(R.id.choose_address_container, HamburgerFragment())
.commit()
}
vl.edit_address.setOnClickListener {
val mycustomview =
LayoutInflater.from(context).inflate(R.layout.alertfialog_costume, null)
val dialogtext = LayoutInflater.from(context).inflate(R.layout.edit_alert_txt, null)
val mBuilder = AlertDialog.Builder(context)
.setView(mycustomview)
.setCustomTitle(dialogtext)
val show = mBuilder.show()
mycustomview.edit_manually.setOnClickListener {
show.dismiss()
childFragmentManager.beginTransaction()
.replace(R.id.choose_address_container, ManuallyAddressFragment())
.commit()
}
}
return vl
}
}```
and this is where i insert data to database :
class ManuallyAddressFragment : Fragment() {
lateinit var viewmodel: ViewModelRoom
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val layoutview = inflater.inflate(R.layout.manually_address_fragment, container, false)
val database = DataBaseRoom(layoutview.context)
val repos = RepositoryCart(database)
val factory = FactoryRoom(repos)
viewmodel = ViewModelProvider(ViewModelStoreOwner { viewModelStore },
factory).get(ViewModelRoom::class.java)
val btncancle: Button = layoutview.btn_cancle
val btnsubmit: Button = layoutview.btn_submit_address
btnsubmit.setOnClickListener {
val edittext = layoutview.edit_address_manually
if (edittext.text.toString().isEmpty()) {
Toast.makeText(context, R.string.submit_btn, Toast.LENGTH_SHORT).show()
} else {
val insert = (AddressTb( null , edittext.text.toString()))
viewmodel.insertaddress(insert)
childFragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.anim_fragment_manually,
R.anim.anim_fragment_chooseaddress
)
.replace(R.id.manually_container, ChosseAddress())
.commit()
Toast.makeText(context, "آدرس شما با موفقیت ثبت شد", Toast.LENGTH_SHORT).show()
}
}
btncancle.setOnClickListener {
childFragmentManager.beginTransaction()
.setCustomAnimations(
R.anim.anim_fragment_manually,
R.anim.anim_fragment_chooseaddress
)
.replace(R.id.manually_container, ChosseAddress())
.commit()
}
return layoutview
}
}```
i tried so things also use update metohd but the database just back the first parameter and i want the new insert value ...
To update the value use the same id, as in your code I see you try to insert new object AddressTb(null , edittext.text.toString()) you are passing null value for id, pass the same id from the AddressTb object that you wish to update which you get from getalladdress.
For starters, if you're getting all the addresses, it should look like this:
#Query("SELECT * FROM addresstable")
fun getalladress () : LiveData<List<AddressTb>>
To select a single address, use something like this:
#Query("SELECT * FROM addresstable WHERE id= :id")
fun findAddressById(id: Long) : AddressTb

How to fetch edittext value from one activity to recyclerview of next activity?

I'm trying to fetch edittext value from one activity and displaying that text in recycleview and storing using room DB....
Basically the idea is adding the address in activity address when clicks on plus it will redirect to next page where the user gets the address form onsubmit it will fetch address and add to previous activity recycleview.
here is my code for room:
table:---
#Entity(tableName = "address")
class Address {
#PrimaryKey
var id = 0
#ColumnInfo(name = "address")
var address: String? = null
}
Database:-
#Database(entities = [Address::class], version = 1)
abstract class Database : RoomDatabase() {
abstract fun AddressDao(): AddressDao
}
AddressDao:
#Dao
interface AddressDao {
#Insert
suspend fun addData(address: Address)
#Query("select * from address")
fun getAddressesWithChanges() :LiveData<List<Address>>
#Query("SELECT EXISTS (SELECT 1 FROM address WHERE id=:id)")
suspend fun isAddressAdded(id: Int): Int
#Delete
suspend fun delete(address: Address)
}
AddAddressActivity activity:
class AddAddressActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.addaddress)
val editText: EditText = findViewById(R.id.longaddress)
val application = application as CustomApplication
saveaddress.setOnClickListener {
val address = Address()
address.address = editText.getText().toString()
lifecycleScope.launch {
application.addressDao.addData(address)
finish()
}
}
}
}
AddressActivity:-
class AddressActivity : AppCompatActivity() {
private val adapter = AddressAdapter()
private lateinit var data: LiveData<List<Address>>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.address)
addbutton.findViewById<View>(R.id.addbutton).setOnClickListener {
val intent = Intent(this, AddAddressActivity::class.java)
startActivity(intent)
}
val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
recyclerView.setHasFixedSize(true)
recyclerView.layoutManager = LinearLayoutManager(this,
LinearLayoutManager.VERTICAL, false)
recyclerView.adapter = adapter
val application = application as CustomApplication
data = application.database.AddressDao().getAddressesWithChanges()
data.observe(this, Observer { words1 ->
// Update the cached copy of the words in the adapter.
words1?.let { adapter.updateData(it) }})
}
}
AddressAdapter activity:-
class AddressAdapter: RecyclerView.Adapter<AddressAdapter.ViewHolder>() {
private var addresses: List<Address> = Collections.emptyList()
override fun onCreateViewHolder(viewGroup: ViewGroup, itemViewType: Int):
ViewHolder =
ViewHolder(LayoutInflater.from(viewGroup.context)
.inflate(R.layout.address_item, viewGroup, false) )
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
val fl: Address = addresses[position]
viewHolder.tv.setText(fl.address)
}
override fun getItemCount(): Int = addresses.size
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var tv: TextView
init {
tv = itemView.findViewById(R.id.ftv_name)
} }
fun updateData(addresses:
List<Address>) {
this.addresses = addresses
notifyDataSetChanged() // TODO: use ListAdapter if animations are needed
}
}
CustomApplication:
class CustomApplication: Application() {
lateinit var database: Database
private set
lateinit var addressDao: AddressDao
private set
override fun onCreate() {
super.onCreate()
this.database = Room.databaseBuilder<Database>(
applicationContext,
Database::class.java, "database"
).build()
}
}
list is not displaying recycleview is empty when came back to address activity
please help ..I'm new to kotlin and room db..
abstract fun AddressDao(): AddressDao?
Why is this nullable?
val favoriteData: List<Table>?
Why is this nullable?
fun addData(favoriteList: Table?)
Why is this nullable?
fun delete(favoriteList: Table?)
Why is this nullable?
Typed nullability is to enable you to represent where a given value can be null, in the sense that it makes sense for it to be absent.
Neither of the above cases represent such a scenario. Room will never return you null instead of an actual dao.
Kotlin's null safety is to allow you to eliminate potential error cases by making it part of the type system, it shouldn't be used to create "false" error cases such as these. If you're trying to add null to a database instead of a real object, you've probably got a bug in your code, and Kotlin should be able to help you eliminate that possibility.
As for your actual question, you're misusing Room.
.allowMainThreadQueries() is a way to disable an error check, but that error is there for a reason. You're not supposed to be reading the data on the UI thread, because with enough data, your app will freeze (ANR).
The code is supposed to look something like this:
#Entity(tableName = "address")
class Address {
#PrimaryKey
var id = 0
#ColumnInfo(name = "address")
var address: String? = null
}
Database:-
#Database(entities = [Address::class], version = 1)
abstract class Database : RoomDatabase() {
abstract fun addressDao(): AddressDao
}
AddressDao:
#Dao
interface AddressDao {
#Insert
suspend fun addData(address: Address)
#Query("select * from address")
fun getAddressesWithChanges(): LiveData<List<Address>>
#Query("SELECT EXISTS (SELECT 1 FROM address WHERE id=:id)")
suspend fun isAddressAdded(id: Int): Int
#Delete
suspend fun delete(address: Address)
}
class CustomApplication: Application() {
lateinit var database: Database
private set
lateinit var addressDao: AddressDao
private set
override fun onCreate() {
super.onCreate()
this.database = Room.databaseBuilder<Database>(
applicationContext,
Database::class.java, "database"
).build()
addressDao = database.addressDao()
}
}
AndroidManifest.xml
<application android:name=".CustomApplication"
...
Address activity:
import androidx.lifecycle.observe
class AddressActivity : AppCompatActivity() {
private val adapter = AddressAdapter()
private lateinit var data: LiveData<List<Address>>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.address_activity)
findViewById<View>(R.id.addButton).setOnClickListener {
val intent = Intent(this, AddAddressActivity::class.java)
startActivity(intent)
}
val recyclerView = findViewById<RecyclerView>(R.id.recyclerview)
recyclerView.setHasFixedSize(true)
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
recyclerView.adapter = adapter
val application = application as CustomApplication
val addressDao = application.addressDao
data = addressDao.getAddressesWithChanges()
data.observe(this) {
adapter.updateData(it)
}
}
}
AddressAdapter:
class AddressAdapter: RecyclerView.Adapter<AddressAdapter.ViewHolder>() {
private var addresses: List<Table> = Collections.emptyList()
override fun onCreateViewHolder(viewGroup: ViewGroup, itemViewType: Int): ViewHolder =
ViewHolder(LayoutInflater.from(viewGroup.context).inflate(R.layout.address_item, viewGroup, false))
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
val address = addresses[position]
viewHolder.tv.setText(address.address)
}
override fun getItemCount(): Int = addresses.size
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val tv = itemView.findViewById(R.id.ftv_name)
}
fun updateData(addresses: List<Address>) {
this.addresses = addresses
notifyDataSetChanged() // TODO: use ListAdapter if animations are needed
}
}
AddAddress activity:-
class AddAddressActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.addaddress)
val editText: EditText = findViewById(R.id.longaddress)
val application = application as CustomApplication
val addressDao = application.addressDao
saveaddress.setOnClickListener {
val address = Address()
address.address = editText.getText().toString()
lifecycleScope.launch {
addressDao.addData(address)
finish()
}
}
}
}
You can potentially also refer to https://codelabs.developers.google.com/codelabs/android-room-with-a-view-kotlin/#6
Please replace your line with
val rv=findViewById<RecyclerView>(R.id.recyclerview)
You have passed the wrong id addressrecyclerview that's why it was assigning null

How to show query to database in ListView in kotlin with android studio?

I am learning to develop in kotlin with android studio, so I don't have any experience.
I am wanting to insert data into a local database using room database, so far I think I'm doing fine. Now I need to be able to consult those data but I cannot do it, I have searched the internet but I have not been able to solve my problem.
I attach the code.
Class #Entity
class TablasBdApp {
#Entity(tableName = TblConteo.TABLE_NAME)
data class TblConteo(
#PrimaryKey(autoGenerate = true) #ColumnInfo(name = "linea_id") val Linea_Id: Int = 0,
#ColumnInfo (name = "articulo") val Articulo : String?
)
{
companion object {
const val TABLE_NAME = "TablaConteo"
}
}
}
Class #Dao
#Dao
public interface ItblConteoDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertArticulo(taskTblConteo: TablasBdApp.TblConteo);
#Query("SELECT * FROM " + TablasBdApp.TblConteo.TABLE_NAME + " ORDER BY Linea_Id desc")
fun getConteoArticulos(): LiveData<List<TablasBdApp.TblConteo>>
}
Class #DataBase
class BaseDeDatos {
#Database(entities = [TablasBdApp.TblConteo::class], version = 1)
abstract class PortatilDataBase : RoomDatabase() {
abstract fun itblconteoDao () : ItblConteoDao
companion object {
private const val DATABASE_NAME = "portatildb"
#Volatile
private var INSTANCE: PortatilDataBase? = null
fun getInstance(context: Context): PortatilDataBase? {
INSTANCE ?: synchronized(this) {
INSTANCE = Room.databaseBuilder(
context.applicationContext,
PortatilDataBase::class.java,
DATABASE_NAME
).build()
}
return INSTANCE
}
}
}
}
Class #Repository
class ConteoRepository(application: Application) {
private val itblConteoDao: ItblConteoDao? = BaseDeDatos.PortatilDataBase.getInstance(application)?.itblconteoDao()
fun insert (tblconteo: TablasBdApp.TblConteo){
if(itblConteoDao != null) InsertAsyncTask(itblConteoDao).execute(tblconteo)
}
fun getConteo(): LiveData<List<TablasBdApp.TblConteo>> {
return itblConteoDao?.getConteoArticulos() ?: MutableLiveData<List<TablasBdApp.TblConteo>>()
}
private class InsertAsyncTask(private val itblConteoDao: ItblConteoDao) :
AsyncTask<TablasBdApp.TblConteo, Void, Void>() {
override fun doInBackground(vararg tblconteos: TablasBdApp.TblConteo?): Void? {
for (tblconteo in tblconteos) {
if (tblconteo != null) itblConteoDao.insertArticulo(tblconteo)
}
return null
}
}
}
Class #CustomAdapter
class CustomAdapter(context: Context): BaseAdapter() {
private val mContext: Context
init{
mContext = context
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
//val repository = ConteoRepository(application = Application())
var textView = TextView(mContext)
val observer = Observer<List<TablasBdApp.TblConteo>> { conteos ->
if (conteos != null) {
var text = ""
for (conteo in conteos) {
text += conteo.Linea_Id.toString() + " " + conteo.Articulo
}
textView.text = text
}
}
//repository.getConteo().observe(this, observer)
return textView
}
override fun getItem(position: Int): Any {
return "Test Articulo"
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun getCount(): Int {
TODO("Not yet implemented")
}
}
Class #Fragment
class CapturaConteoFragment() : Fragment() {
private lateinit var capturaConteoViewModel: CapturaConteoViewModel
#SuppressLint("ResourceType")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
capturaConteoViewModel =
ViewModelProviders.of(this).get(CapturaConteoViewModel::class.java)
val root = inflater.inflate(R.layout.fragment_capturaconteo, container, false)
//val textView: TextView = root.findViewById(R.id.text_capturaconteo)
capturaConteoViewModel.text.observe(viewLifecycleOwner, Observer {
//textView.text = it
val botonGuardar: Button = root.findViewById(R.id.btn_guardar)
val textoArticulo : EditText = root.findViewById(R.id.edit_art_upc)
var listview: ListView = root.findViewById(R.id.list_conteo)
botonGuardar.setOnClickListener {
if(textoArticulo.getText().toString().trim().isEmpty()){
Toast.makeText(activity, "Captura un articulo", Toast.LENGTH_LONG).show()
textoArticulo.requestFocus()
}
else{
saveConteo(TablasBdApp.TblConteo(Articulo = textoArticulo.text.trim().toString()))
Toast.makeText(activity, "Articulo guardado", Toast.LENGTH_LONG).show()
//var listaconteo = arrayOf(muestraConteo())
var prodAdapter = CustomAdapter(**this**) **<- This is where it marks error.**
listview?.adapter = prodAdapter
}
}
})
return root
}
private fun saveConteo(tblConteo: TablasBdApp.TblConteo) {
val repository = ConteoRepository(application = Application())
repository.insert(tblConteo)
val conteo = repository.getConteo()
}
private fun showConteo (){
val repository = ConteoRepository(application = Application())
repository.getConteo()
}
}
Thanks for your help.
Regards.
In your DAO you are returning LiveData that means you need to observe it and best practice would be through ViewModel which implies you should MVVM architecture, there are plenty of tutorials and courses on it.
Here is something from google Codelabs
Other way would would be to return just list rather than live data of a list. This is bad practice because you will have to refresh it all the time and do a lot of extra work.
PS your adapter needs fixing too my suggestion is follow codelabs or watch complete tutorial on Room + MVVM.
Hope this helps

MVVM: ViewModel is null when trying to load data using LiveData

So I'm trying to build a simple MVVM interface to load trips from the database retrieved by the ViewModel into my TripFragment. However, I keep getting this error saying that my TripViewModel is null:
Attempt to invoke virtual method 'void androidx.lifecycle.LiveData.observe(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Observer)' on a null object reference
I can't seem to figure out why it thinks that it's null. I believe the issue is in TripViewModel and has something to do with how the fact that it inherits from AndroidViewModel and that I'm passing the application's context in the constructor.
class TripFragment : Fragment()
private var tripViewModel: TripViewModel? = null
private var textViewTripName: TextView? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view =
inflater.inflate(R.layout.fragment_trip, container, false)
val recyclerView: RecyclerView = view.recycler_view
recyclerView.layoutManager = LinearLayoutManager(context)
recyclerView.setHasFixedSize(true)
val adapter = TripAdapter()
recyclerView.adapter = adapter
tripViewModel = ViewModelProvider(this).get(TripViewModel(activity!!.application)::class.java)
// This is the line where it crashes, it never executes past this
tripViewModel!!.getAll().observe(viewLifecycleOwner, Observer<List<Trip>> {
fun onChanged(trips: List<Trip>) {
adapter.setTrips(trips)
Log.d("TripFragment", "Went through observer")
}
})
return view
}
class TripViewModel(application: Application): AndroidViewModel(application)
private var tripRepository: TripRepository = TripRepository(application)
private var allTrips: LiveData<List<Trip>> = getAll()
fun insert(trip: Trip) {
tripRepository.insert(trip)
}
fun update(trip: Trip) {
tripRepository.update(trip)
}
fun delete(trip: Trip) {
tripRepository.delete(trip)
}
fun clear() {
tripRepository.clear()
}
fun getAll(): LiveData<List<Trip>> {
return allTrips
}
class TripRepository(application: Application)
private lateinit var tripDao: TripDao
private lateinit var allTrips: LiveData<List<Trip>>
init {
CoroutineScope(IO).launch {
tripDao = AppDatabase.getDatabase(application.applicationContext).tripDao()
allTrips = tripDao.getAll()
}
}
fun insert(trip: Trip) {
CoroutineScope(IO).launch {
tripDao.insert(trip)
}
}
fun update(trip: Trip) {
CoroutineScope(IO).launch {
tripDao.update(trip)
}
}
fun delete(trip: Trip) {
CoroutineScope(IO).launch {
tripDao.delete(trip)
}
}
fun clear() {
CoroutineScope(IO).launch {
tripDao.clear()
}
}
fun getAll(): LiveData<List<Trip>> {
return allTrips
}
Trip entity
#Entity
data class Trip(var title: String, var startDate: String, var endDate: String?) {
#PrimaryKey(autoGenerate = true)
var tid: Long = 0
}
EDIT: I've printed a bunch of debug logs and pinpointed the error at this line in TripRepository.
init {
CoroutineScope(IO).launch {
// tripDao is never assigned properly,
tripDao = AppDatabase.getDatabase(application.applicationContext).tripDao()
allTrips = tripDao.getAll()
}
}
The line tripDao = AppDatabase.getDatabase(application.applicationContext).tripDao() causes an error which turns tripDao into a null variable. The problem has something to do with how I fetch the database, so I've attached my AppDatabase class below.
#Database(entities = [Trip::class], version = 2, exportSchema = false)
abstract class AppDatabase : RoomDatabase() {
abstract fun tripDao(): TripDao
companion object {
// Singleton prevents multiple instances of database opening at the
// same time.
#Volatile
private var INSTANCE: AppDatabase? = null
fun getDatabase(context: Context): AppDatabase {
val tempInstance = INSTANCE
if (tempInstance != null) {
Log.d("AppDatabase", "Returning existing database")
return tempInstance
}
synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"tripweaver_database"
)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
Log.d("AppDatabase", "Returning new database")
return instance
}
}
}
}
I couldn't find the solution to fixing my database access code so I restructured my whole codebase according to the architecture components tutorial by Google on Codelabs. Surely enough this helped me fix the issues I was having. Link for whoever needs this tutorial in the future: https://codelabs.developers.google.com/codelabs/android-room-with-a-view-kotlin/#13
In your class TripViewModel, you have:
private var allTrips: LiveData<List<Trip>> = getAll()
fun getAll(): LiveData<List<Trip>> {
return allTrips
}
So you have a circular arrangement here. allTrips is assigned an initial value of whatever getAll() returns, but it's calling getAll() before allTrips is assigned. So you found a way to assign a null to a non-nullable!
Seems like maybe you meant to put allTrips = tripRepository.getAll().

Categories

Resources