chore: better handling of coroutine dispatchers.
All checks were successful
Check PR code / BuildAndTestAndCoverage (pull_request) Successful in 35m16s

This commit is contained in:
Amine Bouabdallaoui 2025-03-25 00:13:30 +01:00
parent 7c65a63315
commit bb901b64fa
10 changed files with 170 additions and 133 deletions

View File

@ -77,7 +77,6 @@ class `1-LoginActivityTest` : WithANRException() {
@Test @Test
fun `4-connectError`() { fun `4-connectError`() {
performLogin("http://10.0.2.2:8889") performLogin("http://10.0.2.2:8889")
onView(withId(R.id.urlView)).perform(click())
onView(withId(R.id.urlView)).check(matches(withError(R.string.wrong_infos))) onView(withId(R.id.urlView)).check(matches(withError(R.string.wrong_infos)))
} }

View File

@ -104,7 +104,7 @@ class HomeActivity :
if (appSettingsService.isItemCachingEnabled()) { if (appSettingsService.isItemCachingEnabled()) {
CountingIdlingResourceSingleton.increment() CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.IO).launch {
repository.tryToCacheItemsAndGetNewOnes() repository.tryToCacheItemsAndGetNewOnes()
CountingIdlingResourceSingleton.decrement() CountingIdlingResourceSingleton.decrement()
} }
@ -120,12 +120,8 @@ class HomeActivity :
binding.swipeRefreshLayout.setOnRefreshListener { binding.swipeRefreshLayout.setOnRefreshListener {
repository.offlineOverride = false repository.offlineOverride = false
lastFetchDone = false lastFetchDone = false
CountingIdlingResourceSingleton.increment() getElementsAccordingToTab()
CoroutineScope(Dispatchers.Main).launch { binding.swipeRefreshLayout.isRefreshing = false
getElementsAccordingToTab()
binding.swipeRefreshLayout.isRefreshing = false
CountingIdlingResourceSingleton.decrement()
}
} }
val swipeDirs = val swipeDirs =
@ -289,7 +285,7 @@ class HomeActivity :
handleRecurringTask() handleRecurringTask()
CountingIdlingResourceSingleton.increment() CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.IO).launch {
repository.handleDBActions() repository.handleDBActions()
CountingIdlingResourceSingleton.decrement() CountingIdlingResourceSingleton.decrement()
} }
@ -463,8 +459,8 @@ class HomeActivity :
itemType: ItemType, itemType: ItemType,
) { ) {
CountingIdlingResourceSingleton.increment() CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.Main).launch { binding.swipeRefreshLayout.isRefreshing = true
binding.swipeRefreshLayout.isRefreshing = true CoroutineScope(Dispatchers.IO).launch {
repository.displayedItems = itemType repository.displayedItems = itemType
items = items =
if (appendResults) { if (appendResults) {
@ -472,8 +468,12 @@ class HomeActivity :
} else { } else {
repository.getNewerItems() repository.getNewerItems()
} }
binding.swipeRefreshLayout.isRefreshing = false CountingIdlingResourceSingleton.increment()
handleListResult() launch(Dispatchers.Main) {
binding.swipeRefreshLayout.isRefreshing = false
handleListResult()
CountingIdlingResourceSingleton.decrement()
}
CountingIdlingResourceSingleton.decrement() CountingIdlingResourceSingleton.decrement()
} }
} }
@ -613,22 +613,26 @@ class HomeActivity :
needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message) { needsConfirmation(R.string.menu_home_refresh, R.string.refresh_dialog_message) {
Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.refresh_in_progress, Toast.LENGTH_SHORT).show()
CountingIdlingResourceSingleton.increment() CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.IO).launch {
val updatedRemote = repository.updateRemote() val updatedRemote = repository.updateRemote()
if (updatedRemote) { CountingIdlingResourceSingleton.increment()
Toast launch(Dispatchers.Main) {
.makeText( if (updatedRemote) {
this@HomeActivity, Toast
R.string.refresh_success_response, .makeText(
Toast.LENGTH_LONG, this@HomeActivity,
).show() R.string.refresh_success_response,
} else { Toast.LENGTH_LONG,
Toast ).show()
.makeText( } else {
this@HomeActivity, Toast
R.string.refresh_failer_message, .makeText(
Toast.LENGTH_SHORT, this@HomeActivity,
).show() R.string.refresh_failer_message,
Toast.LENGTH_SHORT,
).show()
}
CountingIdlingResourceSingleton.decrement()
} }
CountingIdlingResourceSingleton.decrement() CountingIdlingResourceSingleton.decrement()
} }
@ -641,28 +645,31 @@ class HomeActivity :
needsConfirmation(R.string.readAll, R.string.markall_dialog_message) { needsConfirmation(R.string.readAll, R.string.markall_dialog_message) {
binding.swipeRefreshLayout.isRefreshing = true binding.swipeRefreshLayout.isRefreshing = true
CountingIdlingResourceSingleton.increment() CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.IO).launch {
val success = repository.markAllAsRead(items) val success = repository.markAllAsRead(items)
if (success) { CountingIdlingResourceSingleton.increment()
Toast launch(Dispatchers.Main) {
.makeText( if (success) {
this@HomeActivity, Toast
R.string.all_posts_read, .makeText(
Toast.LENGTH_SHORT, this@HomeActivity,
).show() R.string.all_posts_read,
tabNewBadge.removeBadge() Toast.LENGTH_SHORT,
).show()
tabNewBadge.removeBadge()
getElementsAccordingToTab() getElementsAccordingToTab()
} else { } else {
Toast Toast
.makeText( .makeText(
this@HomeActivity, this@HomeActivity,
R.string.all_posts_not_read, R.string.all_posts_not_read,
Toast.LENGTH_SHORT, Toast.LENGTH_SHORT,
).show() ).show()
}
binding.swipeRefreshLayout.isRefreshing = false
CountingIdlingResourceSingleton.decrement()
} }
handleListResult()
binding.swipeRefreshLayout.isRefreshing = false
CountingIdlingResourceSingleton.decrement() CountingIdlingResourceSingleton.decrement()
} }
} }

