3

While RefreshIndicator was previously working fine on both, mobile and web, it doesn't refresh a screen on Flutter Web anymore. It's not possible to overscroll anymore on any of my screens, even if it's a long list, where it is easily possible on the mobile version.

I noticed the problem after updating from flutter 3.3.x to 3.7.9

enter image description here

Here is another simplified example. On phone it works well to reload the random generated numbers, on web nothing happens:

import 'package:flutter/material.dart';
import 'dart:math';

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

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String title = 'Hello';
  var rng = Random();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text(title),
        ),
        body: RefreshIndicator(
          onRefresh: () async => setState(() {
            title = 'Hey';
          }),
          child: ListView.builder(
            physics: const AlwaysScrollableScrollPhysics(),
            itemBuilder: (_, i) => Container(
              padding: const EdgeInsets.all(10),
              color: Colors.lightBlue,
              width: double.infinity,
              height: 50,
              child: Text(
                rng.nextInt(100).toString(),
                style: Theme.of(context).textTheme.bodyLarge!.copyWith(
                      color: Colors.white,
                    ),
              ),
            ),
            itemCount: 200,
          ),
        ),
      ),
    );
  }
}

I tried to google for many hours, but didn't find any helpful information. Any ideas? Thank you

EDIT: Version by Niladri Raychaudhuri. Still the same problem enter image description here

3 Answers3

3

Gif example

I found a better way to fix that, it's simple!

Wrap your main content that receives refresh Indicator or list View with this

On behavior u can define scroll type with bouncing work perfect for me my content will back to up when end to push down

