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
Adding Task Page
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
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 🙂
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 theTask
model to be updated as the argument of theAddingTaskPage
. You will use data from thisTask
instance to fill the fields ofAddingTaskPage
. 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 theonPressed
callback of the edit_note from the following:to
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.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.
No, I don’t think so. There will be no need of recreating a specific
UpdatingTaskPage
since theTask
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
orTaskFormPage
or something more generic to help with semantics that the same page is used for adding and updating tasks.