Skip to content

Migrated SearchFragment -> Search Composable#4703

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

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

Conversation

@atharvyadav22
Copy link
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
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
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.

val context = LocalActivity.current
val coreMainActivity = context as CoreMainActivity

SearchContainer(
Copy link
Collaborator

Choose a reason for hiding this comment

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

The name should be SearchScreenRoute.

): FragmentActivityExtensions.Super {
pendingIntent = intent

if (!isWebViewHistoryRestoring) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are we adding this here? It has no effect here. Because when the onNewIntent runs, then isWebViewHistoryRestoring will always be true, so this condition will never run. Also, we are storing the intent in pendingIntent which will run after the history is restored.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay see for that issue no #4662 whats was happening earlier when we did OnSearch Kiwix it was immediately cancelling intent actions here we had 2 possible ways which kiwix should pass intent one was outside the app and other within reader screen

Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think that this code #4662 will fix. The search travel is a different issue.

The search travel is only possible in the visible area of the EditTextView.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ops looks like an mistake sorry that fix is for different purpose i didn't raise an Issue for that. actually that SearchKiwix didn't work when in readerScreen that fix is for it sorry for that i referenced it with wrong one.

actionMenuItemList: List<ActionMenuItem>,
isLoadingMoreResult: Boolean
isLoadingMoreResult: Boolean,
lazyListState: LazyListState
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are we passing this from the outside? What is the use of this there?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

pagination was affected and i thought it was recomposing so i changed but later realized my code was not the issue left to revert it thanks for pointing

lazyListState = listState
)

DialogHost(dialogShower as AlertDialogShower)
Copy link
Collaborator

Choose a reason for hiding this comment

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

We don't need to set this here, as we already set the DialogHost in mainActivity for this dialogShower.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

okay as Fragment had it so i added up ill revert it

val showFindInPage by viewModel.showFindInPage.collectAsStateWithLifecycle(initialValue = false)

// Handles SideEffects
LaunchedEffect(viewModel) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

The key should not be viewModel since it only needs to be attached once. Use the CollectSideEffectWithActivity instead of this.


SearchScreen(
screenState,
listOfNotNull(
Copy link
Collaborator

Choose a reason for hiding this comment

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

We should split this into a function that returns the actionMenuList.

)
).isSuccess
_effects.trySend(ShowToast(R.string.delete_specific_search_toast)).isSuccess
DeleteRecentSearch(it.searchListItem, recentSearchRoomDao, viewModelScope)
Copy link
Collaborator

Choose a reason for hiding this comment

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

When I am deleting the SearchedItem, then the toast shows the success of deleting. But the item remains shown on the screen.

.collect {
state.value = it

if (it.searchTerm != lastQuery && !_isLoadingMore.value) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why are we checking it.searchTerm != lastQuery when we are using the distinctUntilChanged?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes actually when i migrated i thought pagination and other stuff like read was affected by this reason so i was checking and i left mistakenly there bad code thanks for pointing.

@MohitMaliFtechiz
Copy link
Collaborator

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

@atharvyadav22
Copy link
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
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
Collaborator

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

@atharvyadav22
Copy link
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
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

codecov bot commented Feb 26, 2026

Codecov Report

❌ Patch coverage is 70.62937% with 42 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.34%. Comparing base (02520f2) to head (f4c42ee).

Files with missing lines Patch % Lines
...kiwix/kiwixmobile/core/search/SearchScreenRoute.kt 70.27% 16 Missing and 6 partials ⚠️
...wixmobile/core/search/viewmodel/SearchViewModel.kt 70.17% 8 Missing and 9 partials ⚠️
...ain/java/org/kiwix/kiwixmobile/ui/KiwixNavGraph.kt 87.50% 1 Missing ⚠️
...rg/kiwix/kiwixmobile/core/main/CoreMainActivity.kt 50.00% 0 Missing and 1 partial ⚠️
...kiwixmobile/core/main/reader/CoreReaderFragment.kt 50.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #4703      +/-   ##
============================================
- Coverage     60.70%   60.34%   -0.36%     
+ Complexity     1625     1588      -37     
============================================
  Files           330      330              
  Lines         15726    15668      -58     
  Branches       2162     2158       -4     
============================================
- Hits           9546     9455      -91     
- Misses         4724     4759      +35     
+ Partials       1456     1454       -2     

☔ 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
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
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
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
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
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
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
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
Collaborator

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

Copy link
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

}

// Search Results
LaunchedEffect(viewModel) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

My previous comment has not been taken. Here we should use Unit instead of viewModel. However, the viewModel will not change, but it is not ideal to use the viewModel as a key here. Because it only requires once, so it should be Unit. In the future, it might be possible that the viewModel is changed due to some condition.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

okay i'll keep in mind like i assumed that ViewModel() is attached with lifecycle so it wont change but i agree with you.

showFindInPage: Boolean
): List<ActionMenuItem> {
return listOfNotNull(
ActionMenuItem(
Copy link
Collaborator

Choose a reason for hiding this comment

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

I appreciate this one, that you have found this issue, and placed a fix for it 👏 .

navigationIcon = {
NavigationIcon(
onClick = {
coreMainActivity.onBackPressedDispatcher.onBackPressed()
Copy link
Collaborator

Choose a reason for hiding this comment

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

The keyboard is closing after the reader screen is visible in my Google Pixel 7a. But the keyboard is not closing in the emualtor. So I guess my device autoamtically handling the keyboard hiding. We should hide the keyboard first if the navigation icon is clicked. Because it is taking us to the reader screen, which is not an ideal UX.

  • When the user presses the device back button then first keyboard hides, and on the second back pressed screen comes to the reader. This is the ideal UX for a user. We should keep this same for navigationIcon click.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay as i only tested it one Real Device i'll keep that in mind.

val firstPage = it.getVisibleResults(0).orEmpty()
_visibleResults.value = firstPage

if (it.searchTerm.isNotBlank()) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is a huge memory consumption code. It everytime ask to libkiwix to give suggestions; however, the result already exists. The suggestion API is just for giving suggestions if the search API did not return a result.

If you see the fragment code, it is clearly mentioned there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay i'll update 👍

): FragmentActivityExtensions.Super {
pendingIntent = intent

if (!isWebViewHistoryRestoring) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think that this code #4662 will fix. The search travel is a different issue.

The search travel is only possible in the visible area of the EditTextView.

}

suspend fun getSuggestedSpelledWords(word: String, maxCount: Int): List<String> =
private fun getSuggestedSpelledWords(word: String, maxCount: Int): List<String> =
Copy link
Collaborator

Choose a reason for hiding this comment

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

This operation should not be performed on the main thread, so it should be suspend function to make its usage clear.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes ! my bad

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

Labels

None yet

Projects

None yet

2 participants