View File

@ -108,7 +108,7 @@ class LoginActivity :
private fun goToMain() { private fun goToMain() {
CountingIdlingResourceSingleton.increment() CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.IO).launch {
repository.updateApiInformation() repository.updateApiInformation()
ACRA.errorReporter.putCustomData( ACRA.errorReporter.putCustomData(
"SELFOSS_API_VERSION", "SELFOSS_API_VERSION",
@ -127,6 +127,9 @@ class LoginActivity :
binding.urlView.error = getString(R.string.wrong_infos) binding.urlView.error = getString(R.string.wrong_infos)
binding.loginView.error = getString(R.string.wrong_infos) binding.loginView.error = getString(R.string.wrong_infos)
binding.passwordView.error = getString(R.string.wrong_infos) binding.passwordView.error = getString(R.string.wrong_infos)
binding.urlView.requestFocus()
showProgress(false)
} }
private fun attemptLogin() { private fun attemptLogin() {
@ -160,34 +163,41 @@ class LoginActivity :
repository.refreshLoginInformation(url, login, password) repository.refreshLoginInformation(url, login, password)
CountingIdlingResourceSingleton.increment() CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.IO).launch {
try { try {
repository.updateApiInformation() repository.updateApiInformation()
val result = repository.login()
CountingIdlingResourceSingleton.increment()
launch(Dispatchers.Main) {
if (result) {
val errorFetching = repository.checkIfFetchFails()
if (!errorFetching) {
goToMain()
} else {
preferenceError()
}
} else {
preferenceError()
}
CountingIdlingResourceSingleton.decrement()
}
} catch (e: Exception) { } catch (e: Exception) {
if (e.message?.startsWith("No transformation found") == true) { CountingIdlingResourceSingleton.increment()
Toast launch(Dispatchers.Main) {
.makeText( if (e.message?.startsWith("No transformation found") == true) {
applicationContext, Toast
R.string.application_selfoss_only, .makeText(
Toast.LENGTH_LONG, applicationContext,
).show() R.string.application_selfoss_only,
preferenceError() Toast.LENGTH_LONG,
showProgress(false) ).show()
preferenceError()
}
CountingIdlingResourceSingleton.decrement()
} }
} finally {
CountingIdlingResourceSingleton.decrement()
} }
val result = repository.login()
if (result) {
val errorFetching = repository.checkIfFetchFails()
if (!errorFetching) {
goToMain()
} else {
preferenceError()
}
} else {
preferenceError()
}
showProgress(false)
CountingIdlingResourceSingleton.decrement()
} }
} }

View File

