How to handle drag and drop list into another list?
How can I achieve this?
Thanks for the help!
I have tried with drag_and_drop_lists package, but I'm stuck in handle inside and outside item.
Full Example :
import 'package:drag_and_drop_lists/drag_and_drop_lists.dart';
import 'package:flutter/material.dart';
Future<void> main() async {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ExpansionTileExample(),
);
}
}
class FolderData {
String name;
List<ListData> listData;
FolderData({this.name, this.listData});
}
class ListData {
String name;
ListData({this.name});
}
class ExpansionTileExample extends StatefulWidget {
ExpansionTileExample({Key key}) : super(key: key);
#override
_ListTileExample createState() => _ListTileExample();
}
class _ListTileExample extends State<ExpansionTileExample> {
List<dynamic> _lists = [];
#override
void initState() {
super.initState();
_lists.add(FolderData(name: "Folder1", listData: []));
_lists.add(FolderData(name: "Folder2", listData: []));
_lists.add(ListData(
name: "List1",
));
_lists.add(ListData(
name: "List2",
));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Expansion Tiles with drag and drop'),
),
body: DragAndDropLists(
children: List.generate(_lists.length, (index) => _buildList(index)),
onItemReorder: _onItemReorder,
onListReorder: _onListReorder,
listGhost: Padding(
padding: const EdgeInsets.symmetric(vertical: 30.0),
child: Center(
child: Container(
padding: EdgeInsets.symmetric(vertical: 30.0, horizontal: 100.0),
decoration: BoxDecoration(
border: Border.all(),
borderRadius: BorderRadius.circular(7.0),
),
child: Icon(Icons.add_box),
),
),
),
),
);
}
_buildList(int outerIndex) {
var innerList = _lists[outerIndex];
return (innerList is FolderData)
? DragAndDropListExpansion(
title: Text('List ${innerList.name}'),
subtitle: Text('Subtitle ${innerList.name}'),
leading: Icon(Icons.ac_unit),
children: List.generate(innerList.listData.length, (index) => _buildItem(innerList.listData[index].name)),
listKey: ObjectKey(innerList),
)
: DragAndDropList(
children: <DragAndDropItem>[
DragAndDropItem(
child: ListTile(title: Text(innerList.name)),
),
],
);
}
_buildItem(String item) {
return DragAndDropItem(
child: ListTile(
title: Text(item),
),
);
}
// ======== Stuck here ========
_onItemReorder(int oldItemIndex, int oldListIndex, int newItemIndex, int newListIndex) {
setState(() {
var movedDataOuter = _lists[oldListIndex];
if (movedDataOuter is ListData) {
// 1. drag list inside folder.
var movedItem = _lists.removeAt(oldListIndex);
_lists[newListIndex].listData.insert(newItemIndex, movedItem);
} else {
// 2. remove list from folder.
var movedItem = _lists[oldListIndex].listData.removeAt(oldItemIndex);
_lists.insert(newListIndex, movedItem);
// 3. drag & drop inner list inside folder
// var movedItem = _lists[oldListIndex].listData.removeAt(oldItemIndex);
// _lists[oldListIndex].listData.insert(newItemIndex, movedItem);
}
// 4. drag and drop list outsie folder
// var movedItem = _lists.removeAt(oldListIndex);
// _lists.insert(newListIndex, movedItem);
});
}
_onListReorder(int oldListIndex, int newListIndex) {
setState(() {
var movedList = _lists.removeAt(oldListIndex);
if (movedList is FolderData) {
_lists.insert(newListIndex, movedList);
} else {
_lists[newListIndex].listData.insert(newListIndex, movedList);
}
});
}
}
you can use following package on pub.dev : drag_and_drop_lists
package: link
and if you need a tutorial then link
Related
I am trying to build a view/route that will list items fetched from a REST source.
I want to show a notification item below the list while the data is being fetched.
But my ListView builder is constructed around the fetched data's structure, so I figured just have a ListTile fit some appropriate UX elements below the generated list inside a Column - which was kinda working great - or so I thought - until the list grows to fill the screen causing RenderFlex overflowed error. Wrapping the ListView builder in Expanded fixed that but moved the indicator to the bottom of the screen.
In trying to fix it I seem to have broken more of the plumbing and the boolean variable that should control the idicator widget; isLoading: stockSet.isBusyLoading doesn't seem to update.
At the moment if I hardcode it as `` it does sit in the appropraite position but I am back with the RenderFlex overflow.
Once all of this is working I'll be wanting to automatically load items untill the screen is full - not sure where I'll be triggering that from yet.
class MyStockSet extends StatefulWidget {
const MyStockSet({super.key});
static const indexStr = 'stocks';
static const labelStr = 'Properties';
#override
State<MyStockSet> createState() => _MyStockSetState();
}
class _MyStockSetState extends State<MyStockSet> {
#override
Widget build(BuildContext context) {
const String imagePath = 'assets/images/${MyStockSet.indexStr}.png';
var assetImage = const AssetImage(imagePath);
//var stockSet = context.watch<StockSet>(); <- didn't work either
var stockSet = Provider.of<StockSet>(context,listen: false);
return Scaffold(
appBar: AppBar(
title: Row(
children: [
AscHero(
assetImage: assetImage,
tag: MyStockSet.indexStr,
title: MyStockSet.labelStr,
radius: 32,
),
const SizedBox(width: 12),
const Text(MyStockSet.labelStr),
],
),
actions: [
IconButton(
onPressed: () {
var stockSet = context.read<StockSet>();
int newNr = stockSet.stocks.length + 1;
Stock tmpstock = Stock(
id: newNr,
title: 'test$newNr',
thumbUrl: 'url',
description: 'desc');
stockSet.add(tmpstock);
},
icon: const Icon(Icons.add),
),
IconButton(
onPressed: () {
developer.log('btn before isBusyLoading ${stockSet.isBusyLoading}');
stockSet.fetch();
developer.log('after btn isBusyLoading ${stockSet.isBusyLoading}');
},
icon: const Icon(Icons.handshake),
),
],
),
body: Column(
children: [
Row(
// these will be filters, order toggle etc.
children: [
ElevatedButton(
onPressed: () => developer.log('Btn pressed.'),
child: Text('Btn')),
],
),
Expanded(
child: Column(children: [
_StockListView(),
LoadingStockListItemNotif(
isLoading: true,
),
]),
),
],
),
);
}
}
class _StockListView extends StatefulWidget {
#override
State<_StockListView> createState() => _StockListViewState();
}
class _StockListViewState extends State<_StockListView> {
#override
void didChangeDependencies() {
super.didChangeDependencies();
developer.log('_StockListView didChangeDependencies()');
// developer.log('scroll pos ${_scrollController.position}');
}
#override
Widget build(BuildContext context) {
var stockSet = context.watch<StockSet>();
return ListView.builder(
// controller: _scrollController,
shrinkWrap: true,
itemCount: stockSet.stocks.length,
itemBuilder: (context, index) => InkWell(
child: StockListItem(
stock: stockSet.stocks[index],
),
onTap: () => Navigator.pushNamed(
context,
'/stocks/stock',
arguments: ScreenArguments(stockSet.stocks[index]),
),
),
);
}
void _scrollListener() {
developer.log('_scrollListener');
}
}
and
class StockSet extends ChangeNotifier {
final List<Stock> _stocks = [];
late bool isBusyLoading = false;
List<Stock> get stocks => _stocks;
void add(Stock stock) {
_stocks.add(stock);
developer.log('added stock :${stock.title}');
notifyListeners();
}
void remove(Stock stock) {
_stocks.remove(stock);
notifyListeners();
}
Future<void> fetch() async {
developer.log('fetch() iL T');
isBusyLoading = true;
notifyListeners();
Stock tmpStock = await _fetchAStock();
developer.log('fetch() iL F');
isBusyLoading = false;
notifyListeners();
add(tmpStock);
}
Future<Stock> _fetchAStock() async {
developer.log('fetch stock ');
final response = await http.get(
Uri.https(
//...
),
);
developer.log('response.statusCode:${response.statusCode}');
if (response.statusCode == 200) {
final Map<String, dynamic> map = json.decode(response.body);
return Stock(
id: map['id'] as int,
title: map['title'] as String,
description: map['description'] as String,
thumbUrl: map['thumbUrl'] as String,
);
}
throw Exception('error fetching stocks:');
}
}
Apologies for the convoluted question.
Add mainAxisSize : MainAxisSize.min for the column inside the expanded widget. The expanded doesn't have any bounds and that's why it throws an error. You can wrap the column with a SingleChildScrollView if you have long content to display
This worked for me!
Just set the shrinkWrap attribute to true
Main lesson:
Don't fight the framework.
Answer:
Instead of tying yourself into Möbius knots trying to put the ListView's functionality outside of itself; use the fact that the ListView.builder allows you to sculpt the logic of how it gets built and what it will contain - given that the provider can trigger its rebuild when the variable in the data set changes.
In other words; by increasing the loop of the builder, you can insert a kind of footer to the Listview. The appearance (or not) of that can depend on the provider, provided it fires the appropriate notifyListeners()s etc.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:equatable/equatable.dart';
import 'dart:async';
class ItemSetRoute extends StatefulWidget {
const ItemSetRoute({Key? key}) : super(key: key);
#override
State<ItemSetRoute> createState() => _ItemSetRouteState();
}
class _ItemSetRouteState extends State<ItemSetRoute> {
#override
Widget build(BuildContext context) {
var itemSet = Provider.of<ItemSet>(
context,
listen: true /* in order to rebuild */,
);
return Scaffold(
appBar: AppBar(title: const Text('Test'), actions: [
IconButton(
onPressed: () {
itemSet.fetch();
},
icon: const Icon(Icons.download),
)
]),
body: Column(
//screen
children: [
Row(
children: [
ElevatedButton(
onPressed: () {
itemSet.fetch();
},
child: const Text('Btn')),
],
),
Expanded(
child: ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: itemSet.items.length + 1,
itemBuilder: (context, index) {
/* logic here to create a kind of footer of the ListView */
if (index <= itemSet.items.length - 1) {
return InkWell(
child: ItemListItem(
item: itemSet.items[index],
),
onTap: () {
//('Item tapped, navigate etc.');
});
} else {
return LoadingItemNotifier(
isLoading: itemSet.isBusyLoading,
);
}
},
),
),
],
),
);
}
}
//Models
class ItemListItem extends StatelessWidget {
const ItemListItem({Key? key, required this.item}) : super(key: key);
final Item item;
#override
Widget build(BuildContext context) {
return Material(
child: ListTile(
title: Text(item.title),
subtitle: Text(item.description),
),
);
}
}
class LoadingItemNotifier extends StatefulWidget {
const LoadingItemNotifier({Key? key, this.isLoading = false})
: super(key: key);
final bool isLoading;
#override
State<LoadingItemNotifier> createState() => _LoadingItemNotifierState();
}
class _LoadingItemNotifierState extends State<LoadingItemNotifier> {
#override
Widget build(BuildContext context) {
if (widget.isLoading) {
return Material(
child: ListTile(
leading: SizedBox(
width: 48,
height: 48,
child: ClipOval(
child: Material(
color: Colors.lightBlue.withOpacity(0.25),
child: const Center(
child: Icon(Icons.download),
),
),
),
),
title: const Text('Loading'),
isThreeLine: true,
subtitle: const Text('One moment please...'),
dense: true,
),
);
} else {
return const SizedBox(height: 0);
}
}
}
class ItemSet extends ChangeNotifier {
final List<Item> _items = [];
late bool isBusyLoading = false;
List<Item> get items => _items;
void add(Item item) {
_items.add(item);
notifyListeners();
}
void remove(Item item) {
_items.remove(item);
notifyListeners();
}
Future<void> fetch() async {
isBusyLoading = true;
notifyListeners();
/* handling REST call here */
await Future.delayed(const Duration(milliseconds: 500));
Item newItem = const Item(id: 123, title: 'Title', description: 'Desc');
isBusyLoading = false;
add(newItem);
}
}
class Item extends Equatable {
const Item({
required this.id,
required this.title,
required this.description,
});
final int id;
final String title;
final String description;
#override
List<Object> get props => [id, title, description];
}
Caveats
I don't know if this is the most efficient way of doing this - perhaps there should be fewer states, etc. ...
I need to develop a navigation drawer in flutter and I'm new to flutter, I am using the following code, and this is creating the menu as expected but the problem is
1.handling screen navigation
2.maintaining state and navigating back to the screen which is previously opened
I am unable to use this code in stateful widget as i need to maintain the state of the navigation drawer
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
class ExpansionList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: data.length,
itemBuilder: (BuildContext context, int index) => EntryItem(
data[index],
),
);
}
}
// Welcome to another flutter tutorial
// In this video we will see how to create a multi-level Expansion List
// First Let's create a class for each row in the Expansion List
class Entry {
final String title;
final List<Entry>
children; // Since this is an expansion list ...children can be another list of entries
Entry(this.title, [this.children = const <Entry>[]]);
}
// This is the entire multi-level list displayed by this app
final List<Entry> data = <Entry>[
Entry(
'Chapter A',
<Entry>[
Entry('Section A0',
// <Entry>[
// Entry('Item A0.1'),
// Entry('Item A0.2'),
// Entry('Item A0.3'),
// ],
),
Entry('Section A1'),
Entry('Section A2'),
],
),
// Second Row
Entry('Chapter B', <Entry>[
Entry('Section B0'),
Entry('Section B1'),
]),
Entry(
'Chapter C',
<Entry>[
Entry('Section C0'),
Entry('Section C1'),
Entry(
'Section C2',
<Entry>[
Entry('Item C2.0'),
Entry('Item C2.1'),
Entry('Item C2.2'),
Entry('Item C2.3'),
],
)
],
),
];
// Create the Widget for the row
class EntryItem extends StatelessWidget {
const EntryItem(this.entry);
final Entry entry;
// This function recursively creates the multi-level list rows.
Widget _buildTiles(Entry root) {
if (root.children.isEmpty) {
return ListTile(
title: Text(root.title),
onTap: (){
Fluttertoast.showToast(msg: root.title);
_getDrawerItemWidget(root.title);
},
);
}
return ExpansionTile(
key: PageStorageKey<Entry>(root),
title: Text(root.title),
children: root.children.map<Widget>(_buildTiles).toList(),
);
}
#override
Widget build(BuildContext context) {
return Container(
child: _buildTiles(entry));
}
_getDrawerItemWidget(String screenName) {
switch (screenName) {
case "Section A0":
return new ThirdScreen();
case "Section A1":
return new SecondScreen();
case "Section A2":
return new ThirdScreen();
default:
return new Text("Error");
}
}
}
Basically I am an android app developer I'm looking forward to implement the following concept like sigle activity with navigation drawer and handling multiple fragments in flutter
Please help me to achieve the requirement
Any source code suggestions or fully implemented code is helpful for my need
Finally, No one answered here and I work around and find the solution for my problem
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
class NavDrawer extends StatefulWidget with PreferredSizeWidget {
#override
State<StatefulWidget> createState() {
return new NavDrawerState();
}
#override
// TODO: implement preferredSize
Size get preferredSize => throw UnimplementedError();
}
class NavDrawerState extends State<NavDrawer> {
int _selectedDrawerIndex = 0;
String screenName = "Home";
final ScrollController scroll = ScrollController();
#override
void dispose() {
scroll.dispose();
super.dispose();
}
_getDrawerItemWidget(String pos) {
switch (pos) {
case "Home":
return new HomeScreen();
case "Receiving":
return new FirstScreen();
case "Putaway":
return new SecondScreen();
case "Pallet Transfer":
return new ThirdScreen();
default:
return new Text("Error");
}
}
_onSelectItem(String screen) {
setState(() => screenName = screen);
Navigator.of(context).pop(); // close the drawer
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(
// here we display the title corresponding to the fragment
// you can instead choose to have a static title
child: Text(screenName),
context: context,
),
drawer: Drawer(
child: Container(
decoration: AppConstants.customBoxDecoration,
//you can use your own boxdecoration
child: Column(
children: <Widget>[
UserAccountsDrawerHeader(
decoration: AppConstants.customBoxDecoration,
currentAccountPicture: new CircleAvatar(
radius: 60.0,
// backgroundColor: const Color(0xFF778899),
backgroundImage: AssetImage('assets/logo.png'),
),
accountName: new Text("Name"),
accountEmail: new Text("mail")),
Flexible(
child: ListView.builder(
shrinkWrap: true,
controller: scroll,
itemCount: StringConstants.menuList.length,
itemBuilder: (BuildContext context, int index) => buildList(
StringConstants.menuList[index],
))
)
],
),
)),
body: _getDrawerItemWidget(screenName),
);
}
// This function recursively creates the multi-level list rows.
Widget _buildTiles(Entry root) {
if (root.children.isEmpty) {
return ListTile(
leading: Icon(root.icon),
title: Text(
root.title,
style: AppConstants.textStyleNavDrawer,
),
onTap: () {
Fluttertoast.showToast(msg: root.title);
_onSelectItem(root.title);
},
);
}
return ExpansionTile(
key: PageStorageKey<Entry>(root),
maintainState: true,
title: Text(
root.title,
style: AppConstants.textStyleNavDrawer,
),
children: root.children.map<Widget>(_buildTiles).toList(),
);
}
Widget buildList(Entry entry) {
return _buildTiles(entry);
}
}
Custom app bar class
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
final Widget child;
final double height;
final BuildContext context;
CustomAppBar(
{required this.child,
this.height = kToolbarHeight,
required this.context});
#override
Size get preferredSize => Size.fromHeight(height);
#override
Widget build(BuildContext context) {
return AppBar(
title: child,
flexibleSpace: Container(
decoration: AppConstants.customBoxDecoration,
),
actions: <Widget>[
IconButton(
icon: Icon(
Icons.qr_code_scanner,
color: Colors.white,
),
onPressed: () {
Fluttertoast.showToast(msg: context.toString());
// do something
},
)
],
);
}
}
App constants class
import 'package:flutter/material.dart';
class AppConstants {
static const BoxDecoration customBoxDecoration = BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [CustomColors.iconSplashColor, CustomColors.iconColor]));
// you define your own colors
static const TextStyle textStyleNavDrawer = TextStyle(
color: Colors.white);
}
Try this example if you need a single app bar for different screens
Hi i am new to flutter i have used sample database to get data of 10 users. this data is displayed in list tile with leading item is checkbox (to select / deselect). Now i need help in displaying the selected data on to the other page once i press cart button in the appbar..
here's my main
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_application_http_get/example.dart';
import 'package:flutter_application_http_get/screen.dart';
import 'package:flutter_application_http_get/selected.dart';
import 'package:flutter_application_http_get/sunday_state.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Sunday(),
);
}
}
class Sunday extends StatefulWidget {
const Sunday({Key? key}) : super(key: key);
#override
_SundayState createState() => _SundayState();
}
API Called Here
class _SundayState extends State<Sunday> {
var users = [];
Future getUserData() async {
var res =
await http.get(Uri.https("jsonplaceholder.typicode.com", "users"));
var jsonData = jsonDecode(res.body) as List;
setState(() {
users = jsonData;
});
}
#override
void initState() {
super.initState();
getUserData();
}
final notification = [SundayCheckBoxState()];
late final post;
data from post if checked printed..
getCheckboxItems() {
users.forEach((post) {
if (post['checked'] == true) {
print(post);
}
});
}
here in when onpressed i need to display the checked data on to the other page
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("User Data"),
actions: [
IconButton(
onPressed: getCheckboxItems,
icon: Icon(Icons.shopping_cart))
],
),
body: Container(
child: Card(
margin: EdgeInsets.all(20.0),
child: ListView.builder(
itemCount: users.length,
itemBuilder: (context, i) {
final post = users[i];
return Card(
elevation: 5,
child: Padding(
padding: const EdgeInsets.all(12.0),
child: ListView(shrinkWrap: true, children: [
ListTile(
leading: Checkbox(
value: post['checked'] ?? false,
onChanged: (value) {
setState(() {
post['checked'] = value;
});
}),
title: Text("${post['id']}" + "${post['name']}"),
),
])
));
}),
),
),
);
}
You can try this logic it worked for me :
var _isSelectedCheckBoxArr = [];
var _addSelectedValueArr = [];
Checkbox(
value: _isSelectedCheckBoxArr[i],
materialTapTargetSize:
MaterialTapTargetSize
.shrinkWrap,
onChanged: (s) {
setState(() {
_isSelectedCheckBoxArr[i] =
!_isSelectedCheckBoxArr[i];
});
print(
"$_tag onChanged: (s): $s");
if (s) {
setState(() {
_addSelectedValueArr.add(
"${users[i]}");
});
} else if (!s) {
setState(() {
_addSelectedValueArr
.remove(
users[i]);
});
}
}),
Then on the click of cart button pass the _addSelectedValueArr array in the constructor of the screen you want to display.
Hello I try to display a list of data grouped by date.
First I load all key/value saved with sharedpreference
after I use regex to match for each date the data key and data value
after I use List to add all date/data/value like that :
List<String> MylistString=[{"release_date":"2020-10-30","note3":"2222222"}, {"release_date":"2020-10-29","note3":"1111111"}, {"release_date":"2020-10-31","note3":"3333333"}, {"release_date":"2020-10-29","note4":"bdlbd"}]
I don't successed to use groupBy with a MyListString even after add .toString
var foo = groupBy({MylistString.toString()}, (i) => i["release_date"]);
But If I create a static String with the same data it work
Here is complet code who display a list of key value for each date, I search to group this data by date:
import 'package:flutter/material.dart';
import 'package:flutter_timeline/flutter_timeline.dart';
import 'package:flutter_timeline/indicator_position.dart';
import 'package:intl/date_symbol_data_local.dart';
import 'package:intl/intl.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';
void main() {
runApp(TimelineDemoApp());
}
class TimelineDemoApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Timeline',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: PlainTimelineDemoScreen(),
);
}
}
class PlainTimelineDemoScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() => _PlainTimelineDemoScreenState();
}
class _PlainTimelineDemoScreenState extends State<PlainTimelineDemoScreen> {
List<EventData> eventsData;
List<TimelineEventDisplay> events;
String Data1;
String Data2;
#override
void initState() {
super.initState();
}
List<String> stringList_note_formatted = [];
List<String> stringList_date_note1 =[] ;
String value_note1;
List<String> stringList_date_note2 =[] ;
String value_note2;
load_data() async {
//---------------------------------------------------------------------------------------------------
var input = '"2020-10-30","note1":"value1","2020-10-30","note2":"value2","2020-10-31","note1":"value3","2020-10-32","note2":"value4",';
var inputItarable = input.split(',').where((s) => s.isNotEmpty);
var i = inputItarable.iterator;
var tmp = {};
while (i.moveNext()) {
var key = i.current; i.moveNext();
var value = i.current.split(':'); (tmp[key] ??= []).add(value); } print(tmp);
var output = tmp.keys.map((key) {
var map = {}; map['"release_date"'] = key; tmp[key].forEach((e) => map[e[0]] = e[1]);
return map; }).toList();
print(output);
if (output == null) {
//no data available
eventsData = [];
} else {
//deserialize event data from json
var items = jsonDecode(output) as List;
eventsData = items.map((i) => EventData.fromJson(i)).toList();
}
//render event data
events = eventsData.map(plainEventDisplay).toList();
}
final myController2 = TextEditingController();
final myController3 = TextEditingController();
String note2;
dialog_text() async{
await showDialog<String>(
barrierDismissible: false, // user must tap button!
context: context,
builder: (BuildContext context){
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20.0))),
title: Text(("Write something"),textAlign: TextAlign.center, style: TextStyle(color: Colors.black, fontWeight: FontWeight.w600, fontSize: 20)),
content:
Container(
width: MediaQuery.of(context).size.width*1,
child:
new ListView(
children: <Widget>[
Card(
color: Colors.grey[200],
child: Padding(
padding: EdgeInsets.all(8.0),
child: TextField(
controller: myController2,
maxLines:2,
decoration: InputDecoration.collapsed(hintText: "Your text"),
),
)
),
],
),
),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.pop(context,);
},
child: Text('Validate', textAlign:TextAlign.center,style: TextStyle(
fontWeight: FontWeight.bold,
fontSize:15),
),
),
],
);
}
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Timeline test"),
),
body: FutureBuilder(
future: load_data(),
builder: (context, snapshot) {
return _buildTimeline();
},
),
);
}
TimelineEventDisplay plainEventDisplay(EventData eventData) {
var formattedTime = DateFormat('dd-MM-yyyy').format(eventData.dateTime);
Widget text1() {
if (eventData.data1==null){
return Container();
}else{
return Text("note1 : ${eventData.data1}");
}
}
Widget text2() {
if (eventData.data2==null){
return Container();
}else{
return Text("note2 : ${eventData.data2}");
}
}
return TimelineEventDisplay(
anchor: IndicatorPosition.top,
indicatorOffset: Offset(0, 0),
child: TimelineEventCard(
title: Text("${formattedTime}"),
content: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
new Row(
children: [
new Expanded(
child : new Card(
elevation: 3,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
child: new Container(
padding: const EdgeInsets.all (10.0),
child: Column(
children: [
text1(),
Padding(
padding: EdgeInsets.all(5.0),),
text2(),
],
),
),
)
)
],
)
],
)),
indicator: TimelineDots.of(context).circleIcon,
);
}
Widget _buildTimeline() {
return TimelineTheme(
data: TimelineThemeData(
lineColor: Colors.blueAccent, itemGap: 5, lineGap: 0),
child: Timeline(
anchor: IndicatorPosition.center,
indicatorSize: 56,
altOffset: Offset(10, 10),
events: events,
));
}
}
class EventData {
final DateTime dateTime;
final String data1;
final String data2;
EventData(this.dateTime, this.data1, this.data2);
EventData.fromJson(Map<String, dynamic> json)
: dateTime = DateTime.parse(json['release_date']),
data1 = json['note1'],
data2 = json['note2'];
Map<String, dynamic> toJson() => {
'release_date': dateTime.toIso8601String(),
'note1': data1,
'note2': data2,
};
}
I am filtering results in my application based off of the multichoice chips that are selected. I have it filtering results properly, however when I select the done button in the alert dialog it does not save the selected state of the choice chips. It also does not clear the selected states of the choice chips when I hit the clear button. Any recommendations?
MultiFilterChoiceChips Class:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class MultiFilterChips extends StatefulWidget {
final List<String> filterList;
final Function(List<String>) onSelectionChanged;
MultiFilterChips(this.filterList, {this.onSelectionChanged});
#override
_MultiFilterChipsState createState() => _MultiFilterChipsState();
}
class _MultiFilterChipsState extends State<MultiFilterChips> {
List<String> selectedFilters = List();
_buildFilterList() {
List<Widget> filters = List();
widget.filterList..forEach((item){
filters.add(Container(
padding: const EdgeInsets.all(2.0),
child: ChoiceChip(
label: Text('$item'),
selected: selectedFilters.contains(item),
onSelected: (selected) {
setState(() {
selectedFilters.contains(item)
? selectedFilters.remove(item)
: selectedFilters.add(item);
widget.onSelectionChanged(selectedFilters);
});
},
),
));
});
return filters;
}
#override
Widget build(BuildContext context) {
return Wrap(
children: _buildFilterList(),
);
}
}
Filter Pressed (App Bar Icon) Alert Dialog:
_filterPressed() {
return showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
scrollable: true,
title: Text('Filter Scouts'),
content: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Rank:'),
_multiFilterRankChipState(),
Padding(padding: EdgeInsets.all(5)),
Text('Patrol:'),
_multiFilterPatrolChipState(),
],
)),
actions: <Widget>[
FlatButton(
child: Text("Clear"),
onPressed: () {
filter = ""; //ranks filter string to send to the sqlite database
pfilter = ""; //patrol filter string to send to the sqlite database
setState(() {
selectedRanks.clear(); //List<String> that holds the selected ranks
selectedPatrols.clear(); //List<String> that holds the selected patrols
//sends the query to the database and resets the future list builder state
// back to initial state without filters
_searchResults(searchText);
});
},
),
FlatButton(
child: Text("Done"),
onPressed: () {
Navigator.of(context).pop();
})
],
);
});
}
Rank MultiFilter Call:
_multiFilterRankChipState() {
return MultiFilterChips(ranks, onSelectionChanged: (selectedList) {
setState(() {
//selectedList = selectedRanks;
selectedRanks = selectedList;
debugPrint("SELECTED LIST ${selectedRanks.toString()}");
_RanksFilterSet();
});
});
}
For getting the list of Patrols I am getting the distinct list from the sqlite database as the list patrols change overtime thus using a future builder to get the list of strings:
Patrol MultiFilter Call:
_multiFilterPatrolChipState() {
return Container(
child: FutureBuilder<List<String>>(
future: patrols(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return MultiFilterChips(snapshot.data,
onSelectionChanged: (selectedList) {
setState(() {
selectedPatrols = selectedList;
_PatrolFilterSet();
});
});
}
return Container(
alignment: AlignmentDirectional.center,
child: new CircularProgressIndicator(
strokeWidth: 7,
));
},
),
);
}
Let me know if you need more code! Thanks!
You can store the selected items in a Map. In this sample, multi-select mode will start on long press of an item. Multi-select mode will stop when there's no selected items left.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
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> {
var selectMode = false;
Map<String, bool> listItemSelected = {
'List 1': false,
'List 2': false,
'List 3': false,
'List 4': false,
'List 5': false,
'List 6': false,
'List 7': false,
'List 8': false,
'List 9': false,
};
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: ListView(
children: listItemSelected.keys.map((key) {
return Card(
child: GestureDetector(
onTap: () {
// if multi-select mode is true, tap should select List item
if (selectMode && listItemSelected.containsValue(true)) {
debugPrint('onTap on $key');
setState(() {
listItemSelected[key] = !listItemSelected[key];
});
} else {
// Stop multi-select mode when there's no more selected List item
debugPrint('selectMode STOP');
selectMode = false;
}
},
// Start List multi-select mode on long press
onLongPress: () {
debugPrint('onLongPress on $key');
if (!selectMode) {
debugPrint('selectMode START');
selectMode = true;
}
setState(() {
listItemSelected[key] = !listItemSelected[key];
});
},
child: Container(
// Change List item color if selected
color: (listItemSelected[key])
? Colors.lightBlueAccent
: Colors.white,
padding: EdgeInsets.all(16.0),
child: Text(key),
),
),
);
}).toList(),
),
),
);
}
}
Demo