本文へ移動
cccskills
無料GitHub で公開

flutter-pageview-scroll-position-preservation

Fix Flutter PageView "snap back" or scroll position reset issues on provider/state rebuilds. Use when: (1) PageView scrolls then snaps back to previous page after state update, (2) Scroll position resets when Riverpod/Provider rebuilds, (3) Feed scrolls back to top on data refresh, (4) PageView loses position after orientation change or widget rebuild.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md4.6 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Flutter PageView Scroll Position Preservation

Problem

Users scrolling through a PageView-based feed report that the scroll position resets or "snaps back" to a previous page, especially after state updates from providers (Riverpod, Provider, BLoC) or widget rebuilds.

Context / Trigger Conditions

  • PageView with dynamic content from state management
  • Provider/Riverpod state updates causing widget rebuilds
  • Pagination that changes itemCount
  • Orientation changes
  • Tab switching and returning to the PageView

Root Cause

When Flutter rebuilds the widget tree (due to state changes), the PageView may lose its scroll position if:

  1. No PageStorageKey is provided to persist state
  2. The PageController is recreated during rebuilds
  3. PageController.keepPage is set to false

Note: allowImplicitScrolling is for accessibility focus navigation, NOT scroll position preservation. A specific bug (#76569) involving TextField focus was fixed in Flutter.

Solution

1. Add PageStorageKey (PRIMARY FIX)

PageView.builder(
  // This key tells Flutter to persist scroll state across rebuilds
  key: const PageStorageKey<String>('my_page_view'),
  itemCount: videos.length,
  controller: _pageController,
  ...
)

2. Keep PageController in State (not build)

class _MyScreenState extends State<MyScreen> {
  // Create in initState, NOT in build()
  late PageController _pageController;

  @override
  void initState() {
    super.initState();
    _pageController = PageController();  // Created once
  }

  @override
  void dispose() {
    _pageController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // Use the same controller instance
    return PageView.builder(
      controller: _pageController,
      ...
    );
  }
}

3. Ensure keepPage is true (default)

_pageController = PageController(
  keepPage: true,  // This is the default, but be explicit if needed
  initialPage: 0,
);

4. For complex state: Use PageStorage widget

PageStorage(
  bucket: PageStorageBucket(),
  child: PageView.builder(
    key: const PageStorageKey<String>('feed'),
    ...
  ),
)

What allowImplicitScrolling Actually Does

This is for accessibility, not scroll position:

  • false (default): Accessibility focus exits PageView at page boundaries
  • true: Accessibility focus moves to next page instead of exiting widget

It does NOT cause scroll position issues. Keep it true for better accessibility.

Verification

  1. Scroll to page 10+ in the feed
  2. Trigger a state update (like new data arriving)
  3. Verify scroll position is preserved
  4. Rotate device - verify position preserved
  5. Switch tabs and return - verify position preserved

Example

Full pattern for a Riverpod-based feed:

class _VideoFeedScreenState extends ConsumerState<VideoFeedScreen> {
  late PageController _pageController;

  @override
  void initState() {
    super.initState();
    _pageController = PageController();
  }

  @override
  void dispose() {
    _pageController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final videos = ref.watch(videoFeedProvider);

    return PageView.builder(
      key: const PageStorageKey<String>('video_feed'),
      controller: _pageController,
      itemCount: videos.length,
      allowImplicitScrolling: true,  // For accessibility
      itemBuilder: (context, index) => VideoItem(videos[index]),
    );
  }
}

Notes

  • The Riverpod 3.2.0 issue #4661 about ProviderScope rebuilds is separate
  • If using tabs, wrap in AutomaticKeepAliveClientMixin to preserve state
  • For very long lists, consider using restorationId for app restart persistence

References

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Fix ArgoCD ExternalSecret deployment failing with "namespace X is not permitted in project Y". Use when: (1) ExternalSecret shows OutOfSync in ArgoCD but won't sync, (2) ArgoCD application status shows "namespace X is not permitted in project 'infrastructure'", (3) ExternalSecret targets a namespace managed by a different ArgoCD project, (4) Using apps-of-apps pattern with separate infrastructure and application projects.

日本語の概要は準備中です。原文の説明を表示しています。

divinevideo/divine-mobile2662026年10月12日 更新

Art direction for any content — reads text, PDF, Word, HTML, PPT, then proposes 2-3 creative directions with photography style, mood, and visual language. After selection, generates AI image prompts and visual briefs section-by-section. Use when the user shares content and needs visual direction, image sourcing, or creative direction for any material.

日本語の概要は準備中です。原文の説明を表示しています。

divinevideo/divine-mobile2662026年10月12日 更新

Fix "Null check operator used on a null value" errors when an object is set to null during an async await. Use when: (1) Object reference is nullified while awaiting, (2) Code accesses object with ! after await returns, (3) Cancel/dispose operations run concurrently with async operations on same object. Solution: capture local reference before await.

日本語の概要は準備中です。原文の説明を表示しています。

divinevideo/divine-mobile2662026年10月12日 更新

Add custom metadata headers (x-amz-meta-*) to AWS v4 signed requests for GCS S3-compatible API. Use when: (1) Adding custom metadata to GCS uploads via S3 API, (2) Getting signature mismatch errors after adding new headers, (3) x-amz-meta-* headers being ignored or causing 403 errors. Custom headers MUST be included in canonical headers and signed headers list.

日本語の概要は準備中です。原文の説明を表示しています。

divinevideo/divine-mobile2662026年10月12日 更新

Fix password/secret authentication failures caused by trailing newlines when creating Google Cloud secrets (or similar) with bash here-strings. Use when: (1) Password authentication fails with correct password, (2) Secret created with `<<< "value"` syntax, (3) Error like "password authentication failed" or "invalid token" despite correct value. Bash here-strings (`<<<`) add a trailing newline that corrupts secrets.

日本語の概要は準備中です。原文の説明を表示しています。

divinevideo/divine-mobile2662026年10月12日 更新

Fix silent video/media processing failures caused by URL extraction code that filters on file extensions (.mp4, .webm, .webp). Use when: (1) Media moderation, transcoding, or analysis silently skips files from Blossom or content-addressed storage servers, (2) URL extraction from Nostr event tags (imeta, r tags) drops URLs without recognized extensions, (3) CDN fallback URLs append .mp4 but the actual server uses extensionless content-addressed paths like /{sha256}. Common in Nostr video events (kind 34236) where different clients use different URL formats.

日本語の概要は準備中です。原文の説明を表示しています。

divinevideo/divine-mobile2662026年10月12日 更新

divinevideo のスキルをすべて見る

このスキルの問題を報告する