hcbpassos / drag_select_grid_view

A grid that supports both dragging and tapping to select its items.
BSD 2-Clause "Simplified" License
133 stars 34 forks source link

DragSelectGridViewController disposed prematurely #13

Closed vin-fandemand closed 4 years ago

vin-fandemand commented 4 years ago

Consider the following code :

import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
import 'package:drag_select_grid_view/drag_select_grid_view.dart';

import 'selectable_item.dart';
import 'selection_app_bar.dart';

void main() {
  SystemChrome.setSystemUIOverlayStyle(
    SystemUiOverlayStyle(statusBarColor: Colors.grey[200]),
  );
  runApp(
    MaterialApp(
      home: MyApp(),
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primaryColor: Colors.white,
        accentColor: Colors.white,
        scaffoldBackgroundColor: Colors.white,
        appBarTheme: const AppBarTheme(elevation: 2),
      ),
    ),
  );
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final controller = DragSelectGridViewController();

  @override
  void initState() {
    super.initState();
    controller.addListener(scheduleRebuild);
  }

  @override
  void dispose() {
    controller.removeListener(scheduleRebuild);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: SelectionAppBar(
        selection: controller.selection,
        title: const Text('Grid Example'),
      ),
      body: PageView(
        children: [
          Column(
            children: [
              Center(
                child: Text('first page'),
              )
            ],
          ),
          DragSelectGridView(
            gridController: controller,
            padding: const EdgeInsets.all(8),
            itemCount: 90,
            itemBuilder: (context, index, selected) {
              return SelectableItem(
                index: index,
                color: Colors.blue,
                selected: selected,
              );
            },
            gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
              maxCrossAxisExtent: 150,
              crossAxisSpacing: 8,
              mainAxisSpacing: 8,
            ),
          ),
        ],
      ),
    );
  }

  void scheduleRebuild() => setState(() {});
}

It gave the following error when I switched between Page 1 to Page 2 to Page 1 to Page 2

