Flutter: How to make a page wait until data loads? - flutter

I used async while using the database, however, the page loads before the data. On the page a lot of widgets are connected to the trophyPoint variable and the page loads before the variable. I used AutomaticKeepAliveClientMixin where the bottom navigation bar is located. It worked for the slide navigations, but when I tapped the navigation bar items, the variable returns null at first and it is the problem.
Edit: I also tried using the SharedPreferences class to load old data but it didn't work either.
Code
class MainScreen extends StatefulWidget {
static String id = "MainScreen";
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
int trophyPoint = 0;
final Future<FirebaseApp> _future = Firebase.initializeApp();
//Database instance
final _db = FirebaseDatabase.instance
.reference()
.child("users")
.child(_auth.currentUser.uid);
//method for data
void readData(
_db,
) async {
await _db.once().then((DataSnapshot snapshot) {
if (!mounted) return;
setState(() {
trophyPoint = snapshot.value["tp"];
});
});
}
/* void tp() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
if (!mounted) return;
setState(() {
geciciTP = sharedPreferences.getInt("trophy");
sharedPreferences.setInt("geciciTP", geciciTP);
});
}*/
#override
void initState() {
// TODO: implement initState
super.initState();
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
}
#override
Widget build(BuildContext context) {
readData(_db);
Size size = MediaQuery.of(context).size;
precacheImage(AssetImage('images/indicator.gif'), context);
return buildContainer(size, context);
}
Container buildContainer(Size size, BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: kGradientColor,
),
child: Scaffold(
backgroundColor: Colors.transparent,
body: FutureBuilder(
future: _future,
initialData: trophyPoint,
builder: (context, snapshot) {
return SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Expanded(
flex: 2,
child: Stack(
fit: StackFit.expand,
children: [
Positioned(
right: size.width / 25,
top: size.height / 60,
child: Container(
width: 100,
height: size.height * 0.055,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(45),
color: kDefaultLightColor,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
FontAwesomeIcons.trophy,
color: Color(0xffFFD700),
),
Text(
" $trophyPoint",
...
gif file of the problem

Inside your FutureBuilder you can check:
if(snapshot.hasData){
return body....
} else {
return CircularProgressIndicator()
}
The example at the link is pretty good.

Related

My widget is rebuilt when toggling visibility where it shouldn't

I have a StatefulWidget that basically consists on a background widget (an playing video) and foreground HUD Column of Widget. The later visibility can be toggled by taping on an ElevatedButton.
The number of views StatefulWidget fetches the count from a remote cloud-based database, and while actually fetching, displays a CircularProgressIndicator.
This widget is part of the HUD.
Why does it fetches the database again when I toggle the HUD? And what to do to keep the counter into memory?
This is the main Widget:
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(title: Text(widget.ad.title)),
body: Stack(
children: [
GestureDetector(
onTap: () {
if (_displayHud.value == false) {
_displayHud.value = true;
}
},
// == the background image
child: _buildBackgroundImage(context)),
// === The foreground UI
FutureBuilder(
future: widget.ad.getOwnerUser,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
}
final owner = snapshot.data!;
return ValueListenableBuilder(
valueListenable: _displayHud,
builder: (context, value, child) {
return Visibility(
visible: _displayHud.value,
replacement: Align(
alignment: Alignment.topRight,
child: ElevatedButton(
onPressed: () {
if (_displayHud.value == false) {
_displayHud.value = true;
}
},
child: const Icon(
Icons.fullscreen_exit,
))),
child: Padding(
padding: const EdgeInsets.all(20),
child: _buildForegroundUi(context,
watchingUser: widget.watchingUser,
owner: owner)),
);
});
})
],
));
}
And here is the count Widget:
/// A widget that display a coun from a future
class _CounterWidget extends StatefulWidget {
/// The icon to display
final IconData iconData;
/// The future that fetches the count
final Future<int> getCount;
/// The press callback
final void Function()? onPressed;
const _CounterWidget(
{required this.iconData, required this.getCount, this.onPressed});
#override
State<_CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<_CounterWidget> {
int? _value;
Exception? _error;
#override
void initState() {
_asyncInitState();
super.initState();
}
void _asyncInitState() async {
widget.getCount.then((value) {
if (!mounted) return;
setState(() {
_value = value;
});
}).catchError((e) {
if (!mounted) return;
setState(() {
_error = e;
});
});
}
#override
Widget build(BuildContext context) {
const viewerIconColor = Colors.white;
final viewerTextStyle =
Theme.of(context).textTheme.caption?.copyWith(color: Colors.white);
final countWidget = _error != null
? const Icon(Icons.error)
: _value == null
? const Center(child: CircularProgressIndicator())
: Text("$_value", style: viewerTextStyle);
// === Likes
if (widget.onPressed == null) {
return Column(children: [
Icon(widget.iconData, color: viewerIconColor),
countWidget
]);
}
return ElevatedButton(
style: ButtonStyle(
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(18.0),
//side: BorderSide(color: Colors.red)
))),
onPressed: widget.onPressed,
child: Column(children: [Icon(widget.iconData), countWidget]));
}
}
i've faced this similar issue before. you can see this guy explain with StreamBuilder. since this its a video, i'll cut a clip. you can see here:
problem : https://youtube.com/clip/UgkxU3QOcatZOh1bSRe3KwEEValakNU5josE
solution: https://youtube.com/clip/UgkxME0hQM6r0bXzFB43nAi5kZCKt1DV0IMn
in summary, you can assign your future function as state variable.
class _YourClassWidgetState extends State<YourClass> {
final _futureFunc = widget.ad.getOwnerUser(); // declare as state variable
....
// === The foreground UI
FutureBuilder(
future: _futureFunc,
note: i use setState() to update my UI, and since you are using ValueNotifier() i dont know it will same behaviour. You may try it on your side.
but my hypothesis it will same. because updating ValueListenableBuilder which also update the FutureBuilder *cmiiw

Flutter wifi EspTouch connection stream problem

I want to connect to an ESP32 with my Flutter app. To implement the connection via ESPTouch I use the package "esptouch_flutter".
Now there is the following problem: If I enter my password incorrectly, it tries to connect for a minute and aborts, which is what it is supposed to do. However, if I then enter the correct password, it also searches for a minute and aborts, although it should actually connect. If I enter the correct password without having entered anything wrong before, it works fine.
The actual connection is established via a stream.
stream = widget.task.execute();
I have a suspicion that even after dispose of the page, the stream continues to run and then does not restart properly. Can this be? Or maybe anyone else have an idea?
Full Code:
class wifiConnection extends StatefulWidget {
const wifiConnection({Key? key, required this.task}) : super(key: key);
final ESPTouchTask task;
#override
State<StatefulWidget> createState() => wifiConnectionState();
}
class wifiConnectionState extends State<wifiConnection> {
late final Stream<ESPTouchResult> stream;
late final StreamSubscription<ESPTouchResult> streamSubscription;
late final Timer timer;
final List<ESPTouchResult> results = [];
var connectionSuccessfull = true;
#override
void initState() {
stream = widget.task.execute();
streamSubscription = stream.listen(results.add);
final receiving = widget.task.taskParameter.waitUdpReceiving;
final sending = widget.task.taskParameter.waitUdpSending;
// final cancelLatestAfter = receiving + sending; // Default = 1 min
const testTimer = Duration(seconds: 15);
timer = Timer(
// cancelLatestAfter,
testTimer,
() {
streamSubscription.cancel();
if (results.isEmpty && mounted) {
setState(() {
connectionSuccessfull = false;
});
}
},
);
super.initState();
}
#override
dispose() {
timer.cancel();
streamSubscription.cancel();
super.dispose();
}
_dissolve() {
setState(() async {
await Future.delayed(const Duration(milliseconds: 1000));
setOnboardingEnum(Onboarding.wifiFinished);
Navigator.of(context).pushReplacement(createRoute(const StaticBellPage()));
});
}
getConnectionSite(AsyncSnapshot<ESPTouchResult> snapshot) {
if (snapshot.hasError) {
return connectionError();
}
if (!snapshot.hasData) {
return connectionPending();
}
switch (snapshot.connectionState) {
case ConnectionState.active:
return connectionActive();
case ConnectionState.none:
return connectionError();
case ConnectionState.done:
return connectionActive();
case ConnectionState.waiting:
return connectionPending();
}
}
connectionPending() {
return SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
const MydoobeAvatar(avatarWidget: CircularProgressIndicator()),
connectionPendingText,
MydoobeButton(
buttonText: "Zurück",
callback: () => Navigator.of(context)..pop(),
),
],
),
);
}
connectionActive() {
return SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
const MydoobeAvatar(avatarWidget: CheckArrow()),
connectionSuccessfullText,
MydoobeButton(buttonText: "Weiter", callback: _dissolve),
],
),
);
}
connectionError() {
return SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
const MydoobeAvatar(avatarWidget: ErrorCross()),
connectionErrorText,
MydoobeButton(
buttonText: "Zurück",
callback: () => Navigator.of(context)..pop(),
),
],
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
body: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
StreamBuilder<ESPTouchResult>(
builder: (context, AsyncSnapshot<ESPTouchResult> snapshot) {
return AnimatedSwitcher(
duration: const Duration(seconds: 2),
child: connectionSuccessfull ? getConnectionSite(snapshot) : connectionError() as Widget);
},
stream: stream,
),
],
),
],
),
);
}
}
var connectionPendingText = RichText(...);
var connectionSuccessfullText = RichText(...);
var connectionErrorText = RichText(...);

