On click of floatingActionButton in main.dart file, I'm calling a dialog widget.
main.dart
late ShoppingListDialog dialog;
#override
void initState() {
dialog = ShoppingListDialog();
super.initState();
}
floatingActionButton: FloatingActionButton(
child: Icon(
Icons.add,
),
backgroundColor: Colors.pink,
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) => dialog.buildDialog(
context, ShoppingList(id: 0, name: '', priority: 0), true),
);
},
),
shopping_list_dialog.dart
class ShoppingListDialog {
final txtName = TextEditingController();
final txtPriority = TextEditingController();
Widget buildDialog(BuildContext context, ShoppingList list, bool isNew) {
DbHelper helper = DbHelper();
if (!isNew) {
txtName.text = list.name;
txtPriority.text = list.priority.toString();
}
return AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(30.0)),
title: Text((isNew) ? 'New shopping list' : 'Edit shopping list'),
content: SingleChildScrollView(
child: Column(
children: [
TextField(
controller: txtName,
onTap: () {},
decoration: InputDecoration(hintText: 'Shopping List Name')),
TextField(
controller: txtPriority,
keyboardType: TextInputType.number,
decoration:
InputDecoration(hintText: 'Shopping List Priority (1-3)'),
),
TextButton(
child: Text('Save Shopping List'),
onPressed: () {
list.name = txtName.text;
list.priority = int.parse(txtPriority.text);
helper.insertList(list);
Navigator.pop(context);
},
),
],
),
),
);
}
}
TextField is empty, the first time (showing the hint text). But the second time onwards, it gets filled with the last used values, while I intend them to be empty. Like in the image below, the second time when I hit on floatingActionButton to add something, it gets filled with "fruits"(values I had used previously).
TextField should start empty but it's getting filled with previous used values.
Here is a basic solution for you
TextButton(
child: Text('Save Shopping List'),
onPressed: () {
list.name = txtName.text;
list.priority = int.parse(txtPriority.text);
// Clear TE Controller
txtName.clear();
txtPriority.clear();
// Insert
helper.insertList(list);
Navigator.pop(context);
},
),
Related
I have a button that calls a function that adds user input to a List. If the list.isNotEmpty then an ElevatedButton is displayed with the user's input as it's label. I have a numberPicker that allows the user to select an amount, also in the same dialog box. The code works as intended apart from adding the amount to the List (I assume I would need some sort of Map<int, String>?)
I would like to loop through the List and add a new ElevatedButton every time the user enters a new item and amount. So far, the code only creates a new button with the list.first as its text. How do I create a loop to iterate through the List every time the user adds a new item that corresponds with the index to display the correct item?
Code that creates new button:
children: [
ElevatedActionButton(
icon: const Icon(Icons.add),
buttonText: const Text('Add Ingredient'),
onPressedAction: () {
_addIngredient(context);
},
),
if (ingredientList.isNotEmpty)
ElevatedButton(
onPressed: () {},
child: Text(
'$_currentValue ${ingredientList.first}'),
),
],
Code that showsDialog:
Future<void> _addIngredient(BuildContext context) async {
valueText = 'Add Ingredient';
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Add Ingredient'),
content: Row(
children: [
Container(
width: 150,
child: TextField(
onChanged: (value) {
setState(() {
valueText = value;
});
},
controller: _ingredientController,
decoration: InputDecoration(hintText: '"Tomato"'),
),
),
NumberPicker(
value: _currentValue,
minValue: 0,
maxValue: 100,
onChanged: (value) => setState(() => _currentValue = value),
),
],
),
actions: <Widget>[
ElevatedButton(
child: Text('CANCEL'),
onPressed: () {
setState(() {
Navigator.pop(context);
});
},
),
ElevatedButton(
child: Text('OK'),
onPressed: () {
setState(() {
if (_ingredientController.text.isNotEmpty) {
ingredientList.add(_ingredientController.text);
codeDialog = valueText;
print(ingredientList);
print(ingredientList.length);
Navigator.pop(context);
} else {
Navigator.pop(context);
}
});
},
),
],
);
});
I have an Elevated Icon Button that when pressed displays a Dialog that prompts a user to enter text and select an amount from a NumberPicker. The showDialog function is called that saves the user's entry in a TextEditingController:
How would I create a function that once the user saves their entry in Dialog, creates a new button that displays TextEditingController.text as shown in the below:
_addItem code:
Future<void> _addItem(BuildContext context) async {
valueText = '';
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text('Example'),
content: Row(
children: [
Container(
width: 150,
child: TextField(
onChanged: (value) {
setState(() {
valueText = value;
});
},
controller: _textController,
decoration: InputDecoration(hintText: '"Example"'),
),
),
NumberPicker(
value: _currentValue,
minValue: 0,
maxValue: 100,
onChanged: (value) => setState(() => _currentValue = value),
),
],
),
actions: <Widget>[
ElevatedButton(
child: Text('CANCEL'),
onPressed: () {
setState(() {
Navigator.pop(context);
});
},
),
ElevatedButton(
child: Text('OK'),
onPressed: () {
setState(() {
codeDialog = valueText;
Navigator.pop(context);
});
},
),
],
);
});
}
Currently the button updates to the textEditingController.text. I want to replace the Icon with the number selected by the user but I'll figure that out later. Probably best to just replace it with a conditional statement that renders a list of different buttons. They need to be buttons so they can be edited if needed before all data is sent to a server.
You have to use statement in the build method of your Stateful widget
... State of your widget ...
final TextController _textController;
final int _currentValue;
Widget build(context) {
return YourWidgetTree(
child: Row(
children: [
ElevatedButton(child: Text('Add item')),
if(_controller.text.isNotEmpty)
ElevatedButton(child: Text('$_currentValue ${_controller.text}', onPressed: (){ ... })),
])
)
}
Future<void> _addItem(BuildContext context) {...}
I want to implement a function as following:
There is a button named 自定义. In this page, there is a variable named money. When I click the button, an AlertDialog with a TextFormField inside will occur. I hope that after inputting a number X into the TextFormField and clicking button ok to exit the AlertDialog, money would be changed to X. I have used onSaved to save the variable, and used _formkey.currentState.save(), but money didn't change. What's wrong with my codes? Here are my codes:
void _showMessageDialog() {
//int addMoney = 0;
showDialog(
context: context,
builder: (BuildContext context) {
// return object of type Dialog
return AlertDialog(
key: _formKey,
title: new Text('INPUT'),
content: TextFormField(
maxLines: 1,
keyboardType: TextInputType.emailAddress,
autofocus: false,
style: TextStyle(fontSize: 15),
decoration: new InputDecoration(
border: InputBorder.none,
hintText: 'input',
),
onSaved: (value) {
money = int.parse(value?.trim() ?? '0') as double;
print(money);
}
),
actions: <Widget>[
new TextButton(
key: _formKey,
child: new Text("ok"),
onPressed: () {
_formKey.currentState?.save();
Navigator.of(context).pop();
},
),
],
);
},
);
}
Here are the codes relative to the button 自定义
OutlinedButton(
style: OutlinedButton.styleFrom(
side: BorderSide(
width: 1,
color: Colors.blueAccent
)
),
onPressed: () {
// Navigator.of(context).push(
// _showMessageDialog()
// );
_showMessageDialog();
},
child: Text(
"自定义",
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w700,
color: Colors.blueAccent
),
),
),
I know maybe I have made a big mistake, but it is my first Flutter project. Thanks for your advices.
I would use ValueNotifier for this. But first you need to add a controller to your TextFormField so you can get the text user typed in.
//initialize it
final myController = TextEditingController();
TextFormField(
controller: myController, //pass it to the TextFormField
),
TextButton(
child: new Text("ok"),
onPressed: () {
String input = myController.text; //this is how you get the text input
_formKey.currentState?.save();
Navigator.of(context).pop();
},
),
As I said you also need to initialize ValueNotifier and pass it to _showMessageDialog
ValueNotifier<int> money = ValueNotifier(0);
_showMessageDialog(money); //and pass it to your function
void _showMessageDialog(ValueNotifier<int> money) {
TextButton(
child: new Text("ok"),
onPressed: () {
String input = myController.text; //this is how you get the text input
money.value = int.parse(input); //and then update your money variable
_formKey.currentState?.save();
Navigator.of(context).pop();
},
),
}
I decided not to use the searchdelegate but to use the regular search in JSON (cause I need to solve this problem in the near 2 hours). The problem is that everything seems to work well (I have no errors in the stacktrace), but I just can't understand why the search does not work. I decided that it was necessary to use the Future inside the search widget, but this also did not give any results. Can someone point me to what exactly is going wrong? Maybe I need to create an array to add the search results too? I use not just an ordinary JSON, but it goes through a sorting algorithm and I have seen in other solutions that people use a list of elements, and then fetch one element from all: like this - Future <List<Post>> fetchAllPosts and Future<Post> fetchPost. But I'm doing this:
class MarshrutesPage extends StatefulWidget {
final int ttId;
MarshrutesPage({this.ttId});
#override
_MarshrutesPageState createState() => _MarshrutesPageState();
}
class _MarshrutesPageState extends State<MarshrutesPage> {
Box<RouteWithStops> favoriteRoutesBox;
TransportService service = getIt<TransportService>();
#override
void initState() {
super.initState();
favoriteRoutesBox = Hive.box(favoritesBox);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
icon: Icon(
Icons.search,
color: Colors.white,
),
onPressed: () {
showSearch(context: context, delegate: SearchBar());
},
),
IconButton(
icon: Icon(
Icons.favorite,
color: Colors.white,
),
onPressed: () {
Navigator.pushNamed(context, 'favorite');
},
),
],
elevation: 0.0,
backgroundColor: Colors.green,
title: Text(
'numbers',
style: GoogleFonts.montserrat(
color: Colors.white, fontWeight: FontWeight.w400),
),
),
body: FutureBuilder(
future: service.getMarshrutWithStops(widget.ttId),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
List<RouteWithStops> routes = snapshot.data;
print(routes?.toString());
return (routes == null)
? Center(child: CircularProgressIndicator())
: ValueListenableBuilder(
valueListenable: favoriteRoutesBox.listenable(),
builder: (BuildContext context, value, Widget child) {
return ListView.builder(
itemCount: routes.length + 1,
itemBuilder: (context, index) {
return index == 0
? _searchBar()
: _listItem(index - 1, routes);
},
);
},
);
}),
bottomNavigationBar: BottomAppBar(
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
IconButton(
icon: Icon(
Icons.message,
color: Colors.grey,
),
tooltip: 'to devs',
onPressed: () async {
await launch("mailto: #gmail.com");
}),
IconButton(
icon: Icon(Icons.home, color: Colors.grey), onPressed: () {}),
IconButton(
icon: Icon(Icons.notifications, color: Colors.grey),
onPressed: () {}),
IconButton(
icon: Icon(Icons.map, color: Colors.grey), onPressed: () {}),
]),
),
);
}
Widget getIcon(int index) {
if (favoriteRoutesBox.containsKey(index)) {
return Icon(Icons.favorite, color: Colors.red);
}
return Icon(Icons.favorite_border);
}
void onFavoritePress(int index) {
List<RouteWithStops> routes;
if (favoriteRoutesBox.containsKey(index)) {
favoriteRoutesBox.delete(index);
return;
}
favoriteRoutesBox.put(index, routes[index]);
}
_listItem(index, List<RouteWithStops> routes) {
return ListTile(
title: Text(routes[index].route.mrTitle),
leading: Text(
routes[index].route.mrNum,
style: TextStyle(color: Colors.green, fontSize: 20),
),
trailing: IconButton(
icon: getIcon(index),
onPressed: () => onFavoritePress(index),
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StopsPage(
routeWithStops: routes[index],
)));
},
);
}
_searchBar() {
return FutureBuilder(
future: service.getMarshrutWithStops(widget.ttId),
builder:
(BuildContext context, AsyncSnapshot<List<RouteWithStops>> snapshot) {
List<RouteWithStops> routes = snapshot.data;
print('test1');
return (routes == null)
? Center(child: CircularProgressIndicator()) : Padding(
padding: const EdgeInsets.all(8),
child: TextField(
decoration: InputDecoration(hintText: 'Search',
hoverColor: Colors.green,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
prefixIcon: Icon(Icons.search),),
onChanged: (text) {
text = text.toLowerCase();
setState(() {
routes = routes.where((element) {
var routesTitle = element.route.mrTitle.toLowerCase();
return routesTitle.contains(text);
}).toList();
print('test2');
});
},
),
);
},
);
}
}
Also i think that it dosen't work because I use the parametrs - ttId.
The algorithm I use
Future<List<RouteWithStops>> getMarshrutWithStops(int ttId) async {
if (routesbyTransportType.isEmpty) {
await fetchTransportWithRoutes();
}
List<Routes> routes = routesbyTransportType[ttId].routes;
List<ScheduleVariants> variants = [];
variants.addAll(await api.fetchSchedule());
List<RouteWithStops> routesWithStops = [];
for (Routes route in routes) {
final routeWithStops = RouteWithStops();
routesWithStops.add(routeWithStops);
routeWithStops.route = route;
routeWithStops.variant =
variants.where((variant) => variant.mrId == route.mrId).first;
}
return routesWithStops;
}
Stacktrace after putting several letters in search bar:
W/IInputConnectionWrapper(25362): beginBatchEdit on inactive InputConnection
W/IInputConnectionWrapper(25362): endBatchEdit on inactive InputConnection
I/flutter (25362): [Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops', Instance of 'RouteWithStops']
I/flutter (25362): test2
I/flutter (25362): Basic Vk9MR0E6TkVU
routes = routes.where((element) {
This is setting your local variable named "routes". It won't do anything. I cannot really tell what you wanted to do, maybe set a variable of your state?
The problem was solved quite simply, I just needed to be a little more careful: in general, I need to declare two arrays at the top in the widget tree itself:
List<RouteWithStops> _routes = [];
List<RouteWithStops> _routesToDisplay = [];
The first array is responsible for all the elements of the list that need to be displayed under the search bar. The second array is needed so that when you set up the search and enter a certain name / value there, then it does not leave just the value you just entered, but loads others as well. For example, you are looking for a title by author: Tolstoy "War and Peace". If you leave it as it is, without adding a second array, then you will still have Tolstoy "War and Peace" at the bottom under the search bar as the only item until the next reload. It doesn't have to be that way.
To avoid this, you need to do the following:
#override
void initState() {
service.getMarshrutWithStops(widget.ttId).then((value) {
setState(() {
_routes.addAll(value);
_routesToDisplay = _routes;
});
});
super.initState();
}
Data from the first array is written to the empty second array, so the search becomes, so to speak, dynamic and now all the author's books are displayed and when the author's surname is erased from the search, the rest of the array is loaded, and not just what was typed in the search.
In the tree of widgets, we do everything the same as in my question, with the only difference that in _listItem we pass only index and nothing else, and in order for the loading to occur correctly, I just need to add the load in the main widget in front of ListView.builder, using ternary operators:
body: (_routes == null) ? CircularProgressIndicator() : ValueListenableBuilder(
builder: (BuildContext context, value, Widget child) {
return ListView.builder(
itemCount: _routesToDisplay.length + 1,
itemBuilder: (context, index) {
return index == 0
? _searchBar()
: _listItem(index-1);
},
);
P.S There is no need for a ValueListenableBuilder, I started to save the selected elements in the database, only this line is needed to load the list items: (_routes == null)? CircularProgressIndicator (): ListView.builder
Also, in the widgets for displaying the list and search, instead of _routes, add _routesToDisplay and it turns out like this:
_listItem(index) {
return ListTile(
title: Text(_routesToDisplay[index].route.mrTitle),
leading: Text(
_routesToDisplay[index].route.mrNum,
style: TextStyle(color: Colors.green, fontSize: 20),
),
trailing: IconButton(
icon: getIcon(index),
onPressed: () => onFavoritePress(index, _routes),
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StopsPage(
routeWithStops: _routes[index],
)));
},
);
}
_searchBar() {
print('test1');
return Padding(
padding: const EdgeInsets.all(8),
child: TextField(
decoration: InputDecoration(
hintText: 'Search',
hoverColor: Colors.green,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
),
prefixIcon: Icon(Icons.search),
),
onChanged: (text) {
text = text.toLowerCase();
setState(() {
print('object');
_routesToDisplay = _routes.where((element) {
var routesTitle = element.route.mrTitle.toLowerCase();
print(routesTitle);
return routesTitle.contains(text);
}).toList();
});
},
),
);
}
Of course, you can remove all prints and use it as you want. This is quite simple task, but I am noob and wasn't careful as much as it needed.
I am working on a flutter project, which separated the body: widget from the main.dart and placed it inside a new statefull widget with the file name todu_list.dart now i am trying to call it back to main.dart file body: SingleChildScrollView(child: Lists()), and am getting this error
1 positional argument(s) expected, but 0 found.
Try adding the missing arguments.
I have gone through alot of similar questions here on StackOverFlow and realised i am supposed to add an argument inside the brackets "()" but i don't know which of the function from my Lists widget that i am expected to call there
Below is the "Lists" widget code
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import '../models/todus.dart';
import 'package:intl/intl.dart';
import 'package:sqflite/sqflite.dart';
import '../models/database_helper.dart';
class Lists extends StatefulWidget {
final Function addTx;
Lists(this.addTx);
#override
_ListsState createState() => _ListsState();
}
class _ListsState extends State<Lists> {
final dbHelper = DatabaseHelper.instance;
void _addNewTransaction(BuildContextcontext) {
showModalBottomSheet(
backgroundColor: Colors.white,
isScrollControlled: true,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(25.0))),
context: context,
builder: (_) {
return GestureDetector(
onTap: () {},
// Where i started the code pasting from
child: Padding(
padding: MediaQuery.of(context).viewInsets,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
elevation: 0.000,
child: Container(
padding: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
TextField(
decoration: InputDecoration(labelText: 'Title'),
controller: _titleController,
autofocus: true,
onSubmitted: null,
// onChanged: (val) {
// titleInput = val;
// },
),
TextField(
decoration: InputDecoration(labelText: 'Description'),
controller: _discriptionController,
onSubmitted: null,
// onChanged: (val) => amountInput = val,
),
Container(
height: 70,
child: Row(
children: [
Text(selectedDateAndTime == null
? 'No Date Choosen'
: DateFormat('MM/dd/yyyy HH:mm')
.format(selectedDateAndTime)
// : DateFormat.yMd()
// .format(_selectedDate),
),
FlatButton(
textColor: Theme.of(context).primaryColor,
child: Icon(Icons.calendar_today),
// onPressed: () async {
// var value = await _selectedTime();
// },
onPressed: () => _selectDayAndTimeL(context),
),
],
),
),
RaisedButton(
child: Text('Save Todo'),
color: Theme.of(context).primaryColor,
textColor: Theme.of(context).textTheme.button.color,
onPressed: _submitData,
),
],
),
),
),
),
),
);
},
);
}
final _titleController = TextEditingController();
final _discriptionController = TextEditingController();
var favorite;
// DateTime _selectedDate;
DateTime selectedDateAndTime;
#override
void dispose() {
super.dispose();
_discriptionController.dispose();
_titleController.dispose();
}
Future _selectDayAndTimeL(BuildContext context) async {
DateTime _selectedDay = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2021),
lastDate: DateTime(2030),
builder: (BuildContext context, Widget child) => child);
TimeOfDay _selectedTime = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
if (_selectedDay != null && _selectedTime != null) {
//a little check
}
setState(() {
selectedDateAndTime = DateTime(
_selectedDay.year,
_selectedDay.month,
_selectedDay.day,
_selectedTime.hour,
_selectedTime.minute,
);
// _selectedDate = _selectedDay;
});
// print('...');
}
List<ItemLists> items = [
ItemLists(
title: 'Best Music of the Year',
description: 'Davido',
favorite: false,
),
ItemLists(
title: 'Best Album Cover design',
description: 'Brighter Press',
favorite: false,
),
void _submitData() {
// if (_amountController.text.isEmpty) {
// return;
// }
final enteredTitle = _titleController.text;
final enteredDescription = _discriptionController.text;
if (enteredTitle.isEmpty) {
return;
}
widget.addTx(
enteredTitle,
enteredDescription,
selectedDateAndTime,
);
Navigator.of(context).pop();
}
#override
Widget build(BuildContext context) {
return SizedBox(
child: Container(
child: ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemBuilder: (context, index) {
return Dismissible(
key: ObjectKey(items[index]),
background: Container(
color: Colors.red,
),
child: Card(
child: ListTile(
leading: new IconButton(
icon: Icon(
Icons.check,
color:
items[index].favorite ? Colors.green : Colors.grey,
),
tooltip: 'Add to Favorite',
onPressed: () {
setState(() {
items[index].favorite = !items[index].favorite;
});
}),
title: Text('${items[index].title}'),
subtitle: Text('${items[index].description}'),
trailing: IconButton(
icon: Icon(Icons.calendar_today),
onPressed: () => _selectDayAndTimeL(context),
),
)),
onDismissed: (direction) {
final String myTitle = items[index].title;
// Remove the item from the data source.
setState(() {
var deletedItems = items.removeAt(index);
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text('$myTitle Deleted'),
action: SnackBarAction(
label: 'Undo',
onPressed: () => setState(
() => items.insert(index, deletedItems),
)),
),
);
});
});
},
itemCount: items.length,
),
),
);
floatingActionButton:
FloatingActionButton(
child: Icon(Icons.add),
onPressed: () => _addNewTransaction(context),
backgroundColor: Colors.redAccent,
);
}
}
You have to give a function as parameter in order to build your widget. This is not a function of your widget that you will be calling but the function addTx that you will be calling from within your Lists widget.
Either remove the parameter or pass a function to solve it.
Example: since your function is expected to have 3 parameters:
widget.addTx(
enteredTitle,
enteredDescription,
selectedDateAndTime,
);
you can create:
void addTitleDescDate(string title, string description, string date) { // NB you should probably use a Date object or epoch time.
print(title);
print(description);
print(date);
}
And you use this function Lists(addTitleDescDate)
As a side note I don't really see the point to have this function as a parameter shared to the Lists widget, but if you want to learn more about function as parameter that is still interesting.