Skip to content

Refactor: Improve state management and code organization#213

Closed
Mohammad-Faiz-Cloud-Engineer wants to merge 10 commits intorainxchzed:mainfrom
Mohammad-Faiz-Cloud-Engineer:main
Closed

Refactor: Improve state management and code organization#213
Mohammad-Faiz-Cloud-Engineer wants to merge 10 commits intorainxchzed:mainfrom
Mohammad-Faiz-Cloud-Engineer:main

Conversation

@Mohammad-Faiz-Cloud-Engineer
Copy link

@Mohammad-Faiz-Cloud-Engineer Mohammad-Faiz-Cloud-Engineer commented Feb 1, 2026

This PR refactors several ViewModels and repositories to improve code maintainability and performance. The main focus was on cleaning up the developer profile feature and optimizing how we observe data changes throughout the app.

Changes

ViewModels

  • MainViewModel: Moved the data migration logic out of the ViewModel into a dedicated use case. Also combined the theme preference observers into a single flow instead of having 4 separate ones.
  • HomeViewModel: Combined the installed apps, favorites, and starred repos observers into one flow. This should reduce some overhead.
  • DeveloperProfileViewModel:
    • Extracted the favorite toggle logic into its own method to keep the action handler cleaner
    • Fixed a bug where favorite toggle would update UI even if the database operation failed - now wrapped in try/catch to only update state on success

Repositories

  • DeveloperProfileRepositoryImpl:
    • Pulled out some magic numbers into constants
    • Split the repository fetching logic into smaller helper methods
    • Changed favorite ID lookups to use HashSet instead of List for better performance
    • Fixed a few string operations to use substringBefore instead of split

New Files

  • MigrateInstalledAppsDataUseCase: New use case that handles migrating legacy app data. This was previously sitting in MainViewModel's init block which wasn't ideal.

Other

  • Added some KDoc comments where they were missing
  • Cleaned up a few action handlers that were just empty blocks
  • Updated DI configuration in SharedModules

Why?

The main motivation was to clean up some technical debt, especially around the developer profile feature. The code was working fine but had some areas that could be more maintainable:

  1. MainViewModel was doing too much in its init block
  2. Multiple separate flow collectors when we could combine them
  3. Some methods were getting too long and could be split up
  4. Missing documentation in a few places

Testing

  • Verified all files compile without errors
  • No functional changes, just refactoring
  • Tested developer profile loading, filtering, and favorite toggling
  • Checked home screen pagination and status updates
  • Confirmed theme changes and auth state still work correctly

Notes

This is purely a refactoring PR - no new features or bug fixes. All existing functionality should work exactly the same as before.

Summary by CodeRabbit

  • New Features

    • Added installed apps data migration.
    • Favorites persistence for developer profiles.
  • Improvements

    • Enhanced developer profile browsing with robust filtering, sorting and faster concurrent release checks.
    • Streamlined app startup and unified status observation for installed/favourite/starred repos.
    • Consolidated theme preference handling for more consistent theming.
    • More graceful migration/error handling and optimized repository pagination.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 1, 2026

Walkthrough

This PR restructures startup and observation flows in MainViewModel, adds a MigrateInstalledAppsDataUseCase and DI wiring for it, consolidates theme/status observers into combined flows, and refactors developer-profile and home features for paginated fetching, concurrent release checks, favorites handling, and inline repository status application.

Changes

Cohort / File(s) Summary
App initialization & DI
composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/MainViewModel.kt, composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/di/SharedModules.kt
MainViewModel startup split into explicit steps (initializeAuthState, observeAuthChanges, observeThemePreferences, observeAppState, performInitialMigration). DI updated to provide MigrateInstalledAppsDataUseCase and MainViewModel wiring now accepts that use-case plus installedAppsRepository instead of packageMonitor/platform. Theme observers replaced by a single combined ThemePreferences flow.
Migration use case
composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/core/domain/use_cases/MigrateInstalledAppsDataUseCase.kt
New MigrateInstalledAppsDataUseCase added: enumerates installed packages, deletes repo entries for uninstalled packages, migrates legacy version fields using platform-specific logic (Android system package info vs desktop fallback), and returns a Result with logging on errors.
Developer profile repository & ViewModel
composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/developer_profile/data/repository/DeveloperProfileRepositoryImpl.kt, composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/developer_profile/presentation/DeveloperProfileViewModel.kt
Repository: added fetchAllRepositories with pagination, concurrency-controlled release checks, isInstallableAsset helper, and constants for pagination/concurrency. ViewModel: added loadDeveloperData, applyFiltersAndSort, favourites integration via favouritesRepository, and a toggleFavorite helper.
Home feature status aggregation
composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/home/presentation/HomeViewModel.kt
Replaced separate observers for installed apps/favourites/starred with a single combined status flow; observeRepositoryStatus introduced. loadRepos now fetches status maps in parallel during pagination and applies flags inline; pagination deduplication by fullName.