Stream.periodic clock is updating other StreamBuilder

This is my code:
class MobileHomePage extends StatefulWidget {
const MobileHomePage({Key? key}) : super(key: key);
#override
State<MobileHomePage> createState() => _MobileHomePageState();
}
class _MobileHomePageState extends State<MobileHomePage> {
#override
void setState(fn) {
if (mounted) {
super.setState(fn);
}
}
int? second;
#override
void initState() {
second = int.parse(DateFormat.s().format(DateTime.now()));
Timer.periodic(const Duration(seconds: 1), (timer) => getCurrentTime());
super.initState();
}
void getCurrentTime() {
setState(() {
second = int.parse(DateFormat.s().format(DateTime.now()));
});
}
User user = FirebaseAuth.instance.currentUser!;
final Stream<QuerySnapshot> _mainSubjectStream = FirebaseFirestore.instance
.collection('systems')
.orderBy('system_name', descending: false)
.snapshots();
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
double height = MediaQuery.of(context).size.height;
final GlobalKey<ScaffoldState> _key = GlobalKey();
return Scaffold(
key: _key,
body: SafeArea(
bottom: false,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Expanded(
flex: 1,
child: Container(
color: Colors.transparent,
child: StreamBuilder<QuerySnapshot>(
stream: _mainSubjectStream,
builder: (context, mainSubjectSnapshot) {
if (mainSubjectSnapshot.hasError) {
return const Center(child: Text('Error));
}
if (mainSubjectSnapshot.connectionState ==
ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
}
var length = mainSubjectSnapshot.data!.docs.length;
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: length,
itemBuilder: (context, i) {
var output = mainSubjectSnapshot.data!.docs[i];
return Text(output['name']);
});
},
),
)),
const Spacer(flex: 3),
Expanded(
flex: 5,
child: DatePicker(
DateTime.now(),
initialSelectedDate: _date,
daysCount: 8,
locale: 'pt',
selectionColor: const Color.fromRGBO(67, 97, 238, 1),
selectedTextColor: Colors.white,
onDateChange: (date){
setState(() {
_date = date;
});
},
),
),
Expanded(
flex: 1,
child: Text(second.toString()
),
],
),
));
}
}
When I try to display the clock (as in second.toString()), the StreamBuilder is also updated, resulting in a new progress indicator each second. What is breaking my head is that I just copied the same strutted that I used in other code – which works fine. Did I do something wrong? Why is it influencing the other stream?
EDIT
Have just found out that the error is related to the key in the scaffold. When I deleted it, it worked fine. The thing is that I need I as I have a custom button to open the drawer. 1st question is how does it influences it. 2nd question is how to solve it?

