get data from coroutine globalScope in Room database kotlin

78 Views Asked by At

I want to fill the ProductList in the code below but it's always null. do you have any solution?

fun nameValidation(name: TextInputEditText, cactusDao: CactusDao): String? {

            val nameText = name.text.toString()
            var productList = listOf<String>()
            GlobalScope.launch(Dispatchers.IO) {
                productList = cactusDao.getAllProductNames()
            }

            if (productList.contains(nameText)) {
                return "Product already Exists!"
            }


            if (nameText.isNullOrEmpty() || nameText.isNullOrBlank()) {
                return "Field is Empty!"
            }

            return null
        }

i expected it fills the ProductList, but nothing happened. ProductList is still null and the condition doesn't work

UPDATE(doe to request of darjow):

i used that nameValidation here:

productName.setOnFocusChangeListener { _, focused ->
            if (!focused) {
                productNameContainer.helperText =
                    ProductValidator.nameValidation(productName, cactusDao)
            }
        }

which is inside of AlertDialog class that handles alertDialogs.

and im using this AlertDialog class in a floatingActionButton.setOnClickListener{}

3

There are 3 best solutions below

3
Tenfour04 On BEST ANSWER

See this question for an explanation of what your issue is.

The correct way to solve this is to turn this into a suspend function that directly calls the suspend function in the DAO:

suspend fun nameValidation(name: TextInputEditText, cactusDao: CactusDao): String? {

    val nameText = name.text.toString()
    val productList = cactusDao.getAllProductNames()

    if (productList.contains(nameText)) {
        return "Product already Exists!"
    }

    if (nameText.isNullOrEmpty() || nameText.isNullOrBlank()) {
        return "Field is Empty!"
    }

    return null
}

Then the function that calls this function should also be turned into a suspend function, and so on up the chain until you get to the beginning of your logic flow, which might be inside a lifecycle method like onCreate() or inside a click listener, etc. At this location, launch a coroutine that handles all the sequential logic you have inside a single launch { } block.

And don't use GlobalScope! Use lifecycleScope, or if you're in a Fragment, use viewLifecycleOwner.lifecycleScope.


Based on your comment, this is how you should call it if this code is in an Activity:

productName.setOnFocusChangeListener { _, focused ->
    lifecycleScope.launch {
        if (!focused) {
            productNameContainer.helperText =
                ProductValidator.nameValidation(productName, cactusDao)
        }       
    }
}
4
darjow On

as https://stackoverflow.com/a/76972694/12244168 explains make your nameValidation method suspend.

productName.setOnFocusChangeListener { _, focused ->
    if (!focused) {
        GlobalScope.launch(Dispatchers.Main) {
            val validationMessage = nameValidation(nameText, cactusDao) 
        }
    }
}

You don't want to call runblocking as it will freeze the UI. Therefore you use Dispatchers.Main to execute the async operation. It will call a new coroutine in the background to execute the async operation.

3
聂超群 On

The reason you get null is cactusDao.getAllProductNames() ran in another thread asynchronously.

You can address the issue by replacing

GlobalScope.launch(Dispatchers.IO) {
   productList = cactusDao.getAllProductNames()
}

with

productList = runBlocing {
   cactusDao.getAllProductNames()
}

But the better solution is introduce an CoroutineScope on call site and make nameValidation a suspend method.