Skip to content

홈 화면 갤러리 목록에 페이징을 적용한다#67

Merged
komodgn merged 6 commits intodevelopfrom
feature/#66
Jan 7, 2026
Merged

홈 화면 갤러리 목록에 페이징을 적용한다#67
komodgn merged 6 commits intodevelopfrom
feature/#66

Conversation

@komodgn
Copy link
Owner

@komodgn komodgn commented Jan 7, 2026

Summary by CodeRabbit

  • New Features
    • Pagination support for the gallery: images load progressively as you scroll, with caching for smoother browsing and improved performance.
  • Documentation
    • README and dependency catalog updated to include new paging libraries and formatting tweaks.
  • Minor Fixes
    • Updated toast message text in focused search UI.

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

@coderabbitai
Copy link

coderabbitai bot commented Jan 7, 2026

📝 Walkthrough

Walkthrough

This PR adds Android Paging3 support for gallery images: new PagingData API on the repository, a GalleryPagingSource implementation, a GalleryImageModel, build/deps updates, and refactors the home feature to consume cached PagingData flows and render via LazyPagingItems.

Changes

Cohort / File(s) Summary
Dependencies & Version Catalog
gradle/libs.versions.toml, core/data/api/build.gradle.kts
Added version entries and library entries for Compose Paging (composePaging) and work-runtime; added androidx.paging:paging-compose library and api(libs.androidx.compose.paging) dependency
Core Models
core/model/.../GalleryImageModel.kt, core/model/.../HomeModel.kt
Added new @Stable data class GalleryImageModel(id: Long, uriString: String, dateAdded: Long); removed previous HomeModel and its embedded GalleryImageModel
Repository API
core/data/api/.../GalleryRepository.kt
Added public methods getGalleryPagingData(): Flow<PagingData<GalleryImageModel>> and findMatchedUris(photoNames: List<String>): List<Uri>
Paging Source
core/data/impl/.../datasource/GalleryPagingSource.kt
New GalleryPagingSource(context: Context, ioDispatcher: CoroutineDispatcher) implementing PagingSource<Int, GalleryImageModel>; queries MediaStore, maps Cursor rows to model, handles offsets/limits and errors
Repository Implementation
core/data/impl/.../repository/GalleryRepositoryImpl.kt
Injected @IoDispatcher CoroutineDispatcher into constructor; implemented getGalleryPagingData() returning Pager(...).flow that uses GalleryPagingSource (pageSize 30, initialLoadSize 60, placeholders disabled)
Feature Home State & Presenter
feature/home/.../HomeUiState.kt, feature/home/.../HomePresenter.kt
Changed images from List<Uri> to Flow<PagingData<GalleryImageModel>>; removed isGalleryLoading; presenter now exposes galleryPagingFlow and caches flow with cachedIn(scope)
Feature Home UI
feature/home/.../HomeUi.kt
Switched rendering to LazyPagingItems via collectAsLazyPagingItems(), use itemCount/itemKey, load-state checks (e.g., LoadState.Loading), and updated grid item access to use uriString; added insets/padding adjustments and preview flow
Docs
README.md
Documented addition of Paging3 to Jetpack libraries and minor README formatting adjustments
Minor UI tweak
feature/search/.../FocusingSearchUi.kt
Replaced one toast resource id with another (focusing_search_screen_toast_messagefocusing_search_screen_toast_guide)

Sequence Diagram

sequenceDiagram
    participant HomeUi as Feature:HomeUi
    participant Presenter as Feature:HomePresenter
    participant Repo as Core:GalleryRepository
    participant Pager as Paging3:Pager
    participant Source as Core:GalleryPagingSource
    participant MediaStore as Android:MediaStore

    HomeUi->>Presenter: collect galleryPagingFlow (cachedIn)
    Presenter->>Repo: getGalleryPagingData()
    Repo->>Pager: create Pager(flow) with GalleryPagingSource
    Pager->>Source: load(params)
    Source->>MediaStore: query(_ID, DATE_ADDED, offset/limit)
    MediaStore-->>Source: Cursor(rows)
    Source->>Source: map Cursor -> GalleryImageModel
    Source-->>Pager: LoadResult.Page(data, prevKey, nextKey)
    Pager-->>Repo: emits PagingData<GalleryImageModel>
    Repo-->>Presenter: flow emits PagingData
    Presenter-->>HomeUi: HomeUi collects as LazyPagingItems
    HomeUi->>HomeUi: render items, check LoadState (Loading/NotLoading/Error)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰
I hop through pages with nimble feet,
MediaStore rows I gently meet,
Pager hums a steady tune,
Caches warm beneath the moon,
Scroll on, dear user — images greet.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: applying paging to the gallery list on the home screen, which is fully reflected in the changeset's new paging infrastructure.

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

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 400a9a3 and e4d430f.

📒 Files selected for processing (1)
  • feature/search/src/main/java/com/example/metasearch/feature/search/focusing/FocusingSearchUi.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci-build
🔇 Additional comments (1)
feature/search/src/main/java/com/example/metasearch/feature/search/focusing/FocusingSearchUi.kt (1)

169-169: Confirm whether this change is intentionally included in this PR.

This line updates the toast message to use focusing_search_screen_toast_guide (which is properly defined). However, this change appears unrelated to the PR's stated objective of adding paging to the home gallery list. It modifies UI text in the search feature instead.

Please clarify if this was intentionally included in this PR or if it should be moved to a separate PR.


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

@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: 2

🤖 Fix all issues with AI agents
In
@core/data/impl/src/main/java/com/example/metasearch/core/data/impl/datasource/GalleryPagingSource.kt:
- Around line 54-58: The prevKey calculation in GalleryPagingSource's
LoadResult.Page can produce negative keys when offset < limit; update the logic
that sets prevKey (currently "if (offset == 0) null else offset - limit") to
clamp or guard against negatives — e.g., compute prev = offset - limit and set
prevKey = null if prev <= 0 (or prevKey = max(0, prev) if your loader accepts 0)
so prevKey is never negative; adjust in the same method where LoadResult.Page is
returned.

In @feature/home/src/main/java/com/example/metasearch/feature/home/HomeUi.kt:
- Around line 62-63: HomeUi currently reuses the incoming modifier when
composing MetaSearchMainBottomBar (passing modifier = modifier.padding(...)),
which can apply callers' modifiers to the bottom bar; change the call to use a
fresh Modifier instance instead (e.g., pass modifier =
Modifier.padding(MetaSearchTheme.spacing.spacing3)) so MetaSearchMainBottomBar
gets only its intended local modifiers; locate the usage in HomeUi where
MetaSearchMainBottomBar is invoked and replace the reused `modifier` with a new
`Modifier` combined with the padding.
🧹 Nitpick comments (4)
gradle/libs.versions.toml (2)

24-26: Clarify the scope: WorkManager and Hilt-Work additions.

The PR objectives mention adding paging functionality for gallery images, but this change introduces WorkManager (workRuntime) and commented-out Hilt-Work dependencies. Are these related to the paging feature, or should they be in a separate PR? If they're for the ImageAnalysisWorker (which already exists in the codebase), consider clarifying this in the commit message.

Also applies to: 102-104


129-129: Remove or translate the Korean comment.

The inline comment # 이 줄을 추가하세요 (translation: "Add this line") appears to be a development note that should be removed before merging, or translated to English for consistency with the rest of the codebase.

♻️ Proposed fix
-okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" } # 이 줄을 추가하세요
+okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
core/data/impl/src/main/java/com/example/metasearch/core/data/impl/repository/GalleryRepositoryImpl.kt (1)

39-39: Inconsistent dispatcher usage.

The injected ioDispatcher is used for GalleryPagingSource, but other methods like getAllGalleryImages still use hardcoded Dispatchers.IO. Consider using the injected dispatcher consistently for better testability.

♻️ Suggested fix
-    override suspend fun getAllGalleryImages(): List<Uri> = withContext(Dispatchers.IO) {
+    override suspend fun getAllGalleryImages(): List<Uri> = withContext(ioDispatcher) {

Apply the same change to getFileName, findMatchedUri, and findMatchedUris methods.

feature/home/src/main/java/com/example/metasearch/feature/home/HomeUi.kt (1)

174-191: Paging integration looks correct.

The usage of items(count, key) with lazyPagingItems.itemKey { it.id } is the recommended Paging3 Compose pattern. The null check on line 179 properly handles placeholder scenarios (even though placeholders are disabled).

Consider also showing a loading indicator during append operations for a smoother UX when scrolling to load more items:

💡 Optional: Add append loading indicator
if (lazyPagingItems.loadState.refresh is LoadState.Loading) {
    MetaSearchLoadingIndicator(modifier = Modifier.align(Alignment.Center))
}
// Optional: Show a smaller indicator at bottom during append
if (lazyPagingItems.loadState.append is LoadState.Loading) {
    // Add a bottom-aligned loading indicator
}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9dbbd82 and 400a9a3.

📒 Files selected for processing (11)
  • README.md
  • core/data/api/build.gradle.kts
  • core/data/api/src/main/java/com/example/metasearch/core/data/api/repository/GalleryRepository.kt
  • core/data/impl/src/main/java/com/example/metasearch/core/data/impl/datasource/GalleryPagingSource.kt
  • core/data/impl/src/main/java/com/example/metasearch/core/data/impl/repository/GalleryRepositoryImpl.kt
  • core/model/src/main/java/com/example/metasearch/core/model/GalleryImageModel.kt
  • core/model/src/main/java/com/example/metasearch/core/model/HomeModel.kt
  • feature/home/src/main/java/com/example/metasearch/feature/home/HomePresenter.kt
  • feature/home/src/main/java/com/example/metasearch/feature/home/HomeUi.kt
  • feature/home/src/main/java/com/example/metasearch/feature/home/HomeUiState.kt
  • gradle/libs.versions.toml
💤 Files with no reviewable changes (1)
  • core/model/src/main/java/com/example/metasearch/core/model/HomeModel.kt
🧰 Additional context used
🧬 Code graph analysis (2)
core/data/api/build.gradle.kts (1)
build-logic/src/main/java/com/example/metasearch/convention/Dependencies.kt (1)
  • api (11-13)
feature/home/src/main/java/com/example/metasearch/feature/home/HomeUi.kt (2)
feature/screens/src/main/java/com/example/metasearch/feature/screens/component/MetaSearchMainBottomBar.kt (1)
  • MetaSearchMainBottomBar (36-86)
core/ui/src/main/java/com/example/metasearch/core/ui/component/MetaSearchSquareImage.kt (1)
  • MetaSearchSquareImage (14-30)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: ci-build
🔇 Additional comments (10)
core/data/api/build.gradle.kts (1)

13-13: LGTM! Correct use of api dependency.

The Paging library is correctly exposed via api since the repository interface returns Flow<PagingData<GalleryImageModel>>, which requires consumers to have access to the Paging types.

README.md (1)

50-50: LGTM! Documentation accurately reflects the new Paging3 integration.

The addition of Paging3 to the Jetpack Libraries section correctly documents the new dependency introduced in this PR.

feature/home/src/main/java/com/example/metasearch/feature/home/HomePresenter.kt (1)

62-64: LGTM! Proper paging implementation with caching.

The cachedIn(scope) call ensures that the paging data survives configuration changes and prevents redundant data loads. The remember block correctly creates the flow only once during the lifecycle of this presenter.

core/model/src/main/java/com/example/metasearch/core/model/GalleryImageModel.kt (1)

1-10: LGTM! Well-designed data model.

The GalleryImageModel is properly structured:

  • @Stable annotation optimizes Compose recomposition
  • Immutable properties align with best practices
  • id field clearly maps to MediaStore._ID
  • dateAdded enables chronological sorting
gradle/libs.versions.toml (1)

22-22: Version 3.3.6 is the current latest stable release (February 2025) with no known security vulnerabilities or advisories.

No action needed; the dependency is already up-to-date.

feature/home/src/main/java/com/example/metasearch/feature/home/HomeUiState.kt (1)

16-16: Flow in UI state - ensure it's properly cached.

Storing Flow<PagingData<...>> directly in the UI state data class is a valid pattern with Circuit. Ensure this flow is created with cachedIn(viewModelScope/presenterScope) in the presenter to survive configuration changes and avoid redundant loads.

core/data/api/src/main/java/com/example/metasearch/core/data/api/repository/GalleryRepository.kt (1)

8-14: LGTM!

The repository interface is well-defined. The getGalleryPagingData() correctly returns a cold Flow (non-suspend) which is the standard pattern for Paging3 integration.

core/data/impl/src/main/java/com/example/metasearch/core/data/impl/repository/GalleryRepositoryImpl.kt (1)

28-37: LGTM!

The Paging implementation is correct. The pagingSourceFactory properly creates a new GalleryPagingSource instance for each invalidation, which is the expected pattern for Paging3.

core/data/impl/src/main/java/com/example/metasearch/core/data/impl/datasource/GalleryPagingSource.kt (2)

65-70: LGTM!

The getRefreshKey implementation follows the standard Paging3 pattern for calculating the refresh position based on the anchor position and nearby page keys.


30-35: No API level compatibility issue: The code is compatible with the project's minSdk of 30, which exceeds the API 26 requirement for ContentResolver.QUERY_ARG_* constants. No fallback needed.

Likely an incorrect or invalid review comment.

Comment on lines +54 to +58
LoadResult.Page(
data = imageList,
prevKey = if (offset == 0) null else offset - limit,
nextKey = if (imageList.size < limit) null else offset + imageList.size,
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential negative prevKey value.

When offset < limit (e.g., on initial load with initialLoadSize=60 but pageSize=30), prevKey = offset - limit would produce a negative value. This should be clamped to avoid invalid page keys.

🐛 Proposed fix
                 LoadResult.Page(
                     data = imageList,
-                    prevKey = if (offset == 0) null else offset - limit,
+                    prevKey = if (offset == 0) null else (offset - limit).coerceAtLeast(0).takeIf { it < offset },
                     nextKey = if (imageList.size < limit) null else offset + imageList.size,
                 )

Alternatively, a simpler approach:

-                    prevKey = if (offset == 0) null else offset - limit,
+                    prevKey = if (offset == 0) null else maxOf(0, offset - limit),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
LoadResult.Page(
data = imageList,
prevKey = if (offset == 0) null else offset - limit,
nextKey = if (imageList.size < limit) null else offset + imageList.size,
)
LoadResult.Page(
data = imageList,
prevKey = if (offset == 0) null else (offset - limit).coerceAtLeast(0).takeIf { it < offset },
nextKey = if (imageList.size < limit) null else offset + imageList.size,
)
🤖 Prompt for AI Agents
In
@core/data/impl/src/main/java/com/example/metasearch/core/data/impl/datasource/GalleryPagingSource.kt
around lines 54 - 58, The prevKey calculation in GalleryPagingSource's
LoadResult.Page can produce negative keys when offset < limit; update the logic
that sets prevKey (currently "if (offset == 0) null else offset - limit") to
clamp or guard against negatives — e.g., compute prev = offset - limit and set
prevKey = null if prev <= 0 (or prevKey = max(0, prev) if your loader accepts 0)
so prevKey is never negative; adjust in the same method where LoadResult.Page is
returned.

Comment on lines +62 to +63
modifier = modifier
.padding(bottom = MetaSearchTheme.spacing.spacing3),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid reusing the outer modifier parameter.

The modifier parameter from HomeUi is being passed to MetaSearchMainBottomBar. This can cause unintended side effects since the caller's modifiers (e.g., fillMaxSize()) would be applied to the bottom bar. Use a fresh Modifier instead.

🐛 Proposed fix
             MetaSearchMainBottomBar(
-                modifier = modifier
+                modifier = Modifier
                     .padding(bottom = MetaSearchTheme.spacing.spacing3),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
modifier = modifier
.padding(bottom = MetaSearchTheme.spacing.spacing3),
modifier = Modifier
.padding(bottom = MetaSearchTheme.spacing.spacing3),
🤖 Prompt for AI Agents
In @feature/home/src/main/java/com/example/metasearch/feature/home/HomeUi.kt
around lines 62 - 63, HomeUi currently reuses the incoming modifier when
composing MetaSearchMainBottomBar (passing modifier = modifier.padding(...)),
which can apply callers' modifiers to the bottom bar; change the call to use a
fresh Modifier instance instead (e.g., pass modifier =
Modifier.padding(MetaSearchTheme.spacing.spacing3)) so MetaSearchMainBottomBar
gets only its intended local modifiers; locate the usage in HomeUi where
MetaSearchMainBottomBar is invoked and replace the reused `modifier` with a new
`Modifier` combined with the padding.

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

🤖 Android CI Summary

Step Results:

  • Unit Test: ❌ Failure (0s)
  • Debug Build: ⏭️ Skipped (0s)
  • Code Style Check: ✅ Success (4m 0s)

Total Time: 4m 0s

⚠️ Warning: The following steps failed: Unit Test, Debug Build
See the Actions Log for details.

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

🤖 Android CI Summary

Step Results:

  • Unit Test: ✅ Success (2m 50s)
  • Debug Build: ✅ Success (1m 12s)
  • Code Style Check: ✅ Success (3m 36s)

Total Time: 7m 38s

🎉 All steps completed successfully!

@komodgn komodgn merged commit d29a32e into develop Jan 7, 2026
2 checks passed
@komodgn komodgn deleted the feature/#66 branch January 7, 2026 16:20
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.

1 participant