@ -73,7 +73,7 @@ class MyApp :
), ),
) )
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Default).launch {
connectivityService.networkAvailableProvider.collect { networkAvailable -> connectivityService.networkAvailableProvider.collect { networkAvailable ->
val toastMessage = val toastMessage =
if (networkAvailable) { if (networkAvailable) {

View File

@ -27,7 +27,7 @@ class ReaderActivity :
DIAware { DIAware {
private var currentItem: Int = 0 private var currentItem: Int = 0
private lateinit var toolbarMenu: Menu private var toolbarMenu: Menu? = null
private lateinit var binding: ActivityReaderBinding private lateinit var binding: ActivityReaderBinding
@ -90,8 +90,10 @@ class ReaderActivity :
} }
private fun updateStarIcon() { private fun updateStarIcon() {
val isStarred = allItems.getOrNull(currentItem)?.starred ?: false if (toolbarMenu != null) {
toolbarMenu.findItem(R.id.star)?.icon?.setTint(if (isStarred) Color.RED else Color.WHITE) val isStarred = allItems.getOrNull(currentItem)?.starred ?: false
toolbarMenu!!.findItem(R.id.star)?.icon?.setTint(if (isStarred) Color.RED else Color.WHITE)
}
} }
override fun onSaveInstanceState(oldInstanceState: Bundle) { override fun onSaveInstanceState(oldInstanceState: Bundle) {
@ -133,8 +135,10 @@ class ReaderActivity :
private fun alignmentMenu() { private fun alignmentMenu() {
val showJustify = appSettingsService.getActiveAllignment() == AppSettingsService.ALIGN_LEFT val showJustify = appSettingsService.getActiveAllignment() == AppSettingsService.ALIGN_LEFT
toolbarMenu.findItem(R.id.align_left).isVisible = !showJustify if (toolbarMenu != null) {
toolbarMenu.findItem(R.id.align_justify).isVisible = showJustify toolbarMenu!!.findItem(R.id.align_left).isVisible = !showJustify
toolbarMenu!!.findItem(R.id.align_justify).isVisible = showJustify
}
} }
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {

View File

@ -58,24 +58,28 @@ class SourcesActivity :
binding.recyclerView.layoutManager = mLayoutManager binding.recyclerView.layoutManager = mLayoutManager
CountingIdlingResourceSingleton.increment() CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.IO).launch {
val response = repository.getSourcesDetails() val response = repository.getSourcesDetails()
if (response.isNotEmpty()) { CountingIdlingResourceSingleton.increment()
items = response launch(Dispatchers.Main) {
val mAdapter = if (response.isNotEmpty()) {
SourcesListAdapter( items = response
this@SourcesActivity, val mAdapter =
items, SourcesListAdapter(
) this@SourcesActivity,
binding.recyclerView.adapter = mAdapter items,
mAdapter.notifyDataSetChanged() )
} else { binding.recyclerView.adapter = mAdapter
Toast mAdapter.notifyDataSetChanged()
.makeText( } else {
this@SourcesActivity, Toast
R.string.cant_get_sources, .makeText(
Toast.LENGTH_SHORT, this@SourcesActivity,
).show() R.string.cant_get_sources,
Toast.LENGTH_SHORT,
).show()
}
CountingIdlingResourceSingleton.decrement()
} }
CountingIdlingResourceSingleton.decrement() CountingIdlingResourceSingleton.decrement()
} }

View File

@ -9,6 +9,7 @@ import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import bou.amine.apps.readerforselfossv2.android.databinding.ActivityUpsertSourceBinding import bou.amine.apps.readerforselfossv2.android.databinding.ActivityUpsertSourceBinding
import bou.amine.apps.readerforselfossv2.android.testing.CountingIdlingResourceSingleton
import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException import bou.amine.apps.readerforselfossv2.model.NetworkUnavailableException
import bou.amine.apps.readerforselfossv2.model.SelfossModel import bou.amine.apps.readerforselfossv2.model.SelfossModel
import bou.amine.apps.readerforselfossv2.repository.Repository import bou.amine.apps.readerforselfossv2.repository.Repository
@ -108,36 +109,42 @@ class UpsertSourceActivity :
binding.progress.visibility = View.GONE binding.progress.visibility = View.GONE
} }
CoroutineScope(Dispatchers.Main).launch { CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.IO).launch {
try { try {
val items = repository.getSpouts() val items = repository.getSpouts()
if (items.isNotEmpty()) { CountingIdlingResourceSingleton.increment()
val itemsStrings = items.map { it.value.name } launch(Dispatchers.Main) {
for ((key, value) in items) { if (items.isNotEmpty()) {
spoutsKV[value.name] = key val itemsStrings = items.map { it.value.name }
for ((key, value) in items) {
spoutsKV[value.name] = key
}
binding.progress.visibility = View.GONE
binding.formContainer.visibility = View.VISIBLE
val spinnerArrayAdapter =
ArrayAdapter(
this@UpsertSourceActivity,
android.R.layout.simple_spinner_item,
itemsStrings,
)
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
binding.spoutsSpinner.adapter = spinnerArrayAdapter
if (existingSource != null) {
initFields(items)
}
} else {
handleSpoutFailure()
} }
CountingIdlingResourceSingleton.decrement()
binding.progress.visibility = View.GONE
binding.formContainer.visibility = View.VISIBLE
val spinnerArrayAdapter =
ArrayAdapter(
this@UpsertSourceActivity,
android.R.layout.simple_spinner_item,
itemsStrings,
)
spinnerArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
binding.spoutsSpinner.adapter = spinnerArrayAdapter
if (existingSource != null) {
initFields(items)
}
} else {
handleSpoutFailure()
} }
} catch (e: NetworkUnavailableException) { } catch (e: NetworkUnavailableException) {
handleSpoutFailure(networkIssue = true) handleSpoutFailure(networkIssue = true)
} }
CountingIdlingResourceSingleton.decrement()
} }
} }
@ -160,7 +167,8 @@ class UpsertSourceActivity :
} }
else -> { else -> {
CoroutineScope(Dispatchers.Main).launch { CountingIdlingResourceSingleton.increment()
CoroutineScope(Dispatchers.IO).launch {
val successfullyAddedSource = val successfullyAddedSource =
if (existingSource != null) { if (existingSource != null) {
repository.updateSource( repository.updateSource(
@ -178,16 +186,21 @@ class UpsertSourceActivity :
binding.tags.text.toString(), binding.tags.text.toString(),
) )
} }
if (successfullyAddedSource) { CountingIdlingResourceSingleton.increment()
finish() launch(Dispatchers.Main) {
} else { if (successfullyAddedSource) {
Toast finish()
.makeText( } else {
this@UpsertSourceActivity, Toast
R.string.cant_create_source, .makeText(
Toast.LENGTH_SHORT, this@UpsertSourceActivity,
).show() R.string.cant_create_source,
Toast.LENGTH_SHORT,
).show()
}
CountingIdlingResourceSingleton.decrement()
} }
CountingIdlingResourceSingleton.decrement()
} }
} }
} }

