Flutter: Radio and Switch buttons isn't updating inside a listview.builder, but it works outside

748 Views Asked by At

I'm using a form to create multiples vehicle entries.

enter image description here

every time I click on the floating button it adds a vehicleform to the page one on top of the other. "A List type that receives widgets"

If I try to select the "leased radio option", the form doesn't update, and if hit the switch button right below, nothing happens again. BUT! Curiously if I hit any of the Dropdown State....it works for them (only the dropdown gets updated). and BUT! number 2: If I hit the floating button to add a new vehicle form, the changes made on the previous form gets carried to the new form. My theory is that the buttons are working under the hood, but the setStates are no running correctly

On the main_page.dart there is a stateful widget that calls vehicles_page() which holds all the scaffold and widgets for that form including a dropdown list which is called from a 3rd file(dropdown_forms.dart).

To guide towards the right direction, just lay your eyes at the end of the code on the build() function.

FYI - Flutter Doctor -v returned no errors
Yes -I'm using stateful widgets
Yes - I'm using setstates to update
No - I'm not a professional programmer, I'm a super beginner on flutter and this is my 3rd week playing with flutter

After running some tests.... if I remove them from the listview widget, it works fine.

main.dart
This is the main file

import 'package:flutter/material.dart';
import 'package:learningflutter/screens/parties_page.dart';
import 'package:learningflutter/screens/vehicles_page.dart';
import 'package:learningflutter/screens/incident_page.dart';

// import 'package:learningflutter/parties.dart';
enum VehicleType { pov, leased, pgti }

void main(List<String> args) {
  runApp(const VortexEHS());
}

class VortexEHS extends StatelessWidget {
  const VortexEHS({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'VortexEHS',
      theme: ThemeData(
        primarySwatch: Colors.teal,
      ),
      home: _MainPage(),
    );
  }
}

class _MainPage extends StatefulWidget {
  _MainPage({Key? key}) : super(key: key);

  @override
  State<_MainPage> createState() => __MainPageState();
}

class __MainPageState extends State<_MainPage> {
  // int currentPageIndex = 1;
  final screens = [
    PartiesPage(),
    VehiclesPage(),
    FormIncident(),
    FormIncident()
  ];

  @override
  Widget build(BuildContext context) {
    return Container(child: VehiclesPage());

  }
}

vehicles_page.dart (Code Below)

// ignore_for_file: unused_element, unused_field

import 'package:flutter/material.dart';
import '../dropdown_forms.dart';

enum VehicleType { pov, leased, pgti }

class VehiclesPage extends StatefulWidget {
  const VehiclesPage({Key? key}) : super(key: key);

  @override
  State<VehiclesPage> createState() => _VehiclesPageState();
}

class _VehiclesPageState extends State<VehiclesPage> {
  //Variables

  VehicleType vehicleType = VehicleType.pov;
  bool isCommercial = false;
  String? _make;
  String? _model;
  String? _year;
  String? _color;
  String? _vimNumber;
  String? _plate;
  String? _ownerName;
  String? _ownerAddress;
  String? _ownerCity;
  String? _ownerState;
  String? _ownerZip;
  String? _ownerPhone;
  String? _insuranceCoName;
  String? _insuranceCoPhone;
  String? _policyHolderName;
  String? _policyNumber;
  List<Widget> vehicles = [];

