Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PageView seems to ignore PageController's page after state changes #108961

Closed
jfahrenkrug opened this issue Aug 4, 2022 · 6 comments · Fixed by #108971
Closed

PageView seems to ignore PageController's page after state changes #108961

jfahrenkrug opened this issue Aug 4, 2022 · 6 comments · Fixed by #108971
Assignees
Labels
found in release: 3.0 Found to occur in 3.0 found in release: 3.1 Found to occur in 3.1 framework flutter/packages/flutter repository. See also f: labels. has reproducible steps The issue has been confirmed reproducible and is ready to work on P2 Important issues not at the top of the work list r: fixed Issue is closed as already fixed in a newer version

Comments

@jfahrenkrug
Copy link

Background

I was trying to add "swipe to dismiss" to my easy_image_viewer project. I wrapped a PageView inside of a Dismissible widget in order to change the dismissDirection from down to none when the user has zoomed in to an image (to allow the user to pan around without triggering the dismissal).
However, when changing the dismissDirection, the PageView always jumps back to the first page. I had already filed this here #106708, but I was asked by @maheshmnj to provide a minimal example without any dependencies. Here it is :) Could you take another look @maheshmnj? Thanks!

Steps to Reproduce

  1. Open https://dartpad.dev/?id=3862ceb9c5b74271b99363933e67bdee
  2. Run the sample
  3. Tap on the "Next" button
  4. Tap on the "Change Dismiss Direction" button

Expected results: The app stays on the second page

Actual results: The app jumps back to the first page

Code sample
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'EasyImageViewer Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyPageView(),
    );
  }
}

class MyPageView extends StatefulWidget {
  const MyPageView({Key? key}) : super(key: key);

  @override
  State<MyPageView> createState() => _MyPageViewState();
}

class _MyPageViewState extends State<MyPageView> {
  final PageController _pageController = PageController();
  DismissDirection _dismissDirection = DismissDirection.down;

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: Scaffold(
      body: Dismissible(
        direction: _dismissDirection,
        resizeDuration: null,
        onDismissed: (_) {
          Navigator.of(context).pop();
        },
        key: const Key('dismissable_easy_image_viewer_dialog'),
        child: PageView(
          controller: _pageController,
          children: <Widget>[
            Container(
              color: Colors.red,
              child: Center(
                child: ElevatedButton(
                  onPressed: () {
                    if (_pageController.hasClients) {
                      _pageController.animateToPage(
                        1,
                        duration: const Duration(milliseconds: 400),
                        curve: Curves.easeInOut,
                      );
                    }
                  },
                  child: const Text('Next'),
                ),
              ),
            ),
            Container(
              color: Colors.blue,
              child: Center(
                child: ElevatedButton(
                  onPressed: () {
                    setState(() {
                      _dismissDirection = DismissDirection.none;
                    });
                  },
                  child: const Text('Change Dismiss Direction'),
                ),
              ),
            ),
          ],
        ),
      ),
    ));
  }
}
[✓] Flutter (Channel stable, 3.0.2, on macOS 12.4 21F79 darwin-arm, locale en-US)
    • Flutter version 3.0.2 at /Users/johannes/Code/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision cd41fdd495 (8 weeks ago), 2022-06-08 09:52:13 -0700
    • Engine revision f15f824b57
    • Dart version 2.17.3
    • DevTools version 2.12.2

[✓] Android toolchain - develop for Android devices (Android SDK version 31.0.0)
    • Android SDK at /Users/johannes/Library/Android/sdk
    • Platform android-31, build-tools 31.0.0
    • Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 11.0.11+0-b60-7772763)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 13.4)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • CocoaPods version 1.11.3

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2021.1)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 11.0.11+0-b60-7772763)

[✓] VS Code (version 1.69.2)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.46.0

[✓] Connected device (5 available)
    • sdk gphone64 arm64 (mobile)          • emulator-5554                        • android-arm64  • Android 12 (API 32) (emulator)
    • iPod touch (7th generation) (mobile) • 187677E9-0221-44D3-B8AA-A6382CD4FCB5 • ios            • com.apple.CoreSimulator.SimRuntime.iOS-15-5 (simulator)
    • iPad (9th generation) (mobile)       • 6EEE8A28-98D4-45D9-BB99-2E7AE15EADC2 • ios            • com.apple.CoreSimulator.SimRuntime.iOS-15-5 (simulator)
    • macOS (desktop)                      • macos                                • darwin-arm64   • macOS 12.4 21F79 darwin-arm
    • Chrome (web)                         • chrome                               • web-javascript • Google Chrome 103.0.5060.134
    ! Error: Johannes' iPad Air is not connected. Xcode will continue when Johannes' iPad Air is connected. (code -13)

