1

I'm rather new to development, and started off with web dev, though now work with Flutter for about 2 months now, which means I still learn tons - so please, bear with me.

I'm currently working on a drag & drop periodic table. I already got a working version where the player can drop an element at the correct position (only one DragTarget accepts the Draggable). However, I now want to make an advanced version, where each DragTarget accepts each Draggable and shows some information of the dropped element.

My problem is: I can drop the DraggableElementTile on each "empty" DragTarget (as I want to), but when I hover over one of the DragTargets that already "have data", it changes the text to the one that was added last (to a different DragTarget). So the data of the Draggable is not "tied" to the DragTarget, but I cannot find out how to solve it.

I know that further, in this code, the data of the next element in line is shown in the DragTarget upon onAccept. It doesn't happen with my full code, maybe I deleted something here. Or it points someone to the solution?

As a side note: Eventually, there'll be a check, if the element has the correct position in the table or not, so the DragTarget needs to carry the information of the correct setup (as in my initial version).

import 'dart:math';

import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Draggable & DragTarget',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  List elementData = [
    {
      "key": "1x1",
      "atomicNumber": 1,
      "element": "Wasserstoff",
      "symbol": "H",
      "group": 1,
      "period": 1,
      "accepting": false,
      "successfulDrop": false,
      "correctDrop": false
    },
    {
      "key": "1x2",
      "atomicNumber": 3,
      "element": "Lithium",
      "symbol": "Li",
      "group": 1,
      "period": 2,
      "accepting": false,
      "successfulDrop": false,
      "correctDrop": false
    },
    {
      "key": "1x3",
      "atomicNumber": 11,
      "element": "Natrium",
      "symbol": "Na",
      "group": 1,
      "period": 3,
      "accepting": false,
      "successfulDrop": false,
      "correctDrop": false
    },
    {
      "key": "1x4",
      "atomicNumber": 19,
      "element": "Kalium",
      "symbol": "K",
      "group": 1,
      "period": 4,
      "accepting": false,
      "successfulDrop": false,
      "correctDrop": false
    }
  ];
  int j = 0;
  List<Widget> _elements;
  List shuffledElements;
  int tableRows = 4;
  int tableCols = 1;
  String key;
  int index;
  var tmpElement;
  bool accepting = false;
  bool successfulDrop = false;
  bool correctDrop = false;

  List shuffleElements() {
    var random = Random();
    shuffledElements = List.from(elementData);
    for (var i = shuffledElements.length - 1; i > 0; i--) {
      var n = random.nextInt(i + 1);
      var temp = shuffledElements[i];
      shuffledElements[i] = shuffledElements[n];
      shuffledElements[n] = temp;
    }
    return shuffledElements;
  }

  void nextElement() {
    setState(() {
      if (j < shuffledElements.length - 1) {
        j++;
      } else {}
    });
  }

  List<Widget> getElements() {
    if (_elements != null) {
      return _elements;
    }

    _elements = [];
    for (var j = 0; j < tableCols; j++) {
      for (var i = 0; i < tableRows; i++) {
        key = '${j + 1}x${i + 1}';
        index = elementData.indexWhere((e) => e.containsValue(key));

        if (!index.isNegative) {
          tmpElement = elementData[index];
          _elements.add(elementDragTarget(tmpElement));
        } else {}
      }
    }
    return _elements;
  }

  @override
  void initState() {
    shuffleElements();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white38,
      appBar: AppBar(title: Text('Drag and Drop')),
      body: Column(
        children: [
          Container(
            color: Colors.white38,
            height: MediaQuery.of(context).size.height * 0.7,
            child: GridView.count(
              crossAxisCount: tableRows,
              scrollDirection: Axis.horizontal,
              children: getElements(),
            ),
          ),
          Draggable(
            data: shuffledElements[j],
            child: DraggableElementTile(
              shuffledElements: shuffledElements,
              j: j,
            ),
            feedback: DraggableElementTile(
              shuffledElements: shuffledElements,
              j: j,
            ),
          ),
        ],
      ),
    );
  }

