How to retrieve a specific document in firestore with flutter - flutter

I'm trying to retrieve a firestore document in my app so that I can update it. Here's the current code:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
void main() {
runApp(
MyApp(),
);
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Baby Names',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() {
return _MyHomePageState();
}
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Baby Name Votes')),
body: _buildBody(context),
);
}
Widget _buildBody(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('baby').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return LinearProgressIndicator();
return _buildList(context, snapshot.data.documents);
},
);
}
Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot) {
return ListView(
padding: const EdgeInsets.only(top: 20.0),
children: snapshot.map((data) => _buildListItem(context, data)).toList(),
);
}
Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
final record = Record.fromSnapshot(data);
final docID = record.reference.documentID;
return Padding(
key: ValueKey(record.name),
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(5.0),
),
child: ListTile(
title: Text(record.name),
trailing: Text(record.votes.toString()),
onTap: () {
print('Here is the record you have just clicked on: $docID, ${record.name}, ${record.votes}');
showModalBottomSheet(
context: context,
builder: (context) => EditVoteScreen(),
);
},
),
),
);
}
}
class EditVoteScreen extends StatefulWidget {
#override
_EditVoteScreenState createState() => _EditVoteScreenState();
}
class _EditVoteScreenState extends State<EditVoteScreen> {
String newBabyName = 'Gregg';
#override
Widget build(BuildContext context) {
return Container(
color: Color(0xff757575),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.0), topRight: Radius.circular(20.0)),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Edit A Baby Name'),
SizedBox(height: 20.0),
Text(
'Current Name: ${record.name}',
),
SizedBox(height: 20.0),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Change Baby Name To:',
),
SizedBox(
width: 20.0,
),
DropdownButton<String>(
value: newBabyName,
icon: Icon(Icons.arrow_drop_down_circle),
iconSize: 24,
elevation: 16,
underline: Container(
height: 1,
color: Color(0xFF150A42),
),
onChanged: (String newValue) {
setState(() {
newBabyName = newValue;
});
},
items: <String>['Gregg', 'Mikey', 'Joey', 'Dave']
.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
),
],
),
SizedBox(height: 20.0),
FlatButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0)),
color: Color(0xFF150A42),
textColor: Colors.white,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 12.0),
child: Text(
'💾 Save Changes',
),
),
onPressed: () {},
),
],
),
),
);
}
}
class Record {
final String name;
final int votes;
final DocumentReference reference;
Record.fromMap(Map<String, dynamic> map, {this.reference})
: assert(map['name'] != null),
assert(map['votes'] != null),
name = map['name'],
votes = map['votes'];
Record.fromSnapshot(DocumentSnapshot snapshot)
: this.fromMap(snapshot.data, reference: snapshot.reference);
#override
String toString() => "Record<$name:$votes>";
}
The firestore database only has two fields. Here's some sample data: name: 'James' (String), and votes: 2 (number)
When you click on a record in the app, I've managed to get it to print out the docID in the console, as well as the name and votes. The question is, how can i then take the document that I have just clicked on and display it in the ModalBottomSheet so that the name field can be updated?
If I can get it to display in the name field in the ModalBottomSheet, I should be able to figure out how to update it by myself. But I'm struggling to even get it to show up in there! My current code displays the error undefined name 'record'.
Any help would be greatly appreciated!
Thank you
Jason

You need to add a constructor to your EditVoteScreen widget and pass in the document's information so you can use it in your EditVoteScreen's build method:
class EditVoteScreen extends StatefulWidget {
final Record record;
const EditVoteScreen({Key key, this.record}) : super(key: key);
#override
_EditVoteScreenState createState() => _EditVoteScreenState();
}
Pass in the record when you create the class:
showModalBottomSheet(
context: context,
builder: (context) => EditVoteScreen(record: record,),
);
Then reference it within the state class by refering to the widget variable.
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Edit A Baby Name'),
SizedBox(height: 20.0),
Text(
'Current Name: ${widget.record.name}', // Here
),

Related

Deleting Item out of List, Listview.build shows wrong data

