skip to Main Content

I am new learner in flutter. Currently, I try work on Firebase Firestore CRUD operation, but I encounter problem of by tapping the update icon (refer to image 1), it should be navigate the home page screen to the adding task page that contain the existing data to edit and update it (refer to image 2). I not too understand which part is wrong, is that we have to create another new adding task page view that contain the information and navigate to this new created page by tapping the icon button to get the view that contain data and update it? If yes, how can I do it, please provide some guidelines for me as I still in learning and I can’t understand and digest everything now. Appreciate those help, thank you.

Main Page

MainPage

Adding Task Page

AddingTaskPage

HomePage Code:

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final TaskController controller = Get.put(TaskController());
  DateTime selectedDate = DateTime.now();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: appBar(),
      body: Column(
        children: [
          addTaskBar(),
          addDateBar(),
          const SizedBox(height: 10.0),
          GetX<TaskController>(
            init: Get.put<TaskController>(TaskController()),
            builder: (controller) {
              return Expanded(
                  child: ListView.builder(
                      itemCount: controller.tasks.length,
                      itemBuilder: (context, index) {
                        print('${controller.tasks[index].note} +1');
                        final _task = controller.tasks[index];
                        return Container(
                          margin: const EdgeInsets.symmetric(
                            horizontal: 20,
                            vertical: 10,
                          ),
                          decoration: BoxDecoration(
                            color: _getBGClr(_task.color!),
                            // const Color.fromARGB(255, 230, 230, 230),
                            borderRadius: BorderRadius.circular(20),
                          ),
                          child: Padding(
                            padding: const EdgeInsets.all(8.0),
                            child: Row(
                              children: [
                                Expanded(
                                  child: Column(
                                    crossAxisAlignment:
                                        CrossAxisAlignment.start,
                                    children: [
                                      Text(
                                        _task.title!,
                                        style: GoogleFonts.lato(
                                            textStyle: const TextStyle(
                                                fontSize: 16,
                                                fontWeight: FontWeight.bold,
                                                color: Colors.white)),
                                      ),
                                      const SizedBox(height: 12),
                                      Row(
                                        crossAxisAlignment:
                                            CrossAxisAlignment.center,
                                        children: [
                                          const Icon(
                                            Icons.access_alarm_rounded,
                                            color: Colors.black,
                                            size: 18,
                                          ),
                                          const SizedBox(width: 2),
                                          Text(
                                            '${_task.startTime!} - ${_task.endTime!}',
                                            style: GoogleFonts.lato(
                                                textStyle: const TextStyle(
                                                    fontSize: 16,
                                                    fontWeight: FontWeight.bold,
                                                    color: Colors.white)),
                                          ),
                                        ],
                                      ),
                                      const SizedBox(height: 12),
                                      Row(
                                        crossAxisAlignment:
                                            CrossAxisAlignment.center,
                                        children: [
                                          Text(
                                            _task.note!,
                                            style: GoogleFonts.lato(
                                              textStyle: const TextStyle(
                                                fontSize: 15,
                                                color: Colors.white,
                                                fontWeight: FontWeight.bold,
                                              ),
                                            ),
                                          ),
                                        ],
                                      ),
                                    ],
                                  ),
                                ),
                                IconButton(
                                  onPressed: () {
                                    FirestoreDB.updateTask(
                                        _task.toMap() as Task);
                                  },
                                  icon: const Icon(Icons.edit_note),
                                ),
                                IconButton(
                                  onPressed: () {
                                    FirestoreDB.deleteTask(_task.id!);
                                  },
                                  icon: const Icon(
                                    Icons.delete,
                                    color: Colors.grey,
                                  ),
                                ),
                              ],
                            ),
                          ),
                        );
                      }));
            },
          ),
        ],
      ),
    );
  }

Task Model Class:

class Task {
  String? id;
  String? title;
  String? note;
  bool? isCompleted;
  String? date;
  String? startTime;
  String? endTime;
  int? color;
  int? remind;
  String? repeat;

  Task({
    required this.id,
    required this.title,
    required this.note,
    required this.isCompleted,
    required this.date,
    required this.startTime,
    required this.endTime,
    required this.color,
    required this.remind,
    required this.repeat,
  });

  Map<String, dynamic> toMap() {
    return <String, dynamic>{
      'id': id,
      'title': title,
      'note': note,
      'isCompleted': isCompleted,
      'date': date,
      'startTime': startTime,
      'endTime': endTime,
      'color': color,
      'remind': remind,
      'repeat': repeat,
    };
  }