Sequence Diagram(s)

sequenceDiagram
    participant MainVM as MainViewModel
    participant DI as DI Container
    participant Migrator as MigrateInstalledAppsDataUseCase
    participant Repo as InstalledAppsRepository
    participant PM as PackageMonitor
    participant PF as Platform
    MainVM->>DI: request MainViewModel (startup)
    DI->>MainVM: inject MigrateInstalledAppsDataUseCase, InstalledAppsRepository
    MainVM->>Migrator: invoke() (performInitialMigration)
    Migrator->>PM: getInstalledSystemPackages()
    Migrator->>Repo: getAllInstalledApps()
    Migrator->>Migrator: for each app: decide delete or migrate
    Migrator->>PF: (platform check) get package info if ANDROID
    Migrator->>Repo: update or delete app records
    Migrator-->>MainVM: Result(success/failure)
    MainVM->>Repo: installedAppsRepository.checkAllForUpdates() (post-migration)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰
Migrate the hops, combine the streams so bright,
Themes and apps hop in order, tidy and light.
Concurrent checks bound by a semaphore's care,
Repos find their status — clean, updated, and fair.
A rabbit cheers: "New flows, new springtime flair!"

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 72.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Refactor: Improve state management and code organization' is vague and overly broad, using generic terms that don't convey specific information about the substantive changes made in the changeset. Consider using a more specific title that highlights the primary refactoring focus, such as 'Refactor: Consolidate ViewModels and extract MigrateInstalledAppsDataUseCase' or 'Refactor: Combine theme observers and data migration logic in ViewModels'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/home/presentation/HomeViewModel.kt (1)

160-174: ⚠️ Potential issue | 🟠 Major

Comment claims parallel execution but code is sequential.

The comment says "Fetch all status data in parallel" but the three .first() calls execute sequentially—each must complete before the next starts. Either fix the comment to reflect sequential execution, or use async for true parallelism.

🔧 Option 1: Fix comment to reflect actual behavior
-                    // Fetch all status data in parallel
+                    // Fetch all status data sequentially
⚡ Option 2: Implement true parallel fetching
-                    // Fetch all status data in parallel
-                    val installedAppsMap = installedAppsRepository
-                        .getAllInstalledApps()
-                        .first()
-                        .associateBy { it.repoId }
-
-                    val favoritesMap = favouritesRepository
-                        .getAllFavorites()
-                        .first()
-                        .associateBy { it.repoId }
-
-                    val starredReposMap = starredRepository
-                        .getAllStarred()
-                        .first()
-                        .associateBy { it.repoId }
+                    // Fetch all status data in parallel
+                    val installedAppsDeferred = async {
+                        installedAppsRepository.getAllInstalledApps().first().associateBy { it.repoId }
+                    }
+                    val favoritesDeferred = async {
+                        favouritesRepository.getAllFavorites().first().associateBy { it.repoId }
+                    }
+                    val starredDeferred = async {
+                        starredRepository.getAllStarred().first().associateBy { it.repoId }
+                    }
+
+                    val installedAppsMap = installedAppsDeferred.await()
+                    val favoritesMap = favoritesDeferred.await()
+                    val starredReposMap = starredDeferred.await()
🤖 Fix all issues with AI agents
In
`@composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/developer_profile/presentation/DeveloperProfileViewModel.kt`:
- Around line 201-236: toggleFavorite currently updates _state and calls
applyFiltersAndSort regardless of whether favouritesRepository.toggleFavorite
succeeded, causing UI/db inconsistency; wrap the call to
favouritesRepository.toggleFavorite(repo) in a try/catch (or check its success
return) inside toggleFavorite, and only update _state (the block that maps
repositories and calls applyFiltersAndSort) when the repository operation
succeeds, otherwise log/handle the error and do not flip isFavorite (or revert
the change if you optimistically updated first). Refer to the toggleFavorite
function, favouritesRepository.toggleFavorite, the _state.update mapping that
flips isFavorite, and applyFiltersAndSort to locate where to gate the UI update.
🧹 Nitpick comments (4)
composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/feature/home/presentation/HomeViewModel.kt (1)

70-77: Consider adding debug-level logging for silent failures.

While suppressing non-critical failures is reasonable, completely silent exception swallowing can make troubleshooting difficult. A debug-level log would help identify issues without impacting production.

💡 Optional: Add debug logging
             try {
                 syncInstalledAppsUseCase()
             } catch (e: Exception) {
-                // Silent fail - non-critical operation
+                Logger.d { "Non-critical sync failed: ${e.message}" }
             }
composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/core/domain/use_cases/MigrateInstalledAppsDataUseCase.kt (2)

24-48: Consider converting installedPackageNames to a Set for O(1) lookups.

The !in installedPackageNames check on line 32 is O(n) if getAllInstalledPackageNames() returns a List. With many apps, this results in O(n²) complexity. The PR description mentions using HashSet for favorite ID lookups in DeveloperProfileRepositoryImpl — the same optimization would benefit this migration logic.

♻️ Proposed fix
     suspend operator fun invoke(): Result<Unit> {
         return try {
-            val installedPackageNames = packageMonitor.getAllInstalledPackageNames()
+            val installedPackageNames = packageMonitor.getAllInstalledPackageNames().toSet()
             val appsInDb = installedAppsRepository.getAllInstalledApps().first()

55-92: Minor duplication between Android fallback and desktop case.

Lines 72-79 and 83-90 are identical. Consider extracting to reduce duplication, though this is a low-priority refactor.

♻️ Optional: Extract common fallback logic
     private suspend fun migrateAppVersionData(packageName: String) {
         val app = installedAppsRepository.getAppByPackage(packageName) ?: return

         if (platform.type == PlatformType.ANDROID) {
             val systemInfo = packageMonitor.getInstalledPackageInfo(packageName)
             if (systemInfo != null) {
                 // Use system package info
                 installedAppsRepository.updateApp(
                     app.copy(
                         installedVersionName = systemInfo.versionName,
                         installedVersionCode = systemInfo.versionCode,
                         latestVersionName = systemInfo.versionName,
                         latestVersionCode = systemInfo.versionCode
                     )
                 )
-            } else {
-                // Fallback to legacy tag
-                installedAppsRepository.updateApp(
-                    app.copy(
-                        installedVersionName = app.installedVersion,
-                        installedVersionCode = 0L,
-                        latestVersionName = app.installedVersion,
-                        latestVersionCode = 0L
-                    )
-                )
+                return
             }
-        } else {
-            // Desktop platforms: use legacy tag as version name
-            installedAppsRepository.updateApp(
-                app.copy(
-                    installedVersionName = app.installedVersion,
-                    installedVersionCode = 0L,
-                    latestVersionName = app.installedVersion,
-                    latestVersionCode = 0L
-                )
+        }
+        
+        // Fallback for desktop or when Android system info unavailable
+        installedAppsRepository.updateApp(
+            app.copy(
+                installedVersionName = app.installedVersion,
+                installedVersionCode = 0L,
+                latestVersionName = app.installedVersion,
+                latestVersionCode = 0L
             )
-        }
+        )
     }
composeApp/src/commonMain/kotlin/zed/rainxch/githubstore/app/di/SharedModules.kt (1)

109-115: Consider using simple class name with import for consistency.

MigrateInstalledAppsDataUseCase uses the fully qualified name, while SyncInstalledAppsUseCase (line 101) uses the simple name. Adding an import would maintain consistency across the module.

♻️ Proposed fix

Add to imports:

import zed.rainxch.githubstore.core.domain.use_cases.MigrateInstalledAppsDataUseCase

Then update the singleton:

-    single<zed.rainxch.githubstore.core.domain.use_cases.MigrateInstalledAppsDataUseCase> {
-        zed.rainxch.githubstore.core.domain.use_cases.MigrateInstalledAppsDataUseCase(
+    single<MigrateInstalledAppsDataUseCase> {
+        MigrateInstalledAppsDataUseCase(
             packageMonitor = get(),
             installedAppsRepository = get(),
             platform = get()
         )
     }

@rainxchzed
Copy link
Owner

Hey @Mohammad-Faiz-Cloud-Engineer, thank you so much for this huge and thoughtful refactor — seriously, I really appreciate the time and effort you put into improving the architecture and cleanup here 🙌

Right now I’m in the middle of a large migration from a single-module setup to a multi-module architecture:
#210

Because of that, the project structure and many of the same files you touched are actively changing. If I try to merge this PR now, we’ll likely end up with a lot of heavy merge conflicts and risk breaking things during the migration.

So the best approach would be:

  1. First, I finish and merge the multi-module migration
  2. Then, we can re-apply or adapt your refactor on top of the new structure

Your changes and ideas here are still very valuable, and I’d definitely like to bring them in once the migration dust settles. If you’re up for it later, you could rebase on the updated structure — otherwise I can also help port the improvements over.

Thanks again for the great contribution and for your patience! 🚀

@Mohammad-Faiz-Cloud-Engineer
Copy link
Author

Hey @rainxchzed, thanks for the heads up and totally understand! 👍

Makes complete sense to wait for the multi-module migration to finish first - definitely don't want to create merge conflicts or break anything during that process.

I'll close this PR for now and keep the branch around. Once #210 is merged and the new structure is in place, I'm happy to rebase these changes on top of it. The refactoring should still be relevant since it's mostly about cleaning up the ViewModels and state management logic.

Just ping me when you're ready and I can either:

Rebase and update the PR myself
Share the changes so you can port them over if that's easier
Thanks for taking the time to review this, and good luck with the migration! Let me know if you need any help with it.

@rainxchzed
Copy link
Owner

@Mohammad-Faiz-Cloud-Engineer Yeah i definitely do, thanks for understanding the situation!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants