FutureBuilder always refresh - flutter

i have a function for asking that u have token apikey or not. but when im trying to get some textfield there, i cant texting in it(always refresh causing that futurebuilder i think).
below is the function.
Future<String> getApiKey() async {
WidgetsFlutterBinding.ensureInitialized();
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getString("apikey");
}
FutureBuilder<String>(
future: getApiKey(),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(
child: CupertinoActivityIndicator(),
);
default:
if (snapshot.hasError) {
print("has error");
return Center(child: Text('Error: ${snapshot.error}'));
} else if (snapshot.data == null) {
print("not login");
// return Container();
return Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset("assets/image/images/nodata.svg",
height: 150),
SizedBox(height: 15),
Text(
"no data",
style: TextStyle(
color: baseColor,
fontSize: 18,
fontWeight: FontWeight.w500,
fontFamily: "Sofia"),
),
],
),
);
} else {
return FutureBuilder<List<LineUpListModel>>(
future: prov.setLineUpList(prov),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(
child: CupertinoActivityIndicator(),
);
default:
if (snapshot.hasError)
return Center(
child: Text('Error: ${snapshot.error}'));
else if (snapshot.data == null)
// return Container();
return Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
"assets/image/images/nodata.svg",
height: 150),
SizedBox(height: 15),
Text(
"no data",
style: TextStyle(
color: baseColor,
fontSize: 18,
fontWeight: FontWeight.w500,
fontFamily: "Sofia"),
),
],
),
);
else {
print(prov.getMyProfileModel.apiKey.toString());
return TextField();
}
}
},
);
}
}
},
),
How can I type in the TextField without any refreshing interruptions from the Futurebuilder? what should I fix?

You can take this approach for all of your future builder.
Future _future;
#override
#initState() {
_future = getApiKey();
}
Now in your FutureBuilder use this _future variable.
FutureBuilder<String>(
future: _future,

The issue might come from your future: getApiKey() because you are not saving an instance of your future value so it will be reloaded each time the build() method is called (which will happen a lot). A solution might be to keep your future instance inside a variable and initializing it only one time (you will need a StatefulWidget):
Future<String> _futureString;
#override
void initState() {
super.initState();
_futureString = getApiKey();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: _futureString,
);
}
By doing so the _futureString variable will keep the value of your asynchronous operation and won't reload every time.

Related

Flutter : Instance of Future<dynamic>

I am trying to print a value returned from a function to the screen.
Function:
calculation.dart:
Future<dynamic> getTotalCost(BuildContext context) async {
final user = Provider.of<Userr?>(context, listen: false);
double totalCost = 0.0;
QuerySnapshot snapshot = await FirebaseFirestore.instance
.collection('myOrders')
.doc(user?.uid)
.collection('items')
.get();
for (var doc in snapshot.docs) {
totalCost += doc["price"];
}
print(totalCost.toString());
return totalCost.toString();
}
But instead printing the value , it prints Instance of Future.
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text('Item total', style: textStyle),
Text('${ Provider.of<Calculations>(context, listen: false).getTotalCost(context).toString()}', style: textStyle),
],
),
I also know the reason that this is due to the function is asynchronous, it returns future object instead of actual value. But I need to know how can I change the above code in order to print the actual value.
Try the following code:
FutureBuilder(
future: Provider.of<Calculations>(context, listen: false).getTotalCost(context),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text('Item total', style: textStyle),
Text('${snapshot.data}', style: textStyle),
],
);
} else if (snapshot.hasError) {
return Text("Error: ${snapshot.error.toString()}");
} else {
return const CircularProgressIndicator();
}
},
),
In order to reduce the number of Widget rebuild, and for performance optimization, I suggest to only wrap the Widget to update with the Future value.
By doing so, you limit the Widget tree in the builder of the FutureBuilder to the strict necessary.
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text('Item total', style: textStyle),
FutureBuilder(
future: Provider.of<Calculations>(context, listen: false)
.getTotalCost(context),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text('${snapshot.data}', style: textStyle);
} else if (snapshot.hasError) {
return Text("Error: ${snapshot.error.toString()}");
} else {
return const CircularProgressIndicator();
}
},
),
],
),

Why StreamBuilder always has no data before hot reload?

