androiddatabasekotlinmobileandroid-jetpack-compose

Data from room kotlin only appear in Launcheffects not anywhere else


Problem I am facing is that the data is inserted into table not problems but then I am not able to fetch them anywhere. I tried to do print statements but only the fetch in order repository still retain the data ad Launch effect as well.

This is my Dao function that creates queries ad return types and handles inserts

DAO

@Dao
interface OrderDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertOrder(order: Order)

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertProduct(product: Product)

    @Query("SELECT * FROM orders")
    suspend fun getAllOrders(): List<Order>

    @Query("SELECT * FROM products WHERE orderId = :orderId")
    suspend fun getProductsForOrder(orderId: String): List<Product>

    @Update
    suspend fun updateProduct(product: Product)

    @Query("UPDATE products SET currentCount = :newCount WHERE productId = :productId AND orderId = :orderId")
    suspend fun updateProductCount(productId: String, orderId: String, newCount: String)


    @Query("SELECT COUNT(*) FROM orders")
    suspend fun countOrders(): Int

    @Query("SELECT COUNT(*) FROM products")
    suspend fun countProducts(): Int

    @Query("SELECT * FROM orders")
    fun getAllOrdersAsLiveData(): LiveData<List<Order>>
}

This is the order repository

I know that live data are automatically on main thread but i dont want to use the main thread for this

class OrderRepository(private val db: AppDatabase) {

    val allOrders: LiveData<List<Order>> = db.orderDao().getAllOrdersAsLiveData()

    // Insert an order
    @WorkerThread
    suspend fun insertOrder(order: Order) {
        withContext(Dispatchers.IO) {
            try {
                db.orderDao().insertOrder(order)
            } catch (e: Exception) {
                Log.e("OrderRepository", "Error inserting order: ${e.message}")
            }
        }
    }

    // Fetch all orders as LiveData
    @WorkerThread
    fun fetchAllOrdersAsLiveData(): LiveData<List<Order>> {
        return db.orderDao().getAllOrdersAsLiveData() // Assuming you have this method in your DAO
    }

    // Fetch all orders (suspend function)
    @WorkerThread
    suspend fun fetchAllOrders(): List<Order> {
        return withContext(Dispatchers.IO) {
            try {
                val orders = db.orderDao().getAllOrders()
                Log.d("OrderRepository", "Fetched orders: $orders, Size: ${orders.size}")
                orders
            } catch (e: Exception) {
                Log.e("OrderRepository", "Error fetching orders: ${e.message}")
                emptyList() // Return an empty list on error
            }
        }
    }

    // Insert a product
    @WorkerThread
    suspend fun insertProduct(product: Product) {
        withContext(Dispatchers.IO) {
            try {
                db.orderDao().insertProduct(product)
            } catch (e: Exception) {
                Log.e("OrderRepository", "Error inserting product: ${e.message}")
            }
        }
    }

    // Update product count
    @WorkerThread
    suspend fun updateProductCount(productId: String, orderId: String, newCount: String) {
        withContext(Dispatchers.IO) {
            try {
                db.orderDao().updateProductCount(productId, orderId, newCount)
            } catch (e: Exception) {
                Log.e("OrderRepository", "Error updating product count: ${e.message}")
            }
        }
    }
}

View Model

class OrderViewModel(private val repository: OrderRepository) : ViewModel() {


    val allOrders: LiveData<List<Order>> = repository.allOrders
    

    fun insertOrder(order: Order) {
        viewModelScope.launch {
            repository.insertOrder(order)
        }
    }

    suspend fun fetchAllOrders(): List<Order> {
        return repository.fetchAllOrders()
    }


    fun insertProduct(product: Product) {
        viewModelScope.launch {
            repository.insertProduct(product)
        }
    }

    fun updateProductCount(productId: String, orderId: String, newCount: String) {
        viewModelScope.launch {
            repository.updateProductCount(productId, orderId, newCount)
        }
    }
}

Populating the database I know this is probably not the most efficient way to handle this.

