TextField reloads FutureBuilder when pressed/left in Flutter - flutter

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.

Related

How do I stop my future builder from updating the screen?

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.

FutureBuilder not updated after setState call

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.

I want to use pull to refresh on a Listview that is built from a Riverpod Model provider that gets it's data from a Future Provider

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),
);

Flutter : initial value not updating in Form (FormBuilder)

_updatePersonalFormScreen(String loginId) async {
if (!DartUtility.isNullEmptyOrWhitespace(loginId)) {
_personalInfo = await _service.getUserPersonalDetails(loginId);
setState(() {
if (_personalInfo != null) {
if(!DartUtility.isNullEmptyList(_personalInfo.getContacts())){
contactList = _personalInfo.getContacts();
}
personalInfoMap = _personalInfo.toPersonalInfoMap();
}
print('personalInfo retrieved object ${_personalInfo.toString()}'); //1
});
}
}
formBuilder widget :
FormBuilder buildFormBuilder(BuildContext context) {
print('personalInfoMap $personalInfoMap'); //2
return FormBuilder(
key: _personalDetailFormKey,
initialValue: personalInfoMap, //3
autovalidate: true,
child: Stack(),
);
}
//line-1 and line-2 printing correct values but at line-3, the initial values are not getting assigned to the form builder textbox
'contactList' is populating correctly and in the same block populating 'personalInfoMap' not working properly as expected
or may value assigned at line-3 need some thing else to be modified to make it work
I have tried working with Future builder as well but no luck. If 'contactList' is working fine and assigned to the form values, so why facing issue in other field ? :(
Could someone please help me on this, What else need to be done here and where its getting wrong.
After 4 5 hour struggle, able to resolved finally, and the saviour is 'Future builder'.
here is the solution,
Instead of directly calling FormBuilder in build method, wrap it inside FutureBuilder
#override
Widget build(BuildContext context) =>SafeArea(
child: Scaffold(
body: Container(
height: MediaQuery.of(context).size.height,
child: FutureBuilder(
future: _getPersonalInfoFormInitialValue(),
builder: (context, snapshot) => snapshot.hasData
? buildFormBuilder(context, snapshot.data) // this provide returned data from _getPersonalInfoFormInitialValue()
: const SizedBox(),
),
),
),
);
Modified formBuilder widget :
FormBuilder buildFormBuilder(BuildContext context, data) {
print('datat ::::$data');
return FormBuilder(
key: _personalDetailFormKey,
initialValue:data, //assigned fetched data
autovalidate: true,
child: Stack(),
);
}
It seems like the value initially loaded can't be changed still the _formKey remains in memory. So we need to prevent initializing first time with null
I use reverpod with flutter form following is the relevant code of rough implementation with watch
Widget build(
BuildContext context,
ScopedReader watch,
) {
final loginId = context.read(selectedLoginId).state; // user id to check if there is a valid data
final user = watch(selectedUser).data?.value; // getting user info we need both
return Padding(
padding: const EdgeInsets.all(32.0),
child: Column(
children: [
(loginId != null && user == null)
? CircularProgressIndicator()
: FormBuilder(
key: _personalDetailFormKey,
initialValue:user, //assigned fetched data
autovalidate: true,
child: Stack(),
),]));}
Struggled for a long time but found the solution to form_builder update problem
class _CommonContentFormState extends State<CommonContentForm>
var commonForm = GlobalKey<FormBuilderState>();
#override
Widget build(BuildContext context) {
// Must create new GlobalKey before building form to update
// with new data from Provider...
commonForm = GlobalKey<FormBuilderState>();
formData = Provider.of<FormData>(context);
return SingleChildScrollView(
padding: EdgeInsets.fromLTRB(0,0,20,0),
child: FormBuilder(
key: commonForm,
.....

Flutter: Prevent executed feturebuilder when setState is occurred

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.