# 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&#x20;

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.<br>

{% code title="user\_screen.dart" %}

```dart
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();
}
```

{% endcode %}

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:&#x20;

{% code title="user\_screen.dart" %}

```dart
class _UserScreenState extends State<UserScreen> {
  final UserService _userService = UserService();
  final _formKey = GlobalKey<FormState>();
  List<dynamic> _users = [];
  String name = '';
  String email = '';
  String password = '';
  }
```

{% endcode %}

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](https://finding-nibbles.gitbook.io/finding-nibbles/5.-developing-backend-code). In our frontend, we want to create a Form where a user can input their name, email and password.&#x20;

**'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.&#x20;

Once we have our objects and variables, lets create a method for getting the users. Add this method to the \_**UserScreenState** class:&#x20;

```dart
  Future<void> fetchUsers() async {
    final users = await _userService.getUsers();
    setState(() {
      _users = users;
    });
  }
```

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.&#x20;

```dart
  Future<void> createUser() async {
    if (_formKey.currentState!.validate()) {
      _formKey.currentState!.save();
      await _userService.createUser(
        name: name,
        email: email,
        password: password,
      );
      fetchUsers();
      ScaffoldMessenger.of(
        context,
      ).showSnackBar(SnackBar(content: Text('User created')));
    }
  }
```

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.

&#x20;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.&#x20;

```dart
  @override
  void initState() {
    super.initState();
    fetchUsers();
  }
```

This method will ensure that we have all of the users data when the screen is initially loaded.&#x20;

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.

```dart
@override
 Widget build(BuildContext context) {
   return Scaffold(
     appBar: AppBar(title: const Text('Users')),
     body: Padding(
       padding: const EdgeInsets.all(16),
       child: Column(
         children: [

```

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.**

```dart
           // Form to create a user
           Form(
             key: _formKey,
             child: Column(
               children: [
                 TextFormField(
                   decoration: const InputDecoration(labelText: 'Name'),
                   onSaved: (val) => name = val ?? '',
                   validator:
                       (val) => val == null || val.isEmpty ? 'Required' : null,
                 ),
                 TextFormField(
                   decoration: const InputDecoration(labelText: 'Email'),
                   onSaved: (val) => email = val ?? '',
                   validator:
                       (val) => val == null || val.isEmpty ? 'Required' : null,
                 ),
                 TextFormField(
                   decoration: const InputDecoration(labelText: 'Password'),
                   obscureText: true,
                   onSaved: (val) => password = val ?? '',
                   validator:
                       (val) => val == null || val.isEmpty ? 'Required' : null,
                 ),
                 const SizedBox(height: 10),
                 ElevatedButton(
                   onPressed: createUser,
                   child: const Text('Create User'),
                 ),
               ],
             ),
           ),
```

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()'**&#x73;. 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.&#x20;

Lets now make a widget to view all the users in our database.

```dart
           const SizedBox(height: 20),
           const Divider(),
           const SizedBox(height: 10),
           const Text(
             'All Users',
             style: TextStyle(fontWeight: FontWeight.bold),
           ),
           Expanded(
             child: ListView.builder(
               itemCount: _users.length,
               itemBuilder: (context, index) {
                 final user = _users[index];
                 return ListTile(
                   title: Text(user['name']),
                   subtitle: Text(user['email']),
                 );
               },
             ),
           ),
         ],
       ),
     ),
   );
 }

```

**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.&#x20;

**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.&#x20;

### 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&#x20;

<figure><img src="https://3191976129-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FuNe0Bz4zOAJoU0XMQXnd%2Fuploads%2FJqG46QrXwLzP7AvWVjtB%2Fimage.png?alt=media&#x26;token=b07fa821-a78a-4f56-bd61-99a8f181a945" alt=""><figcaption></figcaption></figure>

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&#x20;

{% code title="user\_screen.dart" %}

```dart
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();
}

class _UserScreenState extends State<UserScreen> {
  final UserService _userService = UserService();
  final _formKey = GlobalKey<FormState>();
  List<dynamic> _users = [];
  String name = '';
  String email = '';
  String password = '';

  @override
  void initState() {
    super.initState();
    fetchUsers();
  }

  Future<void> fetchUsers() async {
    final users = await _userService.getUsers();
    setState(() {
      _users = users;
    });
  }

  Future<void> createUser() async {
    if (_formKey.currentState!.validate()) {
      _formKey.currentState!.save();
      await _userService.createUser(
        name: name,
        email: email,
        password: password,
      );
      fetchUsers();
      ScaffoldMessenger.of(
        context,
      ).showSnackBar(SnackBar(content: Text('User created')));
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Users')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            // Form to create a user
            Form(
              key: _formKey,
              child: Column(
                children: [
                  TextFormField(
                    decoration: const InputDecoration(labelText: 'Name'),
                    onSaved: (val) => name = val ?? '',
                    validator:
                        (val) => val == null || val.isEmpty ? 'Required' : null,
                  ),
                  TextFormField(
                    decoration: const InputDecoration(labelText: 'Email'),
                    onSaved: (val) => email = val ?? '',
                    validator:
                        (val) => val == null || val.isEmpty ? 'Required' : null,
                  ),
                  TextFormField(
                    decoration: const InputDecoration(labelText: 'Password'),
                    obscureText: true,
                    onSaved: (val) => password = val ?? '',
                    validator:
                        (val) => val == null || val.isEmpty ? 'Required' : null,
                  ),
                  const SizedBox(height: 10),
                  ElevatedButton(
                    onPressed: createUser,
                    child: const Text('Create User'),
                  ),
                ],
              ),
            ),
            const SizedBox(height: 20),
            const Divider(),
            const SizedBox(height: 10),
            const Text(
              'All Users',
              style: TextStyle(fontWeight: FontWeight.bold),
            ),
            Expanded(
              child: ListView.builder(
                itemCount: _users.length,
                itemBuilder: (context, index) {
                  final user = _users[index];
                  return ListTile(
                    title: Text(user['name']),
                    subtitle: Text(user['email']),
                  );
                },
              ),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => const AssignDietaryScreen(),
                  ),
                );
              },
              child: const Text('Assign Dietary Requirement'),
            ),
          ],
        ),
      ),
    );
  }
}
```

{% endcode %}

[Link](https://github.com/JackMoses3/FindingNibbles-Spike/tree/main) to Github with the Finding Nibbles Spike and instructions on how to download new files for front end.&#x20;