ScrollConfiguration(
              behavior: ScrollConfiguration.of(context).copyWith(
                physics: const BouncingScrollPhysics(),
                dragDevices: {
                  PointerDeviceKind.touch,
                  PointerDeviceKind.mouse,
                  PointerDeviceKind.trackpad
                },
              ),

On your code

 return MaterialApp(
  title: 'Flutter Demo',
  theme: ThemeData(
    primarySwatch: Colors.blue,
  ),
  home: Scaffold(
    appBar: AppBar(
      title: Text(title),
    ),
    body: ScrollConfiguration(
      behavior: ScrollConfiguration.of(context).copyWith(
        physics: const BouncingScrollPhysics(),
        dragDevices: {
          PointerDeviceKind.touch,
          PointerDeviceKind.mouse,
          PointerDeviceKind.trackpad
        },
      ),
      child: RefreshIndicator(
        onRefresh: () async => setState(() {
          title = 'Hey';
        }),
        child: ListView.builder(
          physics: const AlwaysScrollableScrollPhysics(),
          itemBuilder: (_, i) => Container(
            padding: const EdgeInsets.all(10),
            color: Colors.lightBlue,
            width: double.infinity,
            height: 50,
            child: Text(
              rng.nextInt(100).toString(),
              style: Theme.of(context).textTheme.bodyLarge!.copyWith(
                    color: Colors.white,
                  ),
            ),
          ),
          itemCount: 200,
        ),
      ),
    ),
  ),
);
Angel
  • 33
  • 4
0

UPDATE 2

Added PointerDeviceKind.trackpad to set of PointerDeviceKind

UPDATE:

This is a known issue or maybe intended for Flutter Web and has been reported in Flutter Github

Reported Issue

You need a custom scroll behaviour to override behavior methods and getters like dragDevices.

Follow the steps:

1. Add plugin

custom_refresh_indicator: ^2.0.1.

The solution should work with the native refreshIndicator as well.

2. Write your custom scroll behaviour

class MyCustomScrollBehavior extends MaterialScrollBehavior {
  // Override behavior methods and getters like dragDevices
  @override
  Set<PointerDeviceKind> get dragDevices => {
        PointerDeviceKind.touch,
        PointerDeviceKind.mouse,
        PointerDeviceKind.trackpad
        // etc.
      };
}

3.Define your scroll controller

 final ScrollController controller = ScrollController();

4. Wrap your listView with a ScrollConfiguration widget and add your custom scroll behaviour to it

child: ScrollConfiguration(
        behavior: MyCustomScrollBehavior(),
        child: ListView.builder(
          physics: const AlwaysScrollableScrollPhysics(),
          itemBuilder: (_, i) => Container(
            padding: const EdgeInsets.all(10),
            color: Colors.lightBlue,
            width: double.infinity,
            height: 50,
            child: Text(
              rng.nextInt(100).toString(),
              style: Theme.of(context).textTheme.bodyLarge!.copyWith(
                    color: Colors.white,
                  ),
            ),
          ),
          itemCount: 200,
        ),
      ),

5. Finally wrap it with CustomRefreshIndicator or native RefreshIndicator

CustomRefreshIndicator(
      builder: MaterialIndicatorDelegate(
        builder: (context, controller) {
          return const Icon(
            Icons.ac_unit,
            color: Colors.blue,
            size: 30,
          );
        },
      ),
      child: ScrollConfiguration(
        behavior: MyCustomScrollBehavior(),
        child: ListView.builder(
          physics: const AlwaysScrollableScrollPhysics(),
          itemBuilder: (_, i) => Container(
            padding: const EdgeInsets.all(10),
            color: Colors.lightBlue,
            width: double.infinity,
            height: 50,
            child: Text(
              rng.nextInt(100).toString(),
              style: Theme.of(context).textTheme.bodyLarge!.copyWith(
                    color: Colors.white,
                  ),
            ),
          ),
          itemCount: 200,
        ),
      ),
      onRefresh: () async => setState(
        () {
          title = 'Hey';
          print('Page is refreshed');
        },
      ),
    ),

Final Note: I have used CustomRefreshIndicator here, you could use native RefreshIndicator

Full Code

import 'package:custom_refresh_indicator/custom_refresh_indicator.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'dart:math';

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

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class MyCustomScrollBehavior extends MaterialScrollBehavior {
  // Override behavior methods and getters like dragDevices
  @override
  Set<PointerDeviceKind> get dragDevices => {
        PointerDeviceKind.touch,
        PointerDeviceKind.mouse,
        // etc.
      };
}

class _MyHomePageState extends State<MyHomePage> {
  String title = 'Hello';
  var rng = Random();
  final ScrollController controller = ScrollController();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text(title),
        ),
        body: CustomRefreshIndicator(
          builder: MaterialIndicatorDelegate(
            builder: (context, controller) {
              return const Icon(
                Icons.ac_unit,
                color: Colors.blue,
                size: 30,
              );
            },
          ),
          child: ScrollConfiguration(
            behavior: MyCustomScrollBehavior(),
            child: ListView.builder(
              physics: const AlwaysScrollableScrollPhysics(),
              itemBuilder: (_, i) => Container(
                padding: const EdgeInsets.all(10),
                color: Colors.lightBlue,
                width: double.infinity,
                height: 50,
                child: Text(
                  rng.nextInt(100).toString(),
                  style: Theme.of(context).textTheme.bodyLarge!.copyWith(
                        color: Colors.white,
                      ),
                ),
              ),
              itemCount: 200,
            ),
          ),
          onRefresh: () async => setState(
            () {
              title = 'Hey';
              print('Page is refreshed');
            },
          ),
        ),
      ),
    );
  }
}
  • I tried these steps, but unfortunately it still doesn't work on flutter web, even if I use exactly your final code. I added a new gif to the original post – Joshii Starlet Apr 06 '23 at 11:56
  • Are you using a mouse or trackpad? Its working at my end though, try adding PointerDeviceKind.trackpad to the dragDevices in MyCustomScrollBehavior. Note: The web drag is bit different here, try with a fast swipe on trackpad. – Niladri Raychaudhuri Apr 06 '23 at 12:44
  • I am using the trackpad of a Macbook Pro M1, tried adding all options of PointerDeviceKind now. Even if I scroll from the top to the very bottom of the screen, it is not enough to reload it. One time I made it work somehow, with pushing the trackpad down so hard, but I cannot reproduce it again (I currently don't have a mouse to test it). With my old version of the app it is still easily possible, even with the trackpad. But with the new flutter version, I cannot get it to work. – Joshii Starlet Apr 07 '23 at 12:16
  • I also tested it with mouse now, and I am still not able to refresh a page on flutter web with the current flutter version – Joshii Starlet May 09 '23 at 14:37
0

Workaround until the RefreshIndicator works again for Flutter Web:

Use a scrollListener and do this:

Future<void> _scrollListener() async {
  if (kIsWeb && _scrollController.position.pixels < -40) {
    _scrollController.animateTo(
      -500,
      curve: Curves.easeOut,
      duration: const Duration(milliseconds: 300),
    );
  }
}
Tyler2P
  • 2,324
  • 26
  • 22
  • 31