[✓] HTTP Host Availability
    • All required HTTP hosts are available

• No issues found!
@jfahrenkrug
Copy link
Author

I figured out my mistake by looking at the Dismissible widget's source code:

if (widget.direction == DismissDirection.none) {
When DismissDirection is changed to none, the parent changes because the child is returned directly instead of being wrapped in a GestureDetector. So adding a GlobalKey to the PageView solves the problem. Re-watching this video helped a lot: https://www.youtube.com/watch?v=kn0EOS-ZiIc

@xu-baolin
Copy link
Member

I figured out my mistake by looking at the Dismissible widget's source code:

if (widget.direction == DismissDirection.none) {

When DismissDirection is changed to none, the parent changes because the child is returned directly instead of being wrapped in a GestureDetector. So adding a GlobalKey to the PageView solves the problem. Re-watching this video helped a lot: https://www.youtube.com/watch?v=kn0EOS-ZiIc

Thanks for reporting this.
I think this a bug of framework, change widget propertity should not lose state of the child sub-tree.

@xu-baolin xu-baolin self-assigned this Aug 4, 2022
@jfahrenkrug
Copy link
Author

jfahrenkrug commented Aug 4, 2022

@xu-baolin I agree, it's definitely unexpected. It could probably be solved by always returning the same hierarchy: The child being wrapped in a GestureDetector. When dismissDirection is none, simple change the GestureDetector to not do anything.

@danagbemava-nc danagbemava-nc added the in triage Presently being triaged by the triage team label Aug 4, 2022
@danagbemava-nc
Copy link
Member

Reproducible using the code sample provided above.

video
Simulator.Screen.Recording.-.iPhone.13.Pro.-.2022-08-04.at.12.48.21.mp4
code sample
import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'EasyImageViewer Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyPageView(),
    );
  }
}

class MyPageView extends StatefulWidget {
  const MyPageView({Key? key}) : super(key: key);

  @override
  State<MyPageView> createState() => _MyPageViewState();
}