I have a Stateful widget that i pass a list to (for example 2 items).
After I delete an item, the widget should rebuild itself.
Unfortunately, the deleted item is still displayed and the other one is not.
When I re-enter the widget, the correct item is loaded.
There is a similar problem List not updating on deleting item
but maybe someone can explain me what i did wrong and why provider is helping me here instead of setState?
My code is:
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/material.dart';
import 'package:trip_planner/util/dialog_box.dart';
import 'package:trip_planner/util/previewUrl.dart';
class BookingPage extends StatefulWidget {
final List toDoList;
BookingPage({
super.key,
required this.toDoList,
});
#override
State<BookingPage> createState() => _BookingPageState();
}
class _BookingPageState extends State<BookingPage> {
//text controller
final _controller = TextEditingController();
final _database = FirebaseDatabase.instance.ref();
//Liste is an example what i have in my list
List toDoList2 = [
["https://www.booking.com/Share-Rnv2Kf", true],
["https://www.booking.com/Share-3hKQ0r", true],
];
void initState(){
super.initState();
}
void deleteTask(int index){
setState(() {
widget.toDoList.removeAt(index);
});
//DatabaseReference _testRef = _database.child("Hotel:");
//_testRef.set(widget.toDoList.toString());
}
//save new Item
void saveNewItem(){
setState(() {
widget.toDoList.add([_controller.text, false]);
//DatabaseReference _testRef = _database.child("Hotel:");
//_testRef.set(widget.toDoList.toString());
_controller.clear();
});
Navigator.of(context).pop();
}
void createNewItem(){
showDialog(
context: context,
builder: (context){
return DialogBox(
controller: _controller,
onSave: saveNewItem,
onCancel: () => Navigator.of(context).pop(),
);
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Booking Seiten'),
elevation: 0,
),
floatingActionButton: FloatingActionButton(
onPressed: createNewItem,
child: Icon(Icons.add),
),
body: ListView.builder(
itemCount: widget.toDoList.length,
itemBuilder: (context, index){
return PreviewUrl(
url2: widget.toDoList[index][0],
deleteFunction: (context) => setState(() => deleteTask(index)),
);
},
),
);
}
}
i thought setState does the same thing as when i re-enter the widget, but it doesn't.
import 'package:any_link_preview/any_link_preview.dart';
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:url_launcher/url_launcher.dart';
class PreviewUrl extends StatelessWidget {
final String url2;
//Function(bool?)? onChanged;
Function(BuildContext)? deleteFunction;
PreviewUrl({
super.key,
required this.url2,
required this.deleteFunction,
//required this.onChanged,
});
Future openBrowserURL({
required String url,
bool inApp = false,
}) async {
if(await canLaunch(url)){
await launch(
url,
forceSafariVC: inApp, //iOS
forceWebView: inApp, //Android
enableJavaScript: true, //Android
);
}
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(1.0),
child: Slidable(
endActionPane: ActionPane(
motion: StretchMotion(),
children: [
SlidableAction(
onPressed: deleteFunction,
icon: Icons.delete,
backgroundColor: Colors.red.shade300,
borderRadius: BorderRadius.circular(12),
)
],
),
child: Container(
child: AnyLinkPreview.builder(
link: url2,
itemBuilder: (context, metadata, imageProvider) => Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (imageProvider != null)
GestureDetector(
onTap: () async {
final url = url2;
openBrowserURL(url: url, inApp: true);
},
child: Container(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.width *0.25,
),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(12),
topRight: Radius.circular(12)),
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
),
),
),
),
Container(
width: double.infinity,
color: Theme.of(context).primaryColor.withOpacity(0.6),
padding: const EdgeInsets.symmetric(
vertical: 10, horizontal: 15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (metadata.title != null)
Text(
metadata.title!,
maxLines: 1,
style:
const TextStyle(fontWeight: FontWeight.w500),
),
const SizedBox(height: 5),
if (metadata.desc != null)
Text(
metadata.desc!,
maxLines: 1,
style: Theme.of(context).textTheme.bodySmall,
),
Text(
metadata.url ?? url2,
maxLines: 1,
style: Theme.of(context).textTheme.bodySmall,
),
],
),
),
],
),
),
),
),
);
}
}
If you run the simplified version of your code in DartPad - it will work:
import 'package:flutter/material.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
List toDoList = [
["Button 1", true],
["Button 2", true],
];
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: BookingPage(toDoList: toDoList),
),
),
);
}
}
class BookingPage extends StatefulWidget {
final List toDoList;
const BookingPage({
super.key,
required this.toDoList,
});
#override
State<BookingPage> createState() => _BookingPageState();
}
class _BookingPageState extends State<BookingPage> {
//Liste is an example what i have in my list
List toDoList2 = [
["Button 1", true],
["Button 2", true],
];
#override
void initState() {
super.initState();
}
void deleteTask(int index) {
setState(() {
widget.toDoList.removeAt(index);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Booking Seiten'),
elevation: 0,
),
body: ListView.builder(
itemCount: widget.toDoList.length,
itemBuilder: (context, index) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.lightBlue,
padding: const EdgeInsets.all(12),
textStyle: const TextStyle(fontSize: 22),
),
child: Text(widget.toDoList[index][0]!),
onPressed: () => setState(() => deleteTask(index)),
);
},
),
);
}
}
Which tells me that the problem is your PreviewUrl. My guess is - it is a statful widget, and when the tree rebuilds - it will link the old State object to the first item.
Using Keys might help, something like:
return PreviewUrl(
key: ObjectKey(widget.toDoList[index]),
url2: widget.toDoList[index][0],
deleteFunction: (context) => setState(() => deleteTask(index)),
);

Flutter : Adding item from one list view to another list view

I am trying to select one item from phone contacts list (List view widget)
class PhoneContacts extends StatefulWidget {
const PhoneContacts({Key? key}) : super(key: key);
#override
State<PhoneContacts> createState() => _PhoneContactsState();
}
class _PhoneContactsState extends State<PhoneContacts> {
List<Contact> _contacts = [];
late PermissionStatus _permissionStatus;
late Customer _customer;
#override
void initState(){
super.initState();
getAllContacts();
}
void getAllContacts() async {
_permissionStatus = await Permission.contacts.request();
if(_permissionStatus.isGranted) {
List<Contact> contacts = await ContactsService.getContacts(withThumbnails: false);
setState(() {
_contacts = contacts;
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Phone Contacts"),
backgroundColor: Colors.indigo[600],
),
body: Container(
padding: const EdgeInsets.all(5),
child: ListView.builder(
itemCount: _contacts.length,
itemBuilder: (BuildContext context, int index) {
Contact contact = _contacts[index];
return contactItem(contact);
}
),
),
);
}
Widget contactItem(Contact contact){
return ListTile(
onTap: () {
Navigator.of(context).push(MaterialPageRoute(builder: (context)=>Dashboard(contact)));
},
leading: const CircleAvatar(
backgroundColor: Colors.pinkAccent,
child: Icon(Icons.person_outline_outlined)),
title : Text(contact.displayName.toString()),
subtitle: Text(contact.phones!.first.value.toString()),
);
}
}
and insert and display it to dashboard list (another List view widget)
class Dashboard extends StatefulWidget {
final Contact? contact;
const Dashboard([this.contact]);
#override
State<Dashboard> createState() => _DashboardState();
}
class _DashboardState extends State<Dashboard> {
final Color? themeColor = Colors.indigo[600];
late GlobalKey<RefreshIndicatorState> refreshKey;
late List<CardGenerator> existingCustomerContactList = getCustomerContactList();
#override
void initState(){
super.initState();
refreshKey=GlobalKey<RefreshIndicatorState>();
}
void addCustomerContact() {
existingCustomerContactList.add(
CardGenerator(
Text(widget.contact!.displayName.toString()),
const Icon(Icons.account_circle),
Text(widget.contact!.phones!.first.value.toString())));
}
List<CardGenerator> getCustomerContactList () {
existingCustomerContactList = [
CardGenerator(
const Text('Dave', style: TextStyle(fontSize: 24.0), textAlign: TextAlign.start,),
const Icon(Icons.account_circle, size: 100, color: Colors.white,),
const Text('Address 1')),
CardGenerator(
const Text('John', style: TextStyle(fontSize: 24.0)),
const Icon(Icons.account_circle, size: 100, color: Colors.white),
const Text('Address 2')),
CardGenerator(
const Text('Richard', style: TextStyle(fontSize: 24.0)),
const Icon(Icons.account_circle, size: 100, color: Colors.white),
const Text('Address 3')),
];
return existingCustomerContactList;
}
Future<void> refreshList() async {
await Future.delayed(const Duration(seconds: 1));
setState(() => {
addCustomerContact(),
getCustomerContactList()
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[50],
appBar: AppBar(
title: const Text("Dashboard"),
backgroundColor: themeColor,
),
body: RefreshIndicator(
key: refreshKey,
onRefresh: () async {
await refreshList();
},
child: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: existingCustomerContactList.length,
key: UniqueKey(),
itemBuilder: (BuildContext context, int index) {
return OpenContainer(
closedColor: Colors.transparent,
closedElevation: 0.0,
openColor: Colors.transparent,
openElevation: 0.0,
transitionType: ContainerTransitionType.fadeThrough,
closedBuilder: (BuildContext _, VoidCallback openContainer) {
return Card(
color: Colors.white,
child: GestureDetector(
onTap: openContainer,
child: SizedBox(
height: 140,
child: Row(
children: [
Container(
decoration: const BoxDecoration(
color: Colors.indigo,
borderRadius: BorderRadius.only(topLeft: Radius.circular(7.0),bottomLeft: Radius.circular(7.0))
),
height: 140,
width: 120,
child: existingCustomerContactList[index].icon,
),
Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: existingCustomerContactList[index].title,
),
Padding(
padding: const EdgeInsets.all(8.0),
child: existingCustomerContactList[index].address,
),
],
)
],
),
),
),
);
},
openBuilder: (BuildContext _, VoidCallback openContainer) {
return ConsumerHome();
}
);
}),
),
],
),
),
);
}
}
I found the
selected item has been added to the Dashboard items list but when I refresh it it doesn't newly added item in the dashboard list view.
I am a newcomer in flutter please bare with me. I already did my search for this problem unfortunately, no luck.
Change the order of execution. You are adding the item in the list and then making a new list again in the current order
addCustomerContact(),
getCustomerContactList()
change this to
getCustomerContactList()
addCustomerContact(),

how to send Data to other screen?

import 'package:flutter/material.dart';
import 'package:flutter_bmi_app/second_screen.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class BmiCalc extends StatefulWidget {
const BmiCalc({Key? key}) : super(key: key);
#override
State<BmiCalc> createState() => _BmiCalcState();
}
class _BmiCalcState extends State<BmiCalc> {
Color colorOfLittleBox = Color.fromARGB(255, 27, 28, 48);
Color colorOfLittleBox2 = Colors.pink;
bool isMale = true;
double _value = 150;
int weight = 60;
int age = 25;
double answer = 10;
String calc = "CALCULATE";
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color.fromARGB(255, 12, 9, 34),
body: SafeArea(
child: SingleChildScrollView(
child: Column(
children: [
Row(
children: [
FemaleBox("MALE", Icons.male),
FemaleBox("FEMALE", Icons.female),
],
),
Column(children: [
Container(
padding: EdgeInsets.all(32),
margin: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Color.fromARGB(255, 27, 28, 48),
borderRadius: BorderRadius.circular(15),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text("HEIGHT",
style: TextStyle(color: Colors.grey, fontSize: 20)),
const SizedBox(
height: 10,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(_value.toStringAsFixed(0),
style: const TextStyle(
fontSize: 45,
color: Colors.white,
fontWeight: FontWeight.w900)),
const Text(
"cm",
style:
TextStyle(fontSize: 20, color: Colors.grey),
),
],
),
Slider(
min: 100,
max: 230,
thumbColor: Colors.pink,
value: _value,
onChanged: (value) {
setState(() {
_value = value;
});
},
),
],
))
]),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Operation("Weight"),
Operation("Age"),
],
),
Container(
decoration: BoxDecoration(
color: Colors.pink,
borderRadius: BorderRadius.circular(15),
),
padding: EdgeInsets.only(bottom: 5),
width: MediaQuery.of(context).size.width,
child: TextButton(
child: Text(
calc,
style: const TextStyle(
fontSize: 22,
color: Colors.white,
fontWeight: FontWeight.w900),
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()),
);
},
),
)
],
),
),
),
);
}
void calculate() {
answer = (weight / (_value * _value)) * 10000;
Text(answer.toString(),
style: const TextStyle(fontSize: 40, color: Colors.white));
if (calc == "CALCULATE") {
calc = answer.toStringAsFixed(1);
} else {
calc = "CALCULATE";
}
setState(() {});
}
}
I made bmi calculator, I wanna have answer on other screen. I want to send this function calculate() to the second screen, where I will
have the answer of this calculation. I gave Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()), but how to make it work? Thank you in advance.
Make the SecondScreen constructor take a parameter for the type of data that you want to send to it.
const SecondScreen(
{Key? key,required this.answer, })
: super(key: key);
final String? answer; //define value you want to pass
#override
_SecondScreenScreenState createState() => _SecondScreenState();
}
And pass data when navigate
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondScreen(answer: 'Hello',),
));
here is the example:
import 'package:flutter/material.dart';
class Todo {
final String title;
final String description;
const Todo(this.title, this.description);
}
void main() {
runApp(
MaterialApp(
title: 'Passing Data',
home: TodosScreen(
todos: List.generate(
20,
(i) => Todo(
'Todo $i',
'A description of what needs to be done for Todo $i',
),
),
),
),
);
}
class TodosScreen extends StatelessWidget {
const TodosScreen({Key? key, required this.todos}) : super(key: key);
final List<Todo> todos;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Todos'),
),
body: ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(todos[index].title),
// When a user taps the ListTile, navigate to the DetailScreen.
// Notice that you're not only creating a DetailScreen, you're
// also passing the current todo through to it.
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(todo: todos[index]),
),
);
},
);
},
),
);
}
}
class DetailScreen extends StatelessWidget {
// In the constructor, require a Todo.
const DetailScreen({Key? key, required this.todo}) : super(key: key);
// Declare a field that holds the Todo.
final Todo todo;
#override
Widget build(BuildContext context) {
// Use the Todo to create the UI.
return Scaffold(
appBar: AppBar(
title: Text(todo.title),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(todo.description),
),
);
}
}
I can't get the FemaleBox and Operation in your project so I can't run that try the above example or share you full code include second screen also
add a constructor in your second screen and pass it while calling second screen
const SecondScreen(
{Key? key,required this.answer, })
: super(key: key);
final String? answer; //define value you want to pass
#override
_SecondScreenScreenState createState() => _SecondScreenState();
}
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondScreen(answer: 'Hello',),
));
There is another way to do that.
Create a class as given below and use static keyword to define any variable.
Now you can call this variable at your entire app via- Common.sharedData
So you can modified it according to you
Class Common{
static int sharedData=0;
//Other function
}
There are different ways to solve this
Sending parameters through constructor (Good solution).
Use a State Management package and hold the state in its class and access tit everywhere (Recommended way).
declare variable globally and use it anywhere in the app (not Recommended)

How to fix "Too many positional arguments: 1 expected, but 3 found." issue in flutter

I'm new to flutter.
I need to get product information through a form using flutter provider.
I can get one object(like String name value only). But when I add multiple parameters, it shows the following error.
Too many positional arguments: 1 expected, but 3 found.
This is the code I wrote.
Model class
class Item {
String itemName;
String description;
double itemPrice;
Item(this.itemName, this.description, this.itemPrice);
}
ChangeNotifier class
class ItemAddNotifier extends ChangeNotifier {
List<Item> itemList = [];
addItem(String itemName, String description, double itemPrice) {
Item item = Item(itemName, description, itemPrice);
itemList.add(item);
notifyListeners();
}
}
Add items
class AddItems extends StatelessWidget {
final TextEditingController _itemNameTextEditing = TextEditingController();
final TextEditingController _itemDescriptionTextEditing =
TextEditingController();
final TextEditingController _itemPriceTextEditing = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Kavishka'),
),
body: Container(
padding: EdgeInsets.all(30.0),
child: Column(
children: [
TextField(
controller: _itemNameTextEditing,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(15.0),
hintText: 'Item Name',
),
),
SizedBox(
height: 20.0,
),
TextField(
controller: _itemDescriptionTextEditing,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(15.0),
hintText: 'Item Description',
),
),
SizedBox(
height: 20.0,
),
TextField(
controller: _itemPriceTextEditing,
decoration: InputDecoration(
contentPadding: EdgeInsets.all(15.0),
hintText: 'Item Price',
),
),
SizedBox(
height: 20.0,
),
RaisedButton(
child: Text('ADD ITEM'),
onPressed: () async {
if (_itemNameTextEditing.text.isEmpty) {
return;
}
await Provider.of<ItemAddNotifier>(context, listen: false)
.addItem(
_itemNameTextEditing.text,
_itemDescriptionTextEditing.text,
_itemPriceTextEditing.text);
Navigator.pop(context);
},
),
],
),
),
);
}
}
Home Screen
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Kavishka'),
actions: [
IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
fullscreenDialog: true,
builder: (context) {
return AddItems();
},
),
);
},
icon: Icon(Icons.add))
],
),
body: Container(
padding: EdgeInsets.all(30.0),
child: Column(
children: [
Consumer<ItemAddNotifier>(builder: (context, itemAddNotifier, _) {
return ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: itemAddNotifier.itemList.length,
itemBuilder: (context, index) {
return Padding(
padding: EdgeInsets.all(15.0),
child: Column(
children: [
Text(
itemAddNotifier.itemList[index].itemName,
style:
TextStyle(fontSize: 20.0, color: Colors.black),
),
],
),
);
});
})
],
),
),
);
}
}
Main
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context) {
return ItemAddNotifier();
},
child: MaterialApp(
home: Container(
color: Colors.white,
child: HomeScreen(),
),
),
);
}
}
It shows the error in Item item = Item(itemName, description, itemPrice); line.
If someone can help me to fix this issue.
Thank you.