  Widget buildTypeVeihicle() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Expanded(
          child: ListTile(
            horizontalTitleGap: 0,
            dense: true,
            title: const Text('POV'),
            leading: Radio(
                value: VehicleType.pov,
                groupValue: vehicleType,
                onChanged: (VehicleType? value) {
                  setState(() {
                    vehicleType = value!;
                  });
                }),
          ),
        ),
        Expanded(
          child: ListTile(
            horizontalTitleGap: 0,
            dense: true,
            title: const Text('Leased'),
            leading: Radio(
                value: VehicleType.leased,
                groupValue: vehicleType,
                onChanged: (VehicleType? value) {
                  setState(() {
                    vehicleType = value!;
                  });
                }),
          ),
        ),
        Expanded(
          child: ListTile(
            horizontalTitleGap: 0,
            dense: true,
            title: const Text(
              'PGTI',
              softWrap: false,
            ),
            leading: Radio(
                value: VehicleType.pgti,
                groupValue: vehicleType,
                onChanged: (VehicleType? value) {
                  setState(() {
                    vehicleType = value!;
                  });
                }),
          ),
        ),
      ],
    );
  }

  Widget _buildIsCommercial() {
    return SwitchListTile(
      title: const Text('This is commercial vehicle?'),
      value: isCommercial,
      onChanged: (bool value) {
        setState(() {
          isCommercial = value;
        });
      },
      // secondary: const Icon(Icons.car_repair),
    );
  }

  Widget _buildVehicleMake() {
    return TextFormField(
      decoration: const InputDecoration(labelText: 'Make'),
      validator: (String? value) {
        if (value == null) {
          return 'Make is required';
        }
        return null;
      },
      onSaved: (String? value) {
        _make = value;
      },
    );
  }

  Widget _buildVehicleModel() {
    return TextFormField(
      decoration: const InputDecoration(labelText: 'Model'),
      validator: (String? value) {
        if (value == null) {
          return 'Model is required';
        }
        return null;
      },
      onSaved: (String? value) {
        _model = value;
      },
    );
  }

  Widget _buildVehicleYear() {
    return TextFormField(
      decoration: const InputDecoration(labelText: 'Year'),
      validator: (String? value) {
        if (value == null) {
          return 'Year is required';
        }
        return null;
      },
      onSaved: (String? value) {
        _year = value;
      },
    );
  }

  Widget _buildVehicleColor() {
    return TextFormField(
      decoration: const InputDecoration(labelText: 'Color'),
      validator: (String? value) {
        if (value == null) {
          return 'Color is required';
        }
        return null;
      },
      onSaved: (String? value) {
        _color = value;
      },
    );
  }

  Widget _buildVehicleVinNumber() {
    return TextFormField(
      decoration: const InputDecoration(labelText: 'Vin Number'),
      validator: (String? value) {
        if (value == null) {
          return 'Vin Number is required';
        }
        return null;
      },
      onSaved: (String? value) {
        _vimNumber = value;
      },
    );
  }

  Widget _buildVehiclePlate() {
    return TextFormField(
      decoration: const InputDecoration(labelText: 'Plate Number'),
      validator: (String? value) {
        if (value == null) {
          return 'Plate Number is required';
        }
        return null;
      },
      onSaved: (String? value) {
        _vimNumber = value;
      },
    );
  }

  Widget _buildOwnerName() {
    return TextFormField(
      decoration: const InputDecoration(labelText: 'Owner Name'),
      validator: (String? value) {
        if (value == null) {
          return 'Owner Name is required';
        }
        return null;
      },
      onSaved: (String? value) {
        _vimNumber = value;
      },
    );
  }

  Widget _buildOwnerAddress() {
    return TextFormField(
      decoration: const InputDecoration(labelText: 'Owner Address'),
      validator: (String? value) {
        if (value == null) {
          return 'Owner Address is required';
        }
        return null;
      },
      onSaved: (String? value) {
        _vimNumber = value;
      },
    );
  }

  Widget _buildOwnerCity() {
    return TextFormField(
      decoration: const InputDecoration(labelText: 'Owner City'),
      validator: (String? value) {
        if (value == null) {
          return 'Owner City is required';
        }
        return null;
      },
      onSaved: (String? value) {
        _vimNumber = value;
      },
    );
  }

  Widget _buildOwnerZip() {
    return TextFormField(
      decoration: const InputDecoration(labelText: 'Owner Zip'),
      validator: (String? value) {
        if (value == null) {
          return 'Owner Zip is required';
        }
        return null;
      },
      onSaved: (String? value) {
        _vimNumber = value;
      },
    );
  }

  Widget _buildOwnerPhone() {
    return TextFormField(
      decoration: const InputDecoration(labelText: 'Owner Phone'),
      validator: (String? value) {
        if (value == null) {
          return 'Owner Phone is required';
        }
        return null;
      },
      onSaved: (String? value) {
        _vimNumber = value;
      },
    );
  }

  Widget _buildInsuranceCo() {
    return TextFormField(
      decoration: const InputDecoration(labelText: 'Owner Insurance Company'),
      validator: (String? value) {
        if (value == null) {
          return 'Owner Insurance Company is required';
        }
        return null;
      },
      onSaved: (String? value) {
        _vimNumber = value;
      },
    );
  }

  // ignore: unused_element
  Widget _buildInsurancePhone() {
    return TextFormField(
      decoration: const InputDecoration(labelText: 'Insurance Phone'),
      validator: (String? value) {
        if (value == null) {
          return 'Insurance Phone is required';
        }
        return null;
      },
      onSaved: (String? value) {
        _vimNumber = value;
      },
    );
  }

  Widget _buildPolicyHolderName() {
    return TextFormField(
      decoration: const InputDecoration(labelText: 'Policy Holder'),
      validator: (String? value) {
        if (value == null) {
          return 'Policy Holder is required';
        }
        return null;
      },
      onSaved: (String? value) {
        _vimNumber = value;
      },
    );
  }

  Widget _buildPolicyNumber() {
    return TextFormField(
      decoration: const InputDecoration(labelText: 'Policy Number'),
      validator: (String? value) {
        if (value == null) {
          return 'Policy Number is required';
        }
        return null;
      },
      onSaved: (String? value) {
        _vimNumber = value;
      },
    );
  }

  Widget _buildVehiclesCard() {
    return Container(
      margin: const EdgeInsets.all(8.0),
      decoration: const BoxDecoration(
        boxShadow: [
          BoxShadow(color: Colors.blueGrey, blurRadius: 10, spreadRadius: -10)
        ],
      ),
      child: Card(
        elevation: 0,
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
        child: Container(
          margin: const EdgeInsets.fromLTRB(0, 0, 0, 10),
          padding: const EdgeInsets.all(10),
          child: Column(
            children: [
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  const Icon(Icons.directions_car, color: Colors.teal),
                  IconButton(
                    onPressed: () {
                      setState(() {
                        print('Trash presses');
                      });
                    },
                    icon: const Icon(Icons.delete),
                    color: Colors.red,
                  ),
                ],
              ),
              buildTypeVeihicle(),
              _buildIsCommercial(),
              Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
                Expanded(child: _buildVehicleMake()),
                Expanded(
                    child: Padding(
                        padding: const EdgeInsets.fromLTRB(8, 0, 0, 0),
                        child: _buildVehicleModel()))
              ]),
              Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
                Expanded(child: _buildVehicleYear()),
                Expanded(
                    child: Padding(
                        padding: const EdgeInsets.fromLTRB(8, 0, 0, 0),
                        child: _buildVehicleColor()))
              ]),
              Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
                Expanded(child: _buildVehicleVinNumber()),
                Expanded(
                    child: Padding(
                  padding: const EdgeInsets.fromLTRB(8.0, 0, 0, 0),
                  child: _buildVehiclePlate(),
                )),
                Expanded(
                    child: Padding(
                        padding: const EdgeInsets.fromLTRB(8, 20, 0, 0),
                        child: DropdownStatesUs())),
              ]),
              const SizedBox(
                height: 8,
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.start,
                children: const [
                  Icon(
                    Icons.person,
                    color: Colors.teal,
                  ),
                ],
              ),
              Row(
                children: [
                  Expanded(child: _buildOwnerName()),
                  Expanded(
                      child: Padding(
                    padding: const EdgeInsets.fromLTRB(8, 0, 0, 0),
                    child: _buildOwnerPhone(),
                  )),
                ],
              ),
              _buildOwnerAddress(),
              Row(
                children: [
                  Expanded(child: _buildOwnerCity()),
                  Expanded(
                      child: Padding(
                    padding: const EdgeInsets.fromLTRB(8, 20, 0, 0),
                    child: DropdownStatesUs(),
                  )),
                  Expanded(
                      child: Padding(
                    padding: const EdgeInsets.fromLTRB(8, 0, 0, 0),
                    child: _buildOwnerZip(),
                  )),
                ],
              ),
              Row(
                children: [
                  Expanded(child: _buildPolicyHolderName()),
                  Expanded(
                      child: Padding(
                    padding: const EdgeInsets.fromLTRB(8, 0, 0, 0),
                    child: _buildPolicyNumber(),
                  )),
                ],
              )
            ],
          ),
        ),
      ),
    );
  }