I/flutter ( 3079): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 3079): The following assertion was thrown building NotificationListener<KeepAliveNotification>:
I/flutter ( 3079): A DragSelectGridViewController was used after being disposed.
I/flutter ( 3079): Once you have called dispose() on a DragSelectGridViewController, it can no longer be used.
I/flutter ( 3079): 
I/flutter ( 3079): The relevant error-causing widget was:
I/flutter ( 3079):   PageView
I/flutter ( 3079):   file:///home/vin-fandemand/Downloads/drag_select_grid_view-master/example/lib/example.dart:53:13
I/flutter ( 3079): 
I/flutter ( 3079): When the exception was thrown, this was the stack:
I/flutter ( 3079): #0      ChangeNotifier._debugAssertNotDisposed.<anonymous closure> (package:flutter/src/foundation/change_notifier.dart:108:9)
I/flutter ( 3079): #1      ChangeNotifier._debugAssertNotDisposed (package:flutter/src/foundation/change_notifier.dart:114:6)
I/flutter ( 3079): #2      ChangeNotifier.addListener (package:flutter/src/foundation/change_notifier.dart:144:12)
I/flutter ( 3079): #3      DragSelectGridViewState.initState (package:drag_select_grid_view/src/drag_select_grid_view/drag_select_grid_view.dart:219:22)
I/flutter ( 3079): #4      StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:4684:58)
I/flutter ( 3079): #5      ComponentElement.mount (package:flutter/src/widgets/framework.dart:4520:5)
I/flutter ( 3079): ...     Normal element mounting (33 frames)
I/flutter ( 3079): #38     Element.inflateWidget (package:flutter/src/widgets/framework.dart:3490:14)
I/flutter ( 3079): #39     Element.updateChild (package:flutter/src/widgets/framework.dart:3258:18)
I/flutter ( 3079): #40     SliverMultiBoxAdaptorElement.updateChild (package:flutter/src/widgets/sliver.dart:1164:36)
I/flutter ( 3079): #41     SliverMultiBoxAdaptorElement.createChild.<anonymous closure> (package:flutter/src/widgets/sliver.dart:1149:20)
I/flutter ( 3079): #42     BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2620:19)
I/flutter ( 3079): #43     SliverMultiBoxAdaptorElement.createChild (package:flutter/src/widgets/sliver.dart:1142:11)
I/flutter ( 3079): #44     RenderSliverMultiBoxAdaptor._createOrObtainChild.<anonymous closure> (package:flutter/src/rendering/sliver_multi_box_adaptor.dart:356:23)
I/flutter ( 3079): #45     RenderObject.invokeLayoutCallback.<anonymous closure> (package:flutter/src/rendering/object.dart:1868:58)
I/flutter ( 3079): #46     PipelineOwner._enableMutationsToDirtySubtrees (package:flutter/src/rendering/object.dart:920:15)
I/flutter ( 3079): #47     RenderObject.invokeLayoutCallback (package:flutter/src/rendering/object.dart:1868:13)
I/flutter ( 3079): #48     RenderSliverMultiBoxAdaptor._createOrObtainChild (package:flutter/src/rendering/sliver_multi_box_adaptor.dart:345:5)
I/flutter ( 3079): #49     RenderSliverMultiBoxAdaptor.insertAndLayoutChild (package:flutter/src/rendering/sliver_multi_box_adaptor.dart:491:5)
I/flutter ( 3079): #50     RenderSliverFixedExtentBoxAdaptor.performLayout (package:flutter/src/rendering/sliver_fixed_extent_list.dart:258:17)
I/flutter ( 3079): #51     RenderObject.layout (package:flutter/src/rendering/object.dart:1769:7)
I/flutter ( 3079): #52     RenderSliverEdgeInsetsPadding.performLayout (package:flutter/src/rendering/sliver_padding.dart:137:11)
I/flutter ( 3079): #53     _RenderSliverFractionalPadding.performLayout (package:flutter/src/widgets/sliver_fill.dart:170:11)
I/flutter ( 3079): #54     RenderObject.layout (package:flutter/src/rendering/object.dart:1769:7)
I/flutter ( 3079): #55     RenderViewportBase.layoutChildSequence (package:flutter/src/rendering/viewport.dart:471:13)
I/flutter ( 3079): #56     RenderViewport._attemptLayout (package:flutter/src/rendering/viewport.dart:1465:12)
I/flutter ( 3079): #57     RenderViewport.performLayout (package:flutter/src/rendering/viewport.dart:1374:20)
I/flutter ( 3079): #58     RenderObject._layoutWithoutResize (package:flutter/src/rendering/object.dart:1632:7)
I/flutter ( 3079): #59     PipelineOwner.flushLayout (package:flutter/src/rendering/object.dart:889:18)
I/flutter ( 3079): #60     RendererBinding.drawFrame (package:flutter/src/rendering/binding.dart:404:19)
I/flutter ( 3079): #61     WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:867:13)
I/flutter ( 3079): #62     RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:286:5)
I/flutter ( 3079): #63     SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1117:15)
I/flutter ( 3079): #64     SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1056:9)
I/flutter ( 3079): #65     SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:972:5)
I/flutter ( 3079): #69     _invoke (dart:ui/hooks.dart:253:10)
I/flutter ( 3079): #70     _drawFrame (dart:ui/hooks.dart:211:3)
I/flutter ( 3079): (elided 3 frames from dart:async)
I/flutter ( 3079): 
I/flutter ( 3079): ════════════════════════════════════════════════════════════════════════════════════════════════════
I/flutter ( 3079): Another exception was thrown: 'package:flutter/src/rendering/sliver_multi_box_adaptor.dart': Failed assertion: line 265 pos 16: 'child == null || indexOf(child) > index': is not true.

Looks like the controller is disposed but not initiated again. The state on the appbar is still retained. Can you please help ?

hcbpassos commented 4 years ago

Looks like the controller is disposed but not initiated again.

Once the controller is disposed, it cannot be initiated again.

The problem here is: DragSelectGridViewState.dispose() is disposing the controller, but it shouldn't. Instead, it should just remove the listener, since DragSelectGridViewState.initState() added it.

https://github.com/hugocbpassos/drag_select_grid_view/blob/09253966fe9d811827fd6aac08459acff506ab34/lib/src/drag_select_grid_view/drag_select_grid_view.dart#L217-L227

I'll land a fix soon, just need to write some tests.

Thanks for spotting such problem :)