  // Task.fromMap({required DocumentSnapshot<Map<String, dynamic>> documentSnapshot}) {

  Task.fromMap({required DocumentSnapshot documentSnapshot}) {
    id = documentSnapshot.id;
    title = documentSnapshot['title'];
    note = documentSnapshot['note'];
    isCompleted = documentSnapshot['isCompleted'];
    date = documentSnapshot['date'];
    startTime = documentSnapshot['startTime'];
    endTime = documentSnapshot['endTime'];
    color = documentSnapshot['color'];
    remind = documentSnapshot['remind'];
    repeat = documentSnapshot['repeat'];
  }
}

FirestoreDB class:

class FirestoreDB {
  final FirestoreDB instance = FirestoreDB();
  // add tasks into firebase firestore
  static addTask({required Task taskmodel}) async {
    await FirebaseFirestore.instance
        .collection('users') // modified task to users
        .doc(FirebaseAuth.instance.currentUser!.uid)
        .collection('tasks')
        .add(
          taskmodel.toMap(),
        );
    print(taskmodel);
    print(taskmodel.toMap());
  }

  static Stream<List<Task>> taskStream() {
    return FirebaseFirestore.instance
        .collection('users')
        .doc(FirebaseAuth.instance.currentUser!.uid)
        .collection('tasks')
        .snapshots()
        .map((QuerySnapshot query) {
      List<Task> tasks = [];
      for (var task in query.docs) {
        final taskModel = Task.fromMap(documentSnapshot: task);
        //  task as DocumentSnapshot<Map<String, dynamic>>
        tasks.add(taskModel);
      }
      print(tasks);
      return tasks;
    });
  }

  static updateTask(Task taskModel) async {
    await FirebaseFirestore.instance
        .collection('users')
        .doc(FirebaseAuth.instance.currentUser!.uid)
        .collection('tasks')
        .doc(taskModel.id)
        .update(
          taskModel.toMap(),
        );
  }
}

#Adding Task Page Classes

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

  @override
  State<AddingTaskPage> createState() => _AddingTaskPageState();
}

