StreamBuilder has data when it shouldn't - flutter

I'm using Firebase Realtime database to add chat features to my app. In the database I have the following data:
{
events: {
"event-uuid1": {
"chat-uuid1": {
"message": "hey"
},
"chat-uuid2": {
"message": "hey again"
}
}
}
}
In my Flutter app, I have this StreamBuilder (I know this is lengthy, I'm not sure where the problem is so providing more rather than less):
class _EventChatScreenState extends ConsumerState<EventChatScreen> {
FirebaseDatabase dbInstance = FirebaseDatabase.instance;
late TextEditingController _messageFieldController;
late DatabaseReference eventDbRef;
#override
void initState() {
super.initState();
_messageFieldController = TextEditingController();
eventDbRef = dbInstance.ref("none");
}
#override
void dispose() {
_messageFieldController.dispose();
super.dispose();
}
Map<String, ChatMessage> chatMessages = {};
#override
Widget build(BuildContext context) {
final user = ref.watch(userProvider);
final event = ModalRoute.of(context)!.settings.arguments as EventRepository;
if (eventDbRef.path == "none") {
print("IT IS NONE");
eventDbRef = dbInstance.ref("/events/${event.event.eventId}/");
print(eventDbRef.path); // Print's correct value
}
return StreamBuilder(
stream: eventDbRef.onChildAdded,
builder: (context, snapshot) {
if (chatMessages == {}) {
return const Text("Loading...");
}
DatabaseEvent data;
if (snapshot.hasData) {
data = snapshot.data as DatabaseEvent;
ChatMessage newChatMessage = ChatMessage(
chatMessageId: "",
userId: "",
displayname: "",
message: "",
datetime: "",
);
for (var child in data.snapshot.children) {
switch (child.key) {
case "chatMessageId":
newChatMessage.chatMessageId = child.value.toString();
break;
case "userId":
newChatMessage.userId = child.value.toString();
break;
case "displayName":
newChatMessage.displayname = child.value.toString();
break;
case "message":
newChatMessage.message = child.value.toString();
break;
case "datetime":
final datetime = DateTime.parse(child.value.toString());
final DateFormat formatter = DateFormat('h:mm aa');
final String formatted = formatter.format(datetime);
newChatMessage.datetime = formatted;
break;
default:
}
}
if (chatMessages[data.snapshot.key] == null) {
chatMessages[data.snapshot.key!] = newChatMessage;
}
}
return ListView.builder(
itemCount: chatMessages.length,
itemBuilder: (context, index) {
String key = chatMessages.keys.elementAt(index);
if (chatMessages[key]!.userId == user.user.userId) {
return UnconstrainedBox(
alignment: Alignment.centerRight,
child: Container(
margin: const EdgeInsets.symmetric(vertical: 5),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
padding: const EdgeInsets.only(left: 10),
child: Text(chatMessages[key]!.displayname),
),
Container(
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 15),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(
Radius.circular(20),
),
color: Theme.of(context).colorScheme.primary,
),
child: Text(chatMessages[key]!.message,
style: TextStyle(
color: Theme.of(context).colorScheme.onPrimary,
),
),
),
Container(
padding: const EdgeInsets.only(left: 10),
child: Text(chatMessages[key]!.datetime),
),
],
),
),
);
}
},
);
},
),
The problem is that when the user goes to the chat screen one of the messages will already be present in the chat. I would expect there to be nothing since I am not setting any initial data anywhere, not using Realtime Database's persistence, and not using my own local database yet.
My understanding of StreamBuilders is that they only get new data as it comes in, not data that may already exist and is thus not sent through it (Ie. when a new chat message is sent the stream should receive it, which works, but it should not receive chat message messages already in the database). If that understanding is wrong then why am I only getting one message despite there being 2, 3, 4, etc. in the database?
Perhaps I'm understanding/using StreamBuilders, Firebase Realtime Database, or both incorrectly?

Maybe your understanding about streambuilder is wrong.
Lets say you use FutureBuilder, It'll wait till the future is over and then builds the widget accordingly but It'll not build again if something changes in your database, but for StreamBuilder, It'll basically listen (and get initial data from stream, here your db) to the stream and build whenever it changes or a new data is added to stream (here database) it will get the updated data and build the widget again.
Read here:
https://firebase.flutter.dev/docs/firestore/usage/#realtime-changes