class _MyPageViewState extends State<MyPageView> {
  final PageController _pageController = PageController();
  DismissDirection _dismissDirection = DismissDirection.down;

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Dismissible(
          direction: _dismissDirection,
          resizeDuration: null,
          onDismissed: (_) {
            Navigator.of(context).pop();
          },
          key: const Key('dismissable_easy_image_viewer_dialog'),
          child: PageView(
            controller: _pageController,
            children: <Widget>[
              Container(
                color: Colors.red,
                child: Center(
                  child: ElevatedButton(
                    onPressed: () {
                      if (_pageController.hasClients) {
                        _pageController.animateToPage(
                          1,
                          duration: const Duration(milliseconds: 400),
                          curve: Curves.easeInOut,
                        );
                      }
                    },
                    child: const Text('Next'),
                  ),
                ),
              ),
              Container(
                color: Colors.blue,
                child: Center(
                  child: ElevatedButton(
                    onPressed: () {
                      setState(() {
                        if(_dismissDirection == DismissDirection.none)  {
                          _dismissDirection = DismissDirection.down;
                          return;
                        }
                        _dismissDirection = DismissDirection.none;
                      });
                    },
                    child: const Text('Change Dismiss Direction'),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
flutter doctor -v
[✓] Flutter (Channel stable, 3.0.5, on macOS 12.4 21F79 darwin-arm, locale en-GB)
    • Flutter version 3.0.5 at /Users/nexus/dev/sdks/flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision f1875d570e (3 weeks ago), 2022-07-13 11:24:16 -0700
    • Engine revision e85ea0e79c
    • Dart version 2.17.6
    • DevTools version 2.12.2

[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0)
    • Android SDK at /Users/nexus/Library/Android/sdk
    • Platform android-33, build-tools 33.0.0
    • Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 13.4.1)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • CocoaPods version 1.11.3

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2021.2)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840)

[✓] VS Code (version 1.69.2)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.46.0

[✓] Connected device (3 available)
    • sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64  • Android 13 (API 33) (emulator)
    • macOS (desktop)             • macos         • darwin-arm64   • macOS 12.4 21F79 darwin-arm
    • Chrome (web)                • chrome        • web-javascript • Google Chrome 104.0.5112.79

[✓] HTTP Host Availability
    • All required HTTP hosts are available

• No issues found!
[✓] Flutter (Channel master, 3.1.0-0.0.pre.2084, on macOS 12.4 21F79 darwin-arm, locale en-GB)
    • Flutter version 3.1.0-0.0.pre.2084 on channel master at /Users/nexus/dev/sdks/flutters
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 1b6473f45c (4 hours ago), 2022-08-04 01:14:23 -0400
    • Engine revision 7b6911a843
    • Dart version 2.19.0 (build 2.19.0-61.0.dev)
    • DevTools version 2.16.0

[✓] Android toolchain - develop for Android devices (Android SDK version 33.0.0)
    • Android SDK at /Users/nexus/Library/Android/sdk
    • Platform android-33, build-tools 33.0.0
    • Java binary at: /Applications/Android Studio.app/Contents/jre/Contents/Home/bin/java
    • Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840)
    • All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 13.4.1)
    • Xcode at /Applications/Xcode.app/Contents/Developer
    • Build 13F100
    • CocoaPods version 1.11.3

[✓] Chrome - develop for the web
    • Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Android Studio (version 2021.2)
    • Android Studio at /Applications/Android Studio.app/Contents
    • Flutter plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
      🔨 https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 11.0.12+0-b1504.28-7817840)

[✓] VS Code (version 1.69.2)
    • VS Code at /Applications/Visual Studio Code.app/Contents
    • Flutter extension version 3.46.0

[✓] Connected device (3 available)
    • sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64  • Android 13 (API 33) (emulator)
    • macOS (desktop)             • macos         • darwin-arm64   • macOS 12.4 21F79 darwin-arm
    • Chrome (web)                • chrome        • web-javascript • Google Chrome 104.0.5112.79

[✓] HTTP Host Availability
    • All required HTTP hosts are available

• No issues found!

@danagbemava-nc danagbemava-nc added framework flutter/packages/flutter repository. See also f: labels. has reproducible steps The issue has been confirmed reproducible and is ready to work on found in release: 3.0 Found to occur in 3.0 found in release: 3.1 Found to occur in 3.1 and removed in triage Presently being triaged by the triage team labels Aug 4, 2022
jfahrenkrug added a commit to thesmythgroup/easy_image_viewer that referenced this issue Aug 4, 2022
A ticket asked for swipe-to-dismiss (#14)
It's easy to implement and now supported with the `swipeDismissible` argument.
This allows the user to drag the dialog down to dismiss it.

Initially there was a problem when the user zoomed in and tried to pan around on the
zoomed-in image: The `Dismissible` widget's GestureDetector would take over and
enter the dismissal animation. Watching the image's scale and then changing the Dismissible's
`dismissDirection` from `down` to `none` solves the issue. However, additionally a
`GlobalObjectKey` was needed for the PageView because Dismissible changes the location of
the widget in the widget tree when its `dismissDirection` is changed to `none`.

See https://github.com/flutter/flutter/blob/2aa348b9407e96ffe4eca8e8f213c7984afad3f7/packages/flutter/lib/src/widgets/dismissible.dart#L692
See flutter/flutter#108961
See https://www.youtube.com/watch?v=kn0EOS-ZiIc
@goderbauer goderbauer added the P2 Important issues not at the top of the work list label Aug 9, 2022
@maheshj01 maheshj01 added the r: fixed Issue is closed as already fixed in a newer version label Aug 19, 2022
@jfahrenkrug
Copy link
Author

@xu-baolin Thanks so much for fixing this!

@github-actions
Copy link

github-actions bot commented Sep 2, 2022

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of flutter doctor -v and a minimal reproduction of the issue.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 2, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
found in release: 3.0 Found to occur in 3.0 found in release: 3.1 Found to occur in 3.1 framework flutter/packages/flutter repository. See also f: labels. has reproducible steps The issue has been confirmed reproducible and is ready to work on P2 Important issues not at the top of the work list r: fixed Issue is closed as already fixed in a newer version
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants