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