#1771 Crash Report 3.2: java.lang.IllegalArgumentException CompatFindActionModeCallback.findAll - don't use null and correct searchActivity view states

This commit is contained in:
Sean Mac Gillicuddy 2020-02-14 14:22:53 +00:00
parent fe5a94fe80
commit 80f3294782
6 changed files with 30 additions and 42 deletions

View File

@ -45,9 +45,6 @@
<option name="IMPORT_NESTED_CLASSES" value="true" /> <option name="IMPORT_NESTED_CLASSES" value="true" />
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" /> <option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings> </JetCodeStyleSettings>
<MarkdownNavigatorCodeStyleSettings>
<option name="RIGHT_MARGIN" value="72" />
</MarkdownNavigatorCodeStyleSettings>
<XML> <XML>
<option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" /> <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" />
</XML> </XML>

View File

@ -132,12 +132,12 @@ public class CompatFindActionModeCallback
throw new AssertionError("No WebView for CompatFindActionModeCallback::findAll"); throw new AssertionError("No WebView for CompatFindActionModeCallback::findAll");
} }
CharSequence find = editText.getText(); CharSequence find = editText.getText();
if (find.length() == 0) { if (find == null || find.length() == 0) {
webView.clearMatches(); webView.clearMatches();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
webView.findAllAsync(null); webView.findAllAsync("");
} else { } else {
webView.findAll(null); webView.findAll("");
} }
} else { } else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
@ -169,9 +169,7 @@ public class CompatFindActionModeCallback
editText.requestFocus(); editText.requestFocus();
//show the keyboard //show the keyboard
input.showSoftInput(editText, 0); input.showSoftInput(editText, 0);
}, 100); }, 100);
} }
@Override @Override

View File

@ -52,8 +52,7 @@ import org.kiwix.kiwixmobile.core.search.viewmodel.Action.OnItemClick
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.OnItemLongClick import org.kiwix.kiwixmobile.core.search.viewmodel.Action.OnItemLongClick
import org.kiwix.kiwixmobile.core.search.viewmodel.SearchViewModel import org.kiwix.kiwixmobile.core.search.viewmodel.SearchViewModel
import org.kiwix.kiwixmobile.core.search.viewmodel.State import org.kiwix.kiwixmobile.core.search.viewmodel.State
import org.kiwix.kiwixmobile.core.search.viewmodel.State.Empty import org.kiwix.kiwixmobile.core.search.viewmodel.State.NoResults
import org.kiwix.kiwixmobile.core.search.viewmodel.State.Initialising
import org.kiwix.kiwixmobile.core.search.viewmodel.State.Results import org.kiwix.kiwixmobile.core.search.viewmodel.State.Results
import org.kiwix.kiwixmobile.core.utils.SimpleTextListener import org.kiwix.kiwixmobile.core.utils.SimpleTextListener
import javax.inject.Inject import javax.inject.Inject
@ -136,15 +135,19 @@ class SearchActivity : BaseActivity() {
is Results -> { is Results -> {
searchViewAnimator.setDistinctDisplayedChild(0) searchViewAnimator.setDistinctDisplayedChild(0)
searchAdapter.items = state.values searchAdapter.items = state.values
searchView.setQuery(state.searchString, false) render(state.searchString)
searchInTextMenuItem.isVisible = state.searchString.isNotBlank()
} }
Empty -> searchViewAnimator.setDistinctDisplayedChild(1) is NoResults -> {
Initialising -> { searchViewAnimator.setDistinctDisplayedChild(1)
// do nothing render(state.searchString)
} }
} }
private fun render(searchString: String) {
searchView.setQuery(searchString, false)
searchInTextMenuItem.isEnabled = searchString.isNotBlank()
}
private fun onItemClick(it: SearchListItem) { private fun onItemClick(it: SearchListItem) {
searchViewModel.actions.offer(OnItemClick(it)) searchViewModel.actions.offer(OnItemClick(it))
} }

View File