//
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
      floatingActionButton: FloatingActionButton(
        backgroundColor: Colors.teal,
        child: const Icon(Icons.add),
        onPressed: () {
          vehicles.add(_buildVehiclesCard());
          // print(vehicles.length);
          setState(() {});
        },
      ),
      body: Form(
        child: ListView.builder(
          primary: false,
          itemCount: vehicles.length,
          itemBuilder: (BuildContext context, int i) {
            return vehicles[i];
          },
        ),
      ),
    );
  }
}

Thanks for any help beforehand, any other advices or tips to make the code better, let me know

1

There are 1 best solutions below

5
On BEST ANSWER

The current state changes behavior reflected on newly item because last item created before the radio-buttom or switch tile was changed. Once you switch the button and change the radioGroup value it will only get to the new created widget that will be trigger by floating action button.

Notice that List<Widget> vehicles = []; holds the created widgets. And while creating new widget you are using state variables like vehicleType and isCommercial. Once you click on 1st generated widget, these variables get new data based on your tap event. while the state is holding these variables, then again you click on fab to add item on vehicles to generate item with current state of vehicleType and isCommercial.

While every list-item will have its own state, it is better to create a new StatefulWidget for each item. Also, you can go for state-management like riverpod, bloc for future purpose.

A simplify version but not complete, you need to handle callback to get changes data or better start using riverpod or bloc for state management

