Search code examples
androidkotlinandroid-roomviewmodel

Problem displaying data from Room database with ViewModel and Databinding


I am building an android app where I add products in the database in the Detailfragment and show them in the main fragment: This is the Product class:

@Parcelize
 @Entity(tableName = "product_table")

data class Product (
@ColumnInfo(name = "name")
var name:String = "product",
):Parcelable{@PrimaryKey(autoGenerate = true)
var id:Long = 0L}

This is the ProductViewModel Iam using

    class ProductViewModel(val database: ProductDatabaseDao) : ViewModel() {
    val products = database.getAllProducts()
    val productsString = products.value?.get(0)?.name
    private var _navigateToProductsList = MutableLiveData<Boolean>()

    val navigateToProductsList : LiveData<Boolean>
    get() = _navigateToProductsList

    private suspend fun insert(product: Product) {
        database.insert(product)
    }

    fun onAddProduct(){
        viewModelScope.launch {
            val product = Product()
            product.name = "toy"
            insert(product)
        }
        _navigateToProductsList.value = true
    }
    fun doneNavigation(){
        _navigateToProductsList.value = false
    }
}

This is the ProductDetailFragment:

    class ProductDetailFragment : Fragment() {
    private val productViewModel: ProductViewModel by activityViewModels {
        ProductViewModelFactory(
            getDatabase()
        )
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        // Inflate the layout for this fragment
    val binding = DataBindingUtil.inflate<FragmentProductDetailBinding>(

            inflater,
            R.layout.fragment_product_detail,
            container,
            false
        )

binding.lifecycleOwner = this
        binding.productViewModel = productViewModel
    productViewModel.navigateToProductsList.observe(viewLifecycleOwner, Observer {

            if (it == true) {
                this.findNavController()
                    .navigate(ProductDetailFragmentDirections.actionProductDetailFragmentToProductsListFragment())
                // Reset state to make sure we only navigate once, even if the device
                // has a configuration change
                productViewModel.doneNavigation()
            }

        })
return binding.root
    }

    fun getDatabase(): ProductDatabaseDao {
        val application = requireNotNull(this.activity).application
        val dataSource = ProductDatabase.getInstance(application).productDatabaseDao
        return dataSource
    }
}

and this is product_detail_fragment

    <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="productViewModel"
            type="com.github.mariemmezghani.inventory.viewModel.ProductViewModel" />
    </data>
<androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <EditText
            android:id="@+id/product_name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="32dp"
            android:layout_marginEnd="16dp"
            android:ems="10"
            android:inputType="textPersonName"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <Button
            android:id="@+id/save_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="32dp"
            android:layout_marginEnd="16dp"
            android:background="@drawable/round_button"
            android:text="@string/save_button"
            android:textColor="@color/white"
            android:onClick="@{()->productViewModel.onAddProduct()}"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/product_name" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Here I am calling the productViewmodel.onAddProduct() in the save_Button

And this is the products_list_fragment

     <?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 to make available to the XML via data binding. In this case,
         the whole ViewModel, so that we can access the LiveData,
         click handlers, and state variables. -->
    <data>
        <variable
            name="viewModel"
            type="com.github.mariemmezghani.inventory.viewModel.ProductViewModel" />
    </data>
    <androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
android:layout_height="match_parent"

    tools:context=".ProductsListFragment">

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/add_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="32dp"
        android:clickable="true"
        app:backgroundTint="@color/color_primary"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.498"
        app:layout_constraintStart_toStartOf="parent"
        app:srcCompat="@android:drawable/ic_input_add" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="32dp"
        android:layout_marginEnd="16dp"
        android:text="@{viewModel.productsString}"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.495"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Before implementing the recyclerView to display the products added by the user, I wanted to test that the products are added correctly to the database, so I just added a product and gave it a name "toy" and tried to display it in the TextView of this product_list_fragment.

I can not figure out why this did not work and nothing is displayed in the screen when I press the save_Button

Thank you for your help


Solution

  • I found the solution to my question. The problem is that products.value? ( type LiveData<List>) is returning null from Room. I managed to fix the problem by using Transformations.map. I changed this:

    val productsString = products.value?.get(0)?.name
    

    to this:

    val productsString = Transformations.map(products){products -> products.get(0).name}