I need to get data from a Future to a Stateful widget before it displays on startup. I have tried async/await, FutureBuilder, and the Sync package implementing a WaitGroup within the initState method; however, nothing I do waits for the data to return from the Future before it renders the screen.
In the below examples, I have a simple String strName that I initialize to "Default Name" that I am using for testing and displaying in the Scaffold. It only displays the initialized "Default Name," and not the name returned from the Future. The closest I got was using a FutureBuilder, at least it updated the screen after the initialized "Default Name" was shown. However, I need to get the data prior to the screen rendering. Does anyone have any ideas?
Here's an example of what I tried with Sync WaitGroup:
class _MyHomePageState extends State<MyHomePage> {
String strName = "Default Name";
Future<String> _getName() async {
var name = await Future<String>.delayed(const Duration(seconds: 5), () => "New Name");
return name;
}
#override
void initState() {
WaitGroup wg = WaitGroup();
wg.add(1);
Future<String> futureName = _getName();
futureName.then(
(value) {
strName = value;
wg.done();
},
);
wg.wait();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(strName),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
strName,
style: Theme.of(context).textTheme.headline4,
),
],
),
),
);
}
}
This is what my async/await method looked like:
class _MyHomePageState extends State<MyHomePage> {
String strName = "Default Name";
Future<String> _getName() async {
var name = await Future<String>.delayed(const Duration(seconds: 5), () => "Jimbo");
return name;
}
#override
void initState() {
Future<String> futureName = _getName();
futureName.then(
(value) {
strName = value;
},
);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(strName),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
strName,
style: Theme.of(context).textTheme.headline4,
),
],
),
),
);
}
}
I've never worked with a language where there asynchronous is the default structure of so many parts. How do you deal with making async synchronous in Dart? I haven't even got into the SQLite and HTTP part of it, and it is killing me. I've been at it for four days and got so frustrated I almost broke a keyboard yesterday.
The best is to use a loading screen while fetching your data
and use snapshot.data
full implementation using FutureBuilder:
class _MyHomePageState extends State<MyHomePage> {
String strName = "Default Name";
Future<String> _getName() async {
var name = await Future<String>.delayed(
const Duration(seconds: 5), () => "New Name");
return name;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(strName),
),
body: FutureBuilder<String>(
future: _getName(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
snapshot.data!,
style: Theme.of(context).textTheme.headline4,
),
],
);
}
return Center(
child: CircularProgressIndicator(),
);
}),
);
}
}
This is really a bad practice
but if you really need to resolve some future data before the app renders you can use the void main() method.
void main()async {
Future<String> futureName = _getName();
futureName.then(
(value) {
strName = value;
runApp(MyApp());
},
);
}
Related
I'm new to flutter , so i'm not sure my way of coding is correct.
I'm trying to get data from firebase , and want to load it in initState().
But, it can not wait. whlie the process is in await, it starts to build.
How can I load data before build.
Thanks
These are the codes and logs.
import 'dart:async';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app/entity/quiz.dart';
import '../result.dart';
class Question extends StatefulWidget {
#override
_QuestionState createState() => _QuestionState();
}
class _QuestionState extends State<Question> {
#override
void initState() {
print("init State");
super.initState();
Future(() async {
await Future.delayed(Duration(seconds: 5));
});
setQuiz();
}
List<Quiz> quiz = [];
Future<void> setQuiz() async {
// 検索結果は、一旦はFuture型で取得
Future<QuerySnapshot<Map<String, dynamic>>> snapshot =
FirebaseFirestore.instance.collection("quiz").get();
print("inSetQuiz");
// ドキュメントを取得
quiz = await snapshot.then(
(event) => event.docs.map((doc) => Quiz.fromDocument(doc)).toList());
// await snapshot.then((value) {
// setState(() {
// quiz = value.docs.map((doc) => Quiz.fromDocument(doc)).toList();
// });
// });
print("afterSetQuiz");
print("hoge" + quiz.length.toString());
print("hoge" + quiz.elementAt(0).sentence);
}
// 問題番号
int questionNumber = 0;
// 正解問題数
int numOfCorrectAnswers = 0;
#override
Widget build(BuildContext context) {
print("in build");
return Scaffold(
appBar: AppBar(
title: Text("問題ページ"),
),
body: Center(
child:
Column(mainAxisAlignment: MainAxisAlignment.start, children: [
Container(
height: 50,
color: Colors.red,
child: const Center(
child: Text(
"一旦",
style: TextStyle(fontSize: 20),
),
),
),
Container(
padding: const EdgeInsets.all(8.0),
child: Text(
quiz.elementAt(questionNumber).sentence, ←error happens
style: TextStyle(fontSize: 20),
)),
omit
here logs↓
flutter: init State
flutter: inSetQuiz
flutter: in build
It load build before await function.
You shouldn't be holding off the initState method. This message straight from a Flutter error says it all: "the initState method must be a void method without an async keyword; rather than waiting on asynchronous work directly inside of initState, just call a separate method to do this work without awaiting it".
That's what a FutureBuilder is for. I'd refactor the app this way:
Keep your setQuiz method async, but return not a void Future, but a Future that wraps the data this method returns (in your case, a quiz).
Future<List<Quiz>> setQuiz() {
// your existing code, just at the end do:
return quiz;
}
Feed the return of the setQuiz async method into a FutureBuilder widget:
#override
Widget build(BuildContext context) {
print("in build");
return Scaffold(
appBar: AppBar(
title: Text("問題ページ"),
),
body: FutureBuilder(
future: setQuiz(),
builder: (context, snapshot) {
if (snapshot.hasData) {
// out of the FutureBuilder's snapshot, collect the data
var quiz = snapshot.data as List<Quiz>;
// build your quiz structure here
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
height: 50,
color: Colors.red,
child: const Center(
child: Text("一旦",
style: TextStyle(fontSize: 20),
),
),
),
ListView.builder(
itemCount: quiz.length,
itemBuilder: (context, index) {
var singleQuestion = quiz[index];
return Text(singleQuestion.sentence);
}
)
]
)
);
}
// while waiting for data to arrive, show a spinning indicator
return CircularProgressIndicator();
}
)
);
}
I build a simple search widget for flutter web. Everything working fine but after I got the search results, I have to click twice on the result to select a specific search result. Please help me to figure out the problem. I tried for several day but no luck. I'm using flutter 2.5.2 version.
darpad link to run the code
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
void main() {
runApp(MaterialApp(
home: SearchView(),
));
}
class SearchView extends StatefulWidget {
#override
State<SearchView> createState() => _SearchViewState();
}
class _SearchViewState extends State<SearchView> {
String searchResult = '';
final textController = TextEditingController();
final List<String> data = ['Result 1', 'Result 2', 'Result 3', 'Result 4'];
Future<List<String>> loadData() {
return Future.delayed(Duration(seconds: 1), () {
if (this.textController.text.trim().length != 0) {
return data;
} else {
return [];
}
});
}
#override
void initState() {
this.textController.addListener(this._onTextChanged);
super.initState();
}
void _onTextChanged() {
print('text cahnged');
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Brand'),
),
body: SingleChildScrollView(
child: Column(
children: [
TextFormField(
controller: this.textController,
),
FutureBuilder(
future: loadData(),
builder:
(BuildContext context, AsyncSnapshot<List<String>> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
print("future build");
return Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
for (String result in snapshot.data)
InkWell(
onTap: () {
print('Clicked');
setState(() {
this.textController.clear();
this.searchResult = result;
});
},
child: Text(result),
),
],
);
} else {
return CircularProgressIndicator();
}
},
),
Text('Search result is ${searchResult}'),
],
),
),
);
}
}
Please help me to fix this issue. Thank you and have a nice day
This weird behavior happens because of a flutter issue. Before flutter version 2.5, Text change listeners only listen for text changes. But from version 2.5, listeners also listen for focus change events. Need to use onChanged method in TextFormField until flutter fix the issue.
I want to get the value of a future function (without futurebuilder), in which I load a json file, I put this value in a text widget but it returns me this message "Instance of 'Future void'" I don't know why .
helperLoadJson.dart
//Load Json
Future<String> _loadAStudentAsset() async {
return await rootBundle.loadString('assets/Copie.json');
}
//Load Response
Future loadStudent([int index]) async {
String jsonString = await _loadAStudentAsset();
final jsonResponse = json.decode(jsonString);
QuotList quotList = QuotList.fromJson(jsonResponse);
return quotList.quots[0].country;
}
main.dart
class _MyHomePageState extends State<MyHomePage> {
//Get Future from helperLoadJson.dart
Future<void> getLoadStudent() async {
final String myQuot = await loadStudent();
print(myQuot); // OK , return me good value
}
#override
Widget build(BuildContext context) {
print('getLoadStudent : ${getLoadStudent()}'); // return " Instance of 'Future<void>' "
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'${getLoadStudent()}', // return " Instance of 'Future<void>' "
),
Container(
// child: projectWidget(),
)
],
),
),
);
}
}
I have two screens, one where the user can chat with a particular person and the second screen, where he can see a list of all the chats.
The aim is to display the last message on the second screen. This is done as follows:
The user sends/receives a new message?
Update the database
BloC sends a new stream of data by fetching the newest data.
The problem is, the stream builder isn't listening to the new data (not sure why). To the best of my knowledge, the BloC is sending a new stream of data when the user sends a message, it just doesn't re-render in the list.
Here's a shortened version of the code:
class ChatScreen extends StatelessWidget {
final ContactsBloc _contactsBloc = ContactsBloc();
#override()
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context, true);
},
),
body: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: TextField(
controller: messageTextController,
onChanged: (value) {
message = value;
},
decoration: kMessageTextFieldDecoration,
),
),
FlatButton(
onPressed: () async {
//update remote and local databases
await _contactsBloc.updateContact(
{'last_message': utf8.decode(base64.decode(message))},
'conversation_id = ?',
[conversationId]);
},
child: Text(
'Send',
style: kSendButtonTextStyle,
),
),
],
),
The chats screen:
class ChatsScreen extends StatefulWidget {
static const id = 'chats';
#override
_ChatsScreenState createState() => _ChatsScreenState();
}
class _ChatsScreenState extends State<ChatsScreen> {
final ContactsBloc _contactsBloc = ContactsBloc();
Iterable<Contact> contacts;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Chats'),
body: Container(
child: StreamBuilder(
stream: _contactsBloc.contacts,
builder: (context, results) {
print('New stream: $results');
if (!results.hasData) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: CircularProgressIndicator(),
),
],
);
} else {
List contacts = results.data;
contacts = contacts
.where((element) => element.lastMessage != null)
.toList();
if (contacts.length > 0) {
return ListView.builder(
itemCount: contacts.length,
itemBuilder: (context, index) {
ContactModel contact = contacts[index];
return ChatItem(
name: contact.name,
message: contact.lastMessage,
profilePicture: contact.profilePictureUrl,
lastSeen: contact.lastSeen,
user: currentUser,
toUser: contact.uid,
conversationId: contact.conversationId,
);
},
);
}
return Container();
}
},
)),
);
}
}
The contact BloC:
class ContactsBloc {
ContactsBloc() {
getAllContacts();
}
final _contactsController = StreamController<List<ContactModel>>.broadcast();
Stream<List<ContactModel>> get contacts => _contactsController.stream;
_dispose() {
_contactsController.close();
}
getAllContacts() async {
List<ContactModel> contacts = await DatabaseProvider.db.getAllContacts();
_contactsController.sink.add(contacts);
}
updateContact(var update, String where, List whereArgs) async {
await DatabaseProvider.db.updateContact(update, where, whereArgs);
getAllContacts();
}
}
For now try adding this to create a Singleton instance of ContactBloc
class ContactsBloc{
ContactsBloc._();
static final ContactsBloc _instance = ContactsBloc._();
factory ContactsBloc() => _instance;
/// the rest of your code...
}
I would recommend checking some state management if you want more control of your classes (Bloc, Provider, Redux, etc)
I am new to Flutter and I am working on a chat app, and whenever i choose an user i should be able to talk to him in a private way, that's what I am doing, whenever i click someone I try to move to this Chat Screen, and then I am getting this error (see title).
But when I'm pressing the back button and try again it works and shows the chat like it should, this is really confusing me, and if someone have an idea where it comes from, it would be awesome.
Here's my chat.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:social/responsive/size_config.dart';
var _firestore = Firestore.instance;
FirebaseUser loggedInUser;
String groupChatId;
class Chat extends StatelessWidget {
static const String id = 'chat_screen';
final String peerEmail;
Chat({Key key, #required this.peerEmail}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'CHAT',
style: TextStyle(fontWeight: FontWeight.bold),
),
centerTitle: true,
),
body: ChatScreen(peerEmail: peerEmail));
}
}
class ChatScreen extends StatefulWidget {
final String peerEmail;
ChatScreen({this.peerEmail});
#override
_ChatScreenState createState() => _ChatScreenState(peerEmail: peerEmail);
}
class _ChatScreenState extends State<ChatScreen> {
final _auth = FirebaseAuth.instance;
final messageTextController = TextEditingController();
String peerEmail;
String messageText;
_ChatScreenState({this.peerEmail});
#override
void initState() {
super.initState();
getCurrentUser();
String email = loggedInUser.email;
getGroupId(email);
}
void getGroupId(String email) {
if (peerEmail.hashCode <= email.hashCode) {
setState(() {
groupChatId = '$peerEmail-$email';
});
} else {
setState(() {
groupChatId = '$email-$peerEmail';
});
}
}
void getCurrentUser() async {
try {
final user = await _auth.currentUser();
if (user != null) {
loggedInUser = user;
setState(() {});
}
} catch (e) {
print(e);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: <Widget>[
MessageStream(),
Container(
decoration: BoxDecoration(color: Colors.red,borderRadius: BorderRadius.circular(10)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Expanded(
child: TextField(
controller: messageTextController,
onChanged: (value) {
//Do something with the user input.
messageText = value;
},
),
),
FlatButton(
onPressed: () {
//Implement send functionality.
messageTextController.clear();
print(messageText);
print(loggedInUser.email);
_firestore.collection('messages')
.document(groupChatId)
.collection(groupChatId).add({
'content': messageText,
'emailFrom': loggedInUser.email,
'emailTo': peerEmail,
});
},
child: Text(
'Send',
),
),
],
),
),
],
)));
}
}
class MessageStream extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: _firestore
.collection('messages')
.document(groupChatId)
.collection(groupChatId)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.black),
),
);
} else {
final messages = snapshot.data.documents;
List<MessageDisplay> messageList = [];
for (var msg in messages) {
final message = msg.data['content'];
final emailTo = msg.data['emailTo'];
final emailFrom = msg.data['emailFrom'];
final messageDisplay = MessageDisplay(
message: message,
emailFrom: emailFrom,
emailTo: emailTo,
);
messageList.add(messageDisplay);
}
return Expanded(
child: ListView(
padding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 20.0),
children: messageList != null ? messageList:CircularProgressIndicator(),
),
);
} //
},
);
}
}
class MessageDisplay extends StatelessWidget {
MessageDisplay({this.message, this.emailFrom, this.emailTo});
final String message;
final String emailFrom;
final String emailTo;
#override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(SizeConfig.safeBlockVertical * 3),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
message != null
? Text(
message,
style: TextStyle(
fontSize: SizeConfig.safeBlockVertical * 15,
color: Colors.black54,
),
)
: CircularProgressIndicator(),
emailFrom != null
? Text(
emailFrom,
)
: CircularProgressIndicator(),
],
),
);
}
}
Thanks for reading.
The most likely cause for this type of error is the new screen you are navigating to is trying to access information from the previous screen which it has no access to or has not inherited and therefore doesn't have the correct build context when trying to build causing this error.