Related
I am implementing a sort by function which displays sort options through a modal bottom sheet, I am able to do it in my "Home Page" widget. Would like to check if I can extract these codes and sub it as a widget for better organization. I am unable to do as I am concerned with the return values from the radio value.
Appreciate any help given, thanks!!
Here is my code:
child: TextButton.icon( // Button to press sort
onPressed: (() {
showModalBottomSheet( // show modal
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(10.0)),
context: context,
builder: (BuildContext build) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[ // radio values
RadioListTile(
value: 1,
groupValue: selectedRadioTile,
title: Text(
"Case Earliest to Latest"),
onChanged: (val) {
print(
"Radio Tile pressed $val");
setSelectedRadioTile(val!);
print(selectedRadioTile);
Navigator.pop(context);
},
activeColor:
constants.secondaryBlueColour,
),
RadioListTile(
value: 2,
groupValue: selectedRadioTile,
title: Text(
"Case Latest to Earliest "),
onChanged: (val) {
print(
"Radio Tile pressed $val");
setSelectedRadioTile(val!);
print(selectedRadioTile);
Navigator.pop(context);
},
activeColor:
constants.secondaryBlueColour,
)
],
);
});
}),
icon: Icon(
Icons.sort,
size: 28,
color: constants.textGrayColour,
),
label: Text("Sort",
style: TextStyle(
color: constants.textGrayColour,
fontWeight: FontWeight.bold)))),***
Container(
margin: const EdgeInsets.only(top: 5),
width: MediaQuery.of(context).size.width * 0.5,
decoration: BoxDecoration(
border: Border(
left: BorderSide(
width: 2.0,
color:
constants.categoryButtonBackgroundColour),
bottom: BorderSide(
width: 2.0,
color:
constants.categoryButtonBackgroundColour),
)),
child: TextButton.icon(
onPressed: () {},
icon: Icon(Icons.filter_alt,
size: 28, color: constants.textGrayColour),
label: Text("Filter",
style: TextStyle(
color: constants.textGrayColour,
fontWeight: FontWeight.bold))),
),
],
),
I implemented a SortWidget() but am wondering how I can return the current radio value to my homepage and set the state in the homepage based on the radio value
showModalBottomSheet is a future method, you can use async method for this. and Navigator.pop(context, value); will give you the result. you can also used callback method, seems not needed for your case.
onPressed:()async {
final value = await showModalBottomSheet(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)),
context: context,
builder: (BuildContext build) {
return MyBottomSheetWidget(selectedRadioTile: selectedRadioTile);
},
);
print("$value");
}
class MyBottomSheetWidget extends StatelessWidget {
// make it statefulWidget if you want to update dialog ui
const MyBottomSheetWidget({
Key? key,
required this.selectedRadioTile,
}) : super(key: key);
final selectedRadioTile;
#override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
// radio values
RadioListTile(
value: 1,
groupValue: selectedRadioTile,
title: Text("Case Earliest to Latest"),
onChanged: (val) {
print("Radio Tile pressed $val");
Navigator.pop(context, val);
},
),
RadioListTile(
value: 2,
groupValue: selectedRadioTile,
title: Text("Case Latest to Earliest "),
onChanged: (val) {
print("Radio Tile pressed $val");
// setSelectedRadioTile(val!);
print(selectedRadioTile);
Navigator.pop(context, val);
},
)
],
);
}
}
showModalBottomSheet is actually a function which can't converted to widget without having some other widget in place. What you can do is, create a function which hold code of this showModalBottomSheet and call that function on button click.
But if you want to create a separate widget then you can create the widget from the internal code of the showModalBottomSheet which starts with return Column.
You need to create a widget which can take two properties which are int variable named selected and a Function named setSelected. Then you can call that widget from inside the showModalBottomSheet and pass two props from your page. This selected will be set as selectedRadioTile & setSelected will be set as setSelectedRadioTile.
Example Code
class BottomFilter extends StatelessWidget {
const BottomFilter(
{Key? key,
required this.selected,
required this.setSelected})
: super(key: key);
final int selected;
final Function setSelected;
#override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
// radio values
RadioListTile(
value: 1,
groupValue: selected,
title: Text("Case Earliest to Latest"),
onChanged: (val) {
print("Radio Tile pressed $val");
setSelected(val!);
print(selected);
Navigator.pop(context);
},
activeColor: Colors.amber,
),
RadioListTile(
value: 2,
groupValue: selected,
title: Text("Case Latest to Earliest "),
onChanged: (val) {
print("Radio Tile pressed $val");
setSelected(val!);
print(selected);
Navigator.pop(context);
},
activeColor: Colors.amber,
)
],
);
}
}
Call it like this
builder: (BuildContext build) {
return BottomFilter(selected: selectedRadioTile, setSelected: setSelectedRadioTile);
})
Dartpad link to test this code https://dartpad.dev/?id=9359bc416ae48b996085d6f98a977e27
I am working on an attendance application where I assign wages to the workers. I want to store all the wages given to the workers into the database. But the problem is I want to access all the given values on button click. I have no idea how it can be done in flutter. I am a beginner.
I have given all the codes and the image of what output i want.
Image of Emulator
Here is my code...
ATTENDANCE SCREEN
...rest code...
floatingActionButton: FloatingActionButton(
onPressed: () {
showDialog(
context: context,
barrierDismissible: false, // user must tap button!
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Upload Patti'),
content: SingleChildScrollView(
child: ListBody(
children: [
TextFormField(
controller: _mainWagesController,
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: "Enter Amount",
prefixIcon: Icon(Icons.wallet, color: Colors.blue),
),
),
],
),
),
actions: <Widget>[
ElevatedButton(
onPressed: () {
Navigator.pop(context);
newWages = _mainWagesController.text;
setState(() {});
},
child: const Text("Assign Wages"),
),
],
);
},
);
},
child: const Icon(Icons.check_circle),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(8.00),
child: Column(children: [
const SizedBox(
height: 20,
),
Center(
child: Text(
"Date : ${DateFormat.yMMMEd().format(DateTime.parse(widget.attendanceDate.toString()))}",
style: const TextStyle(fontSize: 20),
),
),
const SizedBox(
height: 20,
),
FutureBuilder(
future: SupervisorAttendanceServices.getAttendancesDetailsList(
widget.attendanceId),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
var data = snapshot.data['hamal'];
return ListView.builder(
itemCount: data.length,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return HamalAttendanceWidget(
workerId: data[index]['worker_id'],
name: data[index]['worker_name'],
wages: newWages,
masterAttendanceId: widget.attendanceId,
isPrensent: data[index]
['attendance_worker_presense']
.toString());
});
} else if (snapshot.hasError) {
return const Center(
child: Text("Something went wrong !"),
);
} else {
return const Center(child: LinearProgressIndicator());
}
},
),
]),
),
),
...rest code
widget
Widget build(BuildContext context) {
return Card(
child: Column(children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
const SizedBox(
width: 10,
height: 50,
),
const Icon(FeatherIcons.user),
const SizedBox(
width: 20,
),
Text(
widget.name,
style: const TextStyle(fontSize: 18),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(
width: 150,
height: 60,
child: TextFormField(
// onChanged: _onChangeHandler,
initialValue: widget.wages.toString(),
decoration: const InputDecoration(
hintText: "Wages",
prefixIcon: Icon(
Icons.wallet,
color: Colors.blue,
)),
)),
],
)
]),
);
}
I suggest you use a StateManager for your application, for example GetX
is a good solution. Create a controller file like the below:
// define this enum outside of class to handle the state of the page for load data
enum AppState { initial, loading, loaded, error, empty, disabled }
Rx<AppState> pageState = AppState.initial.obs;
class AttendanceCntroller extends GetxController{
RxList<dynamic> dataList=RxList<dynamic>();
#override
void onInit() {
//you can write other codes in here to handle data
pageState(AppState.loading);
dataList.value=
SupervisorAttendanceServices.getAttendancesDetailsList(attendanceId);
pageState(AppState.loaded);
super.onInit();
}
}
and in your view(UI) page, handle it in this way:
class AttendanceView extends GetView<AttendanceCntroller>{
#override
Widget body(BuildContext context) {
// TODO: implement body
return Obx( ()=> controller.pageState.value==AppState.loading ? const
Center(child: LinearProgressIndicator()) : ListView.builder(
itemCount: controller.dataList.length,
physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
return HamalAttendanceWidget(
workerId: controller.dataList['worker_id'],
name: controller.dataList['worker_name'],
wages: newWages,
masterAttendanceId: widget.attendanceId,
isPrensent: controller.dataList[index]
['attendance_worker_presense']
.toString());
})
)
}
}
for more data read the GetX link and read clean architecture with the GetX sample repository of my GitHub it have advanced management of states with GetX with dependency injection handling.
If you want to have prefilled value in TextFormField, you can either use initialValue or controller parameter.
The value of controller parameter will help you to get/update the value of TextFormField.
For controller parameter refer below.
TextEditingController controller = TextEditingController(text: 'This is text will be pre-filled in TextFormField');
...
TextFormField(
controller: controller,
);
Create List or Map of those controllers.
List<TextEditingController> listOfControllers = [ controller1, controlle2,...];
Use for loop through this List on onClick() method of Button.
ElevatedButton(
onPressed: () {
for(var controllerItem in listOfControllers) {
print(controllerItem.text); // the value of TextFormField
}
},
)
I am retrieving a list of items by querying on input from textfield in firebase and showing it as list in UI in flutter as shown here
Here is my code-
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app_news/screens/drawer/news_drawer.dart';
import 'package:flutter_app_news/service/search_service/search_service.dart';
class Watchlist extends StatefulWidget {
#override
_WatchlistState createState() => new _WatchlistState();
}
class _WatchlistState extends State<Watchlist> {
var tempSearchStore = [];
var queryResult = [];
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
CollectionReference users = FirebaseFirestore.instance.collection('Users');
initiateSearch(value) {
if (value.length == 0) {
setState(() {
queryResult = [];
tempSearchStore = [];
});
}
if (queryResult.length == 0 && value.length == 1) {
SearchService().searchByName(value).then((QuerySnapshot snapshot) {
for (int i = 0; i < snapshot.docs.length; i++) {
queryResult.add(snapshot.docs[i].data());
setState(() {
tempSearchStore.add(queryResult[i]);
});
}
});
} else {
tempSearchStore = [];
queryResult.forEach((element) {
if (element['name'].toString().startsWith(value)) {
setState(() {
tempSearchStore.add(element);
});
}
});
}
;
if (tempSearchStore.length == 0 && value.length > 1) {
setState(() {});
}
}
#override
Widget build(BuildContext context) {
return new Scaffold(
extendBodyBehindAppBar: true,
appBar: AppBar(
iconTheme: IconThemeData(color: Colors.black),
backgroundColor: Colors.white,
title: Text(
"Finbox",
style: TextStyle(color: Colors.black),
),
),
drawer: Drawer(child: NewsDrawer()),
body: ListView(children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"Your WatchList",
style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500),
),
),
Padding(
padding: const EdgeInsets.all(10.0),
child: TextField(
onChanged: (val) {
initiateSearch(val);
},
decoration: InputDecoration(
prefixIcon: IconButton(
color: Colors.black,
icon: Icon(Icons.arrow_back),
iconSize: 20.0,
onPressed: () {
Navigator.of(context, rootNavigator: true).pop(context);
},
),
contentPadding: EdgeInsets.only(left: 25.0),
hintText: 'Search by name',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(4.0))),
),
),
SizedBox(height: 10.0),
ListView.builder(
shrinkWrap: true,
itemCount: tempSearchStore.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(tempSearchStore[index]['name']),
onTap: () {
users.doc(_firebaseAuth.currentUser.uid).update({
"subscribeTopic": FieldValue.arrayUnion(
[tempSearchStore[index]['name']])
});
Navigator.pop(context);
},
);
}),
SizedBox(
height: 10,
),
StreamBuilder(
stream: FirebaseFirestore.instance
.collection("Users")
.doc(_firebaseAuth.currentUser.uid)
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<DocumentSnapshot> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(
valueColor:
AlwaysStoppedAnimation<Color>(Colors.black)));
default:
return SingleChildScrollView(
child: GridView.builder(
shrinkWrap: true,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio: MediaQuery.of(context)
.size
.width /
(MediaQuery.of(context).size.height / 7),
crossAxisCount: 2),
itemCount: snapshot.data.get('subscribeTopic').length,
itemBuilder: (context, index) {
return GestureDetector(
onLongPress: () {
showAlertDialog(context,
snapshot.data.get('subscribeTopic')[index]);
print(
snapshot.data.get('subscribeTopic')[index]);
},
child: Container(
child: Text(
snapshot.data.get('subscribeTopic')[index],
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500),
),
margin: EdgeInsets.all(15),
decoration: BoxDecoration(
color: Color(0xFF92f7bb),
),
),
);
}),
);
}
})
]));
}
showAlertDialog(BuildContext context, value) {
// set up the buttons
Widget yesButton = ElevatedButton(
child: Text("Yes"),
onPressed: () async {
// await FirebaseMessaging.instance.unsubscribeFromTopic(value);
users.doc(_firebaseAuth.currentUser.uid).update({
"subscribeTopic": FieldValue.arrayRemove([value])
});
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => Watchlist()));
;
},
);
Widget noButton = ElevatedButton(
child: Text("No"),
onPressed: () {
Navigator.of(context).pop();
},
);
// set up the AlertDialog
AlertDialog alert = AlertDialog(
title: Text("Finbox"),
content: Text("Do you like to unsubscribe for " + value + " ?"),
actions: [
yesButton,
noButton,
],
);
// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
}
Here initiateSearch() is function which returns queries in firebase based on input given in textfield and tempSerachStore is list of key value pair. And I am fetching values whose key is "name".
Now the problem I am facing is-
When I write something in textfield and it queries and return a list, list is creating above those green boxes and boxes gets shifted downward as shown in image.
When I tap on one of these items in list or tap on back arrow in textfield , I am poping out to black screen.
What is the output I want-
I want to show this list above these green boxes(whose code is not included here).
When I tap on the item or tap on back arrow , list view should closed.
I will be grateful for help.
Use conditional statement along with Stack. It will work.
Stack(
children: [
// whatever you want to show. Positioned will come on top of it.
data.length == 0 // your search result length
? SizedBox(height: 0)
: Positioned(
top: 0,
width: double.infinity,
child: Container(
// whatever you want to show here
),
]
),
For a good UI experience, it is a good practice to create a separate page for search. On selecting an option, you can bring back the user on your previous page.
I have a map<string, dynamic> ,and want to show it's components in cards by ListView.Builder, but the problem is by getting access to this map's indexes..
When running the app, it return "null" in the card!!
I have tried many solutions that I saw in StackOverFlow for a similar issues, but without a result.
Here where I identified my map:
var _item;
List listCount = [];
Map<String, dynamic> records = {};
String name;
And here where I give the var _item it's value:
MyCard(
colour: Colors.lightBlueAccent,
maker: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
StreamBuilder<int>(
stream: _stopWatchTimer2.rawTime,
initialData: 0,
builder: (context, snap) {
final value = snap.data;
final displayTime = StopWatchTimer.getDisplayTime(
value,
hours: _isHours2);
_item = displayTime;
return Padding(
padding: EdgeInsets.all(5.0),
child: Text(displayTime,
style: TextStyle(
fontSize: 30.0, color: Colors.white)),
);
},
),
],
),
),
And here in the "Save" button I give the 1st parameter of the map and assign in name variable:
createAlertDialog(buildContext, context) {
TextEditingController controller;
return showDialog(
context: context,
// barrierDismissible: false,
builder: (context) {
return AlertDialog(
title: Text(
'Type record name',
textAlign: TextAlign.center,
style: TextStyle(fontSize: 18.0),
),
content: TextField(
controller: controller,
onChanged: (value) {
name = value;
}),
actions: [
MaterialButton(
elevation: 5.0,
child: Text('Save'),
onPressed: () {
listCount.add(_item);
print(_item);
records[name] = _item;
print(records);
Navigator.pop(context);
},
),
MaterialButton(
elevation: 5.0,
child: Text('Cancel'),
onPressed: () {
Navigator.pop(context);
},
),
],
);
},
);
}
Finally here where I tried to show it in a card by listview.builder:
Container(
color: Colors.white,
child: ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: records.length,
itemBuilder: (context, index) {
return MyCard(
colour: Colors.cyanAccent,
maker: Container(
width: 250.0,
height: 75.0,
child: Text(
'${records[index]}',
style: TextStyle(fontSize: 25.0),
textAlign: TextAlign.center,
),
),
);
},
),
),
I think the problem with the 2nd parameter of itemBuilder which it (index) because I replaced this keyword with another one randomly, and get the same result when running my app "Null".
This screenShot explain the problem:
You can get map entries as List as such:
final recordsEntries = records.entries.toList()
Then, you have a List of MapEntry and are able to access key and value associated for each item of the collection.
recordsEntries[index].key
recordsEntries[index].value
I'm implementing this products app where a user can add a product from his wish list and add it to their cart. Once the user clicks the add to cart button, I want to delete the product from the screen and display a "success" Snackbar.
Since the products are loaded and displayed from FireBase Firestore, I delete the item and call setState({}); so that the list on the screen will be updated. The problem is that the SnackBar that comes right after setState({}); isn't shown.
I assume it is because the widget tree is rebuilt so the current state and context are "gone".
I tried finding some information online but haven't managed to find anything useful. I also tried to make a workaround with a bool flag that will be set once the user clicks the "Add to cart" button using and on setState the flag will be true one time to show the SnackBar when the widget tree is rebuilt and then turn the flag back to off but it didn't work as well.
What am I missing? How can I display a SnackBar after calling setState({})?
here is my code: (line in question marked with FIXME:)
slidable package installation
final GlobalKey _repaintBoundaryKey = GlobalKey();
final GlobalKey<ScaffoldState> _scaffoldKeyWishList = new GlobalKey<ScaffoldState>();
final Center _circularProgressIndicator = Center(
child: SizedBox(
width: 60,
height: 60,
child: CircularProgressIndicator(
valueColor: new AlwaysStoppedAnimation<Color>(Colors.lightGreen[800]),
)
),
);
#override
Widget build(BuildContext context) {
return Material(
child: FutureBuilder(
future: FirebaseFirestore.instance.collection("Wishlists").doc(FirebaseAuth.instance.currentUser.uid).get(),
builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> wishListSnapshot) {
if (wishListSnapshot.connectionState != ConnectionState.done) {
return _circularProgressIndicator;
} else if (!wishListSnapshot.hasData ||
0 == wishListSnapshot.data.data()['Wishlist'].length) {
return globals.emptyListErrorScreen(context, 'Wishlist');
}
int totalProducts = wishListSnapshot.data.data()['Wishlist'].length;
return Scaffold(
key: _scaffoldKeyWishList,
backgroundColor: Colors.lightGreen[800],
body: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.0),
topRight: Radius.circular(20.0),
),
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.white,
child: ListView.builder(
itemCount: totalProducts * 2,
shrinkWrap: true,
padding: const EdgeInsets.all(16),
itemBuilder: (BuildContext _context, int i) {
if (i >= 2 * totalProducts) {
return null;
}
if (i.isOdd) {
return Divider(
color: Colors.green,
thickness: 1.0,
);
}
var wishlistIdData = wishListSnapshot.data.data()['Wishlist'];
String productID = wishlistIdData[i ~/ 2];
return FutureBuilder(
future: FirebaseFirestore.instance.collection("Products").doc(productID).get(),
builder: (BuildContext context, AsyncSnapshot<DocumentSnapshot> productSnapshot) {
if (wishListSnapshot.connectionState != ConnectionState.done || !productSnapshot.hasData) {
return _circularProgressIndicator;
}
var productData = productSnapshot.data.data()['Product'];
String prodName = productData['name'];
String prodPrice = productData['price'];
String prodDate = productData['date'];
return Slidable(
actionPane: SlidableDrawerActionPane(),
actionExtentRatio: 0.22,
direction: Axis.horizontal,
actions: <Widget>[
//add to cart
IconSlideAction(
caption: 'Add to cart',
color: Colors.transparent,
foregroundColor: Colors
.amberAccent,
icon: Icons.add_shopping_cart,
onTap: () async {
globals.userCart.add(
globals.Product(
productID,
FirebaseAuth.instance.currentUser.uid,
prodName,
double.parse(prodPrice),
prodDate,
[],
"",
"")
);
///removing product from wishlist
List toRemove = [];
toRemove.add(productID);
await FirebaseFirestore.instance
.collection('Wishlists')
.doc(FirebaseAuth.instance.currentUser.uid)
.get()
.then((value) async {
List<dynamic> list = List
.from(value
.data()['Wishlist']);
list
..removeWhere((e) =>
toRemove.contains(e));
await FirebaseFirestore.instance
.collection('Wishlists')
.doc(FirebaseAuth.instance.currentUser.uid)
.update(
{'Wishlist': list});
});
setState(() {
///to update the list on screen
});
//FIXME: snackbar not displayed after setState!
///showing snackbar upon completion
_scaffoldKeyWishList
.currentState
.showSnackBar(
SnackBar(
content: Text(
'Product Successfully Added to Cart!',
style: GoogleFonts
.lato(
fontSize: 13.0,
color: Colors
.white
),
),
behavior: SnackBarBehavior
.floating,
action: SnackBarAction(
label: 'Checkout',
textColor: Colors
.lime,
onPressed: () =>
showDialog(
context: context,
builder: (
BuildContext context) {
return CustomDialogBox();
},
),
),
)
);
},
),
],
child: ListTile(
title: Text(prodName,
style: GoogleFonts.lato(
fontSize: 18.0,
color: Colors.black,
),
),
subtitle: Text(prodPrice + "\$",
style: GoogleFonts.lato(
fontSize: 13.5,
color: Colors.grey,
),
),
visualDensity: VisualDensity
.adaptivePlatformDensity,
),
);
},
);
},
),
)
)
]
)
)
);
}
)
);
}
In my case I was calling setState method before build method complete process of building widgets.
You can face this error if you are showing snack bar or alert dialog before the completion of build method and in many other cases. so in such situation use below call back function.
WidgetsBinding.instance.addPostFrameCallback((_) {
// add your snackbar code here
});
or You can also use SchedulerBinding which does the same.
SchedulerBinding.instance.addPostFrameCallback((_) {
// add your code here of snackbar.
});
or you can try this too (I am not sure about this one)
if(mounted){
//add your code here of snackbar
}
CREDITS