Flutter Web: RefreshIndicator not working anymore

1.8k Views Asked by At

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

There are 3 best solutions below

4
Neil-NotNeo On

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');
            },
          ),
        ),
      ),
    );
  }
}
0
Joshii Starlet On

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),
    );
  }
}
4
Angel On

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,
        ),
      ),
    ),
  ),
);