Assign this eventDbRef.onChildAdded to a variable in initState and then use the variable as your stream parameter in streambuilder. Having the db call in Streambuilder causes it to be rerun everytime the widget tree builds.

Related

SharedPreferences data does not have time to be displayed in the widget

I created a model class for Provider. Which will fit the function of getting data from SharedPreferences
Future getDataPerson() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
id = prefs.getInt('id') ?? 000;
name = prefs.getString('name') ?? "Фамилия Имя Отчество";
phone = prefs.getString('phone') ?? "3231313";
email = prefs.getString('email') ?? "";
accessToken = prefs.getString('accessToken') ?? "нет токенааа";
_login = prefs.getString('login') ?? "нет логинааа";
_password = prefs.getString('password') ?? "нет пароляяя";
notifyListeners();
}
That's how I implement my Provider
body: MultiProvider(
providers: [
ChangeNotifierProvider<ApiClient>(create: (_)=>ApiClient()),
ChangeNotifierProvider<FinalModel>(create: (_)=>FinalModel()),
],
child: FinalScreenState(),
),
In initState, I call this function.
#override
void initState() {
super.initState();
finalModel = Provider.of<FinalModel>(context,listen: false);
getDataPerson();
}
Future getDataPerson() async{
return await finalModel.getDataPerson();
}
And in the code I get these variables and paste them into Text
idController.text = finalModel.getId.toString();
return Padding(padding: EdgeInsets.only(top: 15, bottom: 10,right: 1,left: 1),
child:Center(
child: TextField(
controller: idController,
))
);
However, only the values that I wrote in the code are inserted into the text. In this line it is "3231313"
prefs.getString('phone') ?? "3231313";
I tried calling the get Data Person function in different places in the code. In the build method itself.
The data I want to insert I take from json immediately after the user logs in using the button and receives a response from the api
var statusOne =await apiClient.signIn(_loginEmail1.text, _passwordParol1.text);
Map<String, dynamic> map = jsonDecode(rawJson);
bool status = map['status'];
if (status == true) {
//Entry in SharedPreferences
setDataPerson();
//The screen on which the data is displayed
Navigator.pushReplacementNamed(context, 'final');}
method setDataPerson()
void setDataPerson() async {
final prefs = await SharedPreferences.getInstance();
var statusOne =
await apiClient.signIn(_loginEmail1.text, _passwordParol1.text);
var rawJson = AuthModel.fromJson(statusOne.data);
await prefs.setInt('id', rawJson.data!.performerId!);
await prefs.setString('name', rawJson.data!.fullName!);
await prefs.setString('phone', rawJson.data!.phone!);
await prefs.setString('accessToken', rawJson.data!.accessToken!);
}
Build method
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
IdWidget(),
NameWidget(),
PhoneWidget(),
EmailWidget(),
],
);
}
PhoneWidget
class PhoneWidget extends StatelessWidget {
Widget build(BuildContext context) {
var finalModel = Provider.of<FinalModel>(context,listen: true);
return Padding(padding: EdgeInsets.only(top: 10, bottom: 10,right:
1,left: 1),
child: Consumer<FinalModel>(
builder: (context, model, widget) {
finalModel.phoneController.text = model.phone;
return Center(
child: TextField(
controller: finalModel.phoneController,
enabled: false,
decoration: const InputDecoration(
labelText: "Телефон",
contentPadding: EdgeInsets.only(left: 10, top: 10, bottom: 10),
border: OutlineInputBorder(),
),
));
})
);
}
}
However, even if you do not receive this data from the network and just write a regular string, the data will not have time to be displayed on the screen.
It's worth noting here that when I restart the screen. And that is, I update the initState and build method. Then the data is updated and immediately displayed on the screen
I am not considering inserting the listen:true parameter into provider, , because the getData Person function will be called too often.
Wrap Consumer widget to your Columnwidget so registered listeners will be called.
Widget IdWidget() {
return Consumer<YourModelClass>(
builder: (context, model, widget) => Padding(
padding: EdgeInsets.only(top: 15, bottom: 10, right: 1, left: 1),
child: Center(
child: TextField(
controller: model.id,
))));
}

StreamBuilder not displaying fetched data from MongoDB database in Flutter

I am trying to implement streambuilder without Firebase, using a MongoDB database. The aim is to build a simple chat app, live streaming the messages. So far, the live streaming when I click on the send button works since I see the message displayed in the UI. I also push that message to my DB successfully.
The problem strives when I try to display the messages fetched from my datbase. They are fetched correctly, but not displayed.
final StreamController<ChatMessageModel> _chatMessagesStreamController =
StreamController<ChatMessageModel>.broadcast();
final Stream<ChatMessageModel> _chatMessagesStream =
_chatMessagesStreamController.stream;
class MessagesStream extends StatefulWidget {
var usermail;
var usermailTo;
var title;
MessagesStream(this.usermail, this.usermailTo, this.title);
#override
_MessagesStreamState createState() => _MessagesStreamState();
}
class _MessagesStreamState extends State<MessagesStream> {
final List<ChatMessageModel> _allMessagesContainedInTheStream = [];
Future<List<dynamic>>? futureMessages;
Future fetchMessagesFromBack4App(
String usermail, String usermailTo, String dogName) async {
final queryBuilder = QueryBuilder(ParseObject('Messages'))
..whereEqualTo('sender', usermail)
..whereEqualTo('receiver', usermailTo)
..whereEqualTo('dogname', dogName)
..orderByAscending('date');
final response = await queryBuilder.query();
if (response.success && response.results != null) {
for (var message in response.results!) {
//check if message was already put into stream
bool messageFoundInAllMessageLogged = false;
for (int i = 0; i < _allMessagesContainedInTheStream.length; i++) {
if (message["sender"] == _allMessagesContainedInTheStream[i].sender &&
message["receiver"] ==
_allMessagesContainedInTheStream[i].receiver &&
message["date"] == _allMessagesContainedInTheStream[i].date &&
message["dogname"] ==
_allMessagesContainedInTheStream[i].dogname &&
message["message"] ==
_allMessagesContainedInTheStream[i].message) {
messageFoundInAllMessageLogged = true;
break;
}
}
// Add message to stream if it was not logged yet
if (!messageFoundInAllMessageLogged) {
ChatMessageModel chatMessageModelRecord = ChatMessageModel(
receiver: message["receiver"],
message: message["message"],
sender: message["sender"],
dogname: message["dogname"],
date: DateTime.parse(message["date"]));
_allMessagesContainedInTheStream.add(chatMessageModelRecord);
debugPrint("putting message to stream: " + message['message']);
}
}
} else {
return [];
}
}
#override
void initState() {
fetchMessagesFromBack4App(widget.usermail, widget.usermailTo, widget.title);
_chatMessagesStream.listen((streamedMessages) {
// _allMessagesContainedInTheStream.clear();
debugPrint('Value from controller: $streamedMessages');
_allMessagesContainedInTheStream.add(streamedMessages);
});
super.initState();
}
#override
Widget build(BuildContext context) {
return StreamBuilder<ChatMessageModel>(
stream: _chatMessagesStream,
builder: (context, snapshot) {
return Expanded(
child: ListView.builder(
// reverse: true,
padding:
const EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
itemCount: _allMessagesContainedInTheStream.length,
itemBuilder: (BuildContext context, int index) {
if (snapshot.hasData) {
return UserChatBubble(
chatMessageModelRecord:
_allMessagesContainedInTheStream[index],
);
} else {
print(snapshot.connectionState);
return Container();
}
},
),
);
},
);
}
}
class UserChatBubble extends StatelessWidget {
final ChatMessageModel chatMessageModelRecord;
const UserChatBubble({
Key? key,
required this.chatMessageModelRecord,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Padding(
padding: const EdgeInsets.symmetric(
vertical: 5,
horizontal: 5,
),
child: Container(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 7 / 10,
),
decoration: const BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(15.0),
bottomRight: Radius.circular(15.0),
topLeft: Radius.circular(15.0),
),
color: primaryColor,
),
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 20,
),
child: ListTile(
title: Text("${chatMessageModelRecord.message}"),
subtitle: Text(chatMessageModelRecord.date.toString()),
),
),
),
],
);
}
}
The method fetchMessagesFromBack4App fetches correctly the data and add records to _allMessagesContainedInTheStream. However, when the method ends this _allMessagesContainedInTheStream list is empty (despite of inside the method is adding records). Therefore, snapshot is empty too.
Only when I press the send button then I am able to see all the messages: the fetched ones and the sent ones.
Summarizing: snapshot has no data when I navigate to my chat screen. It receives the data only when I press the send button to send a message.
_chatMessagesStream.listen() will listen to the _chatMessagesStream stream and if any event occur, everything in the block will execute.
your fetchMessagesFromBack4App() does not emit any new event to the above stream, but add value to _allMessagesContainedInTheStream which is a List
to sum up you need to change _allMessagesContainedInTheStream.add(chatMessageModelRecord); to _chatMessagesStreamController.add(chatMessageModelRecord) add new event to the stream in other for your StreamBuilder to rebuild

