Skip to content

Migrated SearchFragment -> Search Composable#4703

Open
atharvyadav22 wants to merge 14 commits intokiwix:mainfrom
atharvyadav22:migrate/search
Open

Migrated SearchFragment -> Search Composable#4703
atharvyadav22 wants to merge 14 commits intokiwix:mainfrom
atharvyadav22:migrate/search

Conversation

@atharvyadav22
Copy link
Copy Markdown
Contributor

@atharvyadav22 atharvyadav22 commented Feb 18, 2026

Fixes: #4616 - Migrated Search Fragment to Pure Composable with basic test functionality.

Fixes: #4662 - Search keyword travel was broke due to Fragment lifecycle error and intent action issue.
It now works Within app and also outside the app

Fixes #4722

WhatsApp.Video.2026-02-18.at.9.09.27.PM.1.mp4

Fixes: #4702 - Voice to Search was broken due to Regression and Fragment changes.

WhatsApp.Video.2026-02-18.at.9.09.27.PM.mp4

Fixes: #4722 - Fixed Keyboard not closing when back button press on search screen.

WhatsApp.Video.2026-02-26.at.9.51.05.PM.mp4

@atharvyadav22
Copy link
Copy Markdown
Contributor Author

@MohitMaliFtechiz @gouri-panda
For this commit please review only the code files and provide feedback for the changes. Later i'll add Test cases.
Sorry for the delay caused as this migration was little big and keeping every logic was difficult.
Also i have deleted my earlier PR as it was bit messy Apologies for it.

@MohitMaliFtechiz MohitMaliFtechiz self-requested a review February 19, 2026 10:36
Copy link
Copy Markdown
Collaborator

@MohitMaliFtechiz MohitMaliFtechiz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this SEARCH text here? It is making the UI very odd. If you are talking about the voice feature of search. It should not be like this. There should be a mic icon instead of this text. It's confusing the user here.

Image Image
  • The second issue is when we open any item in the reader by clicking the searched item. Then again, open the searchScreen, that item is not showing(in the previous code that shows). But, if I searched for anything and removed the query from searchBox then those items show that we have opened in a previous search.

@MohitMaliFtechiz
Copy link
Copy Markdown
Collaborator

@atharvyadav22 Please rebase this PR as well with the review changes.

@atharvyadav22
Copy link
Copy Markdown
Contributor Author

@atharvyadav22 Please rebase this PR as well with the review changes.

I'll also add that test coverage here should i reference this PR? or only work for migration and create new issue for it.

@MohitMaliFtechiz
Copy link
Copy Markdown
Collaborator

@atharvyadav22 One ticket/PR per thing. First we should atleast achive the existing fucntionality and than we should add testing. Migration is a different thing, and code coverage is different. Doing both together can introduce regressions here.

@MohitMaliFtechiz
Copy link
Copy Markdown
Collaborator

@atharvyadav22 Mark all your PRs along with this one, as draft, on which work is currently in progress.

@atharvyadav22
Copy link
Copy Markdown
Contributor Author

@MohitMaliFtechiz @kelson42
Done with all the changes

Could you verify if there are any more functionalities other than these? as we have seen migration have caused us an trouble with many functionality.

  • Debounce search
  • Search suggestions
  • Recent search
  • Voice search
  • Find in page
  • Pagination
  • Kiwix Search Feature
  • Delete dialogs, Toasts

@atharvyadav22
Copy link
Copy Markdown
Contributor Author

@MohitMaliFtechiz we have Fragment Tests too what do with them now ?

@atharvyadav22 atharvyadav22 marked this pull request as ready for review February 26, 2026 16:29
@codecov
Copy link
Copy Markdown

codecov bot commented Feb 26, 2026

Codecov Report

❌ Patch coverage is 69.76744% with 78 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.63%. Comparing base (0298e5f) to head (6069634).

Files with missing lines Patch % Lines
...wixmobile/core/search/viewmodel/SearchViewModel.kt 67.28% 23 Missing and 12 partials ⚠️
...kiwix/kiwixmobile/core/search/SearchScreenRoute.kt 72.09% 7 Missing and 5 partials ⚠️
.../org/kiwix/kiwixmobile/core/search/SearchScreen.kt 66.66% 5 Missing and 2 partials ⚠️
.../kiwixmobile/core/ui/components/KiwixSearchView.kt 86.53% 2 Missing and 5 partials ⚠️
...kiwixmobile/core/main/reader/CoreReaderFragment.kt 20.00% 3 Missing and 1 partial ⚠️
...ile/core/search/viewmodel/SearchResultGenerator.kt 66.66% 0 Missing and 2 partials ⚠️
.../search/viewmodel/effects/ProcessActivityResult.kt 0.00% 2 Missing ⚠️
...search/viewmodel/effects/ShowDeleteSearchDialog.kt 0.00% 2 Missing ⚠️
.../core/search/viewmodel/effects/StartSpeechInput.kt 0.00% 2 Missing ⚠️
...ain/java/org/kiwix/kiwixmobile/ui/KiwixNavGraph.kt 87.50% 1 Missing ⚠️
... and 4 more

❌ Your patch check has failed because the patch coverage (69.76%) is below the target coverage (70.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files
@@             Coverage Diff              @@
##               main    #4703      +/-   ##
============================================
+ Coverage     60.62%   60.63%   +0.01%     
+ Complexity     1667     1635      -32     
============================================
  Files           333      333              
  Lines         16037    15987      -50     
  Branches       2224     2228       +4     
============================================
- Hits           9722     9694      -28     
+ Misses         4843     4816      -27     
- Partials       1472     1477       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@MohitMaliFtechiz
Copy link
Copy Markdown
Collaborator

@MohitMaliFtechiz we have Fragment Tests too what do with them now ?

They are not for testing the fragment. They are for testing the functionality of that screen. We should rename the test cases, as now we have composables, not fragments.

@MohitMaliFtechiz
Copy link
Copy Markdown
Collaborator

@MohitMaliFtechiz @kelson42 Done with all the changes

Could you verify if there are any more functionalities other than these? as we have seen migration have caused us an trouble with many functionality.

  • Debounce search
  • Search suggestions
  • Recent search
  • Voice search
  • Find in page
  • Pagination
  • Kiwix Search Feature
  • Delete dialogs, Toasts

There is one more functionality where we can directly search from browsers as well via the Search Kiwix option menu.

Screenshot_20260227-123244

@atharvyadav22
Copy link
Copy Markdown
Contributor Author

Yes i have already covered that too mentioned it in description as it was also an Issue it now works for both within and and outside the app

@atharvyadav22
Copy link
Copy Markdown
Contributor Author

atharvyadav22 commented Feb 27, 2026

@MohitMaliFtechiz we have Fragment Tests too what do with them now ?

They are not for testing the fragment. They are for testing the functionality of that screen. We should rename the test cases, as now we have composables, not fragments.

Okay now it should be aligned with SearchScreenRoutes right? so should i raise an another PR for it as you said tests are different
Or should i continue with other migration then after migrations these cases like we can reference in coverage PR

@MohitMaliFtechiz
Copy link
Copy Markdown
Collaborator

@atharvyadav22 Renaming the test cases should happen here. Since we are migrating to composables, related to that should be improved here.

@atharvyadav22
Copy link
Copy Markdown
Contributor Author

@atharvyadav22 Renaming the test cases should happen here. Since we are migrating to composables, related to that should be improved here.

I thought we should add tests too got confused.

@MohitMaliFtechiz
Copy link
Copy Markdown
Collaborator

I thought we should add tests too got confused.

@atharvyadav22 If you have found something that needs to be improved in testing for the current migration screen. Then you can add test cases for that in your current PR. @harshsomankar123-tech did this in his PRs. You can take reference from his PRs. But no unrelated changes should be made in PRs.

@MohitMaliFtechiz
Copy link
Copy Markdown
Collaborator

@atharvyadav22 Thank you for making the changes. I will review this shortly :)

Copy link
Copy Markdown
Collaborator

@MohitMaliFtechiz MohitMaliFtechiz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@atharvyadav22, I don't understand why we are always changing the UI behaviour when this PR is just for removing the fragment and migrating it to pure composables.

Here FIND_IN_PAGE option is not visible when there is no word typed. We already have a ticket for this #1782. But this type of UI change made the review harder(needs more testing/caring, and they have no effect. It only takes a long time to merge any PR).

Image

@atharvyadav22 atharvyadav22 marked this pull request as draft March 9, 2026 07:08
@atharvyadav22
Copy link
Copy Markdown
Contributor Author

atharvyadav22 commented Mar 12, 2026

@MohitMaliFtechiz
I know you would ask about changing To BasicTextField but for dragging cursor it is the only solution as officially by Android purely compose issue.
Only fix available on the internet is https://stackoverflow.com/questions/79482530/compose-basictextfield-drag-cursor-text-doesnt-scroll

Also i found one more issue about that cursor going back to start position on recomposition so i have fixed that too.

Hope i could solve all the reviews please have a look about the changes.
Thanks.

@atharvyadav22
Copy link
Copy Markdown
Contributor Author

Once finalize ill mark this for review

@MohitMaliFtechiz
Copy link
Copy Markdown
Collaborator

@atharvyadav22 Thank you for the update. I will have a look at this one.

Also #4782 please take a look on this as this Stabilize our CI for that dispatcher module i spent 6 hours debugging broken CI 🥲. it would definately speed our migration.

Sure, thank you for debugging and fixing that :).

@atharvyadav22
Copy link
Copy Markdown
Contributor Author

@MohitMaliFtechiz done with these alongside there was an Coroutine Dispatcher issue done that was affecting test.
also removed that boilerplate code for test flow and used kotlin recommended pattern.

Ci failing for emulator issue

@atharvyadav22 atharvyadav22 marked this pull request as ready for review March 30, 2026 11:41
Copy link
Copy Markdown
Collaborator

@MohitMaliFtechiz MohitMaliFtechiz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@atharvyadav22 There are still some test are failing.

Image

@atharvyadav22
Copy link
Copy Markdown
Contributor Author

@MohitMaliFtechiz
Done with the changes.

Copy link
Copy Markdown
Collaborator

@MohitMaliFtechiz MohitMaliFtechiz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@atharvyadav22 The unit coverage is never completed. It is stuck on the ZimSearchResultGeneratorTest.

Use this version of the test it will pass.

internal class ZimSearchResultGeneratorTest {
  @RegisterExtension
  private val mainDispatcherRule = MainDispatcherRule()
  private val zimFileReader: ZimFileReader = mockk()

  private val zimSearchResultGenerator: ZimSearchResultGenerator =
    ZimSearchResultGenerator(mainDispatcherRule.dispatcher)

  @Test
  internal fun `empty search term returns empty list`() = runTest {
    assertThat(zimSearchResultGenerator.generateSearchResults("", zimFileReader))
      .isEqualTo(null)
  }

  @Test
  internal fun `suggestion results are distinct`() = runTest {
    val searchTerm = "a"
    val suggestionSearchWrapper: SuggestionSearchWrapper = mockk()
    every { zimFileReader.searchSuggestions(searchTerm) } returns suggestionSearchWrapper
    assertThat(zimSearchResultGenerator.generateSearchResults(searchTerm, zimFileReader))
      .isEqualTo(suggestionSearchWrapper)
    verify {
      zimFileReader.searchSuggestions(searchTerm)
    }
  }
}

import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.extensions.closeKeyboard

object CloseKeyboard : SideEffect<Unit> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add a test case for this as well. Like we have added the test cases for side effects.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we not test this sideEffect separately? As I have said, we should test this side effect like we are testing other side effects.

An example of what I was asking is SaveSearchToRecents and SaveSearchToRecentsTest. You should take a reference from there.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw your test. But what your test is doing. When the CloseKeyboard action is triggered. It calls the CloseKeyboard side effect, and that's it. What about the CloseKeyboard side-effect functionality? We are not verifying its functionality. Like we are doing for SaveSearchToRecents. That's why i have given you the reference of that side-effect.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay i got your point you are saying i should make New file for This CloseKeyboard right? just like SaveSearchToRecentsTest
Also any more side effects i should add?

Copy link
Copy Markdown
Collaborator

@MohitMaliFtechiz MohitMaliFtechiz Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay i got your point you are saying i should make New file for This CloseKeyboard right?

Yes. It could be CloseKeyboardTest.

Also any more side effects i should add?

Which side effects are used in this search functionality, check them. I guess most of the tests are already written; if not, add for that. Do not include the test for other side-effects which are not related to this functionality(not used in SearchViewModel).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay i checked that there are two cases for null and one for focus that closeKeyBoard uses View scope so ill mock that

cancelAndIgnoreRemainingEvents()
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have not tested the main click functionality, such as onItemClick, onItemLongClick, and onNewTabIconClick. These are the major UI functionalities; please test them.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They were already covered in inner class ActionMapper

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is ActionMapping. How do you think testing the OnItemClick sideEffect also test the same when the user clicks in the UI? the sideEffect only tests the action, which triggers after the onItemClick performs its operation, like closing the keyboard and sending the OnItemClick action. We should test both onItemClick and onItemLongClick methods.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okie, Actually the function naming function was earlier written code so made no changes i got your point i didn't read that function properly i thought it does the work I'll surely change.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okie, Actually the function naming function was earlier written code so made no changes

That was written because at that time, click functionalities were inside the fragment, not inside the viewModel. But now we have moved it to the viewModel, so it should be tested properly.

@atharvyadav22
Copy link
Copy Markdown
Contributor Author

@MohitMaliFtechiz
Done with the changes

Copy link
Copy Markdown
Collaborator

@MohitMaliFtechiz MohitMaliFtechiz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@atharvyadav22 The commit message should be clean.

Image


fun onItemLongClick(it: SearchListItem) {
closeKeyboard()
actions.tryEmit(Action.OnItemLongClick(it))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The OnItemLongClick and OnOpenInNewTabClick can be directly used without Action. Using Action here is redundant; we should remove it.

Image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes Strange windows didnt showed this

import org.kiwix.kiwixmobile.core.base.SideEffect
import org.kiwix.kiwixmobile.core.extensions.closeKeyboard

object CloseKeyboard : SideEffect<Unit> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we not test this sideEffect separately? As I have said, we should test this side effect like we are testing other side effects.

An example of what I was asking is SaveSearchToRecents and SaveSearchToRecentsTest. You should take a reference from there.

fun `OnItemClick offers Saves and Opens`() =
runTest {
val searchListItem = RecentSearchListItem("", "")
actionResultsInEffects(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is the ActionMapping class. How do you think testing the effects also tests the UI clicks functionality? There are some logics that are missing to test. Like when onItemClick is performed from UI, it closes the keyboard and emits the OnItemClick click action. The ActionMapping class is testing after that functionality.

cancelAndIgnoreRemainingEvents()
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is ActionMapping. How do you think testing the OnItemClick sideEffect also test the same when the user clicks in the UI? the sideEffect only tests the action, which triggers after the onItemClick performs its operation, like closing the keyboard and sending the OnItemClick action. We should test both onItemClick and onItemLongClick methods.

atharvyadav22 and others added 14 commits April 14, 2026 17:30
…` via DI and enhancing the `generateSearchResults` method.

* Replaced `Channel` with `MutableSharedFlow` for actions and effects, as it is lifecycle-aware and more Compose-friendly. Refactored related code and test cases accordingly.
* Consolidated multiple `StateFlows` into a single UI state to simplify state management and improve maintainability.
* Fixed: Loading icon was not showing when searching for results.
* Fixed an issue where the loading indicator was not displayed during search.
* Fixed a regression where search results were not returned from libkiwix when the search term contained spaces.
* Fixed an issue where "load more" was triggered indefinitely even when no additional results were available in the ZIM file for the given search term.
* Fixed an issue where frequent typing/removal triggered multiple unnecessary calls to libkiwix. This now follows the same behavior as the Fragment implementation, using Flow operators that automatically cancel previous running jobs.
* Fixed an issue where loadMore was executed on the UI coroutine scope, which could lead to lifecycle and cancellation problems. Introduced a LoadMoreResults action so the ViewModel handles pagination safely.
* Improved the loadMore logic for better reliability and consistency.
* Added a CloseKeyboard side effect to allow the ViewModel to control keyboard dismissal without additional UI handling.
* Removed SearchScreenState. The UI now directly observes uiState from the ViewModel and only sends actions, making the architecture cleaner and more maintainable.
* Fixed minor issues and edge cases.
Added Cases for Keyboard and changed requested review comments
@atharvyadav22
Copy link
Copy Markdown
Contributor Author

@MohitMaliFtechiz
Done with the changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants