Flutter : The onRefresh callback returned null - flutter

Sorry I'm new to flutter and trying to learn it. I have an issue with RefreshIndicator.
When I try pulling it I got an error like below :
════════ Exception caught by material library ══════════════════════════════════════════════════════
The following assertion was thrown when calling onRefresh:
The onRefresh callback returned null.
The RefreshIndicator onRefresh callback must return a Future.
════════════════════════════════════════════════════════════════════════════════════════════════════
Currently I am using flutter_bloc. Here is my sample code
TableList_bloc.dart
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'package:pos_project_bloc/modal/api.dart';
class ModalTableList {
final String Table_Number;
final String Name_Table;
ModalTableList(
this.Table_Number,
this.Name_Table,
);
}
class tablelistbloc extends Bloc<bool, List<ModalTableList>>{
#override
// TODO: implement initialState
List<ModalTableList> get initialState => [];
#override
Stream<List<ModalTableList>> mapEventToState(bool event) async* {
// TODO: implement mapEventToState
List<ModalTableList> tablelist =[];
try {
final response = await http.get(BaseUrl.GetTableList);
final data = jsonDecode(response.body);
if (data.length != 0) {
data.forEach((api) {
tablelist.add(
ModalTableList(
api['Table_Number'],
api['Name_Table'],
)
);
print("test"+api['Table_Number'].toString());
});
print("Get Table List : sukses");
} else {
print('data kosong');
}
} catch (e) {
print("Error GetTableList :");
print(e);
}
yield tablelist;
}
}
TabeList.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:pos_project_bloc/bloc/TableList_bloc.dart';
import 'package:pos_project_bloc/modal/SharedPreferences.dart';
class TableList extends StatefulWidget {
#override
_TableListState createState() => _TableListState();
}
class _TableListState extends State<TableList> {
#override
void initState() {
super.initState();
BlocProvider.of<tablelistbloc>(context).add(true);
}
Widget build(BuildContext context) {
final GlobalKey<RefreshIndicatorState> refresh = GlobalKey<RefreshIndicatorState>();
Future<Null> _refresh() async{
BlocProvider.of<tablelistbloc>(context).add(true);
}
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.lightBlueAccent,
title: new Center(
child: new Text('Available Table',
style: new TextStyle(color: Colors.white, fontSize: 15.0)),
)
),
floatingActionButton: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Padding(
padding: EdgeInsets.all(5.0),
child: FloatingActionButton.extended(
heroTag: 1,
icon: Icon(Icons.refresh),
label: Text('Refresh Table List'),
onPressed: () {
BlocProvider.of<tablelistbloc>(context).add(true);
},
),
)
],
),
),
body: RefreshIndicator(
onRefresh: (){
_refresh();
},
key: refresh,
child: BlocBuilder<tablelistbloc,List<ModalTableList>>(
builder: (context,tablelist)=> ListView.builder(
itemCount: tablelist.length,
itemBuilder: (context,index){
final x = tablelist[index];
return GestureDetector(
onTap: (){
print("Selected Available Table On Tap : " + x.Table_Number);
Shared_Preferences().SaveTableNumber(
x.Table_Number
);
Navigator.pushReplacementNamed(context, '/POS');
},
child: Container(
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
width: 2.0,
color: Colors.grey.withOpacity(0.4),
))),
padding: EdgeInsets.all(10.0),
child: Row(
children: <Widget>[
Icon(
Icons.table_chart,
size: 100.0,
color: Colors.lightBlueAccent,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'Table Number : ' + x.Table_Number,
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
color: Colors.lightBlueAccent),
),
Text(
'Name : ' + x.Name_Table,
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
color: Colors.lightBlueAccent),
),
],
)
],
),
),
);
},
),
),
)
);
}
}

Just add async.
onRefresh: () async {
GetTableList.add(true);
},
onRefresh is a RefreshCallback, and RefreshCallback is a Future Function().
So if GetTableList.add(true) not return Future, must to add async.

it is says onRefresh callback must return a Future.
and it is seems like your are not returning Future from onRefresh
onRefresh: _refresh
Future<Null> _refresh() async{
GetTableList.add(true);
}
hope it helps..

You can problably use the following:
Future<bool> refresh() async {
//your refresh code
return true;
}

Related

Flutter - Refresh widget with button press

I have an app that pulls quotes from an api and displays it.
I am struggling to get my refresh button to work. When the refresh button is pressed a new quote should load.
The current code I have for the page is:
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:share_plus/share_plus.dart';
import './Quote.dart';
class QuoteData extends StatefulWidget {
const QuoteData({Key? key}) : super(key: key);
#override
_QuoteDataState createState() => _QuoteDataState();
}
// call the API and fetch the response
Future<Quote> fetchQuote() async {
final response = await http.get(Uri.parse('https://favqs.com/api/qotd'));
if (response.statusCode == 200) {
return Quote.fromJson(json.decode(response.body));
} else {
throw Exception('Failed to load Quote');
}
}
class _QuoteDataState extends State<QuoteData>
with AutomaticKeepAliveClientMixin {
#override
bool get wantKeepAlive => true;
late Future<Quote> quote;
late Future<List<Quote>> wholeQuotes;
#override
void initState() {
super.initState();
quote = fetchQuote();
}
#override
Widget build(BuildContext context) {
super.build(context);
return FutureBuilder<Quote>(
future: quote,
builder: (context, snapshot) {
if (snapshot.hasData) {
return SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
margin: const EdgeInsets.only(left: 50.0),
child: Text(
snapshot.data!.quoteText,
style: const TextStyle(
fontSize: 30.0,
color: Colors.white,
fontFamily: 'quoteScript'),
),
),
const SizedBox(
height: 20.0,
),
Text(
'-${snapshot.data!.quoteAuthor}-',
style: const TextStyle(
fontSize: 23.0,
color: Colors.white,
fontFamily: 'quoteScript'),
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
//share button
IconButton(
icon: const Icon(
Icons.share,
color: Colors.white,
),
onPressed: () {
Share.share(
'${snapshot.data!.quoteText}--${snapshot.data!.quoteAuthor}');
},
),
//refresh button
IconButton(
icon: const Icon(
Icons.share,
color: Colors.white,
),
onPressed: () {
fetchQuote();
},
),
],
),
],
),
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
// By default, show a loading spinner.
return const Center(child: CircularProgressIndicator());
},
);
}
}
How can I get the refresh button to refresh the page and load a new quote from the api and display it?
Any help would be appreciated.
Reset future using setState.
Future<Quote> quote;
...
IconButton(
...
onPressed: () {
setState(() {
quote = fetchQuote();
});
},
)
Did you try to use setState under onPressed()? You can do something like:
onPressed: () {
setState(()
Share.share(
'${snapshot.data!.quoteText}--${snapshot.data!.quoteAuthor}');
)},

Unexpected null value when try to open drawer in flutter

I am trying to open the drawer and it gives me the below error:
======== Exception caught by gesture ===============================================================
The following TypeErrorImpl was thrown while handling a gesture:
Unexpected null value.
When the exception was thrown, this was the stack:
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 236:49 throw_
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/operations.dart 518:63 nullCheck
packages/l7/screens/main/view_model/main_view_model.dart 15:36 openOrCloseDrawer
packages/l7/screens/main/view/components/header.dart 35:42 <fn>
packages/flutter/src/material/ink_well.dart 989:21 [_handleTap]
...
Handler: "onTap"
Recognizer: TapGestureRecognizer#ae119
debugOwner: GestureDetector
state: possible
won arena
finalPosition: Offset(35.8, 49.7)
finalLocalPosition: Offset(15.8, 12.9)
button: 1
sent tap down
====================================================================================================
and the below method is openOrCloseDrawer():
void openOrCloseDrawer() {
if (_scaffoldKey.currentState!.isDrawerOpen) {
_scaffoldKey.currentState!.openEndDrawer();
setState(ViewState.Idle);
} else {
_scaffoldKey.currentState!.openDrawer();
setState(ViewState.Idle);
}
}
related to the below ViewModel:
import 'package:flutter/material.dart';
import 'package:l7/enums/ScreenState.dart';
import 'package:l7/screens/BaseViewModel.dart';
class MainViewModel extends BaseViewModel {
int _selectedIndex = 0;
GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
int get selectedIndex => _selectedIndex;
List<String> get menuItems =>
["Cases", "Services", "About Us", "Careers", "Blog", "Contact"];
GlobalKey<ScaffoldState> get scaffoldkey => _scaffoldKey;
void openOrCloseDrawer() {
if (_scaffoldKey.currentState!.isDrawerOpen) {
_scaffoldKey.currentState!.openEndDrawer();
setState(ViewState.Idle);
} else {
_scaffoldKey.currentState!.openDrawer();
setState(ViewState.Idle);
}
}
void setMenuIndex(int index) {
_selectedIndex = index;
setState(ViewState.Idle);
}
}
and this is the below BaseViewModel:
import 'package:flutter/widgets.dart';
import 'package:l7/enums/ScreenState.dart';
import 'package:l7/utils/context_extentions.dart';
class BaseViewModel extends ChangeNotifier {
ViewState _state = ViewState.Idle;
ViewState get state => _state;
SwitchState _switchState = SwitchState.CLOSE;
SwitchState get switchState => _switchState;
void setState(ViewState viewState) {
_state = viewState;
notifyListeners();
}
void switchLanguage(bool state, BuildContext context) async {
state == true
? _switchState = SwitchState.OPEN
: _switchState = SwitchState.CLOSE;
notifyListeners();
if (context.locale == const Locale('ar', 'EG')) {
context.setLocale(const Locale('en', 'US'));
} else {
context.setLocale(const Locale('ar', 'EG'));
}
}
}
and this is the below Drawer component:
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:l7/screens/BaseScreen.dart';
import 'package:l7/screens/main/view_model/main_view_model.dart';
import 'package:l7/utils/constants.dart';
class SideMenu extends StatelessWidget {
// final MenuController _controller = Get.put(MenuController());
#override
Widget build(BuildContext context) {
return BaseScreen<MainViewModel>(
onModelReady: (mainViewModel){},
builder: (context, viewModel, _){
return Drawer(
child: Container(
color: kDarkBlackColor,
child: ListView(
children: [
DrawerHeader(
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: kDefaultPadding * 3.5),
child: SvgPicture.asset("assets/icons/logo.svg"),
),
),
...List.generate(
viewModel.menuItems.length,
(index) => DrawerItem(
isActive: index == viewModel.selectedIndex,
title: viewModel.menuItems[index],
press: () {
viewModel.setMenuIndex(index);
},
),
),
],
),
),
);
},
);
}
}
class DrawerItem extends StatelessWidget {
final String? title;
final bool? isActive;
final VoidCallback? press;
const DrawerItem({
Key? key,
#required this.title,
#required this.isActive,
#required this.press,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return SafeArea(
child: ListTile(
contentPadding: EdgeInsets.symmetric(horizontal: kDefaultPadding),
selected: isActive!,
selectedTileColor: kPrimaryColor,
onTap: press,
title: Text(
title!,
style: TextStyle(color: Colors.white),
),
),
);
}
}
and this is the Header Widget which contains the drawer:
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:l7/screens/BaseScreen.dart';
import 'package:l7/screens/main/view/components/web_menu.dart';
import 'package:l7/screens/main/view_model/main_view_model.dart';
import 'package:l7/services/responsive.dart';
import 'package:l7/utils/constants.dart';
import 'package:l7/utils/texts.dart';
import 'header_right_side.dart';
class Header extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BaseScreen<MainViewModel>(
onModelReady: (mainViewModel) {},
builder: (context, viewModel, _) {
return SafeArea(
child: Column(
children: [
Column(
children: [
Row(
children: [
if (!Responsive.isDesktop(context))
IconButton(
icon: Icon(
Icons.menu,
color: kBlackBlue,
),
onPressed: () {
viewModel.openOrCloseDrawer();
},
),
Image.asset("assets/images/l7_image.png", height: MediaQuery.of(context).size.height * 0.15,),
Spacer(),
if (Responsive.isDesktop(context)) WebMenu(),
Spacer(),
HeaderRightSide(),
],
),
// SizedBox(height: kDefaultPadding * 2),
Container(
width: double.infinity,
height: MediaQuery.of(context).size.height * 0.27,
decoration: BoxDecoration(
image: DecorationImage(image: AssetImage('assets/images/blog_bg.png'), fit: BoxFit.cover)
),
child: Row(
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
headLine30TitleText(
tr("Welcome to Our Blog"),
context,
),
Padding(
padding:
const EdgeInsets.symmetric(vertical: kDefaultPadding),
child: Text(
"Stay updated with the newest design and development stories, case studies, \nand insights shared by DesignDK Team.",
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontFamily: 'Raleway',
height: 1.5,
),
),
),
FittedBox(
child: TextButton(
onPressed: () {},
child: Row(
children: [
Text(
"Learn More",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
SizedBox(width: kDefaultPadding / 2),
Icon(
Icons.arrow_forward,
color: Colors.white,
),
],
),
),
),
],
),
),
IconButton(onPressed: (){}, icon: Icon(Icons.keyboard_arrow_right, color: Colors.white,))
],
),
),
if (Responsive.isDesktop(context))
SizedBox(height: kDefaultPadding),
],
)
],
),
);
},
);
}
}
I hope someone could help, and let me know if there's any missing details or code. :)
The solution in the below line code in Header widget:
instead of:
viewModel.openOrCloseDrawer();
add this line:
Scaffold.of(context).openDrawer();

Flutter - unable to produce variable from data in Cloud Firestore - undefined name error

I am trying to use a list from a Cloud Firestore document field (called 'a_ge_vi') to populate my DropdownMenu options. This takes the form of an array of strings (see attached image).
However, when I try to produce the List<String> with data from Cloud Firestore and put it in the variable _partyList2, I get the error in the console of Undefined name '_partyList2'. I created a version of the list in Flutter and this works fine - it is shown as _partyList. But really I want to use the data from Cloud Firestore so I can change the values when I need to.
Can anyone help with why this isn't working?
PS. It is worth noting that this text widget Text(snapshot.data.documents[0]['q01'] does show q01, which is also a field in my Cloud Firestore document so I am connecting to the database.
I have the following code:
import 'package:flutter/material.dart';
import 'package:rewardpolling/pages/login_screen.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
final responseInstance = Firestore.instance;
final _auth = FirebaseAuth.instance;
final GlobalKey<FormBuilderState> _fbKey = GlobalKey<FormBuilderState>();
List<String> _partyList = <String>[
"Conservatives",
"Labour",
"Liberal Democrats",
"SNP",
"Plaid Cymru",
"The Brexit Party",
"Other",
"I would not vote"
];
class TestSurvey extends StatefulWidget {
static const String id = 'TestSurvey';
#override
_TestSurveyState createState() => _TestSurveyState();
}
class _TestSurveyState extends State<TestSurvey> {
var selectedParty, submittedParty, userid;
#override
void initState() async {
super.initState();
await Firestore.instance
.collection('MySurveys')
.document('FirestoreTestSurvey')
.get()
.then((DocumentSnapshot document) {
List<String> _partyList2 = document.data['a_ge_vi'];
print(_partyList2);
});
}
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xffEEEEEE),
appBar: AppBar(
backgroundColor: Color(0xff303841),
title: Center(child: Text('Test Survey')),
actions: <Widget>[
IconButton(
icon: Icon(Icons.close),
onPressed: () {
_auth.signOut();
Navigator.pushNamed(context, LoginScreen.id);
}),
],
leading: Padding(padding: EdgeInsets.only(left: 12), child: Text(' ')),
),
body: Container(
padding: EdgeInsets.symmetric(horizontal: 24.0),
child: FormBuilder(
key: _fbKey,
child: ListView(
children: <Widget>[
Padding(padding: EdgeInsets.all(16.0)),
StreamBuilder(
stream:
Firestore.instance.collection('MySurveys').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return Text('Loading data');
return Column(
children: <Widget>[
Text(
snapshot.data.documents[0]['q01'],
textAlign: TextAlign.left,
overflow: TextOverflow.visible,
style: TextStyle(
fontWeight: FontWeight.bold,
fontFamily: "Roboto",
fontSize: 20),
),
DropdownButton(
items: _partyList2
.map((value) => DropdownMenuItem<dynamic>(
child: Text(
value,
style:
TextStyle(color: Color(0xff303841)),
),
value: value,
))
.toList(),
onChanged: (selectedParty) {
setState(() {
submittedParty = selectedParty;
});
},
value: submittedParty,
isExpanded: true,
hint: Text(
'Please choose an option',
style: TextStyle(color: Color(0xff303841)),
),
),
SizedBox(
width: double.infinity,
child: RaisedButton(
child: Text('Submit Survey'),
onPressed: () async {
final FirebaseUser user =
await _auth.currentUser();
final String userid = user.uid;
responseInstance
.collection("testSurvey08052020")
.add({
"user_id": userid,
"ge_vi": submittedParty,
"submission_time": DateTime.now()
});
})),
],
);
},
),
],
),
)),
);
}
}
I see that you have defined the List<String> _partyList2 = document.data['a_ge_vi']; in the initState.Its an issue of scope as the rest of code doesn't have access to the variable _partyList2;
What you can do is define the variable in the state and then assign it in initState
Hope this answers your question.
As mentioned by Shubham this issue is due to scope, as _partyList2 is only accessible in method initState the solution to this is add a variable that is accesible in other parts of the code and setting it to this value on initState.
I corrected the code and added below:
import 'package:flutter/material.dart';
import 'package:rewardpolling/pages/login_screen.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
final responseInstance = Firestore.instance;
final _auth = FirebaseAuth.instance;
final GlobalKey<FormBuilderState> _fbKey = GlobalKey<FormBuilderState>();
List<String> _partyList = <String>[
"Conservatives",
"Labour",
"Liberal Democrats",
"SNP",
"Plaid Cymru",
"The Brexit Party",
"Other",
"I would not vote"
];
class TestSurvey extends StatefulWidget {
static const String id = 'TestSurvey';
#override
_TestSurveyState createState() => _TestSurveyState();
}
class _TestSurveyState extends State<TestSurvey> {
var selectedParty, submittedParty, userid, usablePartyList;
#override
void initState() async {
super.initState();
await Firestore.instance
.collection('MySurveys')
.document('FirestoreTestSurvey')
.get()
.then((DocumentSnapshot document) {
List<String> _partyList2 = document.data['a_ge_vi'];
print(_partyList2);
usablePartyList = _partyList2;
});
}
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xffEEEEEE),
appBar: AppBar(
backgroundColor: Color(0xff303841),
title: Center(child: Text('Test Survey')),
actions: <Widget>[
IconButton(
icon: Icon(Icons.close),
onPressed: () {
_auth.signOut();
Navigator.pushNamed(context, LoginScreen.id);
}),
],
leading: Padding(padding: EdgeInsets.only(left: 12), child: Text(' ')),
),
body: Container(
padding: EdgeInsets.symmetric(horizontal: 24.0),
child: FormBuilder(
key: _fbKey,
child: ListView(
children: <Widget>[
Padding(padding: EdgeInsets.all(16.0)),
StreamBuilder(
stream:
Firestore.instance.collection('MySurveys').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return Text('Loading data');
return Column(
children: <Widget>[
Text(
snapshot.data.documents[0]['q01'],
textAlign: TextAlign.left,
overflow: TextOverflow.visible,
style: TextStyle(
fontWeight: FontWeight.bold,
fontFamily: "Roboto",
fontSize: 20),
),
DropdownButton(
items: usablePartyList
.map((value) => DropdownMenuItem<dynamic>(
child: Text(
value,
style:
TextStyle(color: Color(0xff303841)),
),
value: value,
))
.toList(),
onChanged: (selectedParty) {
setState(() {
submittedParty = selectedParty;
});
},
value: submittedParty,
isExpanded: true,
hint: Text(
'Please choose an option',
style: TextStyle(color: Color(0xff303841)),
),
),
SizedBox(
width: double.infinity,
child: RaisedButton(
child: Text('Submit Survey'),
onPressed: () async {
final FirebaseUser user =
await _auth.currentUser();
final String userid = user.uid;
responseInstance
.collection("testSurvey08052020")
.add({
"user_id": userid,
"ge_vi": submittedParty,
"submission_time": DateTime.now()
});
})),
],
);
},
),
],
),
)),
);
}
}
flutter dart google-cloud-firestore