Websocket channel listening to same message multiple times

Trying to create a chat application. I'm able to send and listen to the messages sent on the websocket.
The listen stream listens for any data incoming and appends it to the List which I'm using to manage the chat of the widget.
I'm using listview.builder which uses the list length and changes state of the list to show new messages.
I'm a bit confused if the websocket if listening to the same messages multiple times or the listview.builder is building the widget the amount of times length of message list is.
I'm using Django channels for websocket and print the number of messages sent is proper so nothing on server side for sure.
ERROR: -
For example
on 1st message, I see 1 message
on 2nd message, I see 2 messages being built
on 3rd message, I see 3 messages being built
... and so on.
Websocket code
class NotificationController {
static final NotificationController _singleton =
NotificationController._internal();
StreamController<String> streamController =
StreamController.broadcast(sync: true);
IOWebSocketChannel? channel;
late var channelStream = channel?.stream.asBroadcastStream();
factory NotificationController() {
return _singleton;
}
NotificationController._internal() {
initWebSocketConnection();
}
initWebSocketConnection() async {
var storedUserInfo = storage.getUserInfoStorage();
Map storedData = await storedUserInfo;
String userID = storedData['user_id'];
try {
channel = IOWebSocketChannel.connect(
Uri.parse('ws://192.168.1.109:8001/chat/$userID/'),
pingInterval: const Duration(seconds: 10),
);
} on Exception catch (e) {
return await initWebSocketConnection();
}
print("socket connection initializied");
channel?.sink.done.then((dynamic _) => _onDisconnected());
}
//int i = 0;
void sendMessage(messageObject, Function messageListener) {
try {
channelStream?.listen((data) {
//i += 1;
Map _message = json.decode(data);
messageListener(_message);
});
channel?.sink.add(json.encode(messageObject));
} on Exception catch (e) {
print(e);
}
}
void _onDisconnected() {
initWebSocketConnection();
}
}
Lisview.builder code
List<ChatMessage> messages = [];
void messageListener(Map message) {
setState(() {
messages.add(
ChatMessage(message: message['message'], toUser: message['to_user']));
});
}
ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.vertical,
itemCount: messages.length,
itemBuilder: (BuildContext context, int index) {
return Stack(
children: <Widget>[
Container(
padding: const EdgeInsets.only(
left: 14,
right: 14,
top: 10,
bottom: 10,
),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color:
messages[index].toUser == widget.userUid
? Colors.greenAccent
: Colors.blue,
),
alignment:
messages[index].toUser == widget.userUid
? Alignment.bottomRight
: Alignment.topLeft,
padding: const EdgeInsets.all(16),
child: Text(
messages[index].message as String,
style: const TextStyle(fontSize: 15),
),
),
),
],
);
}),

Flutter FutureBuilder Snapshot is null but Future Does return data