class _AddingTaskPageState extends State<AddingTaskPage> {
  //TodoController controller = Get.put(TodoController());
  TextEditingController titleController = TextEditingController();
  TextEditingController noteController = TextEditingController();
  late DateTime _selecteDate = DateTime.now();
  late String _startTime =
      DateFormat('hh:mm a').format(DateTime.now()).toString();
  String _endTime = DateFormat('hh:mm a')
      .format(DateTime.now().add(const Duration(minutes: 60)))
      .toString();
  int _selectedRemind = 5;
  List<int> remindList = [5, 10, 15, 20];
  String _selectedRepeat = 'None';
  List<String> repeatList = ['None', 'Daily', 'Weekly', 'Monthly'];
  int _selectedColor = 0;
  List<Color> colorList = [
    bluishClr,
    yellowClr,
    redClr,
    greenClr,
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Free to add task!"),
        centerTitle: true,
        actions: [
          IconButton(
              onPressed: () {
                // this icon button is for the user to signout
                AuthController.instance.logout();
              },
              icon: const Icon(Icons.logout_rounded))
        ],
      ),
      body: Container(
        padding: const EdgeInsets.symmetric(horizontal: 20.0),
        child: SingleChildScrollView(
          child: Column(
            children: [
              //Title
              InputField(
                title: 'Title',
                hint: 'Enter your title here...',
                controller: titleController,
              ),
              //Note
              InputField(
                title: 'Note',
                hint: 'Enter your note here...',
                controller: noteController,
              ),
              //Date
              InputField(
                title: 'Date',
                hint: DateFormat.yMd().format(_selecteDate),
                child: IconButton(
                  onPressed: () => _getDateFromUser(),
                  icon: const Icon(
                    Icons.calendar_today_outlined,
                    color: Colors.grey,
                  ),
                ),
              ),
              //Date Range
              Row(
                children: [
                  Expanded(
                    child: InputField(
                      title: 'Start Time',
                      hint: _startTime,
                      child: IconButton(
                        onPressed: () => _getTimeFromUser(isStartTime: true),
                        icon: const Icon(
                          Icons.access_time_rounded,
                          color: Colors.grey,
                        ),
                      ),
                    ),
                  ),
                  Expanded(
                    child: InputField(
                      title: 'End Time',
                      hint: _endTime,
                      child: IconButton(
                        onPressed: () => _getTimeFromUser(isStartTime: false),
                        icon: const Icon(
                          Icons.access_time_rounded,
                          color: Colors.grey,
                        ),
                      ),
                    ),
                  ),
                ],
              ),
              //Remind
              InputField(
                title: 'Date',
                hint: '$_selectedRemind minutes early',
                child: DropdownButton(
                  onChanged: (String? newVal) {
                    setState(() {
                      _selectedRemind = int.parse(newVal!);
                    });
                  },
                  items: remindList
                      .map<DropdownMenuItem<String>>(
                        (e) => DropdownMenuItem<String>(
                          value: e.toString(),
                          child: Text('$e minutes'),
                        ),
                      )
                      .toList(),
                  icon: const Icon(
                    Icons.keyboard_arrow_down,
                    size: 32,
                    color: Colors.grey,
                  ),
                  elevation: 3,
                  underline: Container(height: 0),
                  borderRadius: BorderRadius.circular(15),
                ),
              ),
              //Repeat
              InputField(
                title: 'Repeat',
                hint: _selectedRepeat,
                child: DropdownButton<String>(
                  onChanged: (String? value) {
                    setState(() {
                      _selectedRepeat = value!;
                    });
                  },
                  items: repeatList
                      .map<DropdownMenuItem<String>>(
                        (e) => DropdownMenuItem<String>(
                          value: e,
                          child: Text(e),
                        ),
                      )
                      .toList(),
                  icon: const Icon(
                    Icons.keyboard_arrow_down,
                    size: 32,
                    color: Colors.grey,
                  ),
                  elevation: 3,
                  underline: Container(height: 0),
                  borderRadius: BorderRadius.circular(15),
                ),
              ),
              //Color
              Container(
                margin: const EdgeInsets.symmetric(
                    vertical: 10.0, horizontal: 10.0),
                alignment: Alignment.centerLeft,
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    const Text('Color'),
                    Row(
                      children: List.generate(
                        colorList.length,
                        (index) => Padding(
                          padding: const EdgeInsets.only(right: 5.0, top: 8),
                          child: InkWell(
                            onTap: () {
                              setState(() {
                                _selectedColor = index;
                              });
                            },
                            borderRadius: BorderRadius.circular(50),
                            child: CircleAvatar(
                              backgroundColor: colorList[index],
                              child: index == _selectedColor
                                  ? const Icon(
                                      Icons.check,
                                      color: Colors.white,
                                    )
                                  : null,
                            ),
                          ),
                        ),
                      ),
                    )
                  ],
                ),
              ),
            ],
          ),
        ),
      ),
      floatingActionButton: MyButton(
          label: 'Create Task',
          onTap: () {
            _validateData();
          }),
    );
  }

  _validateData() async {
    if (titleController.text.isNotEmpty && noteController.text.isNotEmpty) {
      print(1);
      _addTaskToDb();
      Get.back();
    } else if (titleController.text.isEmpty || noteController.text.isEmpty) {
      print(2);

      Get.snackbar(
        'Required',
        'All fileds are required',
        backgroundColor: Colors.white,
        snackPosition: SnackPosition.BOTTOM,
        colorText: Colors.black,
        icon: const Icon(
          Icons.warning_amber_rounded,
          color: Colors.red,
        ),
      );
    } else {
      print('######## Error Here ! ########');
    }
  }

  updateTask({required Task taskModel, documentId}) async {
    await FirebaseFirestore.instance
        .collection('users')
        .doc(FirebaseAuth.instance.currentUser!.uid)
        .collection('tasks')
        .doc(documentId)
        .update(
          taskModel.toMap(),
        )
        .whenComplete(
            () => const SnackBar(content: Text('Update Successfully')));
  }

  _addTaskToDb() async {
    await FirestoreDB.addTask(
      taskmodel: Task(
        id: FirebaseAuth.instance.currentUser!.uid,
        title: titleController.text,
        note: noteController.text,
        isCompleted: false,
        date: DateFormat.yMd().format(_selecteDate),
        startTime: _startTime,
        endTime: _endTime,
        color: _selectedColor,
        remind: _selectedRemind,
        repeat: _selectedRepeat,
      ),
    );
  }

  _getDateFromUser() async {
    DateTime? _pickedDate = await showDatePicker(
      context: context,
      initialDate: _selecteDate,
      firstDate: DateTime(2020),
      lastDate: DateTime(2030),
    );
    if (_pickedDate != null) {
      setState(() {
        _selecteDate = _pickedDate;
      });
    } else {
      print('picked date empty !');
    }
  }

  _getTimeFromUser({required bool isStartTime}) async {
    TimeOfDay? _pickedTime = await showTimePicker(
      context: context,
      initialTime: TimeOfDay.now(),
    );
    if (_pickedTime != null) {
      setState(() {
        if (isStartTime) {
          _startTime = _pickedTime.format(context);
          print(_startTime);
        } else {
          _endTime = _pickedTime.format(context);
          print(_endTime);
        }
      });
    }
  }
}