having an problem designing Provider design to flutter application

The main reason i want to change the design is that when i scan a product and then scan the same one i get the same product twice while it is the same one so basically i just need to increase the quantity of the product by one.
I have used Provider before but I still can't manage to arrange the classes the right way to make it work.
first i have tried to make a Productobject(which extenes ChangeNotifier) inside ProductCard class and use provider.of(context) and retrieve the quantity of the prodcut.
plus,at the main before the material I used ChangeNotifierProvider
I have a mess in my head and i would love to see someone arranging the code with explanation of how the things works
main
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:sally_smart/utilities/product_notifier.dart';
import 'screens/checkout_screen.dart';
import 'screens/login_screen.dart';
import 'screens/registration_screen.dart';
import 'screens/welcome_screen.dart';
import 'package:provider/provider.dart';
//void main() => runApp(Sally());
void main() {
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp])
.then((_) {
runApp(new Sally());
});
}
class Sally extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
backgroundColor: Colors.teal,
cardColor: Color(0xFF068194),
),
initialRoute: LoginScreen.id,
routes: {
WelcomeScreen.id: (context) => WelcomeScreen(),
RegistrationScreen.id: (context) => RegistrationScreen(),
LoginScreen.id: (context) => LoginScreen(),
CheckoutScreen.id: (context) => CheckoutScreen()
},
);
}
}
ProductCard
import 'package:flutter/material.dart';
import 'package:sally_smart/utilities/constants.dart';
import 'package:sally_smart/utilities/product.dart';
import 'package:sally_smart/utilities/round_icon_button.dart';
class ProductCard extends StatefulWidget {
final Product prodcut;
ProductCard(Product product);
#override
_ProductCardState createState() => _ProductCardState();
}
class _ProductCardState extends State<ProductCard> {
double finalPrice;
// static int quantity = 1;
#override
Widget build(BuildContext context) {
finalPrice = widget.prodcut.quantity * widget.prodcut.productPrice;
return Card(
elevation: 5.0,
child: ListTile(
leading: Padding(
padding: EdgeInsets.only(
left: 2.0,
),
child: Icon(
widget.prodcut.productIcon,
size: 35,
),
),
title: Text(
widget.prodcut.productName,
style: kProductNameTextStyle,
),
subtitle: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
RoundIconButton(
icon: Icons.add,
color: Colors.green,
function: () {
setState(() {
widget.prodcut.quantity++;
});
}),
Text(
'$widget.prodcut.quantity',
),
RoundIconButton(
icon: Icons.remove,
color: Colors.red,
function: () {
setState(() {
widget.prodcut.quantity--;
if ( widget.prodcut.quantity == 0) {
widget.prodcut.quantity++;
}
});
}),
],
),
),
Text(
'${finalPrice.toStringAsFixed(2)} ₪',
style: TextStyle(fontSize: 15),
),
],
),
),
);
}
}
Prodcut
import 'package:flutter/cupertino.dart';
class Product extends ChangeNotifier{
String productName;
double productPrice;
IconData productIcon;
String id;
String barCode;
int quantity;
Product(this.productName, this.productPrice, this.productIcon, this.id,
this.barCode,this.quantity);
}
welcome screen
import 'package:audioplayers/audio_cache.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_barcode_scanner/flutter_barcode_scanner.dart';
import 'package:sally_smart/screens/login_screen.dart';
import 'package:sally_smart/screens/registration_screen.dart';
import 'package:sally_smart/utilities/constants.dart';
import 'package:sally_smart/utilities/product.dart';
import 'package:sally_smart/utilities/product_card.dart';
import 'package:sally_smart/utilities/scan_button_const.dart';
import 'package:sally_smart/utilities/scan_methods.dart';
//import 'package:sally_smart/utilities/scan_pageML.dart';
//import 'package:flutter_camera_ml_vision/flutter_camera_ml_vision.dart';
//import 'package:firebase_ml_vision/firebase_ml_vision.dart';
//working version
//List<ProductCard> shoppingList = [];
final sallyDatabase = Firestore.instance;
class WelcomeScreen extends StatefulWidget {
static const String id = 'welcome_screen';
#override
_WelcomeScreenState createState() => _WelcomeScreenState();
}
class _WelcomeScreenState extends State<WelcomeScreen> {
final _auth = FirebaseAuth.instance;
final textEditorController = TextEditingController();
String _scanBarcode = 'Unknown';
String productName = 'Product Test';
double productPrice;
String productBarCode;
IconData productIcon = Icons.add_shopping_cart;
final List<ProductCard> shoppingList = [];
int productId = 0;
static AudioCache barcodeSound = AudioCache();
// saves barcodes data
List<String> data = [];
Future<void> initPlatformState() async {
String barcodeScanRes;
// Platform messages may fail, so we use a try/catch PlatformException.
try {
barcodeScanRes =
await FlutterBarcodeScanner.scanBarcode("#ff6666", "Cancel", true);
} on PlatformException {
barcodeScanRes = 'Failed to get platform version.';
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_scanBarcode = barcodeScanRes;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFF21bacf),
appBar: AppBar(
elevation: 3,
backgroundColor: Colors.black54,
leading: Icon(
Icons.shopping_basket,
size: 30,
),
title: Text(
'Sally',
textAlign: TextAlign.end,
style: kHeaderTextStyle,
),
actions: <Widget>[
IconButton(icon: Icon(Icons.settings), onPressed: () {}),
VerticalDivider(
color: Color(0x8CFFFFFF),
width: 3,
),
IconButton(
icon: Icon(Icons.power_settings_new),
onPressed: () {
_auth.signOut();
Navigator.pushNamed(context, LoginScreen.id);
}),
VerticalDivider(
color: Color(0x8CFFFFFF),
width: 3,
),
IconButton(icon: Icon(Icons.share), onPressed: () {}),
VerticalDivider(
color: Color(0x8CFFFFFF),
width: 3,
),
// PopupMenuButton(itemBuilder: ),
],
),
body: Container(
decoration: kBackgroundGradientScan,
child: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Text(
'ברוכה הבאה, סאלי',
textAlign: TextAlign.right,
style: kHeaderTextStyle,
),
Padding(
padding: EdgeInsets.only(top: 5, right: 15),
child: Hero(
tag: 'Sally',
child: CircleAvatar(
backgroundImage:
AssetImage('images/missing_avatar_F.png'),
maxRadius: 25,
),
),
)
],
),
Padding(
padding: EdgeInsets.symmetric(vertical: 3, horizontal: 15),
child: TextField(
textAlign: TextAlign.center,
onChanged: (value) async {
_scanBarcode = value;
productPrice = await getProductPrice(_scanBarcode);
productName = await getProductName(_scanBarcode);
},
decoration: kTextFieldDecoration.copyWith(
prefixIcon: IconButton(
icon: Icon(Icons.search),
onPressed: () {
_scanBarcode = '';
textEditorController.clear();
try {
setState(() {
textEditorController.clear();
//checking if a product was already scanned
//adding a ProductCard to the shopping list with the ProductCard const. Works on scan
shoppingList.add(ProductCard( new Product(productName, productPrice, productIcon, shoppingList.length.toString(),
productBarCode, 1)));
});
} catch (e) {
print(e);
}
},
),
// prefix: IconButton(
// icon: Icon(Icons.search),
// onPressed: () {
//
// }),
hintText: '...הכנס ברקוד או שם מוצר ידנית'),
),
),
DividerSally(),
shoppingListBuilder(),
DividerSally(),
Container(
child: Padding(
padding: EdgeInsets.only(bottom: 15.0),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 40),
child: Column(
children: <Widget>[
ScanMainButton(
iconData: Icons.flip,
buttonText: 'סרוק מוצר',
onPressed: () async {
//Navigator.pushNamed(context, ScanScreen.id);
await initPlatformState();
barcodeSound.play('barcode_sound.mp3');
//Changes the product name by referencing to the database
productBarCode = _scanBarcode;
productPrice =
await getProductPrice(_scanBarcode);
productName =
await getProductName(_scanBarcode);
setState(() {
//checking if a product was already scanned
//adding a ProductCard to the shopping list with the ProductCard const. Works on scan
shoppingList.add(ProductCard( new Product(productName, productPrice, productIcon, shoppingList.length.toString(),
productBarCode, 1)));
});
},
color: Colors.teal,
),
ScanMainButton(
iconData: Icons.check,
buttonText: 'מעבר לתשלום',
color: Colors.green,
onPressed: () {
Navigator.pushNamed(
context, RegistrationScreen.id);
}),
],
),
),
),
)
],
),
),
),
));
}
Expanded shoppingListBuilder() {
return Expanded(
child: Container(
color: Colors.black38,
child: ListView.builder(
reverse: true,
itemCount: shoppingList.length,
itemBuilder: (context, index) {
ProductCard item = shoppingList[index];
return Dismissible(
key: Key(item.prodcut.id),
direction: DismissDirection.startToEnd,
onDismissed: (direction) {
setState(() {
shoppingList.removeAt(index);
});
},
background: Container(
child: Icon(
Icons.restore_from_trash,
size: 40,
),
margin: EdgeInsets.symmetric(vertical: 5),
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomLeft,
end: Alignment.topRight,
colors: [
Color(0x8C650223),
Color(0x8CB9013E),
],
stops: [0.1, 0.9],
),
),
),
child: item //ListTile(title: Text('${item.productName}.')),
);
},
),
));
}
}

How to pass data back from a widget?

I have a screen where users can add a location. Here, I have separated all my widgets into there own files as illustrated below;
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:fluttershare/pages/location/location_help_screen.dart';
import 'package:fluttershare/widgets/common_widgets/customDivider.dart';
import 'package:uuid/uuid.dart';
import '../../widgets/camp_type_select.dart';
import '../../widgets/extra_location_notes.dart';
import '../../widgets/location_input.dart';
import '../../widgets/opening_times.dart';
import '../../widgets/post_media.dart';
import '../../widgets/space_avalibility.dart';
import '../../widgets/utility_type_select.dart';
import '../../widgets/width_restriction.dart';
import '../../widgets/height_restriction.dart';
import '../../models/locations.dart';
import '../../models/user.dart';
import '../home.dart';
class AddNewLocation extends StatefulWidget {
static const routeName = '/add-new-location';
final User currentUser;
AddNewLocation({this.currentUser});
_AddNewLocationState createState() => _AddNewLocationState();
}
class _AddNewLocationState extends State<AddNewLocation> {
String postId = Uuid().v4();
final _scaffoldKey = GlobalKey<ScaffoldState>();
PlaceLocation _pickedLocation;
int storyPostCount = 0;
bool isLoading = false;
void _selectPlace(double lat, double lng) {
_pickedLocation = PlaceLocation(lattitude: lat, longitude: lng);
}
getLocationPostCount() async {
setState(() {
isLoading = true;
});
QuerySnapshot snapshot = await locationPostRef
.document(currentUser.id)
.collection('user_location_posts')
.getDocuments();
setState(() {
storyPostCount = snapshot.documents.length;
});
}
createLocationPostInFirestore(
{String mediaUrl,
String description,
double heightRestriction,
double widthRestriction}) {
locationPostRef
.document(currentUser.id)
.collection("user_location_posts")
.document(postId)
.setData({
"postId": postId,
"ownerId": currentUser.id,
"username": currentUser.username,
"description": description,
"timestamp": timestamp,
"lattitude": _pickedLocation.lattitude,
"longitude": _pickedLocation.longitude,
"max_height": heightRestrictionValue.toStringAsFixed(0),
"max_width": widthRestrictionValue.toStringAsFixed(0),
});
}
handlePostSubmit() {
createLocationPostInFirestore(
heightRestriction: heightRestrictionValue,
widthRestriction: widthRestrictionValue,
);
SnackBar snackbar = SnackBar(
content: Text("Profile Updated"),
);
_scaffoldKey.currentState.showSnackBar(snackbar);
setState(() {
postId = Uuid().v4();
});
}
buildUploadUserHeader() {
return Container(
margin: EdgeInsets.only(bottom: 10),
height: 200,
child: Row(
children: <Widget>[
Expanded(
flex: 2,
child: Container(
color: Colors.blue,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ListTile(
leading: CircleAvatar(
backgroundImage:
CachedNetworkImageProvider(currentUser.photoUrl)),
),
],
),
),
),
Expanded(
flex: 6,
child: Container(
color: Colors.pink,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Text(currentUser.displayName),
],
),
),
),
],
),
);
}
buildCampUploadForm() {
return Container(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
//buildUploadUserHeader(), //TODO: This is the profile header that is dissabled for now. Work on possibly a header in the future.
Container(
padding: EdgeInsets.all(15),
child: Column(
children: <Widget>[
CampTypeSelect(),
CustomDivider(),
LocationInput(_selectPlace),
CustomDivider(),
HeightRestriction(),
WidthRestriction(),
SpaceAvalibility(),
OpeningTimes(),
CustomDivider(),
PostMedia(),
CustomDivider(),
UtilityServices(),
CustomDivider(),
ExtraLocationNotes(),
Container(
height: 80,
margin: EdgeInsets.only(top: 10, bottom: 10),
child: Row(
children: <Widget>[
Expanded(
child: FlatButton(
color: Colors.black,
onPressed: () => handlePostSubmit(),
child: Text(
"SUBMIT",
style: Theme.of(context).textTheme.display2,
),
padding: EdgeInsets.all(20),
),
)
],
),
),
],
),
),
],
),
));
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
automaticallyImplyLeading: false,
title: const Text(
'Add New Location',
style: TextStyle(color: Colors.black),
),
actions: <Widget>[
// action button
IconButton(
icon: Icon(Icons.info_outline),
color: Colors.black,
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
fullscreenDialog: true,
builder: (context) => LocationSubmitHelpScreen()),
);
},
),
// action button
IconButton(
icon: Icon(Icons.close),
color: Colors.black,
onPressed: () {
Navigator.of(context).pop();
},
),
],
),
body: buildCampUploadForm(),
backgroundColor: Colors.white,
);
}
}
What I am trying to do is pass the data back from the widget ExtraLocationNotes()
to the function createLocationPostInFirestore().
For context, this is what my widget looks like;
import 'package:flutter/material.dart';
import 'common_widgets/custom_form_card.dart';
class ExtraLocationNotes extends StatefulWidget {
_ExtraLocationNotesState createState() => _ExtraLocationNotesState();
}
class _ExtraLocationNotesState extends State<ExtraLocationNotes> {
TextEditingController descriptionController = TextEditingController();
#override
Widget build(BuildContext context) {
return CustomFormCard(
child: Column(
children: <Widget>[
Container(
child: Row(
children: <Widget>[
Text(
"EXTRA INFORMATION",
style: TextStyle(
fontSize: 18.0,
color: Colors.black,
fontWeight: FontWeight.w400,
letterSpacing: 2.0,
),
),
],
),
),
SizedBox(height: 20),
TextFormField(
controller: descriptionController,
maxLines: 6,
maxLength: 250,
maxLengthEnforced: true,
style:
new TextStyle(fontSize: 18.0, height: 1.3, color: Colors.black),
decoration: const InputDecoration(
hintText:
"Please write a description of this location for fellow travellers.",
alignLabelWithHint: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.only(),
borderSide: BorderSide(color: Colors.black),
),
),
),
],
),
);
}
}
How do I pass the data back to the parent widget?
You need a callback, which will be triggered in the child widget then the value will be updated in the parent widget:
// 1- Define a pointers to executable code in memory, which is the callback.
typedef void MyCallback(String val);
class ExtraLocationNotes extends StatefulWidget {
// 2- You will pass it to this widget with the constructor.
final MyCallback cb;
// 3- ..pass it to this widget with the constructor
ExtraLocationNotes({this.cb});
_ExtraLocationNotesState createState() => _ExtraLocationNotesState();
}
class _ExtraLocationNotesState extends State<ExtraLocationNotes> {
//..
//...
RaisedButton(
//..
// 4- in any event inside the child you can call the callback with
// the data you want to send back to the parent widget:
onPressed: () {
widget.cb("Hello from the other side!");
}
),
}
Then inside the parent widget you need to catch the data which sent form the child:
class AddNewLocation extends StatefulWidget {
//...
_AddNewLocationState createState() => _AddNewLocationState();
}
class _AddNewLocationState extends State<AddNewLocation> {
// 1- Global var to store the data that we're waiting for.
String _dataFromMyChild = "";
buildCampUploadForm() {
return Container(
//...
//...
// 2- Pass the callback with the constructor of the child, this
// will update _dataFromMyChild's value:
ExtraLocationNotes(cb: (v) => setState(() => _dataFromMyChild = v)),
//..
}
// then
createLocationPostInFirestore() {
// Use _dataFromMyChild's value here
}
}
You can use the BuildContext object to get the context widget (might no be the parent!) couldn't read it all but as i understand that you need to pass the info from the child to the parent ,and you can do it with some like this :-
(context.widget as MyType).doStuff();
Note.
please check first with
print(context.widget.runtimeType);
but to make a better solution make a mutable data object that is passed from parent to the child so when changes happens it reflect's on the parent so you can separate business logic from ui logic.