flutterdartflutter-futurebuilder

Tap of textfield causing unnecessary widget rebuild


Description:

I've encountered an issue in my Flutter application where tapping on a TextField is causing an unexpected widget rebuild. This behavior is leading to some performance concerns and is not ideal for the user experience.

I've noticed that every time I interact with the TextField, the widget tree appears to be rebuilding. This behavior is not something I would expect, and it's causing unnecessary overhead in my application.

I've attached an image [if possible, include an image showing the widget tree being rebuilt when tapping the TextField] to illustrate this behavior.

I'm seeking advice on how to address this issue and prevent unnecessary rebuilds when interacting with the TextField. Is there a specific way to handle TextField interactions to avoid this problem?

Here's an image of what's happening

There's relevant code for the described problem

import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:intl/intl.dart';
import 'package:logistics_flutter_client/component/go_flemingo_button.dart';
import 'package:logistics_flutter_client/component/select_hour.dart';
import 'package:logistics_flutter_client/provider_models/booking_form_provider.dart';
import 'package:logistics_flutter_client/provider_models/schedule_model_provider.dart';
import 'package:logistics_flutter_client/provider_models/user_model.dart';
import 'package:logistics_flutter_client/provider_models/vehicle_id_provider.dart';
import 'package:logistics_flutter_client/utils/network_repository.dart';
import 'package:marquee/marquee.dart';
import 'package:provider/provider.dart';

class VehicleDetailsForm extends StatefulWidget {
  const VehicleDetailsForm({super.key});

  @override
  State<VehicleDetailsForm> createState() => _VehicleDetailsFormState();
}

class _VehicleDetailsFormState extends State<VehicleDetailsForm> {
  static final  _formKey = GlobalKey<FormState>();
  List? vehicleList = [];
  TextEditingController tonnageController = TextEditingController();
  TextEditingController vehicleNumberController = TextEditingController();
  TextEditingController driverNameController = TextEditingController();
  TextEditingController driverNumberController = TextEditingController();
  double selectedRating = 0;
  bool isFetchMemberCalled = false;

  late Map<String, dynamic>? userData; // Declare it here

  @override
  void initState() {
    super.initState();

    userData = Provider.of<UserModel>(context, listen: false).userData;
    if (!isFetchMemberCalled) {
      // Add this check
      fetchMemberDetails(userData!['id']);
      isFetchMemberCalled = true;
    }
  }

  Future fetchMemberDetails(int id) async {
    try {
      final apiResponse = await networkRepository.getMember(memberId: id);
      debugPrint('\x1b[97m App Details Response : $apiResponse');
      setState(() {
        //selectedScheduleList = selectedSchedule;
        vehicleList = apiResponse['vehicle_set'];
      });
      // Process the apiResponse as needed
    } catch (e) {
      Fluttertoast.showToast(
        msg: e.toString(),
        backgroundColor: Colors.red,
      );
    }
  }

  String? selectedVehicleNumber;