I use firestore and streambuilder to read data in a list, when i run the application for the first time i get a message "Unexpected null value" and I realized that "snapshot.hasData" is always false and snapshot.ConnectionState.waiting is always true. But when i restart application with hot reload i can retrieve data.
This is my stream:
Stream<QuerySnapshot> _branchStream = FirebaseFirestore.instance.collection('Companies').doc(company_id).collection("Branch Offices").snapshots();
This is my StreamBuilder
StreamBuilder<QuerySnapshot>(
stream: _branchStream,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
/* if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading");
}*/
return ListView(
children: snapshot.data!.docs
.map((DocumentSnapshot document) {
Map<String, dynamic> data =
document.data()! as Map<String, dynamic>;
return Padding(
padding: const EdgeInsets.all(22.0),
child: Card(
elevation: 8,
shadowColor: Colors.blueGrey,
shape: cardShape,
child: Row(
children: [
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.all(22.0),
child: CircleAvatar(
radius: 50,
backgroundImage:
NetworkImage(data['branch_image'],scale: 60),
),
)),
Expanded(
flex: 4,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(22.0),
child: Text(data['branch_name'], style: textBlackTitle, textAlign: TextAlign.center,),
),
Padding(
padding: const EdgeInsets.all(22.0),
child: Text("UbicaciĆ³n: "+data['branch_address'], style: textGraySubTitle, textAlign: TextAlign.center,),
),
],
)),
Expanded(
flex: 2,
child: IconButton(
// focusColor: Color(color1),
// color: Color(color1),
onPressed: (){
Navigator.push(context, MaterialPageRoute(builder: (context) => Home(branch_id : data['branch_id'], company_id : company_id, branch_name : data['branch_name'], branch_image : data['branch_image'])));
}, icon: Image.asset("assets/enter.png", fit: BoxFit.contain, height: 100,)))
],
),
),
);
})
.toList()
.cast(),
);
},
)
This is data that I want to get
This is what I get at the first time
This is what I get after hot reload (That I should have from the beginning).
Because your data is null at the beginning, it takes some time to load the data.
You actually already included a check, but commented it out again. Undo it and try again.
/* if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading");
}*/
It takes some time to load snapshot data. For better UX return specific widgets for each state of the snapshot.
Make sure you're using StreamBuilder inside StatefulWidget.
StreamBuilder<QuerySnapshot>(
stream: _branchStream,
builder: (BuildContext context, snapshot) {
if (snapshot.hasError) {
return //error widget
} else {
switch (snapshot.connectionState) {
case ConnectionState.none:
return //some widget
case ConnectionState.waiting:
return CircularProgressIndicator(),
case ConnectionState.active:
return ListView()
case ConnectionState.done:
return //some widget
}
}
);

how to return a form widget in futureBuild in flutter

I have this code as am trying to code something to update data in firestore.
#override
Widget build(BuildContext context) {
// Use the Todo to create the UI.
return Scaffold(
appBar: AppBar(
title: Text(mid.toString()),
),
body: FutureBuilder<Member?>(
future: readMember(mid),
builder: (context, snapshot) {
if (snapshot.hasData) {
final member = snapshot.data;
/// return a form
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
);
}
if snapshot hasData I want to return a form like this
Card(
child: Row(
children: <Widget>[
TextField(
controller: controllerName,
decoration: decoration('name'),
),
const SizedBox(height: 24),
TextField(
controller: controllerAge,
decoration: decoration('Age'),
keyboardType: TextInputType.number,
),
const SizedBox(height: 24),
ElevatedButton(
child: const Text('Create'),
onPressed: () {},
),
],
));
All my attempt yield no success please I need help.
Check others state like error or if the data is null or not
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text("Got Error");
}
if (snapshot.data == null) {
return Text("No data");
}
if (snapshot.hasData) {
final member = snapshot.data;
return Card( ///here form
child: Row(
children: <Widget>[],
));
} else {
return const Center(child: CircularProgressIndicator());
}
},
And provide width on TextFiled to fix overflow, TextFiled and row are trying to get infinite with.
just wrap with Expanded
Expanded(child: TextField(...)),
You can find more about unbound height& width

How to turn off CircularProgressIndicator flutter

There is a list that displays data from the database that comes to it at the time the data is fetched and until the data appears, the CircularProgressIndicator () appears so that the user knows that there is a process happening in the background.
Excellent but there is a problem with this CircularProgressIndicator () continues to work non-stop if there is no data in the database. Herein lies the problem.
It is supposed to work for a specified time and if there is no data in the database it will stop working and disappear.
Is there a way to do this? So that if there is no data that can be fetched it stops working?
My code:
class MainListView extends StatefulWidget {
MainListViewState createState() => MainListViewState();
}
class MainListViewState extends State {
final String apiURL = 'http://====================/getStudentInfo.php';
Future<List<Studentdata>> fetchStudents() async {
var response = await http.get(apiURL);
if (response.statusCode == 200) {
final items = json.decode(response.body).cast<Map<String, dynamic>>();
List<Studentdata> studentList = items.map<Studentdata>((json) {
return Studentdata.fromJson(json);
}).toList();
return studentList;
}
else {
throw Exception('Failed to load data from Server.');
}
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Studentdata>>(
future: fetchStudents(),
builder: (context, snapshot) {
if (!snapshot.hasData)
return Center(
child: CircularProgressIndicator()
);
return ListView(
children: snapshot.data
.map((data) => Column(children: <Widget>[
GestureDetector(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.fromLTRB(20, 5, 0, 5),
child: Text(data.studentName,
style: TextStyle(fontSize: 21),
textAlign: TextAlign.left))
]),),
Divider(color: Colors.black),
],))
.toList(),
);
},
);
}
}
You can try with the below lines
return FutureBuilder<List<Studentdata>>(
future: fetchStudents(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView(
children: snapshot.data
.map((data) =>
Column(children: <Widget>[
GestureDetector(
child: Row(
crossAxisAlignment: CrossAxisAlignment
.start,
children: [
Padding(
padding: EdgeInsets.fromLTRB(
20, 5, 0, 5),
child: Text(data.studentName,
style: TextStyle(fontSize: 21),
textAlign: TextAlign.left))
]),),
Divider(color: Colors.black),
],))
.toList(),
);
}
else if (!snapshot.hasData) {
return Text("No data Available");
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return Center(
child: CircularProgressIndicator()
);
},
);
You can also set a value to CircularProgressIndicator() to stop at full circle indicator.
Just do this:
if (!snapshot.hasData && !snapshot.hasError) {
return Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return Center(
child: Text("No data Found"),
);
} else {
// do something here
}
It means that if status code is 200 and there is no data then you are going to get an error.

