Home > Mobile >  Flutter to tell which widget fired a ScrollNotification?
Flutter to tell which widget fired a ScrollNotification?

Time:04-13

I have a SingleChildScrollView and it contains a scrollable TabBar, and I have a NotificationListener which listens for the notifications.

My problem is I get ScrollNotification from both widgets and I do not know how to tell which of the two fired the notification.

ScrollNotification contains BuildContext? and the comment above suggests that this can be used to determine the source, but I do not understand how to use the build context to work out which of my objects fired the notification.

/// The build context of the widget that fired this notification.
///
/// This can be used to find the scrollable's render objects to determine the
/// size of the viewport, for instance.
final BuildContext? context;

CodePudding user response:

I found a way that works but is a little cumbersome. However, it might help you if you do not find an easier solution:
Create your own Notification by extending ScrollNotification. This allows you to add some field like an id, which you can use in your NotificationListener to distinguish where the Notification originates from.
But the cumbersome part is, say you have two widgets that dispatch notifications, you will have to wrap both with their own NotificationListener and use its onNotification method to catch the Notifications and dispatch your custom Notification, with an added id field for recognition, instead.
Below is a complete code sample, in which I have used the above-mentioned solution, dealing with notifications from two different ListViews:

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Test',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

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

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

class _MyHomePageState extends State<MyHomePage> {

  List<String> items = List.filled(20, "SomeText");

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: NotificationListener<MyNotification>(
        onNotification: (notif) {
          print(notif.id);
          return true;
        },
        child: LayoutBuilder(
          builder: (context, constraints) {
            return Column(
              children: [
                SizedBox(
                  height: constraints.maxHeight / 2,
                  width: constraints.maxWidth,
                  child: NotificationListener<ScrollNotification>(
                    onNotification: (notification) {
                      MyNotification(
                              id: "First ListView",
                              metrics: notification.metrics,
                              context: notification.context)
                          .dispatch(context);
                      return true;
                    },
                    child: ListView.builder(
                      controller: ScrollController(),
                      itemCount: items.length,
                      itemBuilder: (context, index) {
                        return ListTile(
                          title: Text(
                            items[index],
                          ),
                        );
                      },
                    ),
                  ),
                ),
                SizedBox(
                  height: constraints.maxHeight / 2,
                  width: constraints.maxWidth,
                  child: NotificationListener<ScrollNotification>(
                    onNotification: (notification) {
                      MyNotification(
                              id: "SecondListView",
                              metrics: notification.metrics,
                              context: notification.context)
                          .dispatch(context);
                      return true;
                    },
                    child: ListView.builder(
                      controller: ScrollController(),
                      itemCount: items.length,
                      itemBuilder: (context, index) {
                        return ListTile(
                          title: Text(
                            items[index],
                          ),
                        );
                      },
                    ),
                  ),
                ),
              ],
            );
          },
        ),
      ),
    );
  }
}

class MyNotification extends ScrollNotification {
  MyNotification(
      {required this.id,
      required ScrollMetrics metrics,
      required BuildContext? context})
      : super(metrics: metrics, context: context);
  final String id;
}

CodePudding user response:

Yes, the BuildContext can be used for that. You can use findAncestorWidgetOfExactType to find whether the triggering widget has a certain ancestor widget type.

For example, I have a screen with 3 scrollable things: a ListView, a GridView and a horizontally scrolling SingleChildScrollView, as shown below:

demo image

In the NotificationListener, we can then check the source of the event, like so:

// Cast the `BuildContext` into an `Element`
final element = (notification.context as Element);
// Try to see if the `Element` has an ancestor of type `ListView`, `GridView` or `SingleChildScrollView`.
final listView = element.findAncestorWidgetOfExactType<ListView>();
final gridView = element.findAncestorWidgetOfExactType<GridView>();
final scrollView = element.findAncestorWidgetOfExactType<SingleChildScrollView>();
// One of them should have a non-null value, the other two would be null.
print('listView: $listView');
print('gridView: $gridView');
print('scrollView: $scrollView');

Full example:

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: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(backgroundColor: Colors.black),
      body: NotificationListener(
        onNotification: (notification) {
          if (notification is ScrollUpdateNotification) {
            final element = (notification.context as Element);
            final listView = element.findAncestorWidgetOfExactType<ListView>();
            final gridView = element.findAncestorWidgetOfExactType<GridView>();
            final scrollView =
                element.findAncestorWidgetOfExactType<SingleChildScrollView>();
            print('listView: $listView');
            print('gridView: $gridView');
            print('scrollView: $scrollView');
          }
          return false;
        },
        child: Column(
          children: [
            Expanded(
              child: ListView.builder(
                itemBuilder: (context, index) => ListTile(
                  title: Text('List View Item $index'),
                ),
              ),
            ),
            Expanded(
              child: GridView.builder(
                gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 2,
                ),
                itemBuilder: (context, index) => Center(
                  child: Text('Grid View Item $index'),
                ),
              ),
            ),
            Expanded(
              child: SingleChildScrollView(
                scrollDirection: Axis.horizontal,
                child: Row(
                  children: [
                    for (int i = 0; i < 100; i  ) FlutterLogo(),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
  • Related