You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Room Kotlin数据仅在LaunchedEffect可获取,其他场景无法读取求助

问题:Room数据库数据仅能在LaunchedEffect和Repository的fetch方法中读取,其他场景无法获取

问题详情

数据可正常插入Room数据库,但只有在LaunchedEffect和OrderRepository的fetch方法中能读取到数据,其他场景(比如Composable函数体直接读取State)无法获取有效数据。


相关代码实现

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

OrderRepository代码

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()
    }

    // 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()
            }
        }
    }

    // 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}")
            }
        }
    }
}

ViewModel代码

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)
        }
    }
}

数据库填充逻辑

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)
        }
        qrCodesOrderDataParsed[orderNumber] = data
    }
}

读取数据的Composable代码

@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()
        Log.d("OrderPreviewScreen", "Fetched orders Launch Effect : $orderData_db")
        orderDataState.value = orderData_db
    }

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

    // ... UI代码
}

AppDatabase代码

@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
            }
        }
    }
}

Entity代码

@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
)

ViewModelFactory代码

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

问题原因及修复方案

1. Composable函数体直接读取State的时机错误

你在OrderPreviewScreen的函数体中直接打印orderData_db:

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

这段代码会在Composable初始重组阶段执行,此时LaunchedEffect(Unit)里的异步fetchAllOrders()还未完成,orderDataState.value还是初始的空列表。只有当LaunchedEffect执行完毕更新orderDataState后,Composable会再次重组,但此时这段打印代码不会主动触发(除非有其他重组条件),导致你看不到更新后的数据。

修复:

  • 通过LaunchedEffect监听orderDataState的变化来处理数据:
LaunchedEffect(orderDataState.value) {
    Log.d("OrderPreviewScreen", "Fetched orders (updated): ${orderDataState.value}")
}
  • 或者直接在UI组件中使用该State(比如LazyColumn),Compose会自动追踪State变化并重组UI,确保展示最新数据。

2. 确保Repository/ViewModel实例的一致性

如果在数据填充和数据读取的场景中创建了不同的OrderRepository或OrderViewModel实例,可能会导致数据感知不一致(虽然Room是单例数据库,但LiveData的订阅可能因实例不同出现延迟)。

修复:

  • 全局使用单例OrderRepository,可以通过依赖注入(如Hilt)实现,或者在AppDatabase的单例逻辑中绑定Repository。
  • 在Composable中通过viewModel()函数获取ViewModel(默认是Activity/Fragment级别的单例),避免手动创建多个ViewModel实例。

3. 替换LiveData为Flow适配Compose

LiveData是为传统View体系设计的,Flow更适配Compose的协程环境,能更稳定地感知数据变化。

优化步骤:

  1. 在DAO中替换LiveData为Flow:
@Query("SELECT * FROM orders")
fun getAllOrdersAsFlow(): Flow<List<Order>>
  1. 在Repository中将Flow转换为StateFlow:
class OrderRepository(private val db: AppDatabase) {
    private val ordersFlow = db.orderDao().getAllOrdersAsFlow()
    
    val allOrders: StateFlow<List<Order>> = ordersFlow
        .stateIn(
            scope = CoroutineScope(Dispatchers.IO + SupervisorJob()),
            started = SharingStarted.WhileSubscribed(5000),
            initialValue = emptyList()
        )

    // ... 其他方法
}
  1. 在ViewModel中暴露StateFlow:
class OrderViewModel(private val repository: OrderRepository) : ViewModel() {
    val allOrders: StateFlow<List<Order>> = repository.allOrders
    // ... 其他方法
}
  1. 在Compose中用collectAsState()观察:
val dataFromDatabase by viewModel.allOrders.collectAsState()

4. 优化批量插入效率(可选)

当前的循环插入是单次异步操作,换成批量插入可以减少数据库IO次数,同时确保数据插入的原子性:

  1. 在DAO中添加批量插入方法:
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertOrders(orders: List<Order>)

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertProducts(products: List<Product>)
  1. 修改填充逻辑:
private suspend fun parseQrCodes(qrCodeResults: List<String>) {
    val orders = mutableListOf<Order>()
    val products = mutableListOf<Product>()
    
    for (qrCodeResult in qrCodeResults) {
        val parts = qrCodeResult.split("%")
        val orderNumber = parts[0]
        orders.add(Order(orderNumber))
        
        for (order in parts.subList(1, parts.size)) {
            val (productId, quantity) = order.split("$")
            products.add(Product(productId, orderNumber, quantity))
        }
    }
    
    orderRepository.insertOrders(orders)
    orderRepository.insertProducts(products)
}

内容的提问来源于stack exchange,提问作者IEatGemstones

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 17:34:51