View File

@ -334,7 +334,7 @@ class Repository(
_badgeUnread.value -= 1 _badgeUnread.value -= 1
} }
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Default).launch {
updateDBItem(item) updateDBItem(item)
} }
} }
@ -345,7 +345,7 @@ class Repository(
_badgeUnread.value += 1 _badgeUnread.value += 1
} }
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Default).launch {
updateDBItem(item) updateDBItem(item)
} }
} }
@ -356,7 +356,7 @@ class Repository(
_badgeStarred.value += 1 _badgeStarred.value += 1
} }
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Default).launch {
updateDBItem(item) updateDBItem(item)
} }
} }
@ -367,7 +367,7 @@ class Repository(
_badgeStarred.value -= 1 _badgeStarred.value -= 1
} }
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Default).launch {
updateDBItem(item) updateDBItem(item)
} }
} }

View File

@ -82,7 +82,7 @@ class SelfossApi(
} }
modifyRequest { modifyRequest {
Napier.i("Will modify", tag = "HttpSend") Napier.i("Will modify", tag = "HttpSend")
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.IO).launch {
Napier.i("Will login", tag = "HttpSend") Napier.i("Will login", tag = "HttpSend")
login() login()
Napier.i("Did login", tag = "HttpSend") Napier.i("Did login", tag = "HttpSend")

View File

@ -16,7 +16,7 @@ class ConnectivityService {
fun start() { fun start() {
connectivity = Connectivity() connectivity = Connectivity()
connectivity.start() connectivity.start()
CoroutineScope(Dispatchers.Main).launch { CoroutineScope(Dispatchers.Default).launch {
connectivity.statusUpdates.collect { status -> connectivity.statusUpdates.collect { status ->
when (status) { when (status) {
is Connectivity.Status.Connected -> { is Connectivity.Status.Connected -> {