how to listen to two different futures on one screen?

I am learning flutter and building a simple vocabulary app. I have one future coming from sqlLite database and another from twitter api. I only know how to create one state class with builder.
So the _tweet method, I need to have it show up in the placeholder/hard coded tweet spot. It doesn't work though.
import 'package:flutter/material.dart';
import '../../resources/dbprovider.dart';
import '../../resources/tweetprovider.dart';
import '../../data/models/word.dart';
import 'package:tweet_webview/tweet_webview.dart';
class WordCard extends StatefulWidget {
final int id;
WordCard({
this.id,
});
#override
State<StatefulWidget> createState() {
return _WordCardState();
}
}
class _WordCardState extends State<WordCard> {
#override
Widget build(BuildContext context) {
final dbProvider = DBProvider.get();
return Scaffold(
appBar: AppBar(
title: Text('Meme Vocab'),
),
body: FutureBuilder<Word>(
future: dbProvider.getWordByID(widget.id),
builder: (context, snapshot) {
if (snapshot.hasData) {
Word word = snapshot.data;
return _card(word);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
}));
}
Widget _card(Word word) {
return Padding(
padding: EdgeInsets.all(15.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
word.word.trim(),
style: new TextStyle(
fontFamily: "Roboto",
fontSize: 40.0,
fontWeight: FontWeight.bold,
color: Colors.black.withOpacity(0.45)),
textAlign: TextAlign.left,
),
Text(
word.definition,
style: new TextStyle(
fontFamily: "Roboto",
fontSize: 20.0,
fontStyle: FontStyle.italic),
textAlign: TextAlign.left,
),
TweetWebView.tweetUrl(
"https://twitter.com/realDonaldTrump/status/1159898199802425344") //placeholder
],
),
);
}
}
//Don't know where to put this widget so it renders, how to return the tweets I fetch.
Future<Widget> _tweet(Word word) async {
final tweetProvider = TweetProvider();
List<int> tweets = await tweetProvider.getRelevantTweetIDs(word);
final list = ListView.builder(
scrollDirection: Axis.vertical,
padding: EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 0.0),
itemCount: tweets.length,
itemBuilder: (context, index) {
var tweetID = tweets[index].toString();
return Card(
child: TweetWebView.tweetID(tweetID),
);
},
);
final container =
Container(color: Colors.black26, child: Center(child: list));
return container;
}
I found that Future Builder widget is the way to go. I have it in the place of the TweetWebView or basically I can place it anywhere there is a widget whose's contents are a future.
FutureBuilder(
future: tweetProvider.getRelevantTweetIDs(word),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
print('something went wrong while fetching tweets');
}
return Expanded(child: _tweetList(snapshot.data));
} else {
return Center(child: CircularProgressIndicator());
}
},
)