4

I'm trying to make a game where I need to be able to click on a button while dragging a widget and that button should render som changes to the feedback widget that the user is currently dragging. The childWhenDragging updates just fine but the feedback widget doesn't update during the drag. Is there any way to achieve this?

This is a basic example to recreate it.


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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Draggable Test',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int counter;

  @override
  void initState() {
    this.counter = 0;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Draggable Test'),
      ),
      body: Column(
        children: <Widget>[
          Draggable(
            child: Container(
              color: Colors.red,
              width: 100,
              height: 100,
              child: Text(counter.toString()),
            ),
            feedback: Container(
              color: Colors.red,
              width: 100,
              height: 100,
              child: Text(counter.toString()),
            ),
            childWhenDragging: Container(
              color: Colors.red,
              width: 100,
              height: 100,
              child: Text(counter.toString()),
            ),
          ),
          RaisedButton(
            onPressed: () {
              setState(() {
                counter += 1;
              });
            },
            child: Text("plus"),
          )
        ],
      ),
    );
  }
}

I expect the feedback widget to render the correct counter value but it never updates.

  • 1
    I think you'd be helped by [this answer](https://stackoverflow.com/questions/54470230/how-to-update-draggable-child-when-entering-dragtarget-in-flutter), even though the question differs slightly. Using a `StreamBuilder` should work in your case. – Magnus Oct 01 '19 at 14:17

1 Answers1

1

This behavoir is a result of the actual implementation of the Draggable Widget. Have a look at the following lines from flutter/lib/src/widgets/drag_target:

_DragAvatar({
    required this.overlayState,
    this.data,
    this.axis,
    required Offset initialPosition,
    this.dragStartPoint = Offset.zero,
    this.feedback,
    this.feedbackOffset = Offset.zero,
    this.onDragUpdate,
    this.onDragEnd,
    required this.ignoringFeedbackSemantics,
    required this.ignoringFeedbackPointer,
  }) : assert(overlayState != null),
       assert(ignoringFeedbackSemantics != null),
       assert(ignoringFeedbackPointer != null),
       assert(dragStartPoint != null),
       assert(feedbackOffset != null),
       _position = initialPosition {
    _entry = OverlayEntry(builder: _build);
    overlayState.insert(_entry!);
    updateDrag(initialPosition);
  }

The _entry is the displayed feedback Widget. When looking closely you can see that it's actually an Overlay! (See OverlayEntry)

So when updating the current state of your app via setState the context of the "app widget" updates as expected. But the Overlay in which the feedback widget lives does not belong to the "app widget context".

In order to update the feedback widget you can do the following:

  1. Make the feedback widget a standalone StatefulWidget
  2. Pass main state changes to the feedback widget

Passing the state can be done in different ways depending on frameworks, etc. To keep it simple the following example uses a GlobalKey to access the feedback widget.

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late int counter;

  /// GlobalKey to access CountContainer later
  final GlobalKey _counterKey = GlobalKey();

  @override
  void initState() {
    counter = 0;
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Draggable Test'),
      ),
      body: Column(
        children: <Widget>[
          Draggable(
            /// Set feedback widget as CountContainer with the GlobalKey
            feedback: CountContainer(
              initialCount: counter,
              key: _counterKey,
            ),
            childWhenDragging: Container(
              color: Colors.red,
              width: 100,
              height: 100,
              child: Text(counter.toString()),
            ),
            child: Container(
              color: Colors.red,
              width: 100,
              height: 100,
              child: Text(counter.toString()),
            ),
          ),
          ElevatedButton(
            onPressed: () {
              setState(() {
                counter += 1;
                // pass update to CountContainer
                (_counterKey.currentState as _CountContainerState).updateCount(counter);
              });
            },
            child: Text("plus"),
          )
        ],
      ),
    );
  }
}

class CountContainer extends StatefulWidget
{
  final int initialCount;
  const CountContainer({Key? key, required this.initialCount}) : super(key: key);

  @override
  State<CountContainer> createState() => _CountContainerState();
}

class _CountContainerState extends State<CountContainer>
{
  late int count = widget.initialCount;

  @override
  Widget build(BuildContext context)
  {
    return Container(
      color: Colors.red,
      width: 100,
      height: 100,
      child: Text(count.toString()),
    );
  }

  /// This function gets called by MyHomePage.
  void updateCount(int newCount)
  {
    setState(() {
      count = newCount;
    });
  }
}

This way the state gets passed to the feedback widget and is updated correctly.

Fabian
  • 11
  • 2