I’m working on a Flutter project where I need to implement a drag-and-drop functionality. I want to drag columns from an overlay column chooser and drop them into a specific position (index) in a PlutoGrid.
Specific Questions:
How can I enable drag-and-drop functionality from the overlay chooser to the PlutoGrid?
How can I determine and set the specific index in PlutoGrid where the column should be dropped?
Are there any best practices or libraries in Flutter that can help with this drag-and-drop functionality?
import 'package:flutter/material.dart';
import 'package:pluto_grid/pluto_grid.dart';
import 'package:faker/faker.dart';
List<Map<String, dynamic>> generateFakeData(int count) {
final faker = Faker();
return List.generate(count, (index) {
return {
'id': index + 1,
'name': faker.person.name(),
'email': faker.internet.email(),
'phone': faker.phoneNumber.us(),
'work': faker.address.state(),
'fix': faker.date.justTime(),
'company': faker.company.name(),
};
});
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'PlutoGrid with Column Chooser',
home: Scaffold(
appBar: AppBar(title: const Text('PlutoGrid with Column Chooser')),
body: const PlutoGridExample(),
),
);
}
}
class PlutoGridExample extends StatefulWidget {
const PlutoGridExample({super.key});
@override
_PlutoGridExampleState createState() => _PlutoGridExampleState();
}
class _PlutoGridExampleState extends State<PlutoGridExample> {
late List<PlutoColumn> allColumns;
late List<PlutoColumn> visibleColumns;
late List<bool> columnVisibility;
late List<PlutoRow> rows;
bool showColumnChooser = false;
@override
void initState() {
super.initState();
allColumns = [
PlutoColumn(
title: 'ID',
field: 'id',
type: PlutoColumnType.number(),
),
PlutoColumn(
title: 'Name',
field: 'name',
type: PlutoColumnType.text(),
),
PlutoColumn(
title: 'Email',
field: 'email',
type: PlutoColumnType.text(),
),
PlutoColumn(
title: 'Phone',
field: 'phone',
type: PlutoColumnType.text(),
),
PlutoColumn(
title: 'State',
field: 'work',
type: PlutoColumnType.text(),
hide: true,
),
PlutoColumn(
title: 'Phone Work',
field: 'fix',
type: PlutoColumnType.text(),
hide: true,
),
PlutoColumn(
title: 'Company',
field: 'company',
type: PlutoColumnType.text(),
hide: true,
),
];
columnVisibility = List.filled(allColumns.length, true);
visibleColumns = List.from(allColumns.where((column) => !column.hide));
rows = generateFakeData(100).map((data) {
return PlutoRow(cells: {
'id': PlutoCell(value: data['id']),
'name': PlutoCell(value: data['name']),
'email': PlutoCell(value: data['email']),
'phone': PlutoCell(value: data['phone']),
'work': PlutoCell(value: data['work']),
'fix': PlutoCell(value: data['fix']),
'company': PlutoCell(value: data['company']),
});
}).toList();
}
void _toggleColumnChooser() {
setState(() {
showColumnChooser = !showColumnChooser;
});
}
void _updateColumns(List<PlutoColumn> columns, List<bool> visibility) {
setState(() {
allColumns = List<PlutoColumn>.from(columns);
columnVisibility = List<bool>.from(visibility);
visibleColumns = [
for (int i = 0; i < allColumns.length; i++)
if (columnVisibility[i]) allColumns[i]
];
});
}
void _onColumnDropped(PlutoColumn column, int dropIndex) {
setState(() {
if (!visibleColumns.contains(column)) {
visibleColumns.insert(dropIndex, column);
int columnIndex = allColumns.indexOf(column);
columnVisibility[columnIndex] = true;
}
});
}
int _calculateDropIndex(double dropY) {
double gridTop = 100.0; // Top position of the grid
double rowHeight = 50.0; // Height of a single row
return ((dropY - gridTop) / rowHeight).floor();
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
Column(
children: [
ElevatedButton(
onPressed: _toggleColumnChooser,
child: Text('Choose Columns'),
),
Expanded(
child: DragTarget<PlutoColumn>(
onAcceptWithDetails: (details) {
int dropIndex = _calculateDropIndex(details.offset.dy);
_onColumnDropped(details.data, dropIndex);
},
builder: (context, candidateData, rejectedData) {
return PlutoGrid(
columns: visibleColumns,
rows: rows,
configuration: PlutoGridConfiguration(),
onChanged: (PlutoGridOnChangedEvent event) {
print(event);
},
);
},
),
),
],
),
if (showColumnChooser)
Positioned(
right: 0,
top: 50,
child: ColumnChooserOverlay(
columns: allColumns,
columnVisibility: columnVisibility,
onColumnsChanged: _updateColumns,
),
),
],
);
}
}
class ColumnChooserOverlay extends StatefulWidget {
final List<PlutoColumn> columns;
final List<bool> columnVisibility;
final Function(List<PlutoColumn>, List<bool>) onColumnsChanged;
const ColumnChooserOverlay({
super.key,
required this.columns,
required this.columnVisibility,
required this.onColumnsChanged,
});
@override
_ColumnChooserOverlayState createState() => _ColumnChooserOverlayState();
}
class _ColumnChooserOverlayState extends State<ColumnChooserOverlay> {
late List<bool> _columnVisibility;
late List<PlutoColumn> _columns;
@override
void initState() {
super.initState();
_columnVisibility = List.from(widget.columnVisibility);
_columns = List.from(widget.columns.where((column) => column.hide));
}
@override
Widget build(BuildContext context) {
return Material(
elevation: 8,
child: Container(
width: 300,
height: 400,
padding: const EdgeInsets.all(8),
child: ReorderableListView(
onReorder: (int oldIndex, int newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
final item = _columns.removeAt(oldIndex);
_columns.insert(newIndex, item);
final visibilityItem = _columnVisibility.removeAt(oldIndex);
_columnVisibility.insert(newIndex, visibilityItem);
widget.onColumnsChanged(_columns, _columnVisibility);
});
},
children: List.generate(_columns.length, (index) {
return LongPressDraggable<PlutoColumn>(
key: ValueKey(_columns[index].field), // Ensure each item has a key
data: _columns[index],
feedback: Material(
child: Container(
color: Colors.white,
padding: EdgeInsets.all(8.0),
child: Text(_columns[index].title),
),
),
childWhenDragging: Container(),
child: CheckboxListTile(
key: ValueKey(_columns[index].field), // Ensure each item has a key
title: Text(_columns[index].title),
value: _columnVisibility[index],
onChanged: (bool? value) {
setState(() {
_columnVisibility[index] = value ?? true;
widget.onColumnsChanged(_columns, _columnVisibility);
});
},
),
);
}),
),
),
);
}
}