Method is called twice in StreamBuilder which contains custom dialog in Flutter

I create a loading dialog and put it in StreamBuilder. At the same time, there is a method named _loadingText as the dialog parameter. When I click the 'Go Run' button, the _loadingText method is called twice.
As the same way, I used the flutter build-in dialog showAboutDialog, everything is OK.
If I remove the StreamBuilder, the _loadingText is called once too.
It takes me one day!!!
Any help is appreciated. Thanks in advance...
main.dart:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:view_animation/loading_dialog.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
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> {
StreamController<String> _streamController;
TextEditingController _inputController;
#override
void initState() {
super.initState();
_streamController = StreamController<String>.broadcast();
_inputController = TextEditingController();
_inputController.addListener(() {
_streamController.add(_inputController.text);
});
}
#override
void dispose() {
super.dispose();
_streamController.close();
}
String _loadingText() {
print('===== 2. Method run OVER =====');
return 'Loading...';
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_inputContainer(),
SizedBox(
height: 20,
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(26),
),
child: StreamBuilder(
stream: _streamController.stream.map((text) => text.length > 4),
builder: (context, snap) {
return FlatButton(
color: Color(0xFFFFAC0B),
disabledColor: Colors.black12,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(26),
),
padding: EdgeInsets.symmetric(vertical: 15, horizontal: 12.5),
onPressed: snap.data != null && snap.data
? () {
print('===== 1. show dialog =====');
showDialog(
context: context,
builder: (BuildContext context) {
return LoadingDialog(
loadingText: _loadingText(),
);
});
// showAboutDialog(context: context, applicationName: _loadingText());
}
: null,
child: Text(
'GO RUN',
style: TextStyle(fontSize: 12, color: Colors.white),
),
);
},
),
),
],
)),
);
}
Widget _inputContainer() {
return Container(
width: 200,
padding: EdgeInsets.only(left: 20, right: 20),
decoration: BoxDecoration(
color: Color(0xFFFFAC0B),
borderRadius: BorderRadius.circular(36.0),
),
child: TextField(
controller: _inputController,
keyboardType: TextInputType.number,
maxLines: 1,
cursorColor: Colors.orange,
style: TextStyle(
color: Colors.white,
fontSize: 24,
),
decoration: InputDecoration(
border: InputBorder.none,
hintText: "Let's GO",
hintStyle: TextStyle(color: Colors.white54, fontSize: 20),
),
),
);
}
}
loading_dialog.dart
import 'package:flutter/material.dart';
class LoadingDialog extends StatefulWidget {
final String loadingText;
final bool outsideDismiss;
final Function dismissCallback;
final Future<dynamic> requestCallback;
LoadingDialog(
{Key key,
this.loadingText = "Loading...",
this.outsideDismiss = true,
this.dismissCallback,
this.requestCallback,
})
: super(key: key);
#override
_LoadingDialogState createState() => _LoadingDialogState();
}
class _LoadingDialogState extends State<LoadingDialog> {
void _dismissDialog(){
if(widget.dismissCallback != null) {
widget.dismissCallback();
}
Navigator.of(context).pop();
}
#override
void initState() {
print('===== 3. loading init =====');
if (widget.requestCallback != null) {
widget.requestCallback.then((_) => Navigator.of(context).pop());
}
super.initState();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: widget.outsideDismiss ? _dismissDialog : null,
child: Material(
type: MaterialType.transparency,
child: Center(
child: SizedBox(
width: 120.0,
height: 120.0,
child: Container(
decoration: ShapeDecoration(
color: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0)
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new CircularProgressIndicator(),
new Padding(
padding: const EdgeInsets.only(
top: 20.0,
),
child: new Text(
widget.loadingText,
style: new TextStyle(fontSize: 12.0),
),
),
],
),
),
),
),
),
);
}
}
log gif here
That's because when you tap on button first time your TextField is still active that means new state comes and flutter rebuilds itself. When you tap on button second your Textfield is inactive.
The points are when you pass the function to the onTap widget it's going to execute when it building state and calling a function without tapping on it:
So instead of a passing method to the OnTap, try something like this:
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () widget.outsideDismiss ? ()
{
this._dismissDialog();
} : null,
...