Display ROOM data at UI - problems

Hello guys)
Im new at kotlin and android development. Teaching at this moment google codelabs, all is goes fun until i started use ROOM. i created @Entity, @Dao, @Database. but i dont know how to display data to UI.

for example:
At DAO i have select:

@Query(“Select * from my_table1”)
fun getAllCount(): LiveData<List>

Variant 1.
at ViewModel i getting info from dao:

val counts: LiveData<List> = databasedao.getAllCount()

now i want display it at TextView
i going at xml amd make next for TextView

android:text=“@{xmlttitleViewModel.counts.toString()}”

then it shows me at UI:

[MyEnt{coiuntId=1,countNum=0}] //at entity a have only 2 columns “countId” and “countNum”

Variant 2.
but if i at ViewModel will make next:

val counts: LiveData<List> = databasedao.getAllCount()
val countsDisplay: String = counts.toString()

and at xml i will make

android:text="@{xmlttitleViewModel.countsDisplay

then at UI i get memory address

Questions:

  1. Why variant 2 isnt display the same as variant 1?
  2. How correctly display ROOM data to UI (like i want display only “countNum”)?
1 Like

When using LiveData, the liveData object should be either:

  • Observed in the activity to update the UI.
  • Use Binding to update the views directly

Here, I assume you’re trying to use the Binding.

Also, my impression is, you would like to show the count of your objects in room.
So, I wrote an example which fetches a list of strings from room and shows the size (count) in a TextView.

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
       <!-- this is where you define your bindings -->
        <variable
            name="myViewModel"
            type="com.example.bindings.MyViewModel" />
    </data>

    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:id="@+id/tv_count"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text='@{viewModel.myList.size}'
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintHorizontal_bias="0.501"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.1" />

    </android.support.constraint.ConstraintLayout>
</layout>

MyViewModel.kt

class MyViewModel: ViewModel {

    val myList: LiveData<List<String>> = databasedao.getAll()
}

MainActivity.kt:

class MainActivity: AppCompatActivity {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        val vm = ViewModelProvider(this).get(MyViewModel::class)
        binding.setViewModel(userViewModel)

        // Required to update UI with LiveData
        binding.setLifecycleOwner(this)
    }
}

Please feel free to try it and ask your questions. :slight_smile:

1 Like

Thank you for response)
i already have this code.

“val myList: LiveData<List> = databasedao.getAll()” - this code shows error - type mismatch (required: LiveData<List>. found LiveData<List> ), because getAll() function returned Entity object (as i understand).

So ill show my code more detaly.
What i whabt to do: launch app, if database is empty - i inserted row with default data from entity. Then i have at UI - button, which make +1 to curent count. Also i have at UI TextView, which should display curent count from database.

My entity:

@Entity(tableName="my_table1")
data class MyEnt (
    @PrimaryKey(autoGenerate = true)
    var countId: Long =0L,
  @ColumnInfo(name ="my_count")
    var countNum: Int=0
)

My Dao:

@Dao
interface CountDataBaseDao {

    @Insert
    suspend fun insert(myentity: MyEnt) 

    @Update
     suspend fun update(myentity: MyEnt)

    @Query("Select * from my_table1 where countId=:key")
    suspend fun get(key: Long): MyEnt? 

    @Query("Select * from my_table1 ORDER BY countId DESC LIMIT 1")
    suspend fun getCount(): MyEnt?

    @Query("Select * from my_table1")
    fun getAllCount(): LiveData<List<MyEnt>> 
}

My Database:

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

    abstract val countDataBaseDao: CountDataBaseDao

    companion object {

        @Volatile 
        private var INSTANCE: RowCountDB? = null

        fun getInstance(context: Context): RowCountDB {
            synchronized(this) {
                var instance = INSTANCE

                if (instance == null) {
                    instance = Room.databaseBuilder(
                        context.applicationContext,
                        RowCountDB::class.java,
                        "my_count_database"
                    )
                        .fallbackToDestructiveMigration()
                        .build()
                    INSTANCE = instance
                }
                return instance
            }
        }
    }
}

My ViewModel:

class TitleViewModel( val databasedao: CountDataBaseDao, application: Application): AndroidViewModel(application) {

    private var mydbCount = MutableLiveData<MyEnt?>()

    private var _count = MutableLiveData<Int>()
    val count : LiveData<Int>
    get() = _count

    init {
        _count.value=0
        initilizeMydbCount()
    }

  
     var counts: LiveData<List<MyEnt>> = databasedao.getAllCount() //I shiwed data at UI "[MyEnt{coiuntId=1,countNum=0}]", but i also want find      //a way how to show just "countNum", not all row

    //here i checking when my app is start, if i dont have data at database - i inserted default entity data

    private fun initilizeMydbCount(){
        viewModelScope.launch {
            mydbCount.value= databasedao.getCount()

            if (mydbCount.value==null){  
                databasedao.insert(MyEnt())
                mydbCount.value= databasedao.getCount()
            }
      
        }
    }



     fun plusOne(){
        _count.value = (_count.value)?.plus(1)

         //here i trying to update database row, countNum column with _count.value. But its dont work, data at UI isnt changed...

         fun addPlusAtDB(){
             viewModelScope.launch {

                 mydbCount.value?.countNum=_count.value!!
                 val newCount =mydbCount.value?: return@launch
                 databasedao.update(newCount)
                 //now i need update my LiveData, which displayed at UI, but its dont work....
                 counts = databasedao.getAllCount()

             }
         }


    }

}

