Skip to content

Extract Resources filtering to ResourcesViewModel#11653

Closed
dogi wants to merge 9 commits intomasterfrom
extract-resources-viewmodel-9620540927375388035
Closed

Extract Resources filtering to ResourcesViewModel#11653
dogi wants to merge 9 commits intomasterfrom
extract-resources-viewmodel-9620540927375388035

Conversation

@dogi
Copy link
Copy Markdown
Member

@dogi dogi commented Mar 2, 2026

Extract applyFilter, isValidFilter, and related filter states out of BaseRecyclerFragment into a new @HiltViewModel named ResourcesViewModel. ResourcesFragment now observes a StateFlow from this ViewModel instead of calling the base methods directly.


PR created automatically by Jules for task 9620540927375388035 started by @dogi

dogi and others added 2 commits March 2, 2026 19:41
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
@google-labs-jules
Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@dogi dogi added triage Further information is requested experiment labels Mar 2, 2026
@qltysh
Copy link
Copy Markdown

qltysh bot commented Mar 2, 2026

❌ 13 blocking issues (13 total)

Tool Category Rule Count
qlty Duplication Found 22 lines of similar code in 2 locations (mass = 339) 5
qlty Structure High total complexity (count = 55) 4
qlty Structure Function with many parameters (count = 6): handleConfigurationSuccess 2
qlty Structure Complex binary expression 1
qlty Structure Function with high complexity (count = 21): handleConfigurationSuccess 1

return !(subjects.isEmpty() && languages.isEmpty()
&& mediums.isEmpty() && levels.isEmpty()
&& searchTags.isEmpty() && searchText.isEmpty())
return !(resourcesViewModel.getSubjects().isEmpty() && resourcesViewModel.getLanguages().isEmpty()
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Complex binary expression [qlty:boolean-logic]

Copy link
Copy Markdown
Collaborator

@Okuro3499 Okuro3499 left a comment

Choose a reason for hiding this comment

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

 1. Realm objects stored inside StateFlow / ViewModel

  _allLibraryItems: MutableStateFlow<List<RealmMyLibrary>> and _tagsMap: MutableStateFlow<Map<String, List<RealmTag>>> hold live managed RealmObject instances. The ViewModel
  survives configuration changes; the Realm instance that backs those objects does not. On any rotation or fragment recreation the Realm bound to those objects may be closed, making
   every field access throw IllegalStateException: Realm instance has already been closed. The correct fix is to convert to unmanaged copies (realm.copyFromRealm()) or to the
  already-existing ResourceItem/TagItem DTOs before handing data to the ViewModel.

  2. Eight-way combine with wall-to-wall unchecked casts

  Kotlin's combine only has type-safe overloads up to 5 flows. The 8-flow call falls back to the Array<*> overload, so every argument is cast with @Suppress("UNCHECKED_CAST"):

  val items   = args[0] as List<RealmMyLibrary>
  val query   = args[1] as String
  val tags    = args[2] as List<RealmTag>
  // …and so on for all 8

  If the order of flows in the combine call is ever changed (very easy to do), you get a silent ClassCastException at runtime, not a compile error. Replace the eight individual
  StateFlows with a single data class FilterState(…) held in one MutableStateFlow<FilterState>. The combine collapses to a single _filterState.map { applyAll(it) }.

dogi and others added 2 commits March 3, 2026 17:23
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
@dogi dogi requested a review from Okuro3499 March 3, 2026 17:29
Copy link
Copy Markdown
Collaborator

@Okuro3499 Okuro3499 left a comment

Choose a reason for hiding this comment

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

java.lang.ClassCastException: kotlin.collections.EmptySet cannot be cast to kotlin.collections.MutableSet
	at org.ole.planet.myplanet.ui.resources.ResourcesFilterFragment$initList$1.invokeSuspend(ResourcesFilterFragment.kt:113)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:34)
	at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:375)
	at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:26)
	at kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:358)
	at kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:134)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch(Builders.common.kt:53)
	at kotlinx.coroutines.BuildersKt.launch(Unknown Source:1)
	at kotlinx.coroutines.BuildersKt__Builders_commonKt.launch$default(Builders.common.kt:44)
	at kotlinx.coroutines.BuildersKt.launch$default(Unknown Source:1)
	at org.ole.planet.myplanet.ui.resources.ResourcesFilterFragment.initList(ResourcesFilterFragment.kt:107)
	at org.ole.planet.myplanet.ui.resources.ResourcesFilterFragment.onViewCreated(ResourcesFilterFragment.kt:93)
	at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:3147)
	at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:588)
	at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:272)
	at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1943)
	at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1845)
	at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1782)
	at androidx.fragment.app.FragmentManager$5.run(FragmentManager.java:565)
	at android.os.Handler.handleCallback(Handler.java:958)
	at android.os.Handler.dispatchMessage(Handler.java:99)
	at android.os.Looper.loopOnce(Looper.java:205)
	at android.os.Looper.loop(Looper.java:294)
	at android.app.ActivityThread.main(ActivityThread.java:8177)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
	Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@c6b552b, Dispatchers.Main.immediate]

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
@dogi dogi requested a review from Okuro3499 March 4, 2026 18:32
Copy link
Copy Markdown
Collaborator

@Okuro3499 Okuro3499 left a comment

Choose a reason for hiding this comment

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

1. Dual state — maintainability risk (must-fix)
subjects, languages, mediums, levels, and searchTags are still maintained as local fragment vars and mirrored into the ViewModel. Every mutation site has to update both:

  // ResourcesFragment.kt – clearTagsButton()
  levels.clear()
  mediums.clear()
  subjects.clear()
  languages.clear()
  resourcesViewModel.setFilters(emptySet(), emptySet(), emptySet(), emptySet())

They're kept in sync in the current code, but the pattern invites divergence as soon as anyone touches one without the other. The local vars should be deleted and the ViewModel treated as the sole source of truth.

2. No debounce on text search — performance risk (must-fix for large libraries)

  // ResourcesFragment.kt – searchTextWatcher
  resourcesViewModel.setSearchQuery(s.toString().trim())

Every keystroke triggers _filterState update → full pipeline re-execution on viewModelScope. The old code did the same but at least did it inside a lifecycleScope.launch. Adding a 300 ms debounce with debounce() on the flow in the ViewModel init block is a one-liner fix.

3. Initial render gap — UI inconsistency risk (must-fix)
getAdapter() now returns the adapter without calling checkList(), showNoData(), or changeButtonStatus(). Those are only called inside the filteredLibraryItems collector. The collector won't emit until the ViewModel receives items via setAllLibraryItems, which happens asynchronously after loadDataAsync(). On slow devices or large datasets there will be a window where the empty-state message is shown before the list loads, or the button state is wrong.

4. ResourcesFilterFragment cast fix — correct (no action needed)

  // Before (ClassCastException risk)
  selectedLvls = filterListener?.getSelectedFilter()?.get("levels") as MutableSet<String>

  // After (safe)
  selectedLvls = filterListener?.getSelectedFilter()?.get("levels")?.toMutableSet() ?: mutableSetOf()

This is a genuine crash fix. The original cast would throw at runtime if the underlying Set was not already a MutableSet. Approved as-is.

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
feedback?.isUploaded = true
feedback?.item = JsonUtils.getString("item", act)
feedback?.state = JsonUtils.getString("state", act)
feedback?._rev = JsonUtils.getString("_rev", act)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Found 22 lines of similar code in 2 locations (mass = 339) [qlty:similar-code]

newsObject.addProperty("updatedDate", news.newsUpdatedDate)
newsObject.addProperty("sharedBy", news.sharedBy)
`object`.add("news", newsObject)
return `object`
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Found 32 lines of similar code in 2 locations (mass = 463) [qlty:similar-code]

}
}
val jsonConcatenatedLinks = JsonUtils.gson.toJson(existingConcatenatedLinks)
settings.edit { putString("concatenated_links", jsonConcatenatedLinks) }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Found 20 lines of similar code in 3 locations (mass = 169) [qlty:similar-code]

settings.edit { putString("concatenated_links", jsonConcatenatedLinks) }
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

High total complexity (count = 57) [qlty:file-complexity]

}
} catch (e: Exception) {
e.printStackTrace()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Found 16 lines of similar code in 2 locations (mass = 177) [qlty:similar-code]

}
} catch (e: Exception) {
e.printStackTrace()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Found 21 lines of similar code in 2 locations (mass = 177) [qlty:similar-code]


override fun onSaveConfigAndContinue(dialog: MaterialDialog, binding: DialogServerUrlBinding, defaultUrl: String) {
saveConfigAndContinue(dialog, binding, "", false, defaultUrl)
private fun handleConfigurationSuccess(id: String, code: String, url: String, defaultUrl: String, isAlternativeUrl: Boolean, callerActivity: String) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Function with many parameters (count = 6): handleConfigurationSuccess [qlty:function-parameters]

}
}
)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Function with high complexity (count = 21): handleConfigurationSuccess [qlty:function-complexity]

@dogi dogi requested a review from Okuro3499 March 5, 2026 13:29
@dogi dogi added the close? label Mar 5, 2026
@dogi dogi closed this Mar 5, 2026
@dogi dogi deleted the extract-resources-viewmodel-9620540927375388035 branch March 18, 2026 01:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

close? experiment triage Further information is requested

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants