Implementing Search in ListView builder - flutter

I'm new to coding and I'm trying to make my own app. I'm trying to have a search feature for my listview building but I am having trouble implementing the search. I've tried different tutorials and youtube videos but I can't seem to implement it correctly.
here's the dummy data i'm using
class.dart
class Disease {
String id, title;
List<String> diagnostics;
List<String> management;
Disease({this.id, this.title, this.diagnostics, this.management});
}
data.dart
import 'class.dart';
class Data {
static List<Disease> disease = [
Disease(
id: '1',
title: 'Dengue',
diagnostics: [
'CBC, Plt',
'Dengue NS1 if less than 4 days',
'Dengue Duo if more than 4 days from onset of symptoms',
], management: [
'Hydration',
'No Dark Colored Foods',
'aoiushdioaushd',
]),
Disease(
id: '1',
title: 'Typhoid Fever',
diagnostics: [
'CBC, Plt',
'Dengue NS1 if less than 4 days',
'Dengue Duo if more than 4 days from onset of symptoms',
],
management: [
'Hydration',
'No Dark Colored Foods',
'aoiushdioaushd',
],
)
];
}
and my home page where search is implemented
import 'package:flutter/material.dart';
import 'package:yellow_book/utils/class.dart';
import 'package:yellow_book/utils/data.dart';
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 disease = Data.disease;
TextEditingController editingController = TextEditingController();
final duplicateItems = List<String>.generate(10000, (i) => "Item $i");
var items = List<String>();
#override
void initState() {
items.addAll(duplicateItems);
super.initState();
}
void filterSearchResults(String query) {
List<String> dummySearchList = List<String>();
dummySearchList.addAll(duplicateItems);
if (query.isNotEmpty) {
List<String> dummyListData = List<String>();
dummySearchList.forEach((item) {
if (item.contains(query)) {
dummyListData.add(item);
}
});
setState(() {
items.clear();
items.addAll(dummyListData);
});
return;
} else {
setState(() {
items.clear();
items.addAll(duplicateItems);
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
children: [
TextField(
onChanged: (value) {
filterSearchResults(value);
},
controller: editingController,
decoration: InputDecoration(
hintText: "Search",
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(0.0),
),
),
),
),
Expanded(
child: ListView.builder(
itemCount: disease.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
onTap: () {},
title: Text(disease[index].title),
),
);
},
),
)
],
),
);
}
}
please help. Thank you

Currently your ListView is based on the disease property in your StatefulWidget and you don't change this list in any way (add or remove entries). Your filterSearchResults function as well as in initState you work with your items property so you should reference this one in your ListView or change your overall logic to work on your disease property.
So for now you can change your ListView to:
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
onTap: () {},
title: Text(items[index]),
),
);
},
),
This will show all items entries as ListTile and you should be able to search them. If you want to make this work on your disease property, you need to change your functions to make use of the disease property instead of items.

Related

Access data from custom widget created on different class in flutter