//problem: if I hover over tiles that already show data
// it changes to last data
  Widget elementDragTarget(tmpElement) {
    return DragTarget(
      onWillAccept: (data) {
        if (tmpElement['successfulDrop'] == true) {
          tmpElement['accepting'] = false;
          return false;
        } else {
          setState(() {
            tmpElement['accepting'] = true;
          });
          return true;
        }
      },
      onAccept: (data) {
        setState(() {
          tmpElement['successfulDrop'] = true;

          if (shuffledElements[j]["atomicNumber"] ==
              tmpElement['atomicNumber']) {
            tmpElement['correctDrop'] = true;
            tmpElement['accepting'] = false;
          } else {
            tmpElement['correctDrop'] = false;
            tmpElement['accepting'] = false;
          }
        });
        nextElement();
      },
      onLeave: (data) {
        setState(() {
          tmpElement['accepting'] = false;
        });
        return false;
      },
      builder: (context, acceptedData, rejectedData) {
        return buildElementTileInGrid(tmpElement);
      },
    );
  }

  //show in grid onAccept
  Container buildElementTileInGrid(tmpElement) {
    accepting = tmpElement['accepting'];
    successfulDrop = tmpElement['successfulDrop'];
    correctDrop = tmpElement['correctDrop'];

    return Container(
      padding: EdgeInsets.all(4),
      margin: EdgeInsets.all(4),
      decoration: BoxDecoration(
        border: Border.all(
          width: 4,
          color: accepting == true ? Colors.teal : Colors.transparent,
        ),
        color: Colors.white38,
      ),
      child: successfulDrop == true
          ? Column(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                Text(shuffledElements[j]['atomicNumber'].toString()),
                Text(shuffledElements[j]['symbol']),
              ],
            )
          : Container(),
    );
  }
}

//draggable
class DraggableElementTile extends StatelessWidget {
  const DraggableElementTile({
    Key key,
    @required this.shuffledElements,
    @required this.j,
  }) : super(key: key);

  final List shuffledElements;
  final int j;

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.teal,
      padding: EdgeInsets.all(12),
      margin: EdgeInsets.all(8),
      height: 100,
      width: 80,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Text(
            shuffledElements[j]['symbol'],
            style: TextStyle(fontSize: 14),
          ),
          Text(
            shuffledElements[j]['element'],
            maxLines: 1,
            overflow: TextOverflow.ellipsis,
            style: TextStyle(fontSize: 14),
          ),
        ],
      ),
    );
  }
}

Thankful for any helpful ideas.

edit: I think I'd need to save the data I want to show in a new list or so, but still hit a wall when I try to implement it.

soulcoding
  • 15
  • 5

1 Answers1

0

I managed to make it work by creating a deep copy (called it elementDataCopy) of the initial elementData using a model in getElements(). I could then overwrite the data with the data of the dropped element from the Draggable in onAccept of the DragTarget, leading to the expected behaviour:

import 'dart:math';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Draggable & DragTarget',
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class ElementModel {
  ElementModel({
    this.key,
    this.atomicNumber,
    this.element,
    this.symbol,
    this.group,
    this.period,
    this.droppedKey,
    this.accepting,
    this.successfulDrop,
    this.correctDrop,
    this.answer,
  });
  String key;
  int atomicNumber;
  String element;
  String symbol;
  int group;
  int period;
  String droppedKey = '';
  bool accepting = false;
  bool successfulDrop = false;
  bool correctDrop = false;
  String answer = '';
}

class _MyHomePageState extends State<MyHomePage> {
  List elementData = [
    {
      "key": "1x1",
      "atomicNumber": 1,
      "element": "Wasserstoff",
      "symbol": "H",
      "group": 1,
      "period": 1
    },
    {
      "key": "1x2",
      "atomicNumber": 3,
      "element": "Lithium",
      "symbol": "Li",
      "group": 1,
      "period": 2
    },
    {
      "key": "1x3",
      "atomicNumber": 11,
      "element": "Natrium",
      "symbol": "Na",
      "group": 1,
      "period": 3
    },
    {
      "key": "1x4",
      "atomicNumber": 19,
      "element": "Kalium",
      "symbol": "K",
      "group": 1,
      "period": 4
    }
  ];

  int j = 0;
  List<Widget> _elements;
  List shuffledElements;
  int tableRows = 4;
  int tableCols = 1;
  String key;
  int index;
  var tmpElement;
  bool accepting = false;
  bool successfulDrop = false;
  bool correctDrop = false;

  var droppedItem;

  List droppedItems = [];
  int droppedItemIndex;
  List shuffledElementsCopy;
  List elementDataCopy;

  List shuffleElements() {
    var random = Random();
    shuffledElements = List.from(elementData);
    for (var i = shuffledElements.length - 1; i > 0; i--) {
      var n = random.nextInt(i + 1);
      var temp = shuffledElements[i];
      shuffledElements[i] = shuffledElements[n];
      shuffledElements[n] = temp;
    }
    return shuffledElements;
  }

  void nextElement() {
    setState(() {
      if (j < shuffledElements.length - 1) {
        j++;
      } else {}
    });
  }

