From 5a861436ba6773bc0d8a20135fd613a1a08fdb4a Mon Sep 17 00:00:00 2001 From: Nik Clayton Date: Mon, 2 Dec 2024 17:08:58 +0100 Subject: [PATCH] fix: Prevent race / crash when updating main drawer items (#1150) Previous code could crash if the main drawer items weren't created before setting the optional items (scheduled statuses, lists, etc). Protect the drawer item manipulation with a mutex to prevent this. --- app/src/main/java/app/pachli/MainActivity.kt | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/app/pachli/MainActivity.kt b/app/src/main/java/app/pachli/MainActivity.kt index 98dfbc890..de190b0a2 100644 --- a/app/src/main/java/app/pachli/MainActivity.kt +++ b/app/src/main/java/app/pachli/MainActivity.kt @@ -181,6 +181,8 @@ import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import timber.log.Timber @AndroidEntryPoint @@ -254,6 +256,9 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider { private var pachliAccountId by Delegates.notNull() + /** Mutex to protect modifications to the drawer's items. */ + private val drawerMutex = Mutex() + @SuppressLint("RestrictedApi") override fun onCreate(savedInstanceState: Bundle?) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { @@ -761,7 +766,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider { * (because the top toolbar is hidden), false if any existing search item should be * removed. */ - private fun bindMainDrawerSearch(context: Context, pachliAccountId: Long, showSearchItem: Boolean) { + private suspend fun bindMainDrawerSearch(context: Context, pachliAccountId: Long, showSearchItem: Boolean) = drawerMutex.withLock { val searchItemPosition = binding.mainDrawer.getPosition(DRAWER_ITEM_SEARCH) val showing = searchItemPosition != -1 @@ -802,7 +807,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider { * @param showSchedulePosts True if a "Scheduled posts" menu item should be added * to the list, false if any existing item should be removed. */ - private fun bindMainDrawerScheduledPosts(context: Context, pachliAccountId: Long, showSchedulePosts: Boolean) { + private suspend fun bindMainDrawerScheduledPosts(context: Context, pachliAccountId: Long, showSchedulePosts: Boolean) = drawerMutex.withLock { val existingPosition = binding.mainDrawer.getPosition(DRAWER_ITEM_SCHEDULED_POSTS) val showing = existingPosition != -1 @@ -834,7 +839,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider { } /** Binds [lists] to the "Lists" section in the main drawer. */ - private fun bindMainDrawerLists(pachliAccountId: Long, lists: List) { + private suspend fun bindMainDrawerLists(pachliAccountId: Long, lists: List) = drawerMutex.withLock { binding.mainDrawer.removeItems(*listDrawerItems.toTypedArray()) listDrawerItems.clear() @@ -868,7 +873,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider { * * See [bindMainDrawerLists] and [bindMainDrawerSearch]. */ - private fun bindMainDrawerItems(pachliAccount: PachliAccount, savedInstanceState: Bundle?) { + private suspend fun bindMainDrawerItems(pachliAccount: PachliAccount, savedInstanceState: Bundle?) = drawerMutex.withLock { val pachliAccountId = pachliAccount.id binding.mainDrawer.apply { @@ -1398,7 +1403,7 @@ class MainActivity : BottomSheetActivity(), ActionButtonActivity, MenuProvider { * * Shows/clears a badge showing the number of unread announcements. */ - private fun bindMainDrawerAnnouncements(announcements: List) { + private suspend fun bindMainDrawerAnnouncements(announcements: List) = drawerMutex.withLock { val unread = announcements.count { !it.read } binding.mainDrawer.updateBadge(DRAWER_ITEM_ANNOUNCEMENTS, StringHolder(if (unread <= 0) null else unread.toString())) }