I new to Flutter and i was trying to find a solution for the below issue for several hours. I have searched and every solution provided does not work form me.
I have page where one of the widgets is the autocomplete text input. I have created this autocomplete widget on different class. I have added this widget as StatefulBuilder within my main widget. it is working fine however, i am not able to access its value so I can store it with other fields.
My code look like
class ItemDetails extends StatefulWidget {
const ItemDetails({Key? key}) : super(key: key);
static const routeName = '/item_details';
#override
State<ItemDetails> createState() => _ItemDetails();
}
class _ItemDetails extends State<ItemDetails> {
late TextEditingController labelController;
late TextEditingController valueController;
late TextEditingController notesController;
bool _submitted = false;
late var args;
String _itemLabel2 = "";
// var labelAutoComp = LabelSugg();
#override
void initState() {
super.initState();
labelController = TextEditingController();
valueController = TextEditingController();
notesController = TextEditingController();
}
#override
void dispose() {
labelController.dispose();
valueController.dispose();
notesController.dispose();
// Hive.close();
super.dispose();
}
String? _labelErrorText(context) {
final text = labelController.value.text;
if (text.isEmpty) {
// return 'Can\'t be empty';
return AppLocalizations.of(context)!.noEmpty;
}
}
String? _valueErrorText(context) {
final text = valueController.value.text;
if (text.isEmpty) {
// return 'Can\'t be empty';
return AppLocalizations.of(context)!.noEmpty;
}
}
#override
Widget build(BuildContext context) {
try {
args = ModalRoute.of(context)!.settings.arguments as Map;
} on Exception catch (e) {
// print(e);
}
// print(args);
return Scaffold(
appBar: AppBar(
title: Text(args['title']),
),
body: Container(
padding: const EdgeInsets.all(20),
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(20),
child: Column(
// mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
LabelSugg(getLabelText: (String val) {
print(val);
_itemLabel2 = val;
}),
TextField(
autofocus: true,
decoration: InputDecoration(
labelText: AppLocalizations.of(context)!.label,
hintText: AppLocalizations.of(context)!.labelHint,
errorText:
_submitted ? _labelErrorText(context) : null,
),
controller: labelController,
onChanged: (_) => setState(() {}),
),
const SizedBox(height: 5),
TextField(
autofocus: false,
decoration: InputDecoration(
labelText: AppLocalizations.of(context)!.value,
hintText: AppLocalizations.of(context)!.valueHint,
errorText:
_submitted ? _valueErrorText(context) : null,
),
controller: valueController,
keyboardType: const TextInputType.numberWithOptions(
decimal: true, signed: false),
inputFormatters: <TextInputFormatter>[
FilteringTextInputFormatter.allow(
RegExp(r"[0-9.]")),
TextInputFormatter.withFunction(
(oldValue, newValue) {
try {
final text = newValue.text;
if (text.isNotEmpty) double.parse(text);
return newValue;
} catch (e) {}
return oldValue;
}),
], // Only numbers can be entered
onChanged: (_) => setState(() {}),
),
const SizedBox(height: 5),
TextField(
autofocus: true,
decoration: InputDecoration(
labelText: AppLocalizations.of(context)!.notes,
hintText: AppLocalizations.of(context)!.noteHint,
),
controller: notesController,
onChanged: (_) => setState(() {}),
),
]),
// ],
),
Expanded(
child: Align(
alignment: FractionalOffset.bottomCenter,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton.icon(
onPressed: () {
setState(() => _submitted = true);
if (_labelErrorText(context) == null &&
_valueErrorText(context) == null) {
//insert
var localLabel = labelController.value.text;
var _localValue = 0.0;
if (valueController.value.text != '') {
_localValue =
double.parse(valueController.value.text);
} else {
_localValue = 0.0;
}
var localNotes = notesController.value.text;
addItemToList(
localLabel, _localValue, localNotes);
Navigator.of(context).pop();
labelController.clear();
valueController.clear();
notesController.clear();
}
},
label: Text(AppLocalizations.of(context)!.add),
icon: const Icon(Icons.save, size: 18),
),
const SizedBox(width: 10),
ElevatedButton.icon(
onPressed: () => {Navigator.pop(context)},
label: Text(AppLocalizations.of(context)!.cancel),
icon: const Icon(Icons.cancel, size: 18),
),
],
)),
),
// )
],
)));
}
void addItemToList(String localLabel, double localValue, String localNotes) {
var _item = YearItems()..yearID = args['year'];
_item.itemLabel = localLabel;
_item.itemValue = localValue;
_item.itemNote = localNotes;
print(_itemLabel2);
final itemsBox = ItemsBoxes.getTransactions();
itemsBox.add(_item);
}
}
my labelAutoComp widget code look like
class LabelSugg extends StatefulWidget {
final ValueChanged<String> getLabelText;
const LabelSugg({Key? key, required this.getLabelText}) : super(key: key);
#override
State<LabelSugg> createState() => _LabelSugg();
}
class _LabelSugg extends State<LabelSugg> {
late TextEditingController fieldTextEditingController2;
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
}
getLabel() {
return widget.getLabelText(fieldTextEditingController2.text);
}
#override
Widget build(BuildContext context) {
List<LabelsAc> labelOptions = <LabelsAc>[
LabelsAc(label: AppLocalizations.of(context)!.labelClothes),
LabelsAc(label: AppLocalizations.of(context)!.labelFood),
LabelsAc(label: AppLocalizations.of(context)!.labelPerfumes),
LabelsAc(label: AppLocalizations.of(context)!.labelCapital),
];
return Autocomplete<LabelsAc>(
optionsBuilder: (TextEditingValue textEditingValue) {
return labelOptions
.where((LabelsAc _label) => _label.label
.toLowerCase()
.startsWith(textEditingValue.text.toLowerCase()))
.toList();
},
displayStringForOption: (LabelsAc option) => option.label,
fieldViewBuilder: (BuildContext context,
TextEditingController fieldTextEditingController,
// fieldTextEditingController,
FocusNode fieldFocusNode,
VoidCallback onFieldSubmitted) {
return TextField(
controller: fieldTextEditingController,
focusNode: fieldFocusNode,
style: const TextStyle(fontWeight: FontWeight.bold),
// onChanged: getLabel(),
onChanged: (String val) {
fieldTextEditingController2 = fieldTextEditingController;
getLabel();
});
},
onSelected: (LabelsAc selection) {
fieldTextEditingController2 =
TextEditingController(text: selection.label);
getLabel();
},
optionsViewBuilder: (BuildContext context,
AutocompleteOnSelected<LabelsAc> onSelected,
Iterable<LabelsAc> options) {
return Align(
alignment: Alignment.topLeft,
child: Material(
child: Container(
// width: 350,
// color: Colors.cyan,
child: ListView.builder(
padding: const EdgeInsets.all(10.0),
itemCount: options.length,
itemBuilder: (BuildContext context, int index) {
final LabelsAc option = options.elementAt(index);
return GestureDetector(
onTap: () {
onSelected(option);
},
child: ListTile(
title: Text(option.label,
style: const TextStyle(color: Colors.black)),
),
);
},
),
),
),
);
},
);
// ),
// );
}
}
class LabelsAc {
LabelsAc({required this.label});
String label;
}
first is redundant when you wrap your class that extend StatefullWidget with StatefullBuilder. LabelSugg is a component Widget. you can use it like other widget.
benefit to separate widget with StatefullWidget class is, we can update the value inside the class without re-build the current page. which is good for performance. that's why developer recomend to separete with class insted compared to make local method.
as you see, when you create LabelSugg extend StatefullWidget class , we will have _LabelSugg . underscore means that: all variable only accessible on current file.
thats why we can't call getLabel() or other variable from different file.
its used for handle the State in 'LabelSugg` widget.
now how to pass the value from LabelSugg is by created variable outside the state. here you are:
class LabelSugg extends StatefulWidget {
// use this to pass any changes when we use LabelSugg
final ValueChanged<String> getLabelText;
const LabelSugg({Key? key, required this.getLabelText}) : super(key: key);
#override
State<LabelSugg> createState() => _LabelSugg();
}
then we can call the onChaged inside _LabelSugg state. because its Statefull widget, we can acces by : widget.getLabelText()
class _LabelSugg extends State<LabelSugg> {
late TextEditingController fieldTextEditingController;
.....
getLabel() {
return widget.getLabelText(fieldTextEditingController.text);
}
then in other class we call LabelSugg like common widget
import 'package:../labelsug.dart';
class ItemDetails extends StatefulWidget {
.....
return Scaffold(
appBar: AppBar(
title: Text(args['title']),
),
body: Container(
padding: const EdgeInsets.all(20),
child: Column(
children: <Widget>[
// now use it like a widget
LabelSug(
getLabelText: (String val){
print(val);
}
:)

Infinite-scroll listview.builder - to expand or not to expand... and more provider value not updating and how to fix "RenderFlex overflowed"

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. ...

How to create an expandable list in Flutter navigation drawer that uses single app bar for different screens

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

Unable to transfer a variable into another class

In my program, users can create posts. And every post is called stuff. When an user created a stuff, automatically a stuffID assigns to that stuff. Also, I can list those stuffs in user's profile. When a user presses the edit button of one of their stuffs, a form appears in the screen. So the thing i want to do is when user press the edit button, I want to pass the stuffID from profile_sheet.dart class to update_stuff_form.dart class, so I can know to update the which stuff i will.
I have two .dart file. I used this function in my edit button in profile_sheet.dart file:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => updateStuffForm(
stuffID: data['stuffID'].toString())));
And I added my key function into update_stuff_form.dart like so:
class updateStuffForm extends StatefulWidget {
//const updateStuffForm({Key? key}) : super(key: key);
updateStuffForm({Key? key, this.stuffID}) : super(key: key);
final String? stuffID;
#override
_updateStuffFormState createState() => _updateStuffFormState();
}
And I called stuffID from end of the update_stuff_form.dart like this:
widget.stuffID
But whenever I run the program and press the edit button, It gaves me error below:
ErrorSummary('No Material widget found.'),
ErrorDescription(
'${context.widget.runtimeType} widgets require a Material '
'widget ancestor.\n'
'In material design, most widgets are conceptually "printed" on '
"a sheet of material. In Flutter's material library, that "
'material is represented by the Material widget. It is the '
'Material widget that renders ink splashes, for instance. '
'Because of this, many material library widgets require that '
'there be a Material widget in the tree above them.',
),
ErrorHint(
'To introduce a Material widget, you can either directly '
'include one, or use a widget that contains Material itself, '
'such as a Card, Dialog, Drawer, or Scaffold.',
),
Here is my full profile_sheet.dart file:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:unistuff_main/screens/home/update_stuff_form.dart';
class profileSheet extends StatefulWidget {
const profileSheet({Key? key}) : super(key: key);
#override
_profileSheetState createState() => _profileSheetState();
}
class _profileSheetState extends State<profileSheet> {
final _formkey = GlobalKey<FormState>();
//funcs
#override
Widget build(BuildContext context) {
return Container(
child: userStuffList(),
);
}
}
class userStuffList extends StatelessWidget {
const userStuffList({Key? key}) : super(key: key);
void _editStuff(BuildContext context) {
//stuff_form link.
showModalBottomSheet<dynamic>(
isScrollControlled: true,
context: context,
builder: (context) {
return Container(
padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 60.0),
child: updateStuffForm());
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: _userStuffList(),
);
}
}
class _userStuffList extends StatelessWidget {
void _editStuff(BuildContext context) {
//stuff_form'a yönlendirme.
showModalBottomSheet<dynamic>(
isScrollControlled: true,
context: context,
builder: (context) {
return Container(
padding: EdgeInsets.symmetric(vertical: 20.0, horizontal: 60.0),
child: updateStuffForm());
});
}
#override
Widget build(BuildContext context) {
final FirebaseAuth auth = FirebaseAuth.instance;
final User? user = auth.currentUser;
final _uid = user!.uid;
Query _stuffStream = FirebaseFirestore.instance
.collection('Stuffs')
.where('userID', isEqualTo: _uid);
return Material(
child: StreamBuilder<QuerySnapshot>(
//veri akışı başlatılıyor
//akış oluşturuluyor
stream: _stuffStream.snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Text('Something is wrong.');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text("Loading");
}
return ListView(
//showing the data
children: snapshot.data!.docs.map((DocumentSnapshot document) {
Map<String, dynamic> data =
document.data()! as Map<String, dynamic>;
String stuffID = data['stuffID'];
return ListTile(
title: Text(data['title']),
subtitle: Column(
children: <Widget>[
Text(data['details']),
TextButton(
child: const Text('Düzenle'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => updateStuffForm(
stuffID: data['stuffID'].toString())));
_editStuff(context);
})
],
),
);
}).toList(),
);
}),
);
}
}
And my full update_stuff_form.dart (My key reference at first class, my widget.stuffID call all end of it.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
//import 'package:unistuff_main/screens/home/profile_sheet.dart';
class updateStuffForm extends StatefulWidget {
//const updateStuffForm({Key? key}) : super(key: key);
updateStuffForm({Key? key, this.stuffID}) : super(key: key);
final String? stuffID;
#override
_updateStuffFormState createState() => _updateStuffFormState();
}
class _updateStuffFormState extends State<updateStuffForm> {
final _formkey = GlobalKey<FormState>();
final List<String> categories = ["Spor", "Vasıta", "Elektronik"];
editStuff(stuffID) {
final FirebaseAuth auth = FirebaseAuth.instance;
final User? user = auth.currentUser;
final _uid = user!.uid;
FirebaseFirestore.instance.collection('Stuffs').doc(stuffID).update({
'title': _currentTitle,
'details': _currentDetails,
'price': _currentPrice,
'category': _currentCategory,
'userID': _uid
});
}
bool validatePrice(String str) {
RegExp _numeric = RegExp(r'^-?[0-9]+$');
return _numeric.hasMatch(str);
}
//form values
String? _currentTitle;
String? _currentDetails;
String? _currentPrice;
String? _currentCategory;
#override
Widget build(BuildContext context) {
return Form(
key: _formkey,
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Text(
'Edit your stuff',
style: TextStyle(fontSize: 18.0),
),
SizedBox(height: 20.0),
TextFormField(
decoration: InputDecoration(
fillColor: Colors.brown, hintText: 'Title', filled: true),
validator: (val) {
if ((validatePrice(val!) == false)) {
return "Please enter a number";
}
if (val.isEmpty == true) {
return "Please enter a value";
}
return null;
},
onChanged: (val) => setState(() => _currentTitle = val),
),
SizedBox(height: 20.0),
TextFormField(
keyboardType: TextInputType.multiline,
minLines: 1, //Normal textInputField will be displayed
maxLines: 5,
decoration: InputDecoration(
fillColor: Colors.brown, hintText: 'Details', filled: true),
validator: (val) =>
val!.isEmpty ? 'Please add a detail' : null,
onChanged: (val) => setState(() => _currentDetails = val),
),
SizedBox(height: 20.0),
TextFormField(
decoration: InputDecoration(
fillColor: Colors.brown, hintText: 'Price', filled: true),
validator: (val) =>
val!.isEmpty ? 'Please enter a price' : null,
onChanged: (val) => setState(() => _currentPrice = val),
),
SizedBox(height: 20.0),
DropdownButtonFormField(
items: categories.map((category) {
return DropdownMenuItem(
value: category,
child: Text('$category'),
);
}).toList(),
onChanged: (val) =>
setState(() => _currentCategory = val as String?),
),
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.pink[400],
),
child: Text('Ekle', style: TextStyle(color: Colors.white)),
onPressed: () async {
editStuff(widget.stuffID);
},
),
],
),
),
);
}
}
With the information about screens provided, I can see that your profile_sheet.dart is not having MaterialApp() as the first ancestor. So in place of Material(), use MaterialApp().
Inside build method of profile_sheet.dart, replace Material() to:
return MaterialApp()