2

Answers


  1. I have a similar project where I implemented a To-Do List with Flutter and Firebase. I have documented the CRUD process in this Medium article: https://kymoraa.medium.com/to-do-list-app-with-flutter-firebase-7910bc42cf14
    Let me know if it helps 🙂

    Login or Signup to reply.
  2. … it should be navigate the home page screen to the adding task page that contain the existing data to edit and update it …

    From the above extract, if I get you right, you want that when a user taps the edit icon next to a task on the home page, the app should navigate the user to the "Add Task Page". But this time, this page should be populated with the details of the tasks, so that the user can update and save to Firestore.

    If that’s correct, then you actually need to navigate to the page. Let me suppose that the name of the page is AddingTaskPage (though you didn’t share its code). When navigating, provide the instance of the Task model to be updated as the argument of the AddingTaskPage. You will use data from this Task instance to fill the fields of AddingTaskPage. Then when the user clicks on the button at the end of the page, you will now call the Firestore update function.

    Carry out the following steps:

    1. Route to AddingTaskPage with the Task as an argument.

    In the code for your HomePage, where you have the buttons for editing and deleting notes, change the onPressed callback of the edit_note from the following:

    IconButton(
      onPressed: () {
        FirestoreDB.updateTask(
          _task.toMap() as Task);
        },
      icon: const Icon(Icons.edit_note),
    ),
    

    to

    IconButton(
      onPressed: () {
        Navigator.of(context).push(
          MaterialPageRoute(builder: (context) => AddingTaskPage(task: _task)),
        );
      },
      icon: const Icon(Icons.edit_note),
    ),
    

    2. Use the Task argument in the AddingTaskPage

    You didn’t share the code for the page, but the following is an obtainable skeleton of what you should have. I suppose you are using a StatefulWidget since you have a form.

    class AddingTaskPage extends StatefulWidget {
      Task? task;
      const AddingTaskPage({super.key, this.task});
    
      @override
      State<AddingTaskPage> createState() => _AddingTaskPageState();
    }
    
    class _AddingTaskPageState extends State<AddingTaskPage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          // appBar: AppBar(...)
          body: Form(
            child: Column(
              children: [
                // all the form fields.
                //
                // Your code mustn't follow this skeleton,
                // This should just guide you.
                //
                // The nullable Task? argument will tell if this AddingTaskPage
                // is for updating or creating a task.
                //
                // When null, this page is for creating a new task. When not null,
                // this page is for updating. In that case, 
                // set the text content of the TextFields to be the contents
                // of the Task. You will need to use TextEdittingControllers for
                // that.
                //
                // Finally, call FirestoreDB.update in the last button.
                ElevatedButton(
                  onPressed: () {
                    FirestoreDB.updateTask(_task.toMap() as Task);
                  },
                  child: Text('${widget.task == null ? 'Create' : 'Update'} Task'),
                ),
              ],
            ),
          ),
        );
      }
    }
    

    Miscellaneous

    a.

    if the above is not all-encompassing, it should still help you. It was adapted from how you presented the problem.

    b.

    For the sake of user experience, you may want to have some loading animation in the AddingTaskPage while the task is been created/updated to Firestore. That’s because it is an async operation, and you don’t want the user to leave the page if the task didn’t completely save.

    c.

    … I not too understand which part is wrong, is that we have to create another new adding task page view that contain the information and navigate to this new created page by tapping the icon button to get the view that contain data and update it? …

    No, I don’t think so. There will be no need of recreating a specific UpdatingTaskPage since the Task model to be updated is the same as what you have in creation. What could have required a separate updating page is if you were updating just one or two fields in the task in contrast to the 8 fields of task creation in the image you shared. In such a scenario, then a new page would have been necessary.

    Since you will now be using the same page for adding and updating task, you may want to rename this page to ManageTaskPage or TaskFormPage or something more generic to help with semantics that the same page is used for adding and updating tasks.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search