enum VehicleType { pov, leased, pgti }

class VehiclesPage extends StatefulWidget {
  const VehiclesPage({Key? key}) : super(key: key);

  @override
  State<VehiclesPage> createState() => _VehiclesPageState();
}

class _VehiclesPageState extends State<VehiclesPage> {
  List<MyDataModel> vehicles = [];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
      floatingActionButton: FloatingActionButton(
        backgroundColor: Colors.teal,
        child: const Icon(Icons.add),
        onPressed: () {
          vehicles.add(MyDataModel());
          setState(() {});
        },
      ),
      body: Form(
        child: ListView.builder(
          primary: false,
          itemCount: vehicles.length,
          itemBuilder: (BuildContext context, int i) {
            return ListItem(model: vehicles[i]);
          },
        ),
      ),
    );
  }
}

class MyDataModel {
  final VehicleType vehicleType;
  final bool isCommercial;
  MyDataModel({
    this.vehicleType = VehicleType.pov,
    this.isCommercial = false,
  });

  MyDataModel copyWith({
    VehicleType? vehicleType,
    bool? isCommercial,
  }) {
    return MyDataModel(
      vehicleType: vehicleType ?? this.vehicleType,
      isCommercial: isCommercial ?? this.isCommercial,
    );
  }
}

class ListItem extends StatefulWidget {
  final MyDataModel model;
  const ListItem({
    Key? key,
    required this.model,
  }) : super(key: key);

  @override
  State<ListItem> createState() => _ListItemState();
}

class _ListItemState extends State<ListItem> {
  late MyDataModel model = widget.model;

  Widget buildTypeVeihicle() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Expanded(
          child: ListTile(
            horizontalTitleGap: 0,
            dense: true,
            title: const Text('POV'),
            leading: Radio(
                value: VehicleType.pov,
                groupValue: model.vehicleType,
                onChanged: (VehicleType? value) {
                  setState(() {
                    model = model.copyWith(vehicleType: value);
                  });
                }),
          ),
        ),
        Expanded(
          child: ListTile(
            horizontalTitleGap: 0,
            dense: true,
            title: const Text('Leased'),
            leading: Radio(
                value: VehicleType.leased,
                groupValue: model.vehicleType,
                onChanged: (VehicleType? value) {
                  setState(() {
                    model = model.copyWith(vehicleType: value);
                  });
                }),
          ),
        ),
        Expanded(
          child: ListTile(
            horizontalTitleGap: 0,
            dense: true,
            title: const Text(
              'PGTI',
              softWrap: false,
            ),
            leading: Radio(
                value: VehicleType.pgti,
                groupValue: model.vehicleType,
                onChanged: (VehicleType? value) {
                  setState(() {
                    model = model.copyWith(vehicleType: value);
                  });
                }),
          ),
        ),
      ],
    );
  }

  Widget _buildIsCommercial() {
    return SwitchListTile(
      title: const Text('This is commercial vehicle?'),
      value: model.isCommercial,
      onChanged: (bool value) {
        setState(() {
          model = model.copyWith(isCommercial: value);
        });
      },
      // secondary: const Icon(Icons.car_repair),
    );
  }

  Widget _buildVehiclesCard() {
    return Container(
      margin: const EdgeInsets.all(8.0),
      decoration: const BoxDecoration(
        boxShadow: [
          BoxShadow(color: Colors.blueGrey, blurRadius: 10, spreadRadius: -10)
        ],
      ),
      child: Card(
        elevation: 0,
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
        child: Container(
          margin: const EdgeInsets.fromLTRB(0, 0, 0, 10),
          padding: const EdgeInsets.all(10),
          child: Column(
            children: [
              buildTypeVeihicle(),
              _buildIsCommercial(),
            ],
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return _buildVehiclesCard();
  }
}