RepoPilotOpen in app →

rubensousa/GravitySnapHelper

A SnapHelper that snaps a RecyclerView to an edge.

Mixed

Single-maintainer risk — review before adopting

weakest axis
Use as dependencyMixed

top contributor handles 99% of recent commits; no tests detected

Fork & modifyHealthy

Has a license, tests, and CI — clean foundation to fork and modify.

Learn fromHealthy

Documented and popular — useful reference codebase to read through.

Deploy as-isHealthy

No critical CVEs, sane security posture — runnable as-is.

  • Last commit 2mo ago
  • 2 active contributors
  • Apache-2.0 licensed
Show all 7 evidence items →
  • CI configured
  • Small team — 2 contributors active in recent commits
  • Single-maintainer risk — top contributor 99% of recent commits
  • No test directory detected
What would change the summary?
  • Use as dependency MixedHealthy if: diversify commit ownership (top <90%)

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Embed the "Forkable" badge

Paste into your README — live-updates from the latest cached analysis.

Variant:
RepoPilot: Forkable
[![RepoPilot: Forkable](https://repopilot.app/api/badge/rubensousa/gravitysnaphelper?axis=fork)](https://repopilot.app/r/rubensousa/gravitysnaphelper)

Paste at the top of your README.md — renders inline like a shields.io badge.

Preview social card (1200×630)

This card auto-renders when someone shares https://repopilot.app/r/rubensousa/gravitysnaphelper on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: rubensousa/GravitySnapHelper

Generated by RepoPilot · 2026-05-09 · Source

🤖Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/rubensousa/GravitySnapHelper shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

🎯Verdict

WAIT — Single-maintainer risk — review before adopting

  • Last commit 2mo ago
  • 2 active contributors
  • Apache-2.0 licensed
  • CI configured
  • ⚠ Small team — 2 contributors active in recent commits
  • ⚠ Single-maintainer risk — top contributor 99% of recent commits
  • ⚠ No test directory detected

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live rubensousa/GravitySnapHelper repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/rubensousa/GravitySnapHelper.

What it runs against: a local clone of rubensousa/GravitySnapHelper — the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in rubensousa/GravitySnapHelper | Confirms the artifact applies here, not a fork | | 2 | License is still Apache-2.0 | Catches relicense before you depend on it | | 3 | Default branch master exists | Catches branch renames | | 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 5 | Last commit ≤ 99 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>rubensousa/GravitySnapHelper</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of rubensousa/GravitySnapHelper. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/rubensousa/GravitySnapHelper.git
#   cd GravitySnapHelper
#
# Then paste this script. Every check is read-only — no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of rubensousa/GravitySnapHelper and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "rubensousa/GravitySnapHelper(\\.git)?\\b" \\
  && ok "origin remote is rubensousa/GravitySnapHelper" \\
  || miss "origin remote is not rubensousa/GravitySnapHelper (artifact may be from a fork)"

# 2. License matches what RepoPilot saw
(grep -qiE "^(Apache-2\\.0)" LICENSE 2>/dev/null \\
   || grep -qiE "\"license\"\\s*:\\s*\"Apache-2\\.0\"" package.json 2>/dev/null) \\
  && ok "license is Apache-2.0" \\
  || miss "license drift — was Apache-2.0 at generation time"

# 3. Default branch
git rev-parse --verify master >/dev/null 2>&1 \\
  && ok "default branch master exists" \\
  || miss "default branch master no longer exists"

# 4. Critical files exist
test -f "library/src/main/java/com/github/rubensousa/gravitysnaphelper/GravitySnapHelper.kt" \\
  && ok "library/src/main/java/com/github/rubensousa/gravitysnaphelper/GravitySnapHelper.kt" \\
  || miss "missing critical file: library/src/main/java/com/github/rubensousa/gravitysnaphelper/GravitySnapHelper.kt"
test -f "library/src/main/java/com/github/rubensousa/gravitysnaphelper/GravitySnapRecyclerView.kt" \\
  && ok "library/src/main/java/com/github/rubensousa/gravitysnaphelper/GravitySnapRecyclerView.kt" \\
  || miss "missing critical file: library/src/main/java/com/github/rubensousa/gravitysnaphelper/GravitySnapRecyclerView.kt"
test -f "app/src/main/java/com/github/rubensousa/recyclerviewsnap/MainActivity.kt" \\
  && ok "app/src/main/java/com/github/rubensousa/recyclerviewsnap/MainActivity.kt" \\
  || miss "missing critical file: app/src/main/java/com/github/rubensousa/recyclerviewsnap/MainActivity.kt"
test -f "build.gradle.kts" \\
  && ok "build.gradle.kts" \\
  || miss "missing critical file: build.gradle.kts"
test -f "README.md" \\
  && ok "README.md" \\
  || miss "missing critical file: README.md"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 99 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~69d)"
else
  miss "last commit was $days_since_last days ago — artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) — safe to trust"
else
  echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/rubensousa/GravitySnapHelper"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

TL;DR

GravitySnapHelper is an Android SnapHelper library that automatically snaps RecyclerView items to a specified gravity edge (start, center, end) during scrolling and flinging. It solves the common problem of achieving smooth, predictable item alignment in horizontal/vertical RecyclerViews with full RTL support, built atop the AndroidX RecyclerView SnapHelper pattern. Simple single-module structure: app/ is the demo application (Kotlin activities like MainActivity, GridActivity using the library). Core library code is likely in a separate library/ or gravitysnaphelper/ module (not shown in file list—check root for Gradle modules). App demonstrates three snap modes (start, center, custom) with decorations and fling limiting.

👥Who it's for

Android developers building horizontal or vertical scrolling lists (e.g., app drawer carousels, content feeds) who need items to snap cleanly to edges without writing custom LayoutManager scroll logic.

🌱Maturity & risk

Production-ready. Version 2.3.0 is released, the repo has CI/CD via GitHub Actions (pullrequest.yml, push.yml), and the codebase is stable with both Java (35KB) and Kotlin (15KB) implementations. However, repo activity level is unclear from timestamps alone—check the GitHub commit history for recent maintenance.

Low risk. Single-maintainer project (rubensousa), but library has minimal external dependencies (just AndroidX RecyclerView). Main risk is Android API compatibility—verify minSdkVersion in build.gradle.kts matches your target. No major breaking changes visible in CHANGELOG.md snippet.

Active areas of work

Library appears mature with no active development signals in the visible file list. The README shows version 2.3.0 is current. Check GitHub Issues and the main branch for any open PRs or pending bug reports.

🚀Get running

git clone https://github.com/rubensousa/GravitySnapHelper.git
cd GravitySnapHelper
./gradlew build
./gradlew :app:installDebug  # Install demo app on connected device/emulator

Daily commands: Open in Android Studio, sync Gradle, then either run :app via IDE or: ./gradlew :app:assembleDebug && adb install build/app/outputs/apk/debug/app-debug.apk

🗺️Map of the codebase

  • library/src/main/java/com/github/rubensousa/gravitysnaphelper/GravitySnapHelper.kt — Core SnapHelper implementation that provides the gravity-based snapping logic—every contributor must understand this to modify snapping behavior
  • library/src/main/java/com/github/rubensousa/gravitysnaphelper/GravitySnapRecyclerView.kt — Custom RecyclerView wrapper that exposes GravitySnapHelper via XML attributes—essential for understanding the public API
  • app/src/main/java/com/github/rubensousa/recyclerviewsnap/MainActivity.kt — Primary demo activity showing how to use GravitySnapHelper in real-world scenarios
  • build.gradle.kts — Root build configuration defining dependencies, versioning, and publishing setup
  • README.md — Usage documentation and API reference for both GravitySnapHelper and GravitySnapRecyclerView

🧩Components & responsibilities

  • GravitySnapHelper (Android SnapHelper, LayoutManager API) — Extends SnapHelper to compute and perform gravity-aligned snapping; intercepts fling gestures and redirects velocity to snap target
    • Failure mode: If gravity parameter is invalid or no items are visible, snap may silently skip; poor performance if called rapidly with large item counts
  • GravitySnapRecyclerView (Custom RecyclerView, AttributeSet inflation) — Wrapper RecyclerView that auto-attaches GravitySnapHelper via XML attributes; exposes configuration (snapGravity, snapEnabled, etc.)
    • Failure mode: If snapGravity attribute is missing or invalid, defaults to no snapping; NullPointerException if attached without LayoutManager
  • LayoutManager (via RecyclerView) (RecyclerView LayoutManager) — Calculates visible item positions and layout metrics used by GravitySnapHelper to determine snap targets
    • Failure mode: If LayoutManager is not linear or grid-based, position queries may return unexpected results
  • Demo Activities (MainActivity, GridActivity, SnapList) (Android Activity, RecyclerView, Adapters) — Showcase different snapping scenarios (horizontal, vertical, grid) and serve as integration tests
    • Failure mode: If demo adapters return no items, snapping has nothing to snap to; layout inflation may fail with missing resources

🔀Data flow

  • User Gesture (Fling)RecyclerView — Touch event triggers fling velocity calculation in RecyclerView
  • RecyclerViewGravitySnapHelper.onFling() — RecyclerView invokes registered SnapHelper's fling handler with velocity vector
  • GravitySnapHelper.onFling()LayoutManager — Helper queries LayoutManager for visible item positions and dimensions to determine snap target
  • LayoutManagerGravitySnapHelper — Returns

🛠️How to make changes

Create a New Snapping Configuration

  1. Instantiate GravitySnapHelper with desired gravity (Gravity.START, Gravity.END, Gravity.TOP, Gravity.BOTTOM) (library/src/main/java/com/github/rubensousa/gravitysnaphelper/GravitySnapHelper.kt)
  2. Call attachToRecyclerView(recyclerView) to attach the helper to your RecyclerView (app/src/main/java/com/github/rubensousa/recyclerviewsnap/MainActivity.kt)
  3. Optional: configure max fling fraction or scroll milliseconds via snapHelper properties (README.md)

Add a New Demo Activity with Custom Snapping

  1. Create new Activity Kotlin file in app/src/main/java/com/github/rubensousa/recyclerviewsnap/ (app/src/main/java/com/github/rubensousa/recyclerviewsnap/MainActivity.kt)
  2. Create corresponding layout XML in app/src/main/res/layout/ with RecyclerView or GravitySnapRecyclerView (app/src/main/res/layout/activity_main.xml)
  3. Instantiate GravitySnapHelper with your desired gravity and attach to RecyclerView (library/src/main/java/com/github/rubensousa/gravitysnaphelper/GravitySnapHelper.kt)
  4. Register new Activity in AndroidManifest.xml (app/src/main/AndroidManifest.xml)

Use GravitySnapRecyclerView via XML (No Code Required)

  1. Add GravitySnapRecyclerView to your layout XML with snap configuration attributes (app/src/main/res/layout/activity_main.xml)
  2. Set snapGravity attribute to 'start', 'end', 'top', or 'bottom' (README.md)
  3. Optional: configure snapEnabled, snapLastItem, snapToPadding, snapScrollMsPerInch, snapMaxFlingSizeFraction (library/src/main/java/com/github/rubensousa/gravitysnaphelper/GravitySnapRecyclerView.kt)

🔧Why these technologies

  • Android SnapHelper — Provides official RecyclerView snap-to-position framework; GravitySnapHelper extends this to add gravity-aware edge alignment
  • Kotlin — Modern Android development standard with null-safety and cleaner syntax than Java for UI logic
  • Gradle (Kotlin DSL) — Enables type-safe build configuration and easy multi-module project management

⚖️Trade-offs already made

  • Snap-to-edge via gravity rather than center-snap

    • Why: Enables natural edge-aligned UIs matching Material Design patterns
    • Consequence: Users must align items to start/end/top/bottom rather than center; not suitable for center-focused carousels
  • Single gravity direction per SnapHelper instance

    • Why: Simplifies calculation and avoids ambiguous snap targets
    • Consequence: Cannot snap to multiple edges simultaneously; separate instances needed for different constraints
  • Lightweight SnapHelper extension vs. custom LayoutManager

    • Why: Minimal performance overhead and leverages Android framework stability
    • Consequence: Some advanced custom scroll behaviors may require LayoutManager override instead

🚫Non-goals (don't propose these)

  • Does not provide automatic item centering or center-based snapping
  • Does not handle real-time item insertion/deletion during active scroll
  • Does not include built-in paging or infinite scroll handlers
  • Not designed for 3D transformations or complex item animations

🪤Traps & gotchas

Missing: Library module path not visible in file list—the actual GravitySnapHelper source code must be in a sibling module (likely library/ or gravitysnaphelper/). Check root settings.gradle.kts to find it. RTL gotcha: Library claims RTL support out-of-box, but verify with Gravity.START vs Gravity.END behavior on actual RTL-enabled devices. Fling limits: setMaxFlingSizeFraction is a fraction, not pixels—easy to set too aggressively (>1.0) and break scrolling. Padding: snapToPadding defaults to false, which may surprise developers expecting margin-aware snapping.

🏗️Architecture

💡Concepts to learn

  • SnapHelper pattern — GravitySnapHelper extends AndroidX RecyclerView.SnapHelper; understanding how findTargetSnapPosition() and calculateSpeedPerPixel() work is core to extending or debugging snapping behavior.
  • Gravity and layout direction (RTL support) — Library advertises RTL support out-of-box; Gravity.START behaves differently in RTL contexts, and understanding how RecyclerView resolves gravity with layout direction is critical for predictable cross-locale behavior.
  • Fling velocity and scroll physics — The calculateSpeedPerPixel() and setMaxFlingSizeFraction() APIs control deceleration after fling; understanding how Android's VelocityTracker and OverScroller work informs correct fling limiting.
  • ItemDecoration and visual padding — snapToPadding and the LinearEdgeDecoration demo in the app show how ItemDecoration interacts with snap targets; decorations can visually offset snap points without changing bounds.
  • Nested RecyclerView touch handling — README links to two blog posts on nested RecyclerViews; snapping in nested contexts requires careful parent-child scroll coordination to avoid touch interception issues.
  • androidx/androidx — Official AndroidX RecyclerView package—GravitySnapHelper extends RecyclerView.SnapHelper, so understanding the base class is essential.
  • airbnb/epoxy — Alternative RecyclerView abstraction with built-in snap support; complementary tooling if you need complex item binding alongside snapping.
  • niqo-developer/SnapView — Similar gravity-based snapping library for RecyclerView, useful for comparing implementation approaches and feature trade-offs.
  • rubensousa/Ruler — Companion project by same author for measuring RecyclerView scroll distances, pairs well with GravitySnapHelper for custom scroll animations.

🪄PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add instrumented tests for GravitySnapHelper snap behavior

The repo lacks test coverage for the core snapping logic. There are no visible test files in app/src/test or app/src/androidTest directories. Adding instrumented tests would validate that GravitySnapHelper correctly snaps RecyclerViews to different gravity edges (START, END, TOP, BOTTOM) and that edge cases like snapLastItem and snapToPadding work as intended.

  • [ ] Create app/src/androidTest/java/com/github/rubensousa/gravitysnaphelper/ directory structure
  • [ ] Add GravitySnapHelperTest.kt testing snap behavior for Gravity.START, Gravity.END, Gravity.TOP, Gravity.BOTTOM
  • [ ] Test edge cases: snapLastItem=true/false, snapToPadding=true/false, fling velocity thresholds
  • [ ] Add GravitySnapRecyclerViewTest.kt testing XML attribute parsing and integration
  • [ ] Update app/build.gradle.kts with androidTest dependencies (androidx.test.espresso, junit)

Add example activity demonstrating snapScrollMsPerInch and snapMaxFlingSizeFraction customization

The README mentions snapScrollMsPerInch and snapMaxFlingSizeFraction XML attributes, but the demo app (MainActivity.kt, GridActivity.kt) doesn't visually showcase these customization options. A dedicated example activity would help users understand performance tuning and fling behavior configuration.

  • [ ] Create app/src/main/java/com/github/rubensousa/recyclerviewsnap/ScrollSpeedActivity.kt demonstrating different snapScrollMsPerInch values (default vs custom)
  • [ ] Create app/src/main/res/layout/activity_scroll_speed.xml with GravitySnapRecyclerView using custom snapScrollMsPerInch and snapMaxFlingSizeFraction
  • [ ] Add navigation from MainActivity.kt to ScrollSpeedActivity.kt in the demo menu
  • [ ] Update README.md with a section showing example XML configurations for these attributes

Add unit tests for LinearEdgeDecoration positioning logic

LinearEdgeDecoration.kt exists in the demo app but there are no unit tests validating its edge decoration drawing logic. Adding tests would ensure decorations render correctly at snap edges and don't break with different RecyclerView orientations and paddings.

  • [ ] Create app/src/test/java/com/github/rubensousa/recyclerviewsnap/ directory
  • [ ] Add LinearEdgeDecorationTest.kt with unit tests for getItemOffsets() behavior with different orientations (HORIZONTAL, VERTICAL)
  • [ ] Test edge cases: snapToPadding=true/false, various padding configurations, last item handling
  • [ ] Update app/build.gradle.kts with test dependencies (junit, mockito) if not already present

🌿Good first issues

  • Add unit tests for GravitySnapHelper.findTargetSnapPosition() covering all gravity values (START, CENTER, END) and edge cases (empty list, single item, RTL layouts) in app/src/test/.
  • Document the relationship between snapToPadding, RecyclerView padding, and actual snap position with code examples in README.md and add a demo in GridActivity.kt showing padding-aware snapping.
  • Create an example in app/ showing nested RecyclerViews with GravitySnapHelper, linking to the referenced blog posts (Improving scrolling behavior... / Saving scroll state...) with working demo code.

Top contributors

Click to expand

📝Recent commits

Click to expand
  • 01a438a — Merge pull request #97 from rubensousa/release (rubensousa)
  • 06289d6 — Create push task (rubensousa)
  • eb6da6e — Update readme (rubensousa)
  • 9133e27 — Release 2.3.0 (rubensousa)
  • a214ce1 — Merge pull request #96 from rubensousa/update_recyclerview (rubensousa)
  • 9ecced7 — Update gradle.properties (rubensousa)
  • 4af7afc — Update gradle.properties (rubensousa)
  • bf94e93 — Remove package from manifest (rubensousa)
  • c78885c — Update distribution sha (rubensousa)
  • a25c1cd — Update gradle wrapper (rubensousa)

🔒Security observations

The GravitySnapHelper library demonstrates a generally secure codebase with minimal attack surface. As a UI library for Android RecyclerView functionality, it avoids common web vulnerabilities (XSS, SQLi) and does not expose hardcoded secrets. However, a complete dependency vulnerability assessment cannot be performed without the full build.gradle.kts file. The main recommendation is to provide dependency information for comprehensive scanning and establish formal secrets management documentation. The presence of CI/CD workflows and licensing is a positive security indicator.

  • Low · Missing dependency information in analysis — app/build.gradle.kts. The dependencies/package file content was not provided in the security context, making it impossible to perform a comprehensive dependency vulnerability scan. This should include gradle, Maven, or other dependency management files to identify outdated or vulnerable libraries. Fix: Provide the complete build.gradle.kts file content to scan for vulnerable dependencies. Use tools like OWASP Dependency-Check or Gradle's built-in dependency vulnerability scanning.
  • Low · No secrets management configuration visible — Repository root. While no hardcoded secrets were found in the visible file structure, there are no explicit secrets management practices documented. The codebase could benefit from clear guidelines on handling sensitive data. Fix: Implement a secrets management strategy. Document in README or CONTRIBUTING guidelines how to handle API keys, tokens, and credentials. Use environment variables or secure vaults for any sensitive configuration.

LLM-derived; treat as a starting point, not a security audit.


Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

Mixed signals · rubensousa/GravitySnapHelper — RepoPilot