flutter await does not wait in initState

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();
}
)
);
}

Flutter- Error with Drawer and Listview.Builder()

Here's my code:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
//import 'package:location/location.dart';
class Home extends StatefulWidget {
Home({Key key, this.title}) : super(key: key);
final String title;
#override
_Home createState() => _Home();
}
class _Home extends State<Home> {
//declared var
String key = "ville";
String txt_ville = "PARIS";
List<String> villes = [];
GlobalKey<ScaffoldState> _drawerKey = GlobalKey();
//Location location;
//LocationData locationData;
//Stream<LocationData> stream;
#override
void initState() {
// TODO: implement initState
getData();
print(villes);
super.initState();
//location = new Location();
//getFirstLocation();
}
/*getFirstLocation() async {
try {
print("Position: ${locationData.latitude}/${locationData.longitude}");
}catch (e) {
print("Error when locating $e");
}
}*/
#override
Widget build(BuildContext context) {
return Scaffold(
key: _drawerKey,
drawer: Drawer(
child: new ListView.builder(
itemCount: villes.length,
itemBuilder: (BuildContext ctx, i) {
return new ListTile(title: new Text(villes[i]));
},
)
),
backgroundColor: Colors.amberAccent,
body: new Column(
children: <Widget>[
Expanded(
flex:4,
child: new Container(
width: double.infinity,
decoration: new BoxDecoration(
color: Colors.white,
borderRadius: new BorderRadius.only(bottomLeft: Radius.circular(120))
),
child: new Column(
children: <Widget>[
new Padding(
padding: EdgeInsets.only(top: 50),
child: new Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
new Padding(padding: EdgeInsets.only(left: 20),
child: new IconButton(icon: new Icon(Icons.dehaze, size: 30, color: Colors.black,), onPressed: () {
_drawerKey.currentState.openDrawer();
})),
new Padding(
padding: EdgeInsets.only(left: 30),
child: new Text("TheWeatherApp", style: new TextStyle(
fontSize: 40
),),
),
]),
)
],
),
)
),
Expanded(
flex: 1,
child: new Container(
),
)
],
)
);
}
Future<Null> addTown() async{
return showDialog(barrierDismissible: true, context: context, builder: (BuildContext buildcontext) {
return new SimpleDialog(
contentPadding: EdgeInsets.all(20.0),
title: Text("Add a town"),
children: <Widget>[
new RaisedButton(onPressed: () {
}, child: new Text("Auto locate me"),),
new TextField(
decoration: new InputDecoration(
labelText: "Ville"
),
onSubmitted: (s) {
setData(s);
Navigator.pop(context);
},
)
],
);
});
}
void getData() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
List<String> liste = await sharedPreferences.getStringList(key);
if (liste != null) {
setState(() {
villes = liste;
});
}
}
void setData(String str) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
villes.add(str);
await sharedPreferences.setStringList(key, villes);
getData();
}
void deleteData(String str) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
villes.remove(str);
await sharedPreferences.setStringList(key, villes);
}
}
I'm still a beginner on Flutter and I'm trying to understand why when I launch my application on the emulator and open my drawer with the iconbutton I get this error: Pastbinlink to error
If I deliberately create an error in the code like removing a parenthesis and do a hotreload, and I put the parenthesis back and do a hotreload again then my code works and the listview is displayed...
I have a theory that it's my getData function that initializes the variable villes that doesn't work...
I thank you in advance for any answer!
I can't comment, so I am writing it as an answer.
I haven't tried your code, and I am not sure if it will work, however,
You should try to use FutureBuilder
SharedPreferences.getInstance() is a future that will have value later on, and when you try to build your Drawer, it tries to access a value that doesn't exist.
Instead, if you try it like this. Your drawer will be created once your data is retrieved.
drawer: Drawer(
child: new FutureBuilder<List<String>>(
future: getData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data.length == 0) {
return Center(child: Text("No data found."));
} else {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext ctx, i) {
return new ListTile(title: new Text(villes[i]));
},
)
}
} else if (snapshot.hasError) {
return checkError();
}
// By default, show a loading spinner.
return Center(child: CircularProgressIndicator());
})
),