'DragTarget onWillAccept and onAccept not firing

I'm starting with Flutter and I cannot make drag and drop functionality to work. I followed the documentation but have no idea what I'm doing wrong. This sample app displays three squares and the blue is draggable. The other ones have DragTarget set, one inside the square and one outside the square. When I drag the blue square it prints info that the drag started but there is no print info when dragging or dropping over the DragTargets. Here is the code:

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.red,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Container(
          constraints: BoxConstraints.expand(),
          color: Colors.grey[900],
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.center,
            children: <Widget>[
              Container(
                width: 100,
                height: 100,
                color: Colors.red,
                child: DragTarget(
                  onWillAccept: (d) => true,
                  onAccept: (d) => print("ACCEPT 1!"),
                  onLeave: (d) => print("LEAVE 1!"),
                  builder: (a,data,c) {
                    print(data);
                    return Center();
                  },
                ),
              ),
              DragTarget(
                onWillAccept: (d){return true;},
                onAccept:(d) => print("ACCEPT 2!"),
                onLeave: (d) => print("LEAVE 2!"),
                builder: (context, candidateData, rejectedData){
                  return Container(
                    width: 150,
                    height: 150,
                    color: Colors.purple
                  );
                }
              ),
              Draggable(
                data: ["SOME DATA"],
                onDragStarted: () => print("DRAG START!"),
                onDragCompleted: () => print("DRAG COMPLETED!"),
                onDragEnd: (details) => print("DRAG ENDED!"),
                onDraggableCanceled: (data, data2) => print("DRAG CANCELLED!"),
                feedback: SizedBox(
                  width: 100,
                  height: 100,
                  child: Container(
                    margin: EdgeInsets.all(10),
                    color: Colors.green[800],
                  ),
                ),
                child: SizedBox(
                  width: 100,
                  height: 100,
                  child: Container(
                    margin: EdgeInsets.all(10),
                    color: Colors.blue[800],
                  ),
                ),
              ),
            ],
          )
        ),
      )
    );
  }
}


Solution 1:[1]

Apparently the Draggable and DragTarget need to have the generic type specified if you are passing data, otherwise the onAccept and onWillAccept will not be fired.

For example, if you want to pass data as int then use Draggable<int> and DragTarget<int>?—?this also applies to onAccept and onWillAccept, they need to accept int as a parameter.

Solution 2:[2]

You should setState when you call onAccept and add a boolean value to your stateful widget.

bool accepted = false;

onAccept: (data){
      if(data=='d'){
        setState(() {
          accepted = true;
        });
      },

Solution 3:[3]

I used ChangeNotifyProvider and a model to manage my Draggable and Dragable Target multiplication challenge and results. I built a simple multiplication game using ChangeNotify that updates the Provider that is listening for changes. The GameScore extends the ChangeNotifier which broadcast to the provider when changes occur in the model. The Provider can either be listening or not listening. If the user get the right answer than the Model updates its score and notifies the listeners. The score is then displayed in a text box. I think the provider model is a simplier way to interact with the widget for managing state.

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'dart:math';
class Multiplication
{
  int value1;
  int value2;
  int result;
  int answerKey;
  int fakeResult;
  Multiplication(this.value1,this.value2,this.result,this.answerKey,this.fakeResult);
}
class GameScore with ChangeNotifier
{
    int score=0;
    int number=0;
    List<Multiplication> lstMultiplication=[];
    late Multiplication currentMultiplication;
    GameScore()
    {
      var rng = Random();
      

      for(int i=0; i<=25; i++)
      {
        for(int j=0; j<=25; j++)
        {
          var answerKey=rng.nextInt(2);
          var fakeAnswer=rng.nextInt(25)*rng.nextInt(25);
          lstMultiplication.add(Multiplication(i,j,i*j,answerKey,fakeAnswer));
        }
      }

    }
    int getChallengeValue(int key)
    {
      int retVal=0;
      if (currentMultiplication.answerKey==key)
      {
        retVal=currentMultiplication.result;
      }
      else
      {
        retVal=currentMultiplication.fakeResult;
      }
      return retVal;
    }
    String displayMultiplication()
    {
      String retVal="";
      if (currentMultiplication!=null)
      {
          retVal=currentMultiplication.value1.toString()+ " X "+currentMultiplication.value2.toString();
      }
      return retVal;
    }
    nextMultiplication()
    {
      var rng = Random();
      var index=rng.nextInt(lstMultiplication.length);
      currentMultiplication= lstMultiplication[index];
    }
    changeAcceptedData(int data) {
      var rng = Random();
      score += 1;
      number=rng.nextInt(100);
      notifyListeners();
  }
    changeWrongData(int data) {
      var rng = Random();
      score -= 1;
      number=rng.nextInt(100);
      notifyListeners();
  }
}

void main() {
  runApp(const MyApp());
  //runApp(Provider<GameScore>(create: (context) => GameScore(), child: 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: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home:  //TestDraggableWidget(),
      ChangeNotifierProvider(create:(context)=>GameScore(),child: TestDraggableWidget())
  );
  }
}

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

  @override
  State<TestDraggableWidget> createState() => _TestDraggableWidgetState();
}

class _TestDraggableWidgetState extends State<TestDraggableWidget> {
  @override
  Widget build(BuildContext context) {
    Provider.of<GameScore>(context,listen:false).nextMultiplication();
    return Scaffold(appBar: AppBar(title:Text("Draggable")),body:
    Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
      Row(
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: [
          EvenContainerWidget(),
          NumberContainerWidget(),
          OddContainerWidget(),
          SizedBox(width:100,child:Text("Score: ${Provider.of<GameScore>(context, listen: true).score}",style:TextStyle(color:Colors.green,fontSize:14)))
      ],)
    ],));
  }
}

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

  @override
  State<EvenContainerWidget> createState() => _EvenContainerWidgetState();
}

class _EvenContainerWidgetState extends State<EvenContainerWidget> {
  int? valueAccepted;
  
  _onAccept(BuildContext context, int data)
  {
    if (data==valueAccepted){
        Provider.of<GameScore>(context, listen: false).changeAcceptedData(data);
        setState(() {
          valueAccepted=data;
        });
    }
    else
    {
      Provider.of<GameScore>(context, listen: false).changeWrongData(data);
    }
  }
  bool _willAccept(int? data)
  {
    return true;
  }
  @override
  Widget build(BuildContext context) {
    valueAccepted=Provider.of<GameScore>(context, listen: false).getChallengeValue(1);
    return Container(
        width:60,
        height:60,
        decoration:BoxDecoration(borderRadius: BorderRadius.circular(10),color:Colors.blueAccent),
        child: 
        DragTarget<int>(
          onAccept: (data)=> _onAccept(context,data),
          onWillAccept: _willAccept,
          builder:(context, candidateData, rejectedData) {
            return Center(child:Text("Choice 1: ${valueAccepted==null?'':valueAccepted.toString()}"));
          },
          
          
        )
    );
  }
}



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

  @override
  State<OddContainerWidget> createState() => _OddContainerWidgetState();
}

class _OddContainerWidgetState extends State<OddContainerWidget> {
  int? valueAccepted;
  
  _onAccept(BuildContext context, int data)
  {
    if(data==valueAccepted)
    {
      Provider.of<GameScore>(context, listen: false).changeAcceptedData(data);
      setState(() {
          valueAccepted=data;
        });
    }
    else
    {
      Provider.of<GameScore>(context, listen: false).changeWrongData(data);
    }
    
  }
  bool _willAccept(int? data)
  {
    /*if (data!.isOdd)
    {
      setState(() {
        valueAccepted=data;
      });
    }*/
    return true;
  }

  @override
  Widget build(BuildContext context) {
    valueAccepted=Provider.of<GameScore>(context, listen: false).getChallengeValue(0);
    return Container(
        width:60,
        height:60,
        decoration:BoxDecoration(borderRadius: BorderRadius.circular(10),color:Colors.blueAccent),
        child: 
        DragTarget<int>(
          onAccept: (data)=> _onAccept(context,data),
          onWillAccept: _willAccept,
          builder:(context, candidateData, rejectedData) {
            return Center(child:Text("Choice 2: ${valueAccepted==null?'':valueAccepted.toString()}"));
          },
        )
    );
  }
}


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

  _dragCompleted(BuildContext context){
    
  }
  @override
  Widget build(BuildContext context) {
    return Draggable(
      //information dropped by draggable at dragtarget
      data: Provider.of<GameScore>(context, listen: true).currentMultiplication.result,

      onDragCompleted:   _dragCompleted(context)  ,
      //Widget to be displayed when drag is underway
      feedback: Container(
        width:60,
        height:60,
        decoration:BoxDecoration(borderRadius: BorderRadius.circular(10),color:Colors.black26),
        child: Center(child:Text("${Provider.of<GameScore>(context, listen: false).displayMultiplication()}",style:TextStyle(color:Colors.green,fontSize:14))),

      ),
      child: 
    Container(
      width:60,
      height:60,
      decoration:BoxDecoration(borderRadius: BorderRadius.circular(10),color:Colors.black26),
      child: Center(child:Text("${Provider.of<GameScore>(context, listen: false).displayMultiplication()}",style:TextStyle(color:Colors.blue,fontSize:14))),
    ));
  }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Krzysztof Topolski
Solution 2
Solution 3 Golden Lion