0

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 Answers3

0

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.

darjow
  • 39
  • 8
0

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.

聂超群
  • 1,659
  • 6
  • 14
  • 1
    using runBlocking inside a place where UI intractions are is dangerous, because it may cause ANR problem. – Sep Aug 24 '23 at 15:23
  • Agree. That's why I metioned better solution in my answer. – 聂超群 Aug 25 '23 at 00:31
  • This one helped, but as far as I know, using this with UI interaction is dangerous like what @Sep said. the problem is I can't use that suspend function chain in that `setOnFocusChangeListener()` – Ali Saadati Aug 25 '23 at 09:04
0

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)
        }       
    }
}
Tenfour04
  • 83,111
  • 11
  • 94
  • 154
  • i tried to make `nameValidation` function susped. but the problem is here: `productName.setOnFocusChangeListener { _, focused -> if (!focused) { productNameContainer.helperText = ProductValidator.nameValidation(productName, cactusDao) } }` if `nameValidtion` becomes suspend, i cant use it inside this `setOnFocusChangeListener` – Ali Saadati Aug 25 '23 at 08:55
  • Yes you can, by launching a coroutine from inside your focus change listener, as I explained in the second paragraph above. In your case, the “beginning of your logic flow” is the focus change listener. See updated answer. – Tenfour04 Aug 25 '23 at 10:21
  • Thank you so much, your solution helped. this is kinda my first project. Thank you all. – Ali Saadati Aug 25 '23 at 10:32