8. Designing UI
Lets start off by creating the user_screen.dart file. Inside of the lib directory create a new folder named screens this folder should hold all of the screens you have made for your app. Inside of this folder create a dart file named user_screen.dart. The first thing we need to do in this file is to
The first thing we need to do when creating a new screen is to determine whether the screen will be dynamic or static. As our screen will be dynamically displaying the users, we need to extend a StatefulWidget. Next as Flutter holds track of all widgets in a tree, the next line allows it to keep track of this particular screen. createState() → Creates a new instance of _UserScreenState.
import 'package:flutter/material.dart';
import '../services/user_service.dart';
import 'assign_dietary_screen.dart';
class UserScreen extends StatefulWidget {
const UserScreen({Key? key}) : super(key: key);
@override
State<UserScreen> createState() => _UserScreenState();
}As our screen is dynamic we need to create a class which handles the different states the screen could be in. Create a new State class (backing state class) named _UserScreenState. In Dart a State class is a class where you store and update dynamic values. A state class will hold a build method where the creation of the screen UI will be written. Append the UserScreen:
class _UserScreenState extends State<UserScreen> {
final UserService _userService = UserService();
final _formKey = GlobalKey<FormState>();
List<dynamic> _users = [];
String name = '';
String email = '';
String password = '';
}At the start of the class we have created our class variables, and injected all of the necessary services we need to store, send and track data for and from our Frontend. _userService creates an object of our userService() class we made in the previous part. In our frontend, we want to create a Form where a user can input their name, email and password.
'final _formKey = GlobalKey<FormState>();' creates a key for us to use to track the inputs in the form. In Flutter, whenever you update the state of a widget, Flutter rebuilds the affected parts of the UI from scratch. This key is used to find the Form widget in the widget tree. To trigger a rebuild the method setState() is used.
Once we have our objects and variables, lets create a method for getting the users. Add this method to the _UserScreenState class:
This method will call the .getUsers() method from the user_service.dart class to grab all the users from our database. And then use the setState() to update and rebuild the widget that is in charge of displaying the users.
Lets do the same thing, but for creating a user. Add this method into your UserScreenState class.
This method is where the _formKey is used. Dart has built in validation and save methods, to ensure that the values placed inside a form do not have a bug and are the correct type. The ! means that the form is not null. If the form is valid then we save the form field values (inserted by the user on the app). Then we will send the data to the backend by the createUser method we made in the user_service.dart. fetchUsers() is called to refresh the list of users after a new user is created. Finally, ScaffoldMessenger.of(context).showSnackBar(...) shows a temporary message (SnackBar) at the bottom of the screen indicating that the user was created.
In Flutter, if we want data loaded in on the page build we need to override the initial state of the screen. Add this method to the class.
This method will ensure that we have all of the users data when the screen is initially loaded.
Now that we have all methods to handle data on our UI screen, lets build the UI. All stateful widgets involved in the UI will be found inside of the build method of a state class. For the build method, copy the code sequentially.
The context parameter provides access to location-specific data in the widget tree (like theme, size, etc.). Inside we return a Scaffold which is a high-level layout widget that provides basic visual structure like an app bar, body, and floating action buttons. This basically allows us to build the UI of the screen part by part. Inside the scaffold we included an AppBar with a title ‘Users’, which is the bar at the top of the crease. Next we include a body, which is the main content area of the scaffold. We use the Padding widget to put space around its children (the widgets in the body), in this case 16 pixels of padding on all sides, using const as it won’t change. Then we use Column to arrange the child widgets vertically, and the children argument to list the widgets that will be laid out one after the other.
Now that we have stated the basic formatting we need lets put in a Form.
The first thing we need to do when we create the form is to link it to the _formKey that we instantiated at the start of the class. We use Column to arrange all of the items in our form vertically. Each item in the column can to be a TextFormField(). This method creates a text form entry. In a Form Field, it will have three keys, decoration - in charge of what the field will look like, onSaved - holds a method which is run when '_formKey.currentState!.save()' is called, and validator - which holds a method which is run when '_formKey.currentState!.validate()' is called. As there are three strings a user needs to input 'name', 'email' and 'password' we need three TextFormField()'s. Lastly, in order to submit the form, we need to add a button. The ElevatedButton widget is used to create a button that will call a method (createUser) when clicked. In Dart SizedBox is used to create spaces.
Lets now make a widget to view all the users in our database.
Divider() creates a horizontal line, which can be used to visually seperate widgets. Text is used to make headings of text boxes to insert data.
Expanded is a widget that expands its children to fit the available space in the column (similar to flex in css). ListView.builder dynamically builds a scrollable list of widgets where itemCount is the number of list items, and itemBuilder creates each item in the list. It takes in a context, which just keeps the theme consistent as passed in earlier, and an index, used to identify each item in the list. The itemBuilder acts as a for loop almost, so in the indent you are able to set a user variable, and extract its data to put into a list widget - in this case ListTitle.
We now have a working application with a user screen. And that marks the end of the formal spike, but lets challenge ourselves with a coding challenge.
Coding challenge
Similar to the coding challenge for backend, design another screen which handles assigning a dietary restriction to any user. To do this challenge you are in charge of creating a new screen assign_deitary_requirement.dart. This screen should also have a service to assist in connecting to the backend.
Some pre-requisites for this challenge is to create one or many database entries for Dietary requirements. To do this open your pgAdmin (localhost:5050). Navigate to the Dietary Restriction table, and click on add row. Create the Dietary restriction you desire and then press save results to file to update the database

To assist on the navigation to the new screen, below is the updated, user_screen.dart file, which now has a button that will redirect to the new screen
Link to Github with the Finding Nibbles Spike and instructions on how to download new files for front end.
Last updated