I have a statelsess widget built like this where a futurebuilder fetches data and then returns a StatefullTextFormFieldWidget:
Statless Widget{
build:
Futurebuilder(snapshot, context){
future: GetDocumentFromFirebase(id);
if(snapshot.hasData){
return StatefullTextFormFieldWidget(snapshot);
}
}
}
The StatefullTextFormfieldWidget contains a appbar and scaffold with with 2 TextFormFields and workes as it should (when it was offline).
The bug occurs when any of my forms gets onFocusScope in the StatefullTextFormFieldWidget. Then the future starts to refetch data from firebase. It does NOT trigger the rebuild function so my app actually works fine since the state remains, but the main problem is that the app unnecessarily starts fetching data from firestore everytime the user clicks on a TextFormField. I only want to fetch data once when the user enter the screen and then stick with that snapshot as long as the user remains on the screen.
I really cant understand how the futurebuilder can continue fetch data without being rebuilt.
FuturebuilderWidget:
import 'package:flutter/material.dart';
import 'package:MyApp/manage_orders/handle/widgets/form_has_handling.dart';
import 'package:MyApp/services/firestore.dart';
import 'package:MyApp/services/models.dart';
class HandleOrderScreen extends StatefulWidget {
static const routeName = '/handle-order-screen';
@override
State<HandleOrderScreen> createState() => _HandleOrderScreenState();
}
class _HandleOrderScreenState extends State<HandleOrderScreen> {
@override
Widget build(BuildContext context) {
final String id = ModalRoute.of(context)!.settings.arguments as String;
return FutureBuilder<Map>(
future: FirestoreService().getOrderPriceUserBoat(id),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Scaffold(
appBar: AppBar(
title: Text(''),
),
body: Center(
child: Center(child: CircularProgressIndicator()),
),
);
} else if (snapshot.hasError) {
return Scaffold(
appBar: AppBar(
title: Text('Error'),
),
body: Center(
child: Text(snapshot.error.toString()),
),
);
} else if (snapshot.hasData) {
var snapshotData = snapshot.data;
Order order = snapshotData!['order'];
return TextFormFieldWidget(order: order);
} else {
return Scaffold(
appBar: AppBar(
title: Text('Error'),
),
body: Center(
child: Text('Something went wrong'),
),
);
}
});
}
}
TextFormFieldWidget:
import 'package:flutter/material.dart';
import 'package:MyApp/services/models.dart';
class TextFormFieldWidgetextends StatefulWidget {
const TextFormFieldWidget({
Key? key,
required this.order,
}) : super(key: key);
final Order order;
@override
State<FormHasHandlingWidget> createState() => _TextFormFieldWidgetState();
}
class _TextFormFieldWidgetState extends State<TextFormFieldWidget> {
final commentController = TextEditingController();
final priceController = TextEditingController();
static final formkey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: Text('Handle'),
),
body: SingleChildScrollView(
child: Column(
children: [
Container(
padding: EdgeInsets.all(5),
height: size.height - 120,
child: Form(
key: formkey,
child: Column(
children: [
Container(
padding: EdgeInsets.all(2),
width: double.infinity,
height: 120,
child: Card(
color: Color.fromARGB(255, 241, 235, 235),
child: Container(
child: RichText(
text: TextSpan(
style:
TextStyle(color: Colors.black, fontSize: 20),
children: <TextSpan>[
TextSpan(
text: 'Test Name\n',
),
TextSpan(
text: 'Order.nr:',
style: TextStyle(fontWeight: FontWeight.bold),
),
TextSpan(
text: '${widget.order.orderNumber} \n',
),
TextSpan(
text: 'Car: ',
style: TextStyle(fontWeight: FontWeight.bold),
),
TextSpan(
text: widget.order.carModel '\n',
),
],
),
),
),
),
),
Container(
padding: EdgeInsets.all(2),
child: Align(
alignment: Alignment.topLeft,
child: Text(
" Type:",
style: TextStyle(color: Colors.grey),
),
),
),
SizedBox(
height: 10,
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.black, width: 2),
),
child: Container(
padding: EdgeInsets.all(5),
child: TextFormField(
controller: priceController,
decoration: InputDecoration(labelText: 'Price: '),
validator: (value) {
if (value != null) {
if (value.length < 1) {
return 'Wright the price';
} else {
return null;
}
}
},
keyboardType: TextInputType.number,
style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 20),
),
),
),
SizedBox(
height: 10,
),
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.black, width: 2),
),
child: Container(
padding: EdgeInsets.all(5),
child: TextFormField(
controller: commentController,
decoration:
InputDecoration(labelText: 'Description:'),
keyboardType: TextInputType.multiline,
style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 20),
minLines: 2,
maxLines: 5,
validator: (value) {
if (value != null) {
if (value.length < 10) {
return 'Give a short description';
} else {
return null;
}
}
},
),
),
),
Container(
padding: EdgeInsets.all(5),
child: ElevatedButton(
child: Text('Send in'),
onPressed: () {},
style: ElevatedButton.styleFrom(
fixedSize: Size(size.width, 50),
primary: Color.fromARGB(255, 223, 208, 0)),
),
),
Spacer(),
Container(
padding: EdgeInsets.all(5),
child: ElevatedButton(
child: Text('Finish order'),
onPressed: () {},
style: ElevatedButton.styleFrom(
fixedSize: Size(size.width, 50),
primary: Color.fromARGB(255, 255, 17, 0)),
),
),
],
),
),
),
SizedBox(
height: 200,
),
],
),
),
);
}
}
CodePudding user response:
My guess is that your GetDocumentFromFirebase
function does a new get()
call to Firestore each time it runs, which happens every time the widget is rendered.
If you want to prevent refetching the document every time, put the code in a stateful widget, and keep the Future<DocumentSnapshot>
in a state variable that you only initialize once.
Also see Randal's excellent explanation on Fixing a common FutureBuilder and StreamBuilder problem