How to make widget working faster - custom json convert widget

I have a list where I want to display a list of objects. Each object has a json field where "tags" are defined. Each consists of a field name and a value. The values from the fields contained in json have to be displayed in a nice looking form of "tiles". When a field is focused, the field name (ToolTip) should appears.
I wrote my widget that converts json for this. Works fine, but I've found that performance of list slows down much with number of items (scrolling and hovering over an item)
At 10 it is still ok, but at 50 it's not. When I comment "JsonWidget" and just use Text widget, everything works fast. I wonder if there is a way to write it in a more optimal way.
Her is full code:
import 'dart:convert';
import 'package:flutter/material.dart';
void main() {
runApp(GenistryApp());
}
//My example object with json field
class MyObject {
int id;
String json =
'{"year":1850,"1 name":"Zuzanna","names":"Stefania","surname":"Zabłocka","sex":"W","city":"Warsaw","date":"1850.01.02","father":"Piotr","mothers_anme":"Józefa","mothers_surname":"Wojnicz","info":"Szlachetni"}';
MyObject(this.id);
}
class GenistryApp extends StatelessWidget {
const GenistryApp({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyTableWidget(),
theme: ThemeData(
primarySwatch: Colors.lightGreen,
visualDensity: VisualDensity.adaptivePlatformDensity,
));
}
}
class MyTableWidget extends StatefulWidget {
const MyTableWidget({Key key}) : super(key: key);
#override
_MyTableWidgetState createState() => _MyTableWidgetState();
}
class _MyTableWidgetState extends State<MyTableWidget> {
MyDataTableSource _data = MyDataTableSource([]);
List<MyObject> list = [];
//Generating 50 same elements for testing
getData() {
for (int i = 0; i < 50; i++) {
list.add(MyObject(i));
}
_data = MyDataTableSource(list);
}
#override
Widget build(BuildContext context) {
getData();
return Scaffold(
backgroundColor: Colors.white.withOpacity(0.8),
body: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: PaginatedDataTable(
source: _data,
columns: [
DataColumn(label: Container()),
DataColumn(
label: ConstrainedBox(
constraints: BoxConstraints.expand(
width: MediaQuery.of(context).size.width))),
],
columnSpacing: 50,
horizontalMargin: 10,
rowsPerPage: 50,
showCheckboxColumn: false,
),
));
}
}
class MyDataTableSource extends DataTableSource {
List<MyObject> _data;
MyDataTableSource(this._data);
bool get isRowCountApproximate => false;
int get rowCount => _data.length;
int get selectedRowCount => 0;
DataRow getRow(int index) {
return DataRow(cells: [
DataCell(Text("ID")),
DataCell(
JsonWidget(_data[index]
.json), // Here is my widget to convert json field - it slows all list
// Text(_data[index].json), // Here is simple json field for testing - it works fast
onTap: () => {},
),
]);
}
}
//Widget to convert json to nice looking "tags"
class JsonWidget extends StatelessWidget {
String jdata;
Map<String, dynamic> data;
JsonWidget(String jdata) {
this.jdata = jdata;
}
var containers = <Tooltip>[];
#override
Widget build(BuildContext context) {
this.data = json.decode(this.jdata);
data.entries.forEach((element) {
containers.add(new Tooltip(
message: element.key, //tag's fieldname
textStyle: TextStyle(color: Colors.white),
child: Container(
margin: const EdgeInsets.all(3),
padding: const EdgeInsets.all(4),
child: Text(element.value.toString()), //tag's fieldvalue
decoration: BoxDecoration(
border: Border.all(color: Colors.blueAccent, width: 0.7),
borderRadius: BorderRadius.all(Radius.circular(20))),
)));
});
return Row(children: this.containers);
}
}
The build method is designed in such a way that it should be pure/without side effects. It should only return a widget and not run for loops as in the JsonWidget case. A better way to write this will be using listview builder.
class JsonWidget extends StatelessWidget {
Map<String, dynamic> data;
JsonWidget(Map<String, dynamic> data) {
this.data = data;
}
var containers = <Tooltip>[];
#override
Widget build(BuildContext context) {
return ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: data.entries.length,
itemBuilder: (context, index) {
return Tooltip(
message: data.entries.toList()[index].key, //tag's fieldname
textStyle: TextStyle(color: Colors.white),
child: Container(
margin: const EdgeInsets.all(3),
padding: const EdgeInsets.all(4),
child: Text(data.entries
.toList()[index]
.value
.toString()), //tag's fieldvalue
decoration: BoxDecoration(
border: Border.all(color: Colors.blueAccent, width: 0.7),
borderRadius: BorderRadius.all(Radius.circular(20))),
));
});
}
}
And MyDataTableSoure will look like this
class MyDataTableSource extends DataTableSource {
List<MyObject> _data;
MyDataTableSource(this._data);
bool get isRowCountApproximate => false;
int get rowCount => _data.length;
int get selectedRowCount => 0;
DataRow getRow(int index) {
return DataRow(cells: [
DataCell(Text("ID")),
DataCell(
JsonWidget(json.decode(_data[index]
.json)), // Here is my widget to convert json field - it slows all list
// Text(_data[index].json), // Here is simple json field for testing - it works fast
onTap: () => {},
),
]);
}
}