Saving Data in Flutter

Jan 31 2024 · Dart 3, Flutter 3.10, Visual Studio Code

Part 2: Use SharedPreferences & Secure Storage

07. Connect the User Interface to the Data (Part 2)

Episode complete

Play next episode

Next
About this episode
Leave a rating/review
See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 06. Connect the User Interface to the Data (Part 1) Next episode: 08. Challenge: Add a Field

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Transcript: 07. Connect the User Interface to the Data (Part 2)

OK, we are now ready to build the user interface for the settings screen.

So in the build method, let’s return a Scaffold, whose appBar take an AppBar widget, whose title contains a constant text with “Settings”.

In the body of the Scaffold, we’ll return a ListView, that as you might remember is a scrollable list of widgets arranged one under the other by default.

The ListView has a padding property to create some space between the listview itself and its content. Here let’s set it to take an EdgeInsets.all of 16.0 device independent pixels.

In the children property of the listView let’s add the widgets that will show the settings: two text fields for the name of the list and the calories, and a SwitchListTile for the “Show file size option”.

Let’s begin with the list name, as the first child of the ListView. Here let’s pace a padding to create some more space, and set its padding to EdgeInsets.all with a value of 8. Its child is a TextField, whose controller is _listNameController.

Let’s add a decoration here, that takes an inputdecoration. We want to write a label to tell the user what we expect him to type into this TextField, so the labelText takes “List name”. Let’s also set a labelStyle here: this takes a TextStyle, whose fontSize is fontSize.

When user changes the values of this input, we want to update the _listName field value. So in the onchanged method, just call setState, and into this let’s type_listName is equal to value.

Padding( 
  padding: const EdgeInsets.all(8.0), 
  child: TextField( 
    controller: _listNameController, 
    decoration: InputDecoration( 
        labelText: 'List Name', 
        labelStyle: TextStyle(fontSize: fontSize)), 
    onChanged: (value) { 
      setState(() { 
        _listName = value; 
      }); 
    }, 
  ), 
),

Let’s copy this widget for the calories: in VS code you can just select the Padding widget, then press alt and arrow down to copy and paste with a single shortcut.

Here we just need to change the controller, that takes caloriesController, the labelText, that we can set to Maximum Daily Calories, and the instruction in the setState method. This will set the calories to take int.tryParse(value), and if this is null, just 0. Using int.tryparse here is a good idea, as you can never trust users to write a valid value in an input…

By the way, we can also add the keyboardType here, and this takes TextInputType.number: in this way we can further avoid an invalid input from our user.

child: TextField( 
  controller: _caloriesController, 
  keyboardType: TextInputType.number, 
  decoration: InputDecoration( 
      labelText: 'Maximum Daily Calories', 
      labelStyle: TextStyle(fontSize: fontSize)), 
  onChanged: (value) { 
    setState(() { 
      _calories = int.tryParse(value) ?? 0; 
    }); 
  }, 
), 

Finally, let’s add a SwitchListTile to our screen. Its title takes a Text with ‘Show File Size’, and its style takes a TextStyle whose fontSize is fontSize. Its value is _showFileSize, and when this changes, so in the onchaged callback, we can just call setState, and set the _showFileSize field to take the updated value.

SwitchListTile( 
    title: Text('Show File Size', style: TextStyle(fontSize: fontSize)), 
    value: _showFileSize, 
    onChanged: (bool value) { 
      setState(() { 
        _showFileSize = value; 
      }); 
    }, 
  ), 

There’s one last step before we complete this screen: now we are reading all the values from SharedPreferences, but we also need to write the values to sharedpreferences when our user makes some changes to the settings data.

So let’s create a new method, that returns a Future, and let’s call it _saveSettings. Let’s also mark it as async.

Here we want to write to our SharedPreferences the content of the 2 text fields, and the value of the switchlistTile.

So, first let’s retrieve the sharedpreferences instance, with

final prefs = await SPHelper.getInstance(); 

Remember, all writing actions are asynchronous, so let’s await prefs.setListName, passing the content of the _listNameController text property.

await prefs.setListName(_listNameController.text); 

Let’s repeat the same for the setCalories method: for the argument, here we’ll pass int.tryParse to transform the _caloriesController.text into an integer, and if this fails, let’s just pass a 0. As you may remember, the double question mark is an “if null” operator: what happens here is that only if the tryParse returns null, this expression will return 0, otherwise it will just return the result of the tryParse method.

await prefs.setCalories(int.tryParse(_caloriesController.text) ?? 0); 

OK, next let’s also write the boolean value that stores whether we want to show the file size: here we can call our setShowFIleSize method, and pass the _showFileSize state variable.

await prefs.setShowFileSize(_showFileSize); 

Let’s also give some feedback to our users, so that they know the settings have been saved: we can show a snackbar here, calling the showSnackbar method over ScaffoldMessenger.of(context) (this retrieves the current context). Inside the showSnackbar method, let’s pass a snackbar, whose content is a constant text widget with “Settings saved”

ScaffoldMessenger.of(context).showSnackBar( 
      const SnackBar(content: Text('Settings saved!')), 
    ); 

This completes the saveSettings method. But in this moment this method is never called, so we need to give our users the option to save their settings: we could add a button to our listView, or we could also save the settings automatically when our users change screen. But I think a good option here might be to use a FloatingActionButton. According to the material design specifications, this represents the main action in a screen, and saving the settings here might well be considered the main action for this screen. So at the bottom of the Scaffold, let’s set the floatingActionButton to take a FloatingActionButton.

In its onPressed argument, lets’ pass the _saveSettings method, and for its child let’s pass an Icon, that contains the save icon.

OK, this screen is now ready! We just need to call it from MyApp. So, in the main.dart file, in the build method of MyApp, let’s pass our settingsScreen.

Let’s have a look at our app so far: as you can see, everything looks ok. Let’s set the list name to take “My Veggy Recipes”, and the maximum calories to 2500. Let’s also change the show file size value, and save everything. Now if we hot restart our app, you can see that the values have been saved! This means that SharedPreferences is working as expected, and we can read and write values to it! Well done!

Are you ready for a challenge? Let’s see it next.