My xml:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="xmlttitleViewModel"
            type="android.example.com.rowcounter2.TitleViewModel" />

    </data>


    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="android.example.com.rowcounter2.TitleFragment">

        
        <Button
            android:id="@+id/count_plus"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/button"
            android:onClick="@{()-> xmlttitleViewModel.plusOne()}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toEndOf="@+id/count_display"
            app:layout_constraintTop_toBottomOf="@+id/et_name" />


        <TextView
            android:id="@+id/db_count_num"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{xmlttitleViewModel.counts.toString()}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/count_display" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

I just want understand how to change data at database and display it using ViewModel, ROOM, LiveData.

Thanks for posting more details on your app, it looks all fine.

your code seems fine, please tell me what the problem is when changing the data in Room database.
Feel free to copy the error from the Log Cat. :robot:

The Room is the database, which is being used to store the data, to display the stored data,
Room supports returning LiveData objects.
The LiveData is being used to update the views automatically.

I can see from your code, that you are returning LiveData objects from Room. Which is correct! :white_check_mark:
I can also see that you are using the ViewModel to hold your LiveData objects and use them in your UI, which is also correct! :white_check_mark:
Good job! :slight_smile:

Now, to display that LiveData object, (as I mentioned ) you should either 1- observe that object, or 2-bind it to a View.

Considering the code that you provided earlier, I assumed, that you want to use the binding(2) option. Which I tried to give you examples of how to use them.


Btw, I encourage you to have a look at the awesome Google codelab from Florina Muntenescu , which covers almost everything you need for connecting Android Views to Room database.

Google CodeLab Android Room with a View: Android Room with a View - Kotlin  |  Android Developers
Google CodeLab Databinding in Android: Data Binding in Android  |  Android Developers

Please let me know if that helped! :slight_smile:

1 Like

i already did 2 those metods to different text Views)))
and i always see the " [MyEnt{coiuntId=1,countNum=0}]"

1st metod:

   ttitleViewModel.counts.observe(viewLifecycleOwner,{counts->
            binding.databaseObserve.text= counts.toString()
        })

2nd:
android:text="@{ssleepTrackerViewModel.myDBCountStr}"

You can watch my fragment code (it have some functions not according to database, i testing transfering arguments to another fragments e.t.c.):

class TitleFragment : Fragment() {
    private lateinit var ttitleViewModel: TitleViewModel
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        val binding: FragmentTitleBinding =DataBindingUtil.inflate(
            inflater,R.layout.fragment_title, container, false)

        val application = requireNotNull(this.activity).application
       
        val dataSource= RowCountDB.getInstance(application).countDataBaseDao

        val viewModelFactory= TitleViewModelFactory(dataSource, application) 

        ttitleViewModel=ViewModelProvider(this, viewModelFactory).get(TitleViewModel::class.java)

        binding.xmlttitleViewModel=ttitleViewModel

        //display usual livedata change
        ttitleViewModel.count.observe(viewLifecycleOwner, Observer { newCount ->
            binding.countDisplay.text = newCount.toString()
        })
        //display changing LiveData from DATABASE
        ttitleViewModel.counts.observe(viewLifecycleOwner,{counts->
            binding.databaseObserve.text= counts.toString()
        })

        var cc: Int = 3

        //switch to another fragment
        ttitleViewModel.count.observe(viewLifecycleOwner, Observer {newCount2 ->
            if (newCount2==10){
                this.findNavController().navigate(TitleFragmentDirections.actionTitleFragmentToTransformtest(newCount2))
            }
        })
       
        binding.setLifecycleOwner(this)
        return binding.root
    }

    }


you can see what i get using method 1 and 2.
where you see number 3 its “_count” LiveData variable from a code.
Button “+” its my function “plusOne()”

So i pressing function “plusOne()”, usuall Livedata variable is changin correctly, but database live data variable “counts” isnt change

That screen shot indeed helps! :slight_smile:

I believe that is happening because the xmlttitleViewModel.counts is a List object and you are calling toString() on a list object explicitly to convert it to string.


Another thing which I’m not 100% sure about, is how would you like to show a list of MyEnt objects in a TextView?

Do you want to show each object in a row, like a List, or do you only want to show the object count?

If you wanna show the object count, you can probably try something like this:
android:text="@{xmlttitleViewModel.counts.size}"

If you want to show all of the list items in a scrollable list, you need to either use a RecyclerView (or a LazyColumn in the future).

Hello)) i solved my problem.
Problem was at “fun plusOne()”. Using coroutine at another function = DONT WORK! ))

My old code:

     fun plusOne(){
            _count.value = (_count.value)?.plus(1)

             //here i trying to update database row, countNum column with _count.value. But its dont work, data at UI isnt changed...

             fun addPlusAtDB(){
                 viewModelScope.launch {

                     mydbCount.value?.countNum=_count.value!!
                     val newCount =mydbCount.value?: return@launch
                     databasedao.update(newCount)
                     //now i need update my LiveData, which displayed at UI, but its dont work....
                     counts = databasedao.getAllCount()

                 }
             }


        }

My new correct code which work nice)

 fun plusOne(){
        _count.value = (_count.value)?.plus(1)
        
         addPlusAtDB()

    }

 private fun addPlusAtDB(){
      viewModelScope.launch {

          mydbCount.value?.countNum=_count.value!!
          val newCount =mydbCount.value?: return@launch
          databasedao.update(newCount)
        

      }
  }

Thank you Ouvertus for supporting)

What you did in your old code was that you defined function addPlusAtDB inside plusOne function, but never called it. You can keep your old format if you want, you just need to call function after you define it.