Search code examples
kotlininsertandroid-jetpack-composeandroid-roomreturn-value

How to return newly inserted item(row) id using android room (Kotlin) on Compose?


I need to get the Auto generated ID of a record added to a ROOM database table.

Looking to answer my problem I found the following answer: text

But in my case is not a solution because in Compose I can't use the code: lifecycleScope.launch

//DAO
@Insert
suspend fun addwarrantyget(warrantyEntity: WarrantyEntity): Long

//Repository
suspend fun addwarrantyget(warrantyEntity: WarrantyEntity):Long {
        return inventoryDB.warrantyDao().addwarrantyget(warrantyEntity)
    }

//viewModel
fun addwarrantyget(warranty: WarrantyEntity) =
        viewModelScope.launch {
            repository.addwarrantyget(warranty)
        }

//Inside Compose Fun
lifecycleScope.launch {
    val id = viewModel.addwarrantyget(warranty)
    Log.i("INSERT_ID", "Inserted ID is: $id")
}

As per suggestion and my understanding:

I added:

//viewModel

private val _newItemId = MutableStateFlow(-1L)
val newItemId: StateFlow<Long> = _newItemId

fun addwarrantyget(warranty: WarrantyEntity) = viewModelScope.launch {
    val newId = repository.addwarrantyget(warranty)
    _newItemId.emit(newId!!)
}

//Compose function extract

Button(
                onClick = {

                    viewModel.addwarrantyget(
                       WarrantyEntity(
                            0,
                           null,
                            insn,
                            indescription,
                            null,
                           instartdateP.value,
                           inenddateP.value,
                           inprovider,
                           instartdateM.value,
                           inenddateM.value,
                           inmanufacturer
                        )
                    )
                    val newItemID by viewModel.newItemId.collectAsStateWithLifecycle()
                    LaunchedEffect(newItemID) {
                        if (newItemID == -1L) return@LaunchedEffect
                        // Do what you need with the new id here
                        warrantyId.value = viewModel.newItemId
                        mToast("Warranty Added", mContext)
                    }

                    mToast("Warranty Added", mContext)

                },
                colors = ButtonDefaults.buttonColors(Color.Blue)

            ) {
                Text(text = "Add Warranty")
            }

collectAsStateWithLifecycle: gives undefined reference error

warrantyId.value = viewModel.newItemId: gives Require Long Found StateFlow error

LaunchedEffect: gives @Composable invocations can only happen from the context...


Solution

  • In compose you have LaunchedEffect. You won't get the id at the same place you call viewModel.addwarrantyget(warranty), but you can consume the new id value from the viewModel as a state.

    viewModel

    private val _newItemId = MutableStateFlow(-1)
    val newItemId: StateFlow<Int> = _newItemId
    
    fun addwarrantyget(warranty: WarrantyEntity) = viewModelScope.launch {
        val newId = repository.addwarrantyget(warranty)
        _newItemId.emit(newId)
    }
    

    Composable

        val newItemID by viewModel.newItemId.collectAsStateWithLifecycle()
        LaunchedEffect(newItemID) {
            if (newItemID == -1) return@LaunchedEffect
            // Do what you need with the new id here
        }
    

    Note that i set filtered out the default newItemID value -1, there are other ways to do it of course.

    Edit:

    Regarding Require Long Found StateFlow error. The newItemID already contains the value of the new id, you don't need to read it from the viewModel again.

    mToast("New id: $newItemID", mContext)
    

    Please read about States to gain better understanding of what is going on here.

    About @Composable invocations can only happen from the context... error. As the error says, LaunchedEffect (as a composable function) can not be called from the onClick lambda, which is not a composable function. It's important to understand that consuming newItemId state and calling viewModel.addwarrantyget() are not tied together. You can put the LaunchedEffect to your composable screen level instead.

    @Composable
    fun YourScreen(
        viewModel: YourViewModel = viewModel()
    ) {
        // other code
        val newItemID by viewModel.newItemId.collectAsStateWithLifecycle()
        LaunchedEffect(newItemID) {
            if (newItemID == -1) return@LaunchedEffect
            mToast("New id: $newItemID", mContext)
        }
        // other composables
    }