Search code examples
flutterdartlistviewtimepicker

How to change times one by one in ListView using TimePicker Flutter?


I'm trying to allow users to choose their working hours for a week for that I've done a List of days of the week and used a ListView.builder to display all days as shown below.

enter image description here

Now I would like to allow users to change start hour and end hour for each day. For that I call a TimePicker on click but when I change a start hour, all start hours are changed.

My question is, is that a good idea to use a ListView to generate the days and if yes how to modiify hours one by one for each day without modify the others at the same time.

Here is the code:

Utils

static String displayCorrectTimeOfDay(int hour, int minute) {
    final hours = hour.toString().padLeft(2, '0');
    final minutes = minute.toString().padLeft(2, '0');

    return "${hours}h$minutes";
}

Text

const String monday = "Mon.";
const String tuesday = "Tue.";
const String wednesday = "Wed";
const String thursday = "Thu.";
const String friday = "Fri.";
const String saturday = "Sat.";
const String sunday = "Sun.";

WeeklyScheduleScreen

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

  @override
  State<WeeklyScheduleScreen> createState() => _WeeklyScheduleScreenState();
}

class _WeeklyScheduleScreenState extends State<WeeklyScheduleScreen> {
  TimeOfDay from = const TimeOfDay(hour: 7, minute: 00);
  TimeOfDay to = const TimeOfDay(hour: 23, minute: 00);
  List<String> week = [monday, tuesday, wednesday, thursday, friday, saturday, sunday];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: const CloseButton(
          color: white,
        ),
        title: const Text(
          myWeek,
          style: TextStyle(
            color: white,
          ),
        ),
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const SizedBox(height: 15,),
            buildSubTitle(),
            const SizedBox(height: 25,),
            buildWeek(),
            //buildFrom(),
          ],
        ),
      ),
    );
  }

  Widget buildSubTitle() => const Text(
    myWeekSubTitle,
    style: TextStyle(
      fontSize: 20,
      fontWeight: FontWeight.bold,
    ),
  );

  Widget buildWeek() => ListView.builder(
    itemCount: week.length,
    itemBuilder: (_, i) => buildSelectHour(week[i]),
    shrinkWrap: true,
    physics: const BouncingScrollPhysics(),
  );

  Widget buildSelectHour(String day) => Row(
    children: [
      Text(
        day,
        style: const TextStyle(
          fontSize: 18,
          fontWeight: FontWeight.bold,
        ),
      ),
      Expanded(
        flex: 3,
        child: buildDropDownField(
          text: Utils.displayCorrectTimeOfDay(from.hour, from.minute),
          onClicked: () => pickFromTime(),
        ),
      ),
      Expanded(
        flex: 3,
        child: buildDropDownField(
          text: Utils.displayCorrectTimeOfDay(to.hour, to.minute),
          onClicked: () => pickToTime(),
        ),
      ),
      Expanded(
        flex: 1,
        child: IconButton(
            onPressed: () { print( "Done clicked"); },
            icon: const Icon(
              Icons.check_circle,
              color: phoneButtonColor,
            ),
        ),
      ),
      Expanded(
        flex: 1,
        child: IconButton(
          onPressed: () { print( "Cancel clicked"); },
          icon: const Icon(
            Icons.cancel,
            color: red,
          ),
        ),
      ),
    ],
  );

  Widget buildDropDownField({required String text, required VoidCallback onClicked}) =>
      ListTile(
        title: Text(text),
        trailing: const Icon(Icons.arrow_drop_down),
        onTap: onClicked,
      );

  Future pickFromTime() async {
    TimeOfDay? timePicked = await showTimePicker(
      context: context,
      initialTime: from,
    );

    if(timePicked == null) return;

    setState(() => from = timePicked);
  }

  Future pickToTime() async {
    TimeOfDay? timePicked = await showTimePicker(
      context: context,
      initialTime: to,
    );

    if(timePicked == null) return;

    setState(() => to = timePicked);
  }
}

Thanks in advance


Solution

  • You just created one TimeOfDay that you applied to all items.

    Just create a List with all TimeOfDay's so you can easily access them through the builders index, for example:

    ...
    List<TimeOfDay> froms = [const TimeOfDay(hour: 7, minute: 00),...];
    <TimeOfDay> tos = [const TimeOfDay(hour: 23, minute: 00),...];
    ...
    
    Widget buildSelectHour(String day, int index) => Row(
        children: [
          Text(
            day,
            style: const TextStyle(
              fontSize: 18,
              fontWeight: FontWeight.bold,
            ),
          ),
          Expanded(
            flex: 3,
            child: buildDropDownField(
              text: Utils.displayCorrectTimeOfDay(froms[index].hour, froms[index].minute),
              onClicked: () => pickFromTime(),
            ),
          ),
          Expanded(
            flex: 3,
            child: buildDropDownField(
              text: Utils.displayCorrectTimeOfDay(tos[index].hour, tos[index].minute),
              onClicked: () => pickToTime(),
            ),
          ),
          Expanded(
            flex: 1,
            child: IconButton(
                onPressed: () { print( "Done clicked"); },
                icon: const Icon(
                  Icons.check_circle,
                  color: phoneButtonColor,
                ),
            ),
          ),
          Expanded(
            flex: 1,
            child: IconButton(
              onPressed: () { print( "Cancel clicked"); },
              icon: const Icon(
                Icons.cancel,
                color: red,
              ),
            ),
          ),
        ],
      );
    
    Future pickFromTime(int index) async {
        TimeOfDay? timePicked = await showTimePicker(
          context: context,
          initialTime: from,
        );
    
        if(timePicked == null) return;
    
        setState(() => froms[index] = timePicked);
      }
    
      Future pickToTime(int index) async {
        TimeOfDay? timePicked = await showTimePicker(
          context: context,
          initialTime: to,
        );
    
        if(timePicked == null) return;
    
        setState(() => tos[index] = timePicked);
      }