Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed the app crashes that occurred when deleting letters in a search. #3592

Merged
merged 3 commits into from
Dec 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ import androidx.recyclerview.widget.RecyclerView
import io.reactivex.android.schedulers.AndroidSchedulers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import org.kiwix.kiwixmobile.core.R
import org.kiwix.kiwixmobile.core.base.BaseActivity
Expand Down Expand Up @@ -86,6 +89,7 @@ class SearchFragment : BaseFragment() {
private var searchAdapter: SearchAdapter? = null
private var isDataLoading = false
private var renderingJob: Job? = null
private val searchMutex = Mutex()

override fun inject(baseActivity: BaseActivity) {
baseActivity.cachedComponent.inject(this)
Expand Down Expand Up @@ -252,22 +256,25 @@ class SearchFragment : BaseFragment() {
)
}

private fun render(state: SearchState) {
renderingJob?.cancel()
isDataLoading = false
searchInTextMenuItem?.isVisible = state.searchOrigin == FromWebView
searchInTextMenuItem?.isEnabled = state.searchTerm.isNotBlank()
fragmentSearchBinding?.searchLoadingIndicator?.isShowing(true)
renderingJob = searchViewModel.viewModelScope.launch(Dispatchers.Main) {
val searchResult = withContext(Dispatchers.IO) {
state.getVisibleResults(0)
}
private suspend fun render(state: SearchState) {
searchMutex.withLock {
// `cancelAndJoin` cancels the previous running job and waits for it to completely cancel.
renderingJob?.cancelAndJoin()
isDataLoading = false
searchInTextMenuItem?.isVisible = state.searchOrigin == FromWebView
searchInTextMenuItem?.isEnabled = state.searchTerm.isNotBlank()
fragmentSearchBinding?.searchLoadingIndicator?.isShowing(true)
renderingJob = searchViewModel.viewModelScope.launch(Dispatchers.Main) {
val searchResult = withContext(Dispatchers.IO) {
state.getVisibleResults(0, renderingJob)
}

fragmentSearchBinding?.searchLoadingIndicator?.isShowing(false)
fragmentSearchBinding?.searchLoadingIndicator?.isShowing(false)

searchResult?.let {
fragmentSearchBinding?.searchNoResults?.isVisible = it.isEmpty()
searchAdapter?.items = it
searchResult?.let {
fragmentSearchBinding?.searchNoResults?.isVisible = it.isEmpty()
searchAdapter?.items = it
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package org.kiwix.kiwixmobile.core.search.viewmodel

import kotlinx.coroutines.Job
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem

data class SearchState(
Expand All @@ -26,18 +27,29 @@
val recentResults: List<SearchListItem.RecentSearchListItem>,
val searchOrigin: SearchOrigin
) {
fun getVisibleResults(startIndex: Int): List<SearchListItem.RecentSearchListItem>? =
@Suppress("NestedBlockDepth")
fun getVisibleResults(
startIndex: Int,
job: Job? = null
): List<SearchListItem.RecentSearchListItem>? =
if (searchTerm.isEmpty()) {
recentResults
} else {
searchResultsWithTerm.suggestionSearch?.let {
val maximumResults = it.estimatedMatches
val safeEndIndex =
if (startIndex + 100 < maximumResults) startIndex + 100 else maximumResults
val searchIterator =
it.getResults(startIndex, safeEndIndex.toInt())
val searchResults = mutableListOf<SearchListItem.RecentSearchListItem>()
if (job?.isActive == false) {
// if the previous job is cancel then do not execute the code
return@getVisibleResults searchResults

Check warning on line 42 in core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchState.kt

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchState.kt#L42

Added line #L42 was not covered by tests
}
val safeEndIndex = startIndex + 100
val searchIterator =
it.getResults(startIndex, safeEndIndex)
while (searchIterator.hasNext()) {
if (job?.isActive == false) {
// check if the previous job is cancel while retrieving the data for previous searched item
// then break the execution of code.
break

Check warning on line 51 in core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchState.kt

View check run for this annotation

Codecov / codecov/patch

core/src/main/java/org/kiwix/kiwixmobile/core/search/viewmodel/SearchState.kt#L51

Added line #L51 was not covered by tests
}
val entry = searchIterator.next()
searchResults.add(SearchListItem.RecentSearchListItem(entry.title, entry.path))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ internal class SearchStateTest {
val suggestionSearchWrapper: SuggestionSearchWrapper = mockk()
val searchIteratorWrapper: SuggestionIteratorWrapper = mockk()
val entryWrapper: SuggestionItemWrapper = mockk()
val estimatedMatches = 1
val estimatedMatches = 100
every { suggestionSearchWrapper.estimatedMatches } returns estimatedMatches.toLong()
// Settings list to hasNext() to ensure it returns true only for the first call.
// Otherwise, if we do not set this, the method will always return true when checking if the iterator has a next value,
Expand Down