  List<Widget> getElements() {
    if (_elements != null) {
      return _elements;
    }

    elementDataCopy = elementData
        .map((element) => ElementModel(
              key: element['key'],
              atomicNumber: element['atomicNumber'],
              element: element['element'],
              symbol: element['symbol'],
              group: element['group'],
              period: element['period'],
              accepting: element['accepting'],
              successfulDrop: element['successfulDrop'],
              correctDrop: element['correctDrop'],
              droppedKey: element['droppedKey'],
              answer: element['answer'],
            ))
        .toList();
    _elements = [];
    for (var c = 0; c < tableCols; c++) {
      for (var r = 0; r < tableRows; r++) {
        key = '${c + 1}x${r + 1}';
        index = elementDataCopy.indexWhere((e) => e.key.contains(key));

        if (!index.isNegative) {
          tmpElement = elementDataCopy[index];
          _elements.add(elementDragTarget(tmpElement));
        } else {}
      }
    }
    return _elements;
  }

  @override
  void initState() {
    shuffleElements();
    shuffledElementsCopy = List.from(shuffledElements);
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white38,
      appBar: AppBar(title: Text('Drag and Drop')),
      body: Column(
        children: [
          Container(
            color: Colors.white38,
            height: MediaQuery.of(context).size.height * 0.7,
            child: GridView.count(
              crossAxisCount: tableRows,
              scrollDirection: Axis.horizontal,
              children: getElements(),
            ),
          ),
          Draggable(
            data: shuffledElements[j],
            child: DraggableElementTile(
              shuffledElements: shuffledElements,
              j: j,
            ),
            feedback: DraggableElementTile(
              shuffledElements: shuffledElements,
              j: j,
            ),
            childWhenDragging: Container(
              margin: EdgeInsets.all(8),
              height: 100,
              width: 80,
              color: Colors.blueGrey,
            ),
          ),
        ],
      ),
    );
  }

  Widget elementDragTarget(tmpElement) {
    return DragTarget(
      onWillAccept: (data) {
        if (tmpElement.successfulDrop == true) {
          tmpElement.accepting = false;
          return false;
        } else {
          setState(() {
            tmpElement.accepting = true;
          });
          return true;
        }
      },
      onAccept: (data) {
        setState(() {
          tmpElement.successfulDrop = true;

          if (shuffledElements[j]["atomicNumber"] == tmpElement.atomicNumber) {
            tmpElement.correctDrop = true;
            tmpElement.accepting = false;

            shuffledElementsCopy[j]['answer'] = 'correct';
          } else {
            tmpElement.correctDrop = false;
            tmpElement.accepting = false;
            shuffledElementsCopy[j]['answer'] = 'wrong';
          }

          tmpElement.droppedKey = shuffledElements[j]['key'] + 'dropped';
          shuffledElementsCopy[j]['droppedKey'] = tmpElement.droppedKey;
          droppedItems.add(shuffledElements[j]);
          droppedItemIndex = droppedItems.indexWhere(
              (e) => e['droppedKey'] == shuffledElements[j]['key'] + 'dropped');
          droppedItem = droppedItems[droppedItemIndex];

          tmpElement.symbol = droppedItem['symbol'];
          tmpElement.atomicNumber = droppedItem['atomicNumber'];
          tmpElement.element = droppedItem['element'];
          tmpElement.group = droppedItem['group'];
          tmpElement.period = droppedItem['period'];
          tmpElement.answer = droppedItem['answer'];
        });
        nextElement();
      },
      onLeave: (data) {
        setState(() {
          tmpElement.accepting = false;
        });
        return false;
      },
      builder: (context, acceptedData, rejectedData) {
        return buildElementTileInGrid(tmpElement);
      },
    );
  }

  //show in grid onAccept
  Container buildElementTileInGrid(tmpElement) {
    accepting = tmpElement.accepting;
    successfulDrop = tmpElement.successfulDrop;
    correctDrop = tmpElement.correctDrop;

    return Container(
      padding: EdgeInsets.all(4),
      margin: EdgeInsets.all(4),
      decoration: BoxDecoration(
        border: Border.all(
          width: 4,
          color: accepting == true ? Colors.teal : Colors.transparent,
        ),
        color: Colors.white38,
      ),
      child: successfulDrop == true
          ? Column(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                Text(tmpElement.atomicNumber.toString()),
                Text(tmpElement.symbol),
              ],
            )
          : Container(),
    );
  }
}

//draggable
class DraggableElementTile extends StatelessWidget {
  const DraggableElementTile({
    Key key,
    @required this.shuffledElements,
    @required this.j,
  }) : super(key: key);

  final List shuffledElements;
  final int j;

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.teal,
      padding: EdgeInsets.all(12),
      margin: EdgeInsets.all(8),
      height: 100,
      width: 80,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          Text(
            shuffledElements[j]['symbol'],
            style: TextStyle(fontSize: 14),
          ),
          Text(
            shuffledElements[j]['element'],
            maxLines: 1,
            overflow: TextOverflow.ellipsis,
            style: TextStyle(fontSize: 14),
          ),
        ],
      ),
    );
  }
}
soulcoding
  • 15
  • 5