Error when updating an item in Room database [duplicate] - kotlin

My Coroutine is running on the main thread, which i've specified in my Coroutine context:
class ClickPreference(context: Context, attrs: AttributeSet) : Preference(context, attrs), CoroutineScope, View.OnClickListener {
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main
override fun onClick(v: View?) {
when (key){
"logout" -> {
CoroutineScope(coroutineContext).launch {
CustomApplication.database?.clearAllTables()
Log.d("MapFragment", "Cleared Tables")
}
if (Profile.getCurrentProfile() != null) LoginManager.getInstance().logOut()
FirebaseAuth.getInstance().signOut()
val intent = Intent(context, MainActivity::class.java)
context.startActivity(intent)
}
}
}
But I'm still getting this error:
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
on my above Coroutine call CustomApplication.database?.clearAllTables() to my Room database.
Here is my CustomApplication:
class CustomApplication : Application() {
companion object {
var database: AppDatabase? = null
}
override fun onCreate() {
super.onCreate()
CustomApplication.database = Room.databaseBuilder(this, AppDatabase::class.java, "AppDatabase").build()
}
Why am I still getting the error if my coroutine context runs on the main thread?

The error says that it should NOT run on the main thread. Database operations (and every other form of IO) can take a long time and should be run in the background.
You should use Dispatchers.IO which is designed for running IO operations.

You can't use Dispatchers.Main for the long-running task. You have to use Dispatchers.IO for database operations, look likes:
class ClickPreference(context: Context, attrs: AttributeSet) : Preference(context, attrs), CoroutineScope, View.OnClickListener {
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO
override fun onClick(v: View?) {
when (key){
"logout" -> {
CoroutineScope(coroutineContext).launch {
CustomApplication.database?.clearAllTables()
Log.d("MapFragment", "Cleared Tables")
if (Profile.getCurrentProfile() != null) LoginManager.getInstance().logOut()
FirebaseAuth.getInstance().signOut()
}
val intent = Intent(context, MainActivity::class.java)
context.startActivity(intent)
}
}
}

Hi, this problem is because the database must run on mainthread.
Because of this you have to add this line of code to the database
section
Room.databaseBuilder(context.getApplicationContext(),
DataBaseTextChat.class, Constants.DB_TEXT_CHAT)
.allowMainThreadQueries()
.addCallback(roomCallBack).build();

Related

How to use LifecycleScope to execute coroutine

I am discovering Kotlin and android app dev. I fail to get data from my room database (because of Cannot access database on the main thread). So I try with lifecyclescope.
The concerned code, in Fragment onViewCreated function, is :
lifecycleScope.launch {
withContext(Dispatchers.Default) {
val accountConfiguration = viewModel.get();
println("{${accountConfiguration}}")
}
}
The called function (in viewModel) is :
fun get() = viewModelScope.launch {
repository.get()
}
There is the "full" code (simplified), Entity & DAO :
#Entity
data class AccountConfiguration(
#PrimaryKey val server_address: String,
#ColumnInfo(name = "user_name") val user_name: String,
// [...]
)
#Dao
interface AccountConfigurationDao {
#Query("SELECT * FROM accountconfiguration LIMIT 1")
fun flow(): Flow<AccountConfiguration?>
#Query("SELECT * FROM accountconfiguration LIMIT 1")
suspend fun get(): AccountConfiguration?
// [...]
}
Repository :
package fr.bux.rollingdashboard
import androidx.annotation.WorkerThread
import kotlinx.coroutines.flow.Flow
class AccountConfigurationRepository(private val accountConfigurationDao: AccountConfigurationDao) {
val accountConfiguration: Flow<AccountConfiguration?> = accountConfigurationDao.flow()
// [...]
#Suppress("RedundantSuspendModifier")
#WorkerThread
suspend fun get() : AccountConfiguration? {
return accountConfigurationDao.get()
}
}
ViewModel & Factory :
class AccountConfigurationViewModel(private val repository: AccountConfigurationRepository) : ViewModel() {
val accountConfiguration: LiveData<AccountConfiguration?> = repository.accountConfiguration.asLiveData()
// [...]
fun get() = viewModelScope.launch {
repository.get()
}
// [...]
}
class AccountConfigurationViewModelFactory(private val repository: AccountConfigurationRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(AccountConfigurationViewModel::class.java)) {
#Suppress("UNCHECKED_CAST")
return AccountConfigurationViewModel(repository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}
Fragment :
class AccountConfigurationFragment : Fragment() {
private var _binding: AccountConfigurationFragmentBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
private val viewModel: AccountConfigurationViewModel by activityViewModels {
AccountConfigurationViewModelFactory(
(activity?.application as RollingDashboardApplication).account_configuration_repository
)
}
lateinit var accountConfiguration: AccountConfiguration
// [...]
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.buttonGoBackMain.setOnClickListener {
findNavController().navigate(R.id.action_AccountConfigurationFragment_to_DashboardFragment)
}
lifecycleScope.launch {
withContext(Dispatchers.Default) {
val accountConfiguration = viewModel.get();
println("{${accountConfiguration}}")
}
}
binding.buttonSave.setOnClickListener {
save()
}
}
// [...]
}
In your current code,
lifecycleScope.launch {
withContext(Dispatchers.Default) {
val accountConfiguration = viewModel.get();
println("{${accountConfiguration}}")
}
}
viewModel.get() is not a suspend function, so it returns immediately and proceeds to the next line. It actually returns the Job created by viewModelScope.launch().
If you want your coroutine to wait for the result before continuing you should make the get() function suspend and return the AccountConfiguration?
suspend fun get(): AccountConfiguration? {
return repository.get()
}
You need not change dispatchers to Dispatchers.Default because Room itself will switch to a background thread before executing any database operation.
Right now if there is a configuration change while coroutines inside lifecyclerScope are running, everything will get cancelled and restarted.
A better way would have been to put the suspending calls inside the ViewModel and expose a LiveData/Flow to the UI.
The problem is the viewModel function :
fun get() = viewModelScope.launch {
repository.get()
}
This function must be the coroutine instead launch the coroutine itself. Correct code is :
suspend fun get(): AccountConfiguration? {
return repository.get()
}

Coroutine never calls API after job either cancelled or pending

How do I make calls when I click loan1 button happen after getting a JobCancellationException on other calls ?
class MainRepository{
//called many times
suspend fun getLoanOptions(): Resource<LoanOptionsResponse> {
return try {
val response = apiService.getLoanOptions("id")
responseHandler.handleSuccess(response)
} catch (e: Exception) {
responseHandler.handleException(e)
}
}
}
class MainViewModel : ViewModel(), CoroutineScope {
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
Timber.e("$throwable")
}
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + SupervisorJob() + exceptionHandler
private val mainRepo: MainRepository by lazy { MainRepository() }
//extra calls to this fails
fun getLoanOptions(): LiveData<Resource<LoanOptionsResponse>> {
return liveData(coroutineContext) {
val data = mainRepo.getLoanOptions()
emit(Resource.loading(null))
emit(data)
}
}
override fun onCleared() {
super.onCleared()
coroutineContext.cancel()
}
}
//in mainactivity I call it
class MainActivity : BaseActivity() {
val vm: MainViewModel by lazy { ViewModelProvider(this).get(MainViewModel::class.java) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
loan1.setOnClickListener {
// if any previous api has 403 this one does not work any more ?
vm.getLoanOptions().observe(this, Observer {
//data
}
}
loan2.setOnClickListener {
}
}
}
I clicked on button -> Api is called -> got success response
Again I clicked on -> Api is called -> got success response
I get 403 in other API call
I clicked on button -> Api is not called
No other Api calls gets called in this Activity :(
I get this after few minutes
kotlinx.coroutines.JobCancellationException: Job was cancelled; job=StandaloneCoroutine

Second async method is not executed from specified scope if first execution threw exception

I am trying to achieve is to have UI, IO and DEFAULT CoroutineScope within my BaseActivity activity. Then use these scopes for some operations.
Everything seems to work fine unless exception is thrown inside suspendCoroutine {}.
If no exception is thrown I can use ioScope multiple times without no problems. But if exception is thrown in Response.ErrorListener and I call loginAsync(), async won't be executed and coroutine will stuck at userDeferred.await().
I checked ioScope.isActive flag. Before Exception was thrown flag was set to true. After exception was thrown flag is set to false and I can exception in was thrown in scope.
I found that when I use instead of ioScope.async{ } function withContext(ioScope.coroutineContext){} exception will not break scope and it can be used again.
Could anyone please help me to resolve this issue. I could not find any help in documentations nor in blogs.
BaseActivity
CoroutineScopes creation.
abstract class BaseActivity : AppCompatActivity() {
private val ioJob = Job()
private val defaultJob = Job()
private val uiJob = Job()
protected val ioScope: CoroutineScope
get() = CoroutineScope(Dispatchers.IO + ioJob)
protected val uiScope: CoroutineScope
get() = CoroutineScope(Dispatchers.Main + uiJob)
protected val defaultScope: CoroutineScope
get() = CoroutineScope(Dispatchers.Default + defaultJob)
final override fun finish() {
super.finish()
uiJob.cancel()
defaultJob.cancel()
ioJob.cancel()
getActivityTransitions().setFinishActivityTransition(this)
}
}
UserRepository
Usage of ioScope from BaseActivity.
#Throws(LoginException::class)
suspend fun loginAsync(loginData: LoginData, context: Context): Deferred<User> {
return ioScope.async {
suspendCoroutine<User> { continuation ->
val jsonObjectRequest = HttpClient.createJsonObjectRequest(
"/users/me2",
loginData.toJsonString(),
Response.Listener {
val httpResponse : HttpResponse<User> = it.toString().jsonToObject()
continuation.resume(httpResponse.response)
},
Response.ErrorListener {
continuation.resumeWithException(LoginException(it))
}
)
HttpClient.getInstance(context).addToRequestQueue(jsonObjectRequest)
}
}
}
LoginActivity
private suspend fun performLogin() {
val loginData = LoginData(login_username_text_input.value.toString(), login_password_text_input.value.toString())
val userDeferred = UserServerRepository(ioScope).loginAsync(loginData,this#LoginActivity);
try {
val result = userDeferred.await()
login_username_text_input.value = result.company
//HomeActivity.startActivity(this#LoginActivity)
//finish()
}catch (loginException: LoginException){
login_username_text_input.value = loginException.message
}
}
LoginActivity button setup
loginButton.main_button.setAsyncSafeOnClickListener(uiScope, suspend {
performLogin()
})
setAsyncSafeOnClickListener implemetation
fun View.setAsyncSafeOnClickListener(uiScope: CoroutineScope, action: suspend () -> Unit) {
val safeClickListener = SafeClickListener {
uiScope.launch {
isEnabled = false
action()
isEnabled = true
}
}
setOnClickListener(safeClickListener)
}
Short answer: you have to use SupervisorJob() instead of Job() if you want to have robust scopes.
Long answer: here is great article about how error handling in coroutine scopes works https://proandroiddev.com/kotlin-coroutine-job-hierarchy-finish-cancel-and-fail-2d3d42a768a9

Exepting member declaration [duplicate]

I'm trying to finish an activity from another (android) with kotlin. I know the wat to do it with java is with the following code (https://stackoverflow.com/a/10379275/7280257)
at the first activity:
BroadcastReceiver broadcast_reciever = new BroadcastReceiver() {
#Override
public void onReceive(Context arg0, Intent intent) {
String action = intent.getAction();
if (action.equals("finish_activity")) {
finish();
// DO WHATEVER YOU WANT.
}
}
};
registerReceiver(broadcast_reciever, new IntentFilter("finish_activity"));
On the other activity:
Intent intent = new Intent("finish_activity");
sendBroadcast(intent);
For some reason converting the java activity to kotlin doesn't give a valid output, if someone could give me the correct syntax to do it properly with kotlin I will appreciate it
kotlin output (first activity) [OK]:
val broadcast_reciever = object : BroadcastReceiver() {
override fun onReceive(arg0: Context, intent: Intent) {
val action = intent.action
if (action == "finish_activity") {
finish()
// DO WHATEVER YOU WANT.
}
}
}
registerReceiver(broadcast_reciever, IntentFilter("finish_activity"))
kotlin output (2nd activity) [OK]
val intent = Intent("finish_activity")
sendBroadcast(intent)
ERROR: http://i.imgur.com/qaQ2YHv.png
FIX: THE CODE SHOWN IS RIGHT, YOU JUST NEED TO PLACE IT INSIDE THE onCreate FUNCTION
Simple code to finish a particular activity from another:
class SplashActivity : AppCompatActivity(), NavigationListner {
class MyClass{
companion object{
var activity: Activity? = null
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
MyClass.activity = this#SplashActivity
}
override fun navigateFromScreen() {
val intent = Intent(this,LoginActivity::class.java)
startActivity(intent)
}
}
Now call SplashActivity.MyClass.activity?.finish() from another activity to finish above activity.
The error Expecting member declaration is there because you wrote a statement (the function call) inside a class. In that scope, declarations (functions, inner classes) are expected.
You have to place your statements inside functions (and then call those from somewhere) in order for them to be executed.

Wait for service to be bound using coroutines

So I have a method that binds to the service.
fun bindService() {
val intent = Intent(this, BluetoothService::class.java)
bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE)
}
Inside onCreate method I use this code:
bindService()
launch {
delay(500L)
service = serviceConnection.serviceBinder?.getService() as BluetoothService
}
Is there more elegant way to wait for the service to be bound than using delay()?
I wrote this just now, and haven't tried it, but hopefully something like it could work. The magic is in suspendCoroutine, which pauses the current coroutine and then gives you a continuation thingy you can use to resume it later. In our case we resume it when the onServiceConnected is called.
// helper class which holds data
class BoundService(
private val context: Context,
val name: ComponentName?,
val service: IBinder?,
val conn: ServiceConnection) {
fun unbind() {
context.unbindService(conn)
}
}
// call within a coroutine to bind service, waiting for onServiceConnected
// before the coroutine resumes
suspend fun bindServiceAndWait(context: Context, intent: Intent, flags: Int) = suspendCoroutine<BoundService> { continuation ->
val conn = object: ServiceConnection {
override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
continuation.resume(BoundService(context, name, service, this))
}
override fun onServiceDisconnected(name: ComponentName?) {
// ignore, not much we can do
}
}
context.bindService(intent, conn, flags)
}
// just an example
suspend fun exampleUsage() {
val bs = bindServiceAndWait(context, intent, Context.BIND_AUTO_CREATE)
try {
// ...do something with bs.service...
} finally {
bs.unbind()
}
}