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.
Related
I have a problem and it happens when I use the camera and it is that my screen is updated. I guess it is because of the future builder and the set state that I use.
Example:: I edit the 2 textforms and when I want to use the camera and take the photo, the 2 textforms are updated as they were before editing it without using any update method.
If the text "Roxana Luz" and "RoxanaLuz19#gmail.com" appears, I change to "Roxana L" and "Roxana#gmail.com" and then I open the camera and take the photo.
"Roxana Luz" and "RoxanaLuz19#gmail.com" reappear as it was from the beginning.
I will leave code and image::
Widget FotoF2(String fotousuarioo){
return new Container(
child:image==null? newphoto(fotousuarioo):photoprofile(),
);
}
****************************
Future <dynamic> futureup;
#override
void initState() {
print("initstate");
futureup = editarperfilservices.EditarPerfil() ;
super.initState();
}
Future OpenCamara() async {
final pickedFile = await ImagePicker().pickImage(source: ImageSource.camera);
setState(() {
if (pickedFile != null) {
image =File(pickedFile.path);
} });
}
****************************************
Widget FullName(NombreUsuario){
return new Container(
child: new TextFormField(
controller: NombreUsuario,
suffixIcon: IconButton(
icon: Icon(Icons.cancel,color: Colors.black,),
onPressed: () {
NombreUsuario.clear();
}
)
);
}
*****************************************
#override
Widget build(BuildContext context) {
body: Container(
child: FutureBuilder(
future: futureup,
builder:(context, AsyncSnapshot snapshot) {
List busqueda = snapshot.data;
if(snapshot.hasData ) {
return Center(
child: new Container(
child: new ListView.builder(
itemCount: busqueda.length,
itemBuilder: (context,i){
Correo.text = busqueda[i].UsuarioCorreo ;
NombreUsuario.text = busqueda[i].UsuarioApodo ;
fotousuarioo = busqueda[i].FotoUsuario;
return Container(
child: ListView(
shrinkWrap: true,
children:[
SizedBox(height: 18,),
FotoF2(fotousuarioo),
SizedBox(height: 18,),
FullName(NombreUsuario),
SizedBox(height: 18,),
CorreoF2(Correo),
]
),
);
}
}
Break out your FotoF2 into its own Stateful Widget. You call setState in the parent it appears, which forces an entirely new build of your page and creates a brand new snapshot. If you break it out, it'll only rebuild the image, as you want.
body: Container(
child: Consumer(builder: (context, watch, child) {
var wallet = watch(walletBuilderProvider);
//print(wallet.allWalletItems[0].eventName);
return WalletList(wallets: wallet.allWalletItems);
}),
)
final walletBuilderProvider =
ChangeNotifierProvider.autoDispose<WalletModel>((ref) {
final walletData = ref.watch(dataProvider);
// Create an object by calling the constructor of WalletModel
// Since we now have memory allocated and an object created, we can now call functions which depend on the state of an object, a "method"
final walletModel = WalletModel();
walletModel.buildWallet(walletItems: walletData);
return walletModel;
});
What I do initially to refresh all the data before it loads is I just call
context.refresh(dataProvider);
context.refresh(walletBuilderProvider);
Here is the List that gets called to display the data.
class WalletList extends StatelessWidget {
final List<Wallet> wallets;
WalletList({required this.wallets});
#override
Widget build(BuildContext context) {
return Container(
child: wallets.isEmpty
? Container(
height: 150,
child: Center(
child: Text(
"List is empty",
style: TextStyle(fontSize: 18, color: Colors.white),
),
),
)
: getWalletListItems());
// return ListView(
// children: getWalletListItems(),
// );
}
ListView getWalletListItems() {
print(wallets.length);
print("afterwallets");
var walletList = wallets
.map((walletItem) => WalletListItem(wallet: walletItem))
.toList();
return ListView.builder(
itemCount: walletList.length,
physics: BouncingScrollPhysics(),
itemBuilder: (context, index) {
double scale = 1.0;
return Opacity(
opacity: scale,
child: Align(
heightFactor: 0.7,
alignment: Alignment.topCenter,
child: walletList[index]),
);
});
}
}
What I want to do in the end is use some form of RefreshIndictator to refresh both providers but when I have been attempting to implement that in either the Consumer or the WalletList I haven't been seeing any change at all.
First walletBuilderProvider watch dataProvider so you only need to refresh dataProvider, that will force a refresh on all providers that depend on it
Have you tried using RefreshIndicator Widget?
RefreshIndicator(
onRefresh: () async => context.refresh(dataProvider),
child: WalletList(wallets: wallet.allWalletItems),
);
I have a FutureBuilder that loads data from sqflite and builds horizontal listview.
FutureBuilder<List<Favorite>>(
future: DatabaseHelper.instance.retrieveFavorite(),
builder: (BuildContext context,
AsyncSnapshot<List<Favorite>> snapshot){
return snapshot.hasdata?Row(
//data from sqflite
children: <Widget>[
Expanded(
child: SizedBox(
height: 200.0,
child: ListView.separated(
addAutomaticKeepAlives: false,
scrollDirection: Axis.horizontal,
itemCount: snapshot.data.length,
itemBuilder:
(BuildContext context,
int index) {
return (index < 6)
? InkWell(
onTap: () {
load().then((value) {
counter=value;
});
print('$counter');
if (counter >= 100) {
ModelChannel t =
convertSingle(
snapshot.data[
index]);
Navigator.push(
context,
MaterialPageRoute(
builder:
(context) =>
LiveTvPlayer(
channel: t,
),
),
);
}
}
the load function looks like this-
Future<int> load() async {
final SharedPreferences prefs = await _prf;
int i = (prefs.getInt("Rcounter") ?? 500);
return i;
}
Everytime someone clicks any list item the counter decreses by 100. But the load function returns the same value. It only returns different value if the app is restarted. And the function works perfectly if it it is called from any other place. Why it is happening inside futurebuilder and how to solve this?
put counter = value inside setState function. But beware as it will refresh the page too as you are using a future builder.
A more effective way of achieving this is to load the data by calling a function in initState, while showing showing a loading screen
I recommend to watch this video https://www.youtube.com/watch?v=nLlVANBmFJM&t=92s for an idea on that.
After u do that u can store the counter in a variable and then decrease it per click using setState.
A dismissed Dismissible widget is still part of the tree.
Make sure to implement the onDismissed handler and to immediately remove the Dismissible widget from the application once that handler has fired.
I already know there are many questions about this issue in stackoverflow, and also read almost of them. But I have no idea why this happens because I have no setState issue, also dismissible key is correct. Could you find problem that I missed?? Someone said Dismissible should not be in ListView widget, but it has worked very well until yesterday even in the ListView. I tried key: UniqueKey() but it didn't work. Please let me know if you know any solutions. Thanks in advance.
Widget vocaBuilder() {
return FutureBuilder(
future: loadTodayVoca(),
builder: (context, snap) {
if (snap.data.length == 0 || snap.data.isEmpty) {
return Container();
} else {
return ListView.builder(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemCount: snap.data.length,
scrollDirection: Axis.vertical,
itemBuilder: (context, index) {
Voca voca = snap.data[index];
return GestureDetector(
onTap: () {
editPage(voca.id);
},
child: Dismissible(
direction: DismissDirection.endToStart,
background: Container(
color: Colors.red,
padding: EdgeInsets.only(top: 23, right: 30)),
key: Key(snap.data[index].toString()),
...
onDismissed: (direction) {
setState((){
deleteVoca(voca.id);
snap.data.removeAt(index);
}); }));
Future<void> deleteVoca(String id) async {
DBHelper sd = DBHelper();
await sd.deleteVoca(id);
}
Future<List<Voca>> loadTodayVoca() async {
DBHelper sd = DBHelper();
var list = await sd.vocas();
return list
.where((list) =>
list.createTime ==
DateFormat('yyyy-MM-dd')
.format(DateTime(now.year, now.month, now.day)))
.toList();
}
Hai,I have the same problem,and I solve it like this:modify the value of the key to become a unique value。
For you like this:key:Key(snap.data[index].id.toString()), or if voca.id is a string,you should key:Key(snap.data[index].id)
The user can either enter the answer with InputChips or manually type it in the TextField. When I try with InputChips, the correct answer is not detected. When I try to manually type it, the FutureBuilder reloads when I enter and leave the TextField. What is the reason?
The Future function should only be called once because it fetches a random document from Firestore, splits the String and scrambles the different pieces. It is some form of quiz.
class _buildPhrases extends State<PhrasesSession>{
TextEditingController _c;
String _text = "initial";
#override
void initState(){
_c = new TextEditingController();
super.initState();
}
#override
void dispose(){
_c?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final Arguments args = ModalRoute.of(context).settings.arguments;
var height = MediaQuery.of(context).size.height;
var width = MediaQuery.of(context).size.width;
// TODO: implement build
return Scaffold(
body: Column(
children: <Widget>[
Flexible(flex: 2, child: _buildRest(context),),
Flexible(flex: 5,
child: FutureBuilder(
future: getEverything(args.colName),
builder: (context, snapshot){
if(!snapshot.hasData){
return Center(child: CircularProgressIndicator(),);
}else{
return Column(
children: <Widget>[
Flexible(flex: 1, child: Text(snapshot.data[1]),),
Divider(),
Flexible(flex: 2, child: Container(
child: TextField(
onChanged: (t){
_text += "$t ";
if(_c.text == snapshot.data[0]){
return print("CORRECT ANSWER");
}
},
controller: _c,
textAlign: TextAlign.center,
enabled: true,
),
),),
Flexible(flex: 3,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: snapshot.data.length - 2,
itemBuilder: (context, index){
if(index>snapshot.data.length - 2){
return null;
}else{
return Padding(
padding: const EdgeInsets.all(4.0),
child: InputChip(
label: Text(snapshot.data[index + 2]),
onPressed: (){
_c.text += "${snapshot.data[index + 2]} ";
},
),
);
}
},
))
],
);
}
},
),)
],
)
);
}
}
Let's solve this in parts.
When I try to manually type it the FutureBuilder reloads when I enter and leave the TextField. What is the reason?
This is hapenning because when the keyboard is showing or hidding the flutter framework calls build method of your widget and this default behavior is the reason why your FutureBuilder is realoading. You should avoid call network methods inside build method and I advise you to use BLoC pattern to handle state of your widget.
My Future needs the String that is passed from another route, though. See the Arguments args = .... Any idea how I get it in the initState?
Well if you need context instance to get this String you can't access current context inside initState method because your widget isn't full initialized yet. A simple way to solve this in your case but not the best is verify if the data was already fetched from network or not.
Future _myNetworkFuture; // declare this as member of your stateWidgetClass
Widget build(BuildContext context){
final Arguments args = ModalRoute.of(context).settings.arguments;
var height = MediaQuery.of(context).size.height;
var width = MediaQuery.of(context).size.width;
// this line says if(_myNetworkFuture == null) do the thing.
_myNetworkFuture ??= getEverything(args.colName);
return ...
Flexible(flex: 5,
child: FutureBuilder(
future: _myNetworkFuture,
builder: (context, snapshot){
// ...
}
}
With this approach when flutter framework calls build method if you already fetched the data you don't download the data again. But I really advise you to use BLoC pattern in this kind of situation.