  @override
  Widget build(BuildContext context) {
    if (vehicleList != null && vehicleList!.isNotEmpty) {
      double width = MediaQuery.of(context).size.width;
      final vehicleIdProvider = Provider.of<VehicleIdProviderModel>(context,listen: false);
      final scheduleModel = Provider.of<ScheduleProviderModel>(context,listen: false);
      String source = scheduleModel.selectedSchedule!['source'];
      String plant = scheduleModel.selectedSchedule!['plant'];

    double contractRate= scheduleModel.selectedSchedule!['contract_rate'];

      String formattedAmount = NumberFormat.currency(
        locale: 'en_IN', // 'en_IN' for Indian English
        symbol: '₹ ', // Indian Rupee symbol
        decimalDigits: 0,
      ).format(contractRate);

      String unit = scheduleModel.selectedSchedule!['unit'];
      String goods = scheduleModel.selectedSchedule!['goods_type'];
      double quantity = scheduleModel.selectedSchedule!['rem_qty'];
      TextEditingController freightRateController =
          TextEditingController(text: contractRate.toString());

      String remQty =
          '${quantity.toString()} $unit of $goods ($formattedAmount / $unit)';

      // Accessing the user data

      final bookingData = Provider.of<BookingFormDataModel>(context);

      void handleRatingUpdate(double rating) {
        selectedRating = rating;
      }

      bool validateIndianVehicleNumber(String vehicleNumber) {
        // Regular expression pattern for Indian vehicle numbers
        String pattern = r'^[A-Z]{2}\s?[0-9]{2}\s?[A-Z]{1,2}\s?[0-9]{4}$';

        RegExp regex = RegExp(pattern);

        return regex.hasMatch(vehicleNumber.toUpperCase());
      }

      Widget cardData({
        required IconData iconData,
        required String labelText,
        required String data,
      }) {
        return Padding(
          padding: const EdgeInsets.symmetric(horizontal: 22.0, vertical: 12),
          child: Row(
            children: [
              Icon(
                iconData,
                color: const Color(0xFF44BFF7),
                size: 20,
              ),
              const SizedBox(
                width: 10,
              ),
              Text(
                labelText,
                style: const TextStyle(fontWeight: FontWeight.w500),
              ),
              const Spacer(),
              SizedBox(
                width: 200, // Adjust the width as needed
                child: SizedBox(
                  height: 20,
                  child: Marquee(
                    text: data,
                    velocity: 25,
                    numberOfRounds: 1,
                    blankSpace: 55.0,
                    style: const TextStyle(
                      fontWeight: FontWeight.w500,
                      // Align the text to the right
                    ),
                  ),
                ),
              ),
            ],
          ),
        );
      }

      Future<int> addVehicle(String vehicleNum, List vehicleList) async {
        var data = await networkRepository.postVehicleDetails(
            vehicleNumber: vehicleNum, memberId: userData!['id']);
        return data['body']['id'];
      }

      Future<int> findVehicleId(
          List vehicleList, String selectedVehicleNumber) async {
        List vehicleIsolated = [];
        for (var i = 0; i < vehicleList.length; i++) {
          log(vehicleIsolated.toString());
          vehicleIsolated.add(vehicleList[i]['vehicle_num']);
        }

        if (vehicleIsolated.contains(selectedVehicleNumber) == true) {
          int index = vehicleList.indexWhere(
            (vehicle) {
              return vehicle['vehicle_num'] == selectedVehicleNumber;
            },
          );
          return vehicleList[index]['id'];
        } else {
          log('else');
          int id = await addVehicle(selectedVehicleNumber, vehicleList);
          return id; // or any other value that indicates no match was found
        }
      }

      log('rebuild');

      return Scaffold(
        appBar: AppBar(
          title: const Text('Add Vehicle Details'),
        ),
        body: SingleChildScrollView(
          child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 8.0),
            child: Form(
              key: _formKey,
              child: Column(
                children: [
                  const SizedBox(
                    height: 5,
                  ),
                  Card(
                    elevation: 4,
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(22),
                    ),
                    child: Column(
                      children: [
                        cardData(
                            data: source,
                            iconData: Icons.factory,
                            labelText: 'From'),
                        cardData(
                            data: plant,
                            iconData: Icons.location_on,
                            labelText: 'To'),
                        cardData(
                            data: remQty,
                            iconData: FontAwesomeIcons.weightScale,
                            labelText: 'Quantity'),
                        const SizedBox(
                          height: 5,
                        ),
                      ],
                    ),
                  ),
                  const SizedBox(
                    height: 10,
                  ),
                  Card(
                    elevation: 4,
                    shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(22),
                    ),
                    child: Padding(
                      padding: const EdgeInsets.symmetric(vertical: 30),
                      child: Column(
                        children: [
                          Padding(
                            padding: const EdgeInsets.symmetric(
                              horizontal: 24,
                            ),
                            child: TextFormField(
                              controller: tonnageController,
                              decoration: InputDecoration(
                                labelText: 'Vehicle Tonnage in $unit',
                              ),
                              validator: (value) {
                                if (value == '') {
                                  return 'Please enter the tonnage';
                                }
                                return null;
                              },
                            ),
                          ),
                          Padding(
                            padding: const EdgeInsets.symmetric(
                                horizontal: 24, vertical: 30),
                            child: TextFormField(
                              controller: freightRateController,
                              decoration: InputDecoration(
                                labelText: 'Freight Rate / $unit',
                              ),
                              validator: (value) {
                                if (value == '') {
                                  return 'Please enter the freight rate';
                                }
                                return null;
                              },
                            ),
                          ),
                          Padding(
                            padding: const EdgeInsets.symmetric(
                                horizontal: 24, vertical: 10),
                            child: TextFormField(
                              enabled: false,
                              initialValue: userData!['name'],
                              decoration: const InputDecoration(
                                labelText: 'Vehicle Owner',
                              ),
                              validator: (value) {
                                if (value == '') {
                                  return 'Please enter the owner name';
                                }
                                return null;
                              },
                            ),
                          ),
                          Padding(
                            padding: const EdgeInsets.symmetric(
                              horizontal: 24,
                              vertical: 25,
                            ),
                            child: Autocomplete<String>(
                              optionsBuilder:
                                  (TextEditingValue textEditingValue) {
                                return vehicleList!.where(
                                  (vehicle) {
                                    String vehicleNum = vehicle['vehicle_num'];
                                    return vehicleNum.toLowerCase().contains(
                                          textEditingValue.text.toLowerCase(),
                                        );
                                  },
                                ).map<String>((vehicle) {
                                  String vehicleNum = vehicle['vehicle_num'];

                                  return vehicleNum.toUpperCase();
                                }).toList();
                              },
                              onSelected: (String selectedOption) {
                                selectedVehicleNumber = selectedOption;
                              },
                              optionsViewBuilder:
                                  (context, onSelected, options) {
                                return Align(
                                  alignment: Alignment.topLeft,
                                  child: ClipRRect(
                                    borderRadius: BorderRadius.circular(
                                        15), // Adjust the radius as needed

                                    child: Material(
                                      elevation: 10.0,
                                      child: ConstrainedBox(
                                        constraints: BoxConstraints(
                                            maxHeight: 300,
                                            maxWidth: width / 1.2),
                                        child: ListView.builder(
                                          padding: EdgeInsets.zero,
                                          shrinkWrap: true,
                                          itemCount: options.length,
                                          itemBuilder: (BuildContext context,
                                              int index) {
                                            final String option =
                                                options.elementAt(index);
                                            return InkWell(
                                              onTap: () {
                                                onSelected(option);
                                              },
                                              child: Builder(builder:
                                                  (BuildContext context) {
                                                final bool highlight =
                                                    AutocompleteHighlightedOption
                                                            .of(context) ==
                                                        index;
                                                if (highlight) {
                                                  SchedulerBinding.instance
                                                      .addPostFrameCallback(
                                                          (Duration timeStamp) {
                                                    Scrollable.ensureVisible(
                                                        context,
                                                        alignment: 0.5);
                                                  });
                                                }
                                                return Container(
                                                  color: highlight
                                                      ? Theme.of(context)
                                                          .focusColor
                                                      : null,
                                                  padding: const EdgeInsets.all(
                                                      16.0),
                                                  child: Text(option),
                                                );
                                              }),
                                            );
                                          },
                                        ),
                                      ),
                                    ),
                                  ),
                                );
                              },
                              fieldViewBuilder: (BuildContext context,
                                  TextEditingController textEditingController,
                                  FocusNode focusNode,
                                  VoidCallback onFieldSubmitted) {
                                return TextFormField(
                                  controller: textEditingController,
                                  textCapitalization:
                                      TextCapitalization.characters,
                                  focusNode: focusNode,
                                  onFieldSubmitted: (String value) {
                                    onFieldSubmitted();
                                  },
                                  onChanged: (value) {
                                    selectedVehicleNumber = value;
                                  },
                                  decoration: InputDecoration(
                                    labelText: 'Vehicle Number',
                                    filled: true,
                                    fillColor: const Color.fromARGB(
                                        255, 233, 233, 233),
                                    contentPadding: const EdgeInsets.only(
                                      left: 15,
                                      top: 18,
                                      bottom: 18,
                                    ),
                                    border: OutlineInputBorder(
                                      borderSide: BorderSide.none,
                                      borderRadius: BorderRadius.circular(15),
                                    ),
                                  ),
                                  validator: (value) {
                                    if (!validateIndianVehicleNumber(value!)) {
                                      return 'Please enter a valid Indian vehicle number';
                                    } else {
                                      return null;
                                    }
                                  },
                                );
                              },
                            ),
                          ),
                          Padding(
                            padding: const EdgeInsets.symmetric(
                                horizontal: 24, vertical: 15),
                            child: TextFormField(
                              controller: driverNameController,
                              decoration: const InputDecoration(
                                labelText: 'Driver Name',
                              ),
                              validator: (value) {
                                if (value == '') {
                                  return 'Please enter the driver name';
                                }
                                return null;
                              },
                            ),
                          ),
                          Padding(
                            padding: const EdgeInsets.symmetric(
                                horizontal: 24, vertical: 20),
                            child: TextFormField(
                              controller: driverNumberController,
                              maxLength: 10,
                              keyboardType: TextInputType.number,
                              decoration: const InputDecoration(
                                labelText: 'Driver Number',
                              ),
                              validator: (value) {
                                if (value!.length != 10) {
                                  return 'Please enter the driver number';
                                }
                                return null;
                              },
                            ),
                          ),
                          const Align(
                            alignment: Alignment.topLeft,
                            child: Padding(
                              padding: EdgeInsets.only(left: 25),
                              child: Text('Estimated time (in Hours)'),
                            ),
                          ),
                          Padding(
                            padding: const EdgeInsets.symmetric(vertical: 20),
                            child: SelectHoursButton(
                              onRatingUpdate: (value) =>
                                  handleRatingUpdate(value),
                            ),
                          ),
                          Align(
                            alignment: Alignment.bottomCenter,
                            child: Center(
                              child: Padding(
                                padding: const EdgeInsets.symmetric(
                                  horizontal: 22.0,
                                ),
                                child: Row(
                                  children: [
                                    GoFlamingoButton(
                                      buttonText: 'CANCEL',
                                      onPressed: () {
                                        Navigator.pop(context);
                                      },
                                      customWidth: width / 2.5,
                                      fullWidth: false,
                                    ),
                                    const Spacer(),
                                    GoFlamingoButton(
                                      buttonText: 'BOOK',
                                      onPressed: () async {
                                        if (_formKey.currentState!.validate()) {
                                          int id = await findVehicleId(
                                              vehicleList!,
                                              selectedVehicleNumber!);
                                          var tonnage = tonnageController.text;
                                          var freightRate =
                                              freightRateController.text;
                                          var driverName =
                                              driverNameController.text;
                                          var driverNumber =
                                              driverNumberController.text;

                                          // Update values in the provider
                                          bookingData.updateField(
                                              'freightRate', freightRate);
                                          bookingData.updateField(
                                              'vehicleOwner',
                                              userData!['id'].toString());
                                          bookingData.updateField(
                                              'vehicleNum', id.toString());
                                          bookingData.updateField(
                                              'driverName', driverName);
                                          bookingData.updateField(
                                              'driverNumber', driverNumber);

                                          bookingData.updateField(
                                              'schedule', id.toString());

                                          bookingData.updateField(
                                              'disQty', tonnage);
                                          bookingData.updateField(
                                              'estimatedTime',
                                              (selectedRating * 2).toString());
                                          vehicleIdProvider.selectVehicleId(id);
                                          if (!context.mounted) return;

                                          Navigator.pushNamed(
                                              context, '/selectBankForm');
                                        }
                                      },
                                      customWidth: width / 2.5,
                                      fullWidth: false,
                                    ),
                                  ],
                                ),
                              ),
                            ),
                          ),
                        ],
                      ),
                    ),
                  ),
                  const SizedBox(
                    height: 30,
                  ),
                ],
              ),
            ),
          ),
        ),
      );
    } else {
      return const Scaffold(
        body: Center(
          child: CircularProgressIndicator(),
        ),
      );
    }
  }
}

Here's what's shown on my console log

[log] rebuild
flutter: URL :http://139.144.8.11/api/newTodaySchOnSource/
flutter: http://139.144.8.11/api/todaySchOnSource/}
[log] rebuild
flutter: URL :http://139.144.8.11/api/newTodaySchOnSource/
flutter: http://139.144.8.11/api/todaySchOnSource/}
[log] rebuild
flutter: URL :http://139.144.8.11/api/newTodaySchOnSource/
flutter: http://139.144.8.11/api/todaySchOnSource/}
[log] rebuild
flutter: URL :http://139.144.8.11/api/newTodaySchOnSource/
flutter: http://139.144.8.11/api/todaySchOnSource/}
[log] rebuild
flutter: URL :http://139.144.8.11/api/newTodaySchOnSource/
flutter: http://139.144.8.11/api/todaySchOnSource/}
[log] rebuild
flutter: URL :http://139.144.8.11/api/newTodaySchOnSource/
flutter: http://139.144.8.11/api/todaySchOnSource/}

It's not just rebuilding this page but also calling API on other pages


Solution

  • Using sizer: ^2.0.15 instead of

    double width = MediaQuery.of(context).size.width; helped