While working with Flutter for a new application client for Kanboard, I encountered the following problem. I have a FutureBuilder that should return a select dropdown menu with items but, for some reason, the Snapshot data is null, although the Future method does resolves and has data on return.
Full page.dart code here: https://pastebin.com/J48nxsdZ
The block having the problem is the following:
Widget _columnSelect() {
return FutureBuilder(
future: columnProvider.getColumns(task.projectId),
builder: (BuildContext context, AsyncSnapshot snapshot) {
List<DropdownMenuItem<String>> columnList = [];
if (snapshot.hasData) {
columnList.add(DropdownMenuItem<String>(
child: Text('Select Column'), value: 0.toString()));
_columns = snapshot.data;
} else {
columnList.add(DropdownMenuItem<String>(
child: Text('Loading..'), value: 0.toString()));
}
_columns.forEach((column) {
columnList.add(DropdownMenuItem<String>(
child: Container(
child: Text(
column.title,
),
),
value: column.id.toString()));
});
return Container(
// margin: EdgeInsets.only(left: 40.0),
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: DropdownButtonFormField(
icon: Padding(
padding: const EdgeInsets.only(right: 12),
child: Icon(Icons.view_column, color: Colors.blue),
),
items: columnList,
value: _columnId,
decoration: InputDecoration(helperText: 'Optional'),
onChanged: (newValue) {
_columnId = newValue;
},
),
);
},
);
}
This is a duplicate of a widget in the same form for a user dropdown select. The original widget (in the same page) is the following:
Widget _ownerSelect() {
return FutureBuilder(
future: userProvider.getUsers(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
List<DropdownMenuItem<String>> usernameList = [];
if (snapshot.hasData) {
usernameList.add(DropdownMenuItem<String>(
child: Text('Select Owner'), value: 0.toString()));
_users = snapshot.data;
} else {
usernameList.add(DropdownMenuItem<String>(
child: Text('Loading..'), value: 0.toString()));
}
_users.forEach((user) {
usernameList.add(DropdownMenuItem<String>(
child: Container(
child: Text(
user.name,
),
),
value: user.id.toString()));
});
return Container(
// margin: EdgeInsets.only(left: 40.0),
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: DropdownButtonFormField(
icon: Padding(
padding: const EdgeInsets.only(right: 12),
child: Icon(Icons.person, color: Colors.blue),
),
items: usernameList,
value: _ownerId,
decoration: InputDecoration(helperText: 'Optional'),
onChanged: (newValue) {
_ownerId = newValue;
},
),
);
},
);
}
For some reason, the "_columnSelect" AsyncSnapshot is null always, even when the Future method is working fine:
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:kanboard/src/models/column_model.dart';
import 'package:kanboard/src/preferences/user_preferences.dart';
class ColumnProvider {
final _prefs = new UserPreferences();
Future<List<ColumnModel>> getColumns(projectId) async {
final Map<String, dynamic> parameters = {
"jsonrpc": "2.0",
"method": "getColumns",
"id": 887036325,
"params": {"project_id": projectId}
};
final credentials = "${_prefs.username}:${_prefs.password}";
Codec<String, String> stringToBase64 = utf8.fuse(base64);
String encoded = stringToBase64.encode(credentials);
final resp = await http.post(
Uri.parse(_prefs.endpoint),
headers: <String, String>{"Authorization": "Basic $encoded"},
body: json.encode(parameters),
);
final decodedData = json.decode(utf8.decode(resp.bodyBytes));
final List<ColumnModel> columns = [];
final List<dynamic> results = decodedData['result'];
if (decodedData == null) return [];
results.forEach((column) {
final columnTemp = ColumnModel.fromJson(column);
columns.add(columnTemp);
});
print(columns);
return columns;
}
}
The output of "print(columns)" returns:
I/flutter ( 9486): [Instance of 'ColumnModel', Instance of 'ColumnModel', Instance of 'ColumnModel', Instance of 'ColumnModel']
I don't know what I'm missing here. The form has 2 users dropdown select (with the original FutureBuilder Widget) which works just fine. The Column widget with the Future Builder is the one with the "null" problem in snapshot.data.
Thank you in advance for your time and support with this!
I just found where the problem was:
In the form page(new Task page), The columnProvider.getColumns(task.projectId)) wasn't executing because the "task.projectId" parameter is a String, but the API needs an int.
I was confused because the method were being called by the previous page (A project Page with all the tasks) and the getColumn's argument was indeed an integer: int.parse(projectId).
The Kanboard API doesn't return an error code if the ID parameter is other than INT with this specific call "getColumns" (for some reason).
Of course, Flutter (or Dart) is waiting for a response from http.post that would never arrive. When comparing the two calls from the two pages, I noticed the difference.
So, in conclusion, I specified the int data type argument in the getColumn definition in order to avoid any confusion:
Future<List<ColumnModel>> getColumns(int projectId) async {
Best Regards!

Stop listening to snapshot updates in cloud firestore in flutter

I want to stop listening to snapshot updates. The snapshot keeps listening to updates even after the screen is closed. I am using the below code to listen to the updates.
CollectionReference reference = Firestore.instance.collection('Events');
reference.snapshots().listen((querySnapshot) {
querySnapshot.documentChanges.forEach((change) {
// Do something with change
});
})
Your listener is of type StreamSubscription, so you can call some helpful methods on your listener such as cancel()
CollectionReference reference = Firestore.instance.collection('Events');
StreamSubscription<QuerySnapshot> streamSub = reference.snapshots().listen((querySnapshot) {
querySnapshot.documentChanges.forEach((change) {
// Do something with change
});
});
//somewhere
streamSub.cancel();
The listen method returns a Subscription
This class is used to cancel the listening.
You should store inside your state that object to cancel subscription on dispose.
Very late answer, but I thought I'd complete the previous answers with a code sample as it might be useful to others.
class EventsScreen extends StatefulWidget {
EventsScreen({Key key}) : super(key: key);
#override
_EventsScreenState createState() => _EventsScreenState();
}
class _EventsScreenState extends State<EventsScreen> {
StreamSubscription<QuerySnapshot> _eventsSubscription;
#override
void initState() {
// Initialise your stream subscription once
CollectionReference eventsReference = Firestore.instance.collection('Events');
_eventsSubscription = eventsReference.snapshots().listen((snapshot) => _onEventsSnapshot);
super.initState();
}
#override
Widget build(BuildContext context) {
// Build your widget here
return Container();
}
void _onEventsSnapshot(QuerySnapshot snapshot) {
// Remove the setState() call if you don't want to refresh your screen whenever you get a fresh snapshot
setState(() {
snapshot?.documentChanges?.forEach(
(docChange) => {
// If you need to do something for each document change, do it here.
},
);
// Anything you might do every time you get a fresh snapshot can be done here.
});
}
#override
void dispose() {
// Cancel your subscription when the screen is disposed
_eventsSubscription?.cancel();
super.dispose();
}
}
This approach leverages the StatefulWidget's state to handle documents changes.
A better approach would be to use a Provider or the BLoC pattern so that the subscription to your Firestore is not created and handled in the UI.
the correct way is stream.pause()
so the listener will be on pause mode
cancel() destroy the listener and his content
For me, I simply use take on the snapshot method. Basically, the same principle as rxjs with Firebase. You take one stream of data and stop listening.
return collection
.withConverter<Discount>(
fromFirestore: (snapshots, _) => Discount.fromMap(snapshots
.data()!),
toFirestore: (discount, _) => discount
.toMap(),
)
.snapshots()
.take(1); // Take 1 to stop listening to events
If you run and print out the ConnectionState, you'll see it goes to Done once it fetches all the docs in the collection.
Since this is relating to Flutter and therefore probably to widgets changing on new snapshot events, please consider using StreamBuilder to easily delegate stream management and build great reactive UIs.
Container(
alignment: FractionalOffset.center,
color: Colors.white,
child: StreamBuilder<int>(
stream: _bids,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
List<Widget> children;
if (snapshot.hasError) {
children = <Widget>[
const Icon(
Icons.error_outline,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${snapshot.error}'),
),
];
} else {
switch (snapshot.connectionState) {
case ConnectionState.none:
children = const <Widget>[
Icon(
Icons.info,
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Select a lot'),
)
];
break;
case ConnectionState.waiting:
children = const <Widget>[
SizedBox(
width: 60,
height: 60,
child: CircularProgressIndicator(),
),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Awaiting bids...'),
)
];
break;
case ConnectionState.active:
children = <Widget>[
const Icon(
Icons.check_circle_outline,
color: Colors.green,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('\$${snapshot.data}'),
)
];
break;
case ConnectionState.done:
children = <Widget>[
const Icon(
Icons.info,
color: Colors.blue,
size: 60,
),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('\$${snapshot.data} (closed)'),
)
];
break;
}
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: children,
);
},
)
Or just use snapshot.hasData and snapshot.hasError to switch between showing Circle, error, and actual data.