In the Activity Detail screen, we need to add the code that saves an Activity into the Firestore database.
Let’s add a new asynchronous method, called saveUser. Here, first we need to update the fields of the activity with the text properties of the fields that have been updated by the user: so, in the widget activity description field, place the content of the txtDescription controller, at its text property.
Repeat the same for the other three fields: the activity day takes txtDay.text; the activity beginTIme takes txtBegintime.text, and finally the activity endTime takes txtEndTime.
Now that the activity has been updated, we can call the method to insert it into the database: so, declare a final, called result, that awaits helper insertActivity, passing the widget activity. Finally return result.
Future saveUser() async {
widget.activity.description = txtDescription.text;
widget.activity.day = txtDay.text;
widget.activity.beginTime = txtBeginTime.text;
widget.activity.endTime = txtEndTime.text;
if (galleryImage != null) {
widget.activity.imagePath = galleryImage?.path ?? '';
}
final result = await helper.insertActivity(widget.activity);
return result;
}
In the onpressed method of the FloatingActionButton, call saveUser. Next, create a route that calls ActivitiesScreen, as after saving an activity through this screen we want to get back to the list of Activities. So, declare a final route that takes a MaterialPageRoute of dynamic. The builder parameter takes the current context: here just use an arrow function to return ActivitiesScreen. Finally, just call Navigator.push to get to the Activities Screen.
onPressed: () {
saveUser();
final route = MaterialPageRoute<dynamic>(
builder: (context) => const ActivitiesScreen());
final Future push = Navigator.push(context, route);
},
One last step: we also need to call the ActivityDetailScreen from the ActivitiesScreen. So open Activities_screen.dart and in the build method of the State class, add a FloatingActionButton to the Scaffold. Its child is an Icon that takes the add icon from the Icons set.
In the onPressed method, declare a final activity that creates a new instance of Activity. This is a new Activity that we want to pass to the Activity Detail screes, so let’s leave null for the ID, and an empty string for the remaining parameters. Next, create a route, that takes a MaterialPageRoute, and in its builder just return an ActivityDetailScreen, passing the empty Activity that you’ve just created.
Finally, create a push tha calls the Navigator.push metod, passing the current context and the new route. Let’s run the app to check whether everything works as expected.
child: const Icon(Icons.add),
onPressed: () {
final activity = Activity('', '', '', '', '', '');
final route = MaterialPageRoute<dynamic>(
builder: (context) => ActivityDetailScreen(activity));
final Future push = Navigator.push(context, route);
}),
From the activities screen, press the floatingAction button. This pushes the ActivityDetailScreen, with the four text fields that allow us to set the properties of the activity.
Let’s say we want to add swimming as the description. For the date let’s say december 12 2022. The activity starts at 8.30 and ends at 9:30. OK, now let’s press the save button. And you should be able to see the new activity in the list! Well done!
Now there’s another moment where we want to get from the activities screen to the activity detail screen, and it’s when our user clicks on an item of the listview. In this case, instead of adding a new activity to the database, we want to update an existing one.
So, in the onTap property, declare a final route, that creates an instance of MaterialPageRoute, that in its builder returns the ActivityDetailScreen. THis time, instead of creating a new empty activity, just pass the activity at the current position in the list. Finally push the new route calling the Navigator.push method and passing context and route.
onTap: () {
final route = MaterialPageRoute<dynamic>(
builder: (context) => ActivityScreen(activity));
final Future push = Navigator.push(context, route);
},
There’s an issue though: at this time, we cannot update an existing activity, as in our helper we haven’t created the method yet. So, let’s fix this.
In the firebase_helper.dart file, add a new asynchronous method, called updateActivity. This takes an activity and a String containing the id of the activity.
Inside the method, from the activities collection, in the doc that has the id that was passed, call the update method, passing activity.toMap.
And that’s it: if you know the id of an activity, you can retrieve it easily by passing it to the doc method, that returns a DocumentReference. Then the update method takes the fields that you want to change and updates the document.
Future updateActivity(Activity activity, String id) async {
try {
await activities.doc(id).update(activity.toMap());
} on Exception catch (e) {
print(e.toString());
}
}
One last step: in the activity detail screen, we need to call updateActivity instead of insertActivity when the user updates an existing activity. THe way we know if an activity is existing is if the id exists: so, in the saveActivity method, if widget.activity.id is null, then we call insertActivity. Otherwise, we call updateActivity, passing the widget activity and its id. Put an exclamation mark here, as even if id is nullable, we are certain that there is an id here, as it’s not null.
if (widget.activity.id == null) {
await helper.insertActivity(widget.activity);
}
else {
await helper.updateActivity(widget.activity, widget.activity.id!);
}
Let’ run the app. Now click on an item on the listview. As you can see the data of the activity that you’ve selected show up on the screen.
Now let’s change the description: for example, instead of “running”, put “walking in the park”. Then click the save floating action button. And the description of the activity has changed.
Great! Now we only need a way to delete an existing activity, and we’ll do that next.