private suspend fun parseQrCodes(qrCodeResults: List<String>) {
        for (qrCodeResult in qrCodeResults) {
            val parts = qrCodeResult.split("%")
            val orderNumber = parts[0]
            val data: HashMap<String, HashMap<String, String>> = HashMap()

            // Insert the order into the database
            orderRepository.insertOrder(Order(orderNumber))

            for (order in parts.subList(1, parts.size)) {
                val (productId, quantity) = order.split("$")
                val orderDetails: HashMap<String, String> = HashMap()
                orderDetails[quantity] = "0"
                data[productId] = orderDetails

                // Create a Product object and insert it into the database
                val product = Product(productId, orderNumber, quantity)
                orderRepository.insertProduct(product) // Call to insertProduct in OrderRepository
            }
            qrCodesOrderDataParsed[orderNumber] = data
        }
    }

This is the way I am trying to access the database in another Activity

 // Database and local context initialization
    val context = LocalContext.current
    val database = AppDatabase.getDatabase(context)
    val orderRepository = remember { OrderRepository(database) }

    // Create ViewModel
    val viewModel: OrderViewModel = viewModel(factory = OrderViewModelFactory(orderRepository))

    // Observe the orders LiveData
    val dataFromDatabase by viewModel.allOrders.observeAsState(emptyList())

    LaunchedEffect(dataFromDatabase){
        Log.d("OrderPreviewScreen", "Data from database: $dataFromDatabase")

    }

App Database

@Database(entities = [Order::class, Product::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun orderDao(): OrderDao

    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getDatabase(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "app_database"
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }
}

Entities

@Entity(tableName = "orders")
data class Order(
    @PrimaryKey val orderId: String
)

@Entity(tableName = "products")
data class Product(
    @PrimaryKey val productId: String,
    val orderId: String,
    val quantity: String,
    var currentCount: String = "0" // Default value
)

ViewFactory

class OrderViewModelFactory(private val repository: OrderRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(OrderViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return OrderViewModel(repository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }
}

This is the way I initialize db

@Composable
fun OrderPreviewScreen(
    navController: NavController,
    qrCodeResults: List<String> = emptyList(),
) {
    // Database and local context initialization
    val context = LocalContext.current
    val database = AppDatabase.getDatabase(context)
    val orderRepository = remember { OrderRepository(database) }

    // Create ViewModel
    val viewModel: OrderViewModel = viewModel(factory = OrderViewModelFactory(orderRepository))

    // Observe the orders LiveData
    val dataFromDatabase by viewModel.allOrders.observeAsState(emptyList())

    LaunchedEffect(dataFromDatabase) {
        Log.d("OrderPreviewScreen", "Data from database: $dataFromDatabase")
    }

    val orderDataState = remember { mutableStateOf<List<Order>>(emptyList()) }
    LaunchedEffect(Unit) {
        val orderData_db = viewModel.fetchAllOrders()
//        val productData_db = viewModel.fetchAllProducts()
        Log.d("OrderPreviewScreen", "Fetched orders Launch Effect : $orderData_db")
        // You can use orderData as needed here
        orderDataState.value = orderData_db
    }

    val orderData_db = orderDataState.value
//    val productData_db =
    Log.d("OrderPreviewScreen", "Fetched orders: $orderData_db")

Solution

  • Room supports LiveData, it means whenever you update the table, Room will notify the changes through LiveData, so you don't have to call the Dao function again to get the latest data.

    LiveData.observeAsState() will notify Compose to recompose when the LiveData is updated.

    This will be the only part you need.

    @Composable
    fun OrderPreviewScreen(
        navController: NavController,
        qrCodeResults: List<String> = emptyList(),
    ) {
        // Database and local context initialization
        val context = LocalContext.current
        val database = AppDatabase.getDatabase(context)
        val orderRepository = remember { OrderRepository(database) }
    
        // Create ViewModel
        val viewModel: OrderViewModel = viewModel(factory = OrderViewModelFactory(orderRepository))
    
        // Observe the orders LiveData
        val dataFromDatabase by viewModel.allOrders.observeAsState(emptyList())
    
        // use dataFromDatabase in your UI
    }
    

    I don't know why you want to get the data separately again in another variable and make it a State.

    Also you don't need to switch CoroutineScope for Room suspend functions as room uses withContext(Dispatchers.IO) internally anyways.