I have a SplashScreen that retrieve information from my Api. When response is success i would like open my second screen but I have a error because after my method to open new screen I need to add some return widget.
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: StreamBuilder(
stream: bloc.appInformation,
builder: (context, AsyncSnapshot<AppInformationModel> snapshot){
return onResponse(snapshot);
},
),
);
}
My response method look like this.
Widget onResponse(AsyncSnapshot<AppInformationModel> snapshot) {
if (snapshot.hasData) {
openMain();
return Center(
child: Text(
snapshot.data.version,
style: TextStyle(color: Colors.white, fontSize: 30.0),
),
);
} else if (snapshot.hasError) {
return Text('Error');
}
return Center(child: CircularProgressIndicator());
}
And openMain() is just
void openMain() {
Navigator.pushNamed(context, 'home');
}
My second screen work perfect.
The error is
setstate or markneedsbuild called during build onTap
I understand is because my return widget is after opanNavigation() but i must return a view.
Thanks
Related
The code below displays list of records from FirebaseFirestore using AsyncSnapshot with StreamBuilder. It works great, however I want to display the total number of records in the AppBar title and tht works when the app is launched, but doesn't update after any addition or deletion.
Question: How can I update the number of records (and display in Appbar title) after the list has an addition or deletion?
Note that I'm displaying the total number of records in the AppBar title using title: Text('# Waiting: $numberWaiting'),, but I can't figure out how to refresh this after the list changes. Any suggestions are greatly appreciated.
class HomePageState extends State<HomePage> {
Query waitingList = FirebaseFirestore.instance
.collection('waiting')
.orderBy('Time_In');
int numberWaiting = 0; // Starts at 0; updated in StreamBuilder
Future<void> delete(String docID) async {
await FirebaseFirestore.instance.collection('waiting').doc(docID).delete();
// TODO: How to update numberWaiting in AppBar title?
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("# Waiting: ${numberWaiting.toString()}"),
),
body: SizedBox(
width: double.infinity,
child: Center(
child: StreamBuilder(
stream: waitingList.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Column(
...
);
}
else if (snapshot.hasData) {
return ListView.builder (
itemCount: snapshot.data?.docs.length,
itemBuilder: (BuildContext context, index) {
numberWaiting = index + 1;
String name = snapshot.data?.docs[index]['Name'];
return Card(
child: SizedBox(
child:ListTile(
title:
Row(
children: <Widget>[
Text(name),
],
),
onTap: () {
// Create or Update Record
// TODO: Update numberWaiting for title
Navigator.push(context, MaterialPageRoute(builder: (BuildContext context){
return CrudPage(
docId: snapshot.data?.docs[index].id.toString() ?? "",
docSnap: snapshot.data?.docs[index]);
}));
},
onLongPress: () {
// Delete Record
// TODO: Update numberWaiting for title
delete(snapshot.data?.docs[index].id.toString() ?? "");
},
),
),
);
},
);
}
else {
return const Text('No Data');
}
}, // Item Builder
),
),
),
);
}
}
Unfortunately this code only updates the # Waiting: X title once and doesn't refresh when an item is deleted or added.
Thank you for your help!
Simply update value and rebuild on "else if (snapshot.hasData)"
class HomePageState extends State {
Query waitingList = FirebaseFirestore.instance
.collection('waiting')
.orderBy('Time_In');
Future<int> countStream(Stream<QuerySnapshot<Object?>> stream) async =>
stream.length;
#override
Widget build(BuildContext context) {
var numberWaiting = "";
return Scaffold(
appBar: AppBar(
title: Text("# Waiting: $numberWaiting"),
),
body: SizedBox(
width: double.infinity,
child: Center(
child: StreamBuilder(
stream: waitingList.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Column(
...
);
}
else if (snapshot.hasData) {
setState((){
numberWaiting = snapshot.data?.docs.length.toString();
})
return ListView.builder (
itemCount: snapshot.data?.docs.length,
itemBuilder: (BuildContext context, index) {
String name = snapshot.data?.docs[index]['Name'];
return Card(
child: SizedBox(
child:ListTile(
title:
Row(
children: <Widget>[
Text(name),
],
),
),
),
);
},
);
}
else {
return const Text('No Data');
}
}, // Item Builder
),
),
),
);
}
}
I'm trying to find out if there is a specific user in the bank. I intend to get this user's information and give them the option to update their information or not.
For that, I'm bringing his identifier from the previous page and trying to get his data like this:
Future<List>? _list;
.
.
#override
void initState() {
_list = widget.userServices.searchUser(widget.cpf);
super.initState();
}
This returns me a json correctly.
This is my service to get a user's data:
searchUser(cpf) async {
try {
final response = await http.get(
Uri.parse(BaseUrl.baseUrl + 'api/user/buscaProdutorPorCPF/$cpf'));
if (response.statusCode == 200) {
return json.decode(response.body);
} else {
throw Exception("Error");
}
} catch (e) {
throw Exception(e.toString());
}
}
Finally, I'm trying to display the information with this example code:
#override
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _list,
builder: (context, snapshot) {
print(snapshot);
if (snapshot.hasData)
return _buildBody(snapshot.data);
else if (snapshot.hasError)
return _buildErrorPage(snapshot.error);
else
return _buildLoadingPage();
},
);
}
Widget _buildBody(List userList) => Scaffold(
appBar: AppBar(
title: Text('Employee Title = ${userList[0].nome}'),
),
body: ListView.builder(
itemCount: userList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(userList[index].email!),
);
},
),
);
Widget _buildErrorPage(error) => Material(
child: Center(
child: Text("ERROR: $error"),
),
);
Widget _buildLoadingPage() => Material(
child: Center(
child: CircularProgressIndicator(),
),
);
But I'm getting the following error:
I appreciate if anyone can help me analyze this error!
I think modifying the code like this:
return FutureBuilder(
//rest of code
);
and
Widget _buildBody(List userList) => //rest of code
will resolve the problem.
here im try to use FutureBuilder for my list but I can't refresh by on pullRefresh
#override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: _refreshPhotos, // fatch snapshot.data!
child: FutureBuilder<String>(
future: userId as Future<String>,
builder: (context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return LayoutBuilder(builder: (context, constraints) {
return ListView(
scrollDirection: Axis.vertical,
children: [
AddBanners(userId: snapshot.data!), // future builder,it fatches data from api
DealsOfTheDay(userId: snapshot.data!), //future builder, , it fatches data from api
]);
});
} else {
return Center(child: JumpingText('Loading...'));
}
}),
);
I want fresh these widgets along with
refreshPhotos()
AddBanners(userId: snapshot.data!),
DealsOfTheDay(userId: snapshot.data!)
If you are looking for pull to refresh. Wrap your widgets with 'RefreshIndicator' widget on your desired screen.
Here is an example of my home screen which has pull to refresh.
#override
Widget build(BuildContext context) {
return Scaffold(
key: _con.scafoldKey,
body: WillPopScope(
onWillPop:() => DeviceUtils.instance.onWillPop(),
child: SafeArea(
child: Container(
color: ColorUtils.themeColor,
child: RefreshIndicator( //Just add this to your screen
color: ColorUtils.themeColor,
key: _con.refreshIndicatorKey,
strokeWidth: 4,
displacement: 80,
onRefresh: _refresh, //this is a function which you need to place under your home view state
child: SingleChildScrollView(
physics: const BouncingScrollPhysics(parent: AlwaysScrollableScrollPhysics()),
child: Container(
color: Colors.white,
child: /// some more widgets
),
),
),
),
);
}
After adding the refresh indicator to your widgets, you need to add the _refresh function which will have all your api's that you want to reload.
Future<Null> _refresh() async{
//these two are my api's that i want to reload everytime an user pulls to refresh screen. You have to add your own apis here.
_con.getProfile(context);
_con.getUpcoming(context);
}
Voila. Now your user can reload data in the page and get the new state.
Hope this answers your question.
If the above is not what you want. You can use setState() inside your future builder. See the code below for example:
class _MyHomePageState extends State<MyHomePage> {
Future<List<String>> _myData = _getData(); //<== (1) here is your Future
#override
Widget build(BuildContext context) {
var futureBuilder = new FutureBuilder(
future: _myData; //<== (2) here you provide the variable (as a future)
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return new Text('loading...');
default:
if (snapshot.hasError)
return Column(
children: [
Icon(Icons.error),
Text('Failed to fetch data.'),
RaisedButton(
child: Text('RETRY'),
onPressed: (){
setState(){
_myData = _getData(); //<== (3) that will trigger the UI to rebuild an run the Future again
}
},
),
],
);
else
return createListView(context, snapshot);
}
},
);
return new Scaffold(
appBar: new AppBar(
title: new Text("Home Page"),
),
body: futureBuilder,
);
}
setState() will rebuild the widget with new values.
you can simply use in your main screen
setState((){});
it will rebuild all of the futureBuilder widgets in your screen and retrieve new data
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: Colors.transparent,
// appBar: AppBar(
// title: Text('Future Demo Page'),
// ),
body: FutureBuilder(
builder: (ctx, snapshot) {
// Checking if future is resolved or not
if (snapshot.connectionState == ConnectionState.done) {
// If we got an error
if (snapshot.hasError) {
return Center(
child: Text(
'${snapshot.error} occured',
style: TextStyle(fontSize: 18),
),
);
// if we got our data
} else if (snapshot.hasData) {
// Extracting data from snapshot object
final data = snapshot.data as String;
getGoodsData(data);
// return getGoodsData(data);
}
}
// Displaying LoadingSpinner to indicate waiting state
return Center(
child: CircularProgressIndicator(),
);
},
// Future that needs to be resolved
// inorder to display something on the Canvas
future: startBarcodeScanStream(),
),
),
);
}
Future<void> startBarcodeScanStream() async {
FlutterBarcodeScanner.getBarcodeStreamReceiver(
'#ff6666', 'Cancel', true, ScanMode.BARCODE)
.listen((barcode) {
// if (!mounted) return;
this.barcodeScanner = barcode;
print(barcode);
// Don't show form if barcode sacnner is cancelled
if (barcode == "-1") {
Navigator.pop(context);
}
});
return barcodeScanner;
}
I am implementing barcode scanner in my flutter application. Once scanning is done I want to show progressIndicator at the center of scanning and I have to call the api.The above code is not showing any progress indicator.
Note: I want to show progressIndicator at the center of scanner after scanning and I have to call the api one response comes successfully I have to hide that progress indicator.
Easiest way is to add a new condition in your FutureBuilder that will detect that you are still waiting for the scan to complete and will display a loading until scan is done, then it will display normal widgets
Code:
body: FutureBuilder(
builder: (ctx, snapshot) {
// This is new code: state NOT "done"
if (snapshotGames.connectionState != ConnectionState.done) {
return Center(child: CircularProgressIndicator());}
}
// The rest of your code is the same
if (snapshot.connectionState == ConnectionState.done) {
You should add a bool variable to control the progress indicator and a Stack to put widget in front.
//Add variable
bool _loading=false;
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: Colors.transparent,
body: Stack(
children:[
Positioned(
child: FutureBuilder(
builder: (ctx, snapshot) {
// Checking if future is resolved or not
if (snapshot.connectionState == ConnectionState.done) {
// If we got an error
if (snapshot.hasError) {
return Center(
child: Text(
'${snapshot.error} occured',
style: TextStyle(fontSize: 18),
),
);
// if we got our data
} else if (snapshot.hasData) {
// Extracting data from snapshot object
final data = snapshot.data as String;
getGoodsData(data);
// return getGoodsData(data);
}
}
// Displaying LoadingSpinner to indicate waiting state
return Center(
child: CircularProgressIndicator(),
);
},
// Future that needs to be resolved
// inorder to display something on the Canvas
future: startBarcodeScanStream(),
),
),
),
if(_loading==true) //show progress indicator if loading is true
Positioned(
child: Center(
child: CircularProgressIndicator(),
)
)
]
),
);
}
Future<void> startBarcodeScanStream() async {
setState(() {_loading=true;}); //start showing Circular Progress Indicator
FlutterBarcodeScanner.getBarcodeStreamReceiver(
'#ff6666', 'Cancel', true, ScanMode.BARCODE)
.listen((barcode) {
// if (!mounted) return;
this.barcodeScanner = barcode;
print(barcode);
// Don't show form if barcode sacnner is cancelled
if (barcode == "-1") {
Navigator.pop(context);
}
});
return barcodeScanner;
}
I am using Futurebuilder in flutter and having issue while closing the showDialog
Widget build(BuildContext context) {
return Scaffold(
appBar: PreferredSize(
preferredSize: Size( 50.0),
body: FutureBuilder(
future: widget.getAutoCompleteData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
Navigator.of(context).pop();
} else if (snapshot.hasError) {
} else {
LoadingDialog.showLoadingDialog(context, _scaffoldKey);
}
return Center(
child: Container(child: Text("sds"),),
);
}));
}
Getting below error when screen loads
package:flutter/src/widgets/navigator.dart': Failed assertion: line 5013 pos 12: '!_debugLocked': is not true
Change this
FutureBuilder(
future: widget.getAutoCompleteData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
Navigator.of(context).pop();
} else if (snapshot.hasError) { //here this is empty
} else {//remove else statement
LoadingDialog.showLoadingDialog(context, _scaffoldKey);
}
return Center(
child: Container(child: Text("sds"),),
);
})
To This
FutureBuilder<List<Post>>(
future: _dataFetcher.getPosts(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasError) {
return LoadingDialog.showLoadingDialog(context, _scaffoldKey);
}
return Center(child: Text('${snapshot.data![0].title}'));
},
)
It may be caused by the re-entrant of the Navigator (you can check the answer here: Error thrown on navigator pop until : “!_debugLocked': is not true.”
)
Or, maybe you don't want to use FutureBuilder. The FutureBuilder is meant to stay in the same widget/page and show different screens when future data is not ready. If you want to push a loading dialog and close it when data is ready, you can just simply use a Future function
Future pressTheButton(context) async {
LoadingDialog.showLoadingDialog(context, _scaffoldKey); // showDialog here
final data = await getAutoCompleteData(); // await the data
Navigator.of(context).pop(); // pop the loading dialog
// return your data or error
// or rebuild the current widget with the data
}