Im stuck in how I can manage to call a future Builder again on a button press to repeat the process indefinitely until it goes OK.
Here is my code:
Widget build(BuildContext context)
{
return FutureBuilder( future: sincroProcess(),
builder: (BuildContext context, AsyncSnapshot snapshot)
{
if (snapshot.hasData)
{
Map sincroProcessResponse = snapshot.data;
if( sincroProcessResponse['allok'] )
{
return ListBody(children: [ OutlinedButton
(
child: Text("Continue"),
onPressed: goInit(),
),
Text( sincroProcessResponse['msg'] )
]);
}
else
{
return ListBody(children: [ OutlinedButton
(
child: Text("Try again"),
onPressed: sincroProcess(), //need this to re-run
),
Text( sincroProcessResponse['msg'] )
]);
}
}
else
{
return ListBody(children: [ CircularProgressIndicator(backgroundColor: Colors.grey),
Text("We are preparing everything for your first usage =)...") ]);
}
});
}
}
I don't know if I am being clear enough, the behavior of the page should be:
show circular progress while running the sincroProcess, if the response its ok, show a button to go to another page, if not, show a button to re-run the sincroprocess with the circular progress indicator.
I cant imagine how to re-use my code!
Make your widget a Statefull widget and call setState() to rebuild the widget
...
else {
return ListBody(children: [
OutlinedButton(
child: Text("Try again"),
onPressed: (){setState((){});}, //need this to re-run // call setState
),
Text(sincroProcessResponse['msg'])
]);
}
Related
I have a FutureBuilder that returns a ListViewBuilder in a class.
When loading the class the FutureBuilder loads the future which is a call to a API, then the ListView shows the received items inside Cards.
It is working fine, at this moment there are three items that should and are showed.
Then I am trying to verify if the class is updated when executing setState at a button click action. I am manually adding or removing items from the database that is called from the API, but clicking on the refres button after adding/removing items from the database, the list is not changing.
Here you have the code:
Container(
height: 120,
child:
FutureBuilder(
future: fetchFotosInformesIncidenciasTodos(
widget.informeActual.codigo),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<dynamic>? filteredList =
snapshot.data as List?;
filteredList ??= [];
listaFotosInformeIncidenciasActual =
filteredList;
WidgetsBinding.instance
.addPostFrameCallback((t) {
setState(() {
numeroFotosSubidas =
filteredList!.length +
numeroFotosSubidasAhora;
});
});
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: filteredList.length,
shrinkWrap: false,
itemBuilder: (BuildContext context, index) {
FotoInformeIncidenciasModelo foto =
filteredList![index];
var urlFoto = Constantes
.adminInformesIncidenciasUrl +
foto.archivo;
return GestureDetector(
onTap: () {
print("pulsada foto ${foto.id}");
},
child: Card(
elevation: 6,
child: (Column(
children: [
Image.network(
urlFoto,
width: 60,
height: 80,
),
],
)),
));
},
);
}
return Image.asset(
"imagenes/vacio.png",
fit: BoxFit.contain,
);
},
),
),
And here the refresh button:
InkWell(
onTap: (){
setState(() {
print("refrescando");
});
},
child: Text("refrescar")),
I would like to know why is the call to setState not forcing to update the FutureBuilder and the ListView Builder
The future function fetchFotosInformesIncidenciasTodos(widget.informeActual.codigo)
which is being called directly from the Future block. You need to make an instance of the future and invoke the same whenever you want a new request for the future eg.
Future<Response> _futureFun;
....
#override
void initState() {
super.initState();
_futureFun =
fetchFotosInformesIncidenciasTodos(widget.informeActual.codigo)
}
_futureFun = fetchFotosInformesIncidenciasTodos(widget.informeActual.codigo){}
#override
Widget build(BuildContext context) {
....
FutureBuilder<Response>(
future: _futureFun,
....
}
And to refresh the data again, just call the function fetchFotosInformesIncidenciasTodos(widget.informeActual.codigo) again and there is not need to setState.
This is the code I have written for my home screen of the app. There is not any error but when I run the program, it just stops on the indicator. The screen doesn't go forward to the entire code and screen. I don't know what happened there.
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(
title: '',
),
body: BlocBuilder<SwipeBloc, SwipeState>(
builder: (context, state) {
if (state is SwipeLoading) {
return Center(
child: CircularProgressIndicator(),
);
} else if (state is SwipeLoaded) {
var userCount = state.users.length;
return Column(
children: [
InkWell(
onDoubleTap: () {
Navigator.pushNamed(context, '/users',
arguments: state.users[0]);
},
child: Draggable<User>(
data: state.users[0],
child: UserCard(user: state.users[0]),
feedback: UserCard(user: state.users[0]),
childWhenDragging: (userCount > 1) ?
UserCard(user: state.users[1]): Container(),
onDragEnd: (drag) {
if (drag.velocity.pixelsPerSecond.dx < 0) {
context.read<SwipeBloc>()
..add(SwipeLeft(user: state.users[0]));
print('Swiped left');
} else {
context.read<SwipeBloc>()
..add(SwipeRight(user: state.users[0]));
print('Swiped right');
}
},
),
),
Just able to see this screen. This screen doesn't go forward for the entire code.
you forget stop condition in if (state is SwipeLoading).
add some code to change your variable state and call setstate.
you will always loading if you not change variable state
I am trying to load DropDownMenu inside Future builder.In my widget i have a Column. Inside Column I have a few widget :
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Row(),
Divider(),
Container(),
...widget._detailsModel.data.appletActions.map((item) {
.....
...item.appletInputs.map((inputs) {
FutureBuilder(
future: MyToolsProvider()
.getDropDownConfiges(inputs.dataUrl),
builder:
(ctx,AsyncSnapshot<DropDownModel.DropDownConfigToolsModle>snapshot) {
if (!snapshot.hasData ||
snapshot.connectionState ==
ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasData &&
snapshot.connectionState ==
ConnectionState.done) {
_dropDown = snapshot.data.data[0];
return DropdownButton<DropDownModel.DataModle>(
hint: Text("Select Item"),
value: _dropDown,
onChanged: (data) {
setState(() {
_dropDown = data;
});
},
items: snapshot.data.data.map((item) {
return DropdownMenuItem<
DropDownModel.DataModle>(
value: item,
child: Row(
children: <Widget>[
Icon(Icons.title),
SizedBox(
width: 10,
),
Text(
item.title,
style: TextStyle(
color: Colors.black),
),
],
),
);
}).toList(),
);
} else {
return Center(
child: Text('failed to load'),
);
}
}),
}
}
]
As you can see i have FutureBuilder inside a loop to show DropdownButton.everything is ok and code works as a charm but my problem is :
onChanged: (data) {
setState(() {
_dropDown = data;
})
every time setState called, future: MyToolsProvider().getDropDownConfiges(inputs.dataUrl), is executed and
_dropDown = snapshot.data.data[0]; again initialized and it get back in a first time .
It is not possible declared MyToolsProvider().getDropDownConfiges(inputs.dataUrl), in initState() method because inputs.dataUrl it is not accessible there.
How can i fixed that?
Updating parent state from within a builder is anti-pattern here. To reduce future errors and conflicts I recommend to wrap the parts that use and update _dropDown variable as a statefull widget.
Afterward the builder is just responsible of selecting correct widget based on future results and separated widget will only update itself based on interactions. Then hopefully many current and potential errors will disappear.
Do one thing, change this
_dropDown = snapshot.data.data[0];
to
_dropDown ??= snapshot.data.data[0];
What this will do is, it will check if _dropDown is null then assign it with value otherwise it won't.
i am making a mobile app using flutter. And i am using stream builder for this screen. I am not getting the point where i am wrong in the code. Can you please help me in this. I am sharing code and screenshot for this particular row which is causing problem
var timeSelected = 'Click here';
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text(
'Time Slot:',
style: TextStyle(color: Colors.white),
),
Spacer(),
GestureDetector(
onTap: () {
_asyncInputDialog(context);
//_displayDialog();
},
child: StreamBuilder(stream: cartManager.getTimeSlotSelected,
initialData: timeSelected,
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.hasData){
timeShow(snapshot,);
}
else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
return Center(
child: Container(
child: Text('Select time slot'),
),
);
},)
),
],
),
This alert dialog will show when i click on the text of row:
_asyncInputDialog(
BuildContext context,
) {
return showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Center(child: Text('Available Time Slot')),
content: TEAlertDialogContent(),
actions: <Widget>[
new FlatButton(
child: new Text('CANCEL'),
onPressed: () {
Navigator.of(context).pop();
},
)
],
);
});
}
When i got the value from showdialog i will store the value in streamcontroller that is present in CartManager.
static StreamController<Timeslot> timeSlotController = BehaviorSubject();
timeSlotSelected(Timeslot time){
timeSlotController.sink.add(time);
}
get getTimeSlotSelected{
return timeSlotController.stream;
}
And we call the above method in stream property of streamcontroller and get the snapshot. This is the method which was called when our snapshot has data:
Widget timeShow(AsyncSnapshot<Timeslot> snapshot ) {
timeSelected = '${snapshot.data.firstTimeSlot}-${snapshot.data.secondTimeSlot}';
timeslotid = snapshot.data.id.toString();
return Text(timeSelected);
}
But i am getting error: type 'BehaviorSubject' is not a subtype of type 'Stream'
Please let me know where i am wrong. I had also shared a screen shot of screen showing this error too.
As your error states, you are trying to pass a type Timeslot to a Stream builder expecting a stream of type String. You must check which one is correct (String or Timeslot) and use the same type on both sides.
Apparently, your problem is in the timeSelected variable. Where is it defined? If this is a String, the Stream builder will infer that your stream is of type String, which is not true. You must set this variable as a Timeslot, since this is your stream type.
Also, you have an error in your code. You have to return a widget to be rendered if snapshot has data. Check the code below:
StreamBuilder(stream: cartManager.getTimeSlotSelected,
initialData: timeSelected,
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.hasData){
return timeShow(snapshot,);
}
else if (snapshot.hasError) {
return Text(snapshot.error.toString());
}
return Center(
child: Container(
child: Text('Select time slot'),
),
);
},)
I have a Future Builder that builds a ListView.builder, The builder ListTiles are build by another widget that have an ontap function. I have figured out how to get something to run on the final widget by using the back button, but have not been able to figure out how to call something on the original widget on back button. For instance, I need to refresh the top level data when the user clicks back button and not just the data in the secondary widget which is already working.
I hope this makes sense, any help would be great.
UPDATE Here is the code. I am simplifying what I am showing because making a simple example will lose the context. Below you see that I have a FutureBuilder that returns a ListBuilder that returns a new ChatWidget. This is the top level, this Future needs to be refreshed when I click on the back button. However the onTap to trap the callback is in the ChatWidget.
new Expanded(
child: new RefreshIndicator(
child: new FutureBuilder<List<Map>>(
future: chatlist,
builder: (BuildContext context, AsyncSnapshot<List<Map>> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none: return new Text('Waiting to start');
case ConnectionState.waiting: return new Text('Loading...');
default:
if (snapshot.hasError) {
return new Text('Error: ${snapshot.error}');
} else {
return new ListView.builder(
itemBuilder: (context, index) {
ChatServerList mychat = new ChatServerList(snapshot.data[index]['msgkey'],snapshot.data[index]['refid'],snapshot.data[index]['referralname'], snapshot.data[index]['oid'],snapshot.data[index]['sendname'],snapshot.data[index]['pid'],snapshot.data[index]['receivename'],snapshot.data[index]['pgrpid'],snapshot.data[index]['prid'],snapshot.data[index]['message'],);
bool isgroup = true;
if (mychat.grpid == 0) {
isgroup = false;
}
return new ChatWidget(mychat: mychat,threaded: threaded, isgroup: isgroup);
},
itemCount: snapshot.data.length,
);
}
}
},
),
onRefresh: _onRefresh
),
)
This is built in the ChatWidget, you notice the _onTap:
new MyListTile(
leading: new CircleAvatar(
child: _chatAvatar(),
//child: !isgroup ? _indMsg() : _grpMsg(), radius: 18.0,
),
//subtitle: new Text(widget.mychat.oname),
title: new Text(widget.mychat.referralname),
trailing: new Text(widget.mychat.oname, textAlign: TextAlign.right,),
//isThreeLine: true,
//onTap: doTap(threaded),
onTap: _onTap,
onLongPress: _doArchive,
),
new MyListTile(
leading: new Text(' '),
title: new Text(submymessage, textAlign: TextAlign.left,
style: new TextStyle(fontSize: 15.0,
color: Colors.grey, fontStyle: FontStyle.italic),),
trailing: _unreadBabdge(),
onTap: _onTap,
onLongPress: _doArchive,
),
That _onTap is below and has the code to handle the back button.
_onTap() async {
ChatDB.instance.updateRead(widget.mychat.msgkey);
if (threaded) {
//TODO
} else {
Route route = new MaterialPageRoute(
settings: new RouteSettings(name: "/ChatServerDivided"),
builder: (BuildContext context) => new ChatServerDivided(mychat: widget.mychat, title: "Chat Messages",),
);
//Navigator.of(context).push(route);
var nav = await Navigator.of(context).push(route);
if(nav==true||nav==null){
unread = ChatDB.instance.getUnread(widget.mychat.msgkey);
}
}
}
So what I am trying to find is if this code can somehow commmunicate up to the originating widget so that I can run the original Future again. I hope this makes more sense and is easier to understand.
Yes you can do that. Couldn't see exactly where to fit it into your code but I'll give you the way to handle this. The navigator calls are all Futures which means you can await them on the calling side. It seems like you're just missing passing a value to the .pop call. Below is an example.
Where you navigate you can await for your result
var navigationResult = await Navigator.push(
context,
new MaterialPageRoute(
builder: (context) => Page2()));
Then you can check the navigationResult with a simple if.
if(navigationResult == 'rerun_future') {
uploadFiles(); // Perform your custom functionality here.
}
The way you pass that information back is that when you do a pop call (to navigate back) you'll pass the value 'rerun_future' in there.
Navigator.of(context).pop('rerun_future')
Additionally if you also want to add this functionality to the back button you should surround your Scaffold with WillPopScope, return false to onWillPop and supply a leading item to the appBar where you perform your custom pop call. Example below from this post
#override
Widget build(BuildContext context) {
return new WillPopScope(
onWillPop: () async => false,
child: new Scaffold(
appBar: new AppBar(
title: new Text("data"),
leading: new IconButton(
icon: new Icon(Icons.ac_unit),
onPressed: () => Navigator.of(context).pop('rerun_future'), //<----- pass value here too
),
),
),
);
}