@ -42,8 +42,7 @@ import org.kiwix.kiwixmobile.core.search.viewmodel.Action.OnItemClick
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.OnItemLongClick import org.kiwix.kiwixmobile.core.search.viewmodel.Action.OnItemLongClick
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ReceivedPromptForSpeechInput import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ReceivedPromptForSpeechInput
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.StartSpeechInputFailed import org.kiwix.kiwixmobile.core.search.viewmodel.Action.StartSpeechInputFailed
import org.kiwix.kiwixmobile.core.search.viewmodel.State.Empty import org.kiwix.kiwixmobile.core.search.viewmodel.State.NoResults
import org.kiwix.kiwixmobile.core.search.viewmodel.State.Initialising
import org.kiwix.kiwixmobile.core.search.viewmodel.State.Results import org.kiwix.kiwixmobile.core.search.viewmodel.State.Results
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.DeleteRecentSearch import org.kiwix.kiwixmobile.core.search.viewmodel.effects.DeleteRecentSearch
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.Finish import org.kiwix.kiwixmobile.core.search.viewmodel.effects.Finish
@ -64,7 +63,7 @@ class SearchViewModel @Inject constructor(
private val searchResultGenerator: SearchResultGenerator private val searchResultGenerator: SearchResultGenerator
) : ViewModel() { ) : ViewModel() {
val state = MutableLiveData<State>().apply { value = Initialising } val state = MutableLiveData<State>().apply { value = NoResults("") }
val effects = PublishProcessor.create<SideEffect<*>>() val effects = PublishProcessor.create<SideEffect<*>>()
val actions = PublishProcessor.create<Action>() val actions = PublishProcessor.create<Action>()
private val filter = BehaviorProcessor.createDefault("") private val filter = BehaviorProcessor.createDefault("")
@ -108,10 +107,7 @@ class SearchViewModel @Inject constructor(
} }
private fun searchPreviousScreenWhenStateIsValid(): Any = private fun searchPreviousScreenWhenStateIsValid(): Any =
when (val currentState = state.value) { effects.offer(SearchInPreviousScreen(state.value!!.searchString))
is Results -> effects.offer(SearchInPreviousScreen(currentState.searchString))
else -> Unit
}
private fun showDeleteDialog(longClick: OnItemLongClick) { private fun showDeleteDialog(longClick: OnItemLongClick) {
effects.offer(ShowDeleteSearchDialog(longClick.searchListItem, actions)) effects.offer(ShowDeleteSearchDialog(longClick.searchListItem, actions))
@ -144,7 +140,7 @@ class SearchViewModel @Inject constructor(
Results(searchString, zimSearchResults) Results(searchString, zimSearchResults)
searchString.isEmpty() && recentSearchResults.isNotEmpty() -> searchString.isEmpty() && recentSearchResults.isNotEmpty() ->
Results(searchString, recentSearchResults) Results(searchString, recentSearchResults)
else -> Empty else -> NoResults(searchString)
} }
private fun searchResultsFromZimReader() = filter private fun searchResultsFromZimReader() = filter

View File

@ -21,7 +21,8 @@ package org.kiwix.kiwixmobile.core.search.viewmodel
import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem import org.kiwix.kiwixmobile.core.search.adapter.SearchListItem
sealed class State { sealed class State {
data class Results(val searchString: String, val values: List<SearchListItem>) : State() abstract val searchString: String
object Empty : State()
object Initialising : State() data class Results(override val searchString: String, val values: List<SearchListItem>) : State()
data class NoResults(override val searchString: String) : State()
} }

View File

@ -47,8 +47,7 @@ import org.kiwix.kiwixmobile.core.search.viewmodel.Action.OnItemClick
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.OnItemLongClick import org.kiwix.kiwixmobile.core.search.viewmodel.Action.OnItemLongClick
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ReceivedPromptForSpeechInput import org.kiwix.kiwixmobile.core.search.viewmodel.Action.ReceivedPromptForSpeechInput
import org.kiwix.kiwixmobile.core.search.viewmodel.Action.StartSpeechInputFailed import org.kiwix.kiwixmobile.core.search.viewmodel.Action.StartSpeechInputFailed
import org.kiwix.kiwixmobile.core.search.viewmodel.State.Empty import org.kiwix.kiwixmobile.core.search.viewmodel.State.NoResults
import org.kiwix.kiwixmobile.core.search.viewmodel.State.Initialising
import org.kiwix.kiwixmobile.core.search.viewmodel.State.Results import org.kiwix.kiwixmobile.core.search.viewmodel.State.Results
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.DeleteRecentSearch import org.kiwix.kiwixmobile.core.search.viewmodel.effects.DeleteRecentSearch
import org.kiwix.kiwixmobile.core.search.viewmodel.effects.Finish import org.kiwix.kiwixmobile.core.search.viewmodel.effects.Finish
@ -99,7 +98,7 @@ internal class SearchViewModelTest {
inner class StateTests { inner class StateTests {
@Test @Test
fun `initial state is Initialising`() { fun `initial state is Initialising`() {
viewModel.state.test().assertValue(Initialising) viewModel.state.test().assertValue(NoResults(""))
} }
@Test @Test
@ -115,13 +114,13 @@ internal class SearchViewModelTest {
} }
@Test @Test
fun `non empty search string with no search results is Empty`() { fun `non empty search string with no search results is NoResults`() {
emissionOf( emissionOf(
searchTerm = "a", searchTerm = "a",
searchResults = emptyList(), searchResults = emptyList(),
databaseResults = listOf(RecentSearchListItem("")) databaseResults = listOf(RecentSearchListItem(""))
) )
resultsIn(Empty) resultsIn(NoResults("a"))
} }
@Test @Test
@ -136,13 +135,13 @@ internal class SearchViewModelTest {
} }
@Test @Test
fun `empty search string with no database results is Empty`() { fun `empty search string with no database results is NoResults`() {
emissionOf( emissionOf(
searchTerm = "", searchTerm = "",
searchResults = listOf(ZimSearchResultListItem("")), searchResults = listOf(ZimSearchResultListItem("")),
databaseResults = emptyList() databaseResults = emptyList()
) )
resultsIn(Empty) resultsIn(NoResults(""))
} }
@Test @Test
@ -157,7 +156,7 @@ internal class SearchViewModelTest {
viewModel.actions.offer(Filter(searchString)) viewModel.actions.offer(Filter(searchString))
viewModel.state.test() viewModel.state.test()
.also { testScheduler.advanceTimeBy(100, MILLISECONDS) } .also { testScheduler.advanceTimeBy(100, MILLISECONDS) }
.assertValueHistory(Initialising, Results(searchString, listOf(item))) .assertValueHistory(NoResults(""), Results(searchString, listOf(item)))
} }
@Test @Test
@ -175,7 +174,7 @@ internal class SearchViewModelTest {
) )
viewModel.state.test() viewModel.state.test()
.also { testScheduler.advanceTimeBy(100, MILLISECONDS) } .also { testScheduler.advanceTimeBy(100, MILLISECONDS) }
.assertValueHistory(Initialising, Results("b", listOf(item))) .assertValueHistory(NoResults(""), Results("b", listOf(item)))
} }
} }
@ -205,11 +204,6 @@ internal class SearchViewModelTest {
) )
} }
@Test
fun `ClickedSearchInText in invalid state does nothing`() {
actionResultsInEffects(ClickedSearchInText)
}
@Test @Test
fun `ClickedSearchInText in Result state offers SearchInPreviousScreen`() { fun `ClickedSearchInText in Result state offers SearchInPreviousScreen`() {
val item = ZimSearchResultListItem("") val item = ZimSearchResultListItem("")
@ -288,8 +282,7 @@ internal class SearchViewModelTest {
searchResults: List<ZimSearchResultListItem>, searchResults: List<ZimSearchResultListItem>,
databaseResults: List<RecentSearchListItem> databaseResults: List<RecentSearchListItem>
) { ) {
val item = every { searchResultGenerator.generateSearchResults(searchTerm) } returns searchResults
every { searchResultGenerator.generateSearchResults(searchTerm) } returns searchResults
viewModel.actions.offer(Filter(searchTerm)) viewModel.actions.offer(Filter(searchTerm))
recentsFromDb.offer(databaseResults) recentsFromDb.offer(databaseResults)
} }