fix: Prevent crash when showing account chooser (#1117)
Chooser dialog could start before any accounts have loaded. Fix by collecting the account flow and waiting for the first emission (convert the flow to shared instead of state so there's no initial empty list). Guard against the potential for a similar issue when fetching notifications. Order the list of accounts with active account first so that code that skips it by ignoring the first item works correctly.
This commit is contained in:
parent
5c048311b2
commit
632282d0e2
|
@ -40,6 +40,8 @@ import kotlin.collections.set
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.take
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -57,11 +59,11 @@ class NotificationFetcher @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
@ApplicationContext private val context: Context,
|
||||||
) {
|
) {
|
||||||
suspend fun fetchAndShow(pachliAccountId: Long) {
|
suspend fun fetchAndShow(pachliAccountId: Long) {
|
||||||
Timber.d("NotificationFetcher.fetchAndShow() started")
|
Timber.d("NotificationFetcher.fetchAndShow(%d) started", pachliAccountId)
|
||||||
|
|
||||||
val accounts = buildList {
|
val accounts = buildList {
|
||||||
if (pachliAccountId == NotificationWorker.ALL_ACCOUNTS) {
|
if (pachliAccountId == NotificationWorker.ALL_ACCOUNTS) {
|
||||||
addAll(accountManager.accountsOrderedByActive)
|
addAll(accountManager.accountsOrderedByActiveFlow.take(1).first())
|
||||||
} else {
|
} else {
|
||||||
accountManager.getAccountById(pachliAccountId)?.let { add(it) }
|
accountManager.getAccountById(pachliAccountId)?.let { add(it) }
|
||||||
}
|
}
|
||||||
|
@ -69,7 +71,7 @@ class NotificationFetcher @Inject constructor(
|
||||||
|
|
||||||
for (account in accounts) {
|
for (account in accounts) {
|
||||||
Timber.d(
|
Timber.d(
|
||||||
"Checking %s$, notificationsEnabled = %s",
|
"Checking %s, notificationsEnabled = %s",
|
||||||
account.fullName,
|
account.fullName,
|
||||||
account.notificationsEnabled,
|
account.notificationsEnabled,
|
||||||
)
|
)
|
||||||
|
|
|
@ -59,6 +59,8 @@ import dagger.hilt.android.EntryPointAccessors.fromApplication
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.properties.Delegates
|
import kotlin.properties.Delegates
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.take
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
@ -229,17 +231,26 @@ abstract class BaseActivity : AppCompatActivity(), MenuProvider {
|
||||||
bar.show()
|
bar.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a dialog allowing the user to choose from the available accounts.
|
||||||
|
*
|
||||||
|
* @param dialogTitle
|
||||||
|
* @parma showActiveAccount True if the active account should be included in
|
||||||
|
* the list of accounts.
|
||||||
|
* @parma listener
|
||||||
|
*/
|
||||||
fun showAccountChooserDialog(
|
fun showAccountChooserDialog(
|
||||||
dialogTitle: CharSequence?,
|
dialogTitle: CharSequence?,
|
||||||
showActiveAccount: Boolean,
|
showActiveAccount: Boolean,
|
||||||
listener: AccountSelectionListener,
|
listener: AccountSelectionListener,
|
||||||
) {
|
) {
|
||||||
val accounts = accountManager.accountsOrderedByActive.toMutableList()
|
lifecycleScope.launch {
|
||||||
|
val accounts = accountManager.accountsOrderedByActiveFlow.take(1).first().toMutableList()
|
||||||
val activeAccount = accounts.first()
|
val activeAccount = accounts.first()
|
||||||
when (accounts.size) {
|
when (accounts.size) {
|
||||||
1 -> {
|
1 -> {
|
||||||
listener.onAccountSelected(activeAccount)
|
listener.onAccountSelected(activeAccount)
|
||||||
return
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
2 -> {
|
2 -> {
|
||||||
|
@ -247,7 +258,7 @@ abstract class BaseActivity : AppCompatActivity(), MenuProvider {
|
||||||
for (account in accounts) {
|
for (account in accounts) {
|
||||||
if (activeAccount !== account) {
|
if (activeAccount !== account) {
|
||||||
listener.onAccountSelected(account)
|
listener.onAccountSelected(account)
|
||||||
return
|
return@launch
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -257,20 +268,19 @@ abstract class BaseActivity : AppCompatActivity(), MenuProvider {
|
||||||
accounts.remove(activeAccount)
|
accounts.remove(activeAccount)
|
||||||
}
|
}
|
||||||
val adapter = AccountSelectionAdapter(
|
val adapter = AccountSelectionAdapter(
|
||||||
this,
|
this@BaseActivity,
|
||||||
sharedPreferencesRepository.animateAvatars,
|
sharedPreferencesRepository.animateAvatars,
|
||||||
sharedPreferencesRepository.animateEmojis,
|
sharedPreferencesRepository.animateEmojis,
|
||||||
)
|
)
|
||||||
adapter.addAll(accounts)
|
adapter.addAll(accounts)
|
||||||
AlertDialog.Builder(this)
|
AlertDialog.Builder(this@BaseActivity)
|
||||||
.setTitle(dialogTitle)
|
.setTitle(dialogTitle)
|
||||||
.setAdapter(adapter) { _: DialogInterface?, index: Int ->
|
.setAdapter(adapter) { _: DialogInterface?, index: Int ->
|
||||||
listener.onAccountSelected(
|
listener.onAccountSelected(accounts[index])
|
||||||
accounts[index],
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val openAsText: String?
|
val openAsText: String?
|
||||||
get() {
|
get() {
|
||||||
|
|
|
@ -68,6 +68,7 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.shareIn
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
@ -190,11 +191,11 @@ class AccountManager @Inject constructor(
|
||||||
val accounts: List<AccountEntity>
|
val accounts: List<AccountEntity>
|
||||||
get() = accountsFlow.value
|
get() = accountsFlow.value
|
||||||
|
|
||||||
private val accountsOrderedByActiveFlow = accountDao.getAccountsOrderedByActive()
|
val accountsOrderedByActiveFlow = accountDao.getAccountsOrderedByActive()
|
||||||
.stateIn(externalScope, SharingStarted.Eagerly, emptyList())
|
.shareIn(externalScope, SharingStarted.Eagerly, replay = 1)
|
||||||
|
|
||||||
val accountsOrderedByActive: List<AccountEntity>
|
val accountsOrderedByActive: List<AccountEntity>
|
||||||
get() = accountsOrderedByActiveFlow.value
|
get() = accountsOrderedByActiveFlow.replayCache.first()
|
||||||
|
|
||||||
@Deprecated("Caller should use getPachliAccountFlow with a specific account ID")
|
@Deprecated("Caller should use getPachliAccountFlow with a specific account ID")
|
||||||
val activePachliAccountFlow = accountDao.getActivePachliAccountFlow()
|
val activePachliAccountFlow = accountDao.getActivePachliAccountFlow()
|
||||||
|
|
|
@ -86,7 +86,7 @@ interface AccountDao {
|
||||||
"""
|
"""
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM AccountEntity
|
FROM AccountEntity
|
||||||
ORDER BY isActive, id ASC
|
ORDER BY isActive DESC, id ASC
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
fun getAccountsOrderedByActive(): Flow<List<AccountEntity>>
|
fun getAccountsOrderedByActive(): Flow<List<AccountEntity>>
|
||||||
|
|
Loading…
Reference in New Issue