Chapters

Hide chapters

Flutter Apprentice

First Edition - Early Access 1 · Flutter 1.20 · Dart 2.9 · Android Studio 4.0.1

Section III: Navigating Between Screens

Section 3: 3 chapters
Show chapters Hide chapters

Section V: Deployment

Section 5: 4 chapters
Show chapters Hide chapters

10. Shared Preferences
Written by Kevin D Moore

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

Picture this: you’re browsing recipes and find one you like. You’re in a hurry and want to bookmark it to check it later. Can you build a Flutter app that does that? You sure can! Read on to find out how.

In this chapter, your goal is to learn how to use shared preferences to save important pieces of information to your device.

You’ll start with a new project that shows three tabs at the bottom of the screen for three different views: Recipes, Bookmarks and Groceries.

The first screen is where you’ll search for recipes you want to prepare. Once you find a recipe you like, just bookmark it and the app will add the recipe to your Bookmarks page and also add all the ingredients you need to your shopping list. You’ll use a web API to search for recipes and store the ones you bookmark in a local database.

The completed app will look something like:

This shows the Recipes tab with the results you get when searching for “Pasta”. It’s as easy as typing in the search text field and pressing the Search icon. The app stores your search term history in the combo box to the right of the text field.

When you tap a card, you’ll see something like:

To save a recipe, just tap the Bookmark button. When you navigate to the Bookmarks tab, you’ll see that the recipe has been saved:

If you don’t want the recipe any more, swipe left or right and you’ll see a delete button that allows you to remove it from the list of bookmarked recipes.

The Groceries tab shows the ingredients you need to make the recipes you’ve bookmarked.

You’ll build this app over the next few chapters. In this chapter, you’ll use shared preferences to save simple data like the selected tab and also to cache the searched items in the Recipes tab.

By the end of the chapter, you’ll know:

  • What shared preferences are.
  • How to use the shared_preferences plugin to save and retrieve objects.

Now that you know what your goal is, it’s time to jump in!

Getting started

Open the starter project for this chapter in Android Studio, run flutter pub get if necessary, then run the app.

Notice the three tabs at the bottom — each will show a different screen when you tap it. Only the Recipes screen currently has any UI showing. It looks like this:

App libraries

The starter project includes the following libraries in pubspec.yml:

dependencies:
  ...
  cached_network_image: ^2.3.2+1
  flutter_slidable: ^0.5.7
  flutter_svg: ^0.19.0
  flutter_statusbarcolor: ^0.2.3

Saving data

There are three primary ways to save data to your device:

Why save small bits of data?

There are many reasons to save small bits of data. For example, you could save the user ID when the user has logged in — or if the user has logged in at all. You could also save the onboarding state or data that the user has bookmarked to consult later.

SharedPreferences

shared_preferences is a Flutter plugin that allows you to save data in a key-value format so you can easily retrieve it later. Behind the scenes, it uses the aptly named SharedPreferences on Android and the similar UserDefaults on iOS.

  shared_preferences: ">=0.5.8 <2.0.0"

flutter pub get

Saving UI states

You’ll use shared_preferences to save a list of saved searches in this section. Later, you’ll also save the tab that the user has selected so the app always opens to that tab.

Adding an entry to the search list

First, you’ll change the UI so that when the user presses the search icon, the app will add the search entry to the search list.

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class _RecipeListState extends State<RecipeList> {
  static const String prefSearchKey = "previousSearches";
bool inErrorState = false;
List<String> previousSearches = List<String>();

Running code in the background

To understand the code you’ll add next, you need to know a bit about running code in the background.

Saving previous searches

Now that you’ve laid some groundwork, you’re ready to implement saving the searches.

void savePreviousSearches() async {
  // 1
  SharedPreferences prefs = await SharedPreferences.getInstance();
  // 2
  prefs.setStringList(prefSearchKey, previousSearches);
}
void getPreviousSearches() async {
  // 1
  SharedPreferences prefs = await SharedPreferences.getInstance();
  // 2
  if (prefs.containsKey(prefSearchKey)) {
    // 3
    previousSearches = prefs.getStringList(prefSearchKey);
    // 4
    if (previousSearches == null) {
      previousSearches = List<String>();
    }
  }
}
getPreviousSearches();

Adding a button to save a search

Next, you’ll give the user the power to save their searches by adding a save button.

    IconButton(
      icon: Icon(Icons.search),
      // 1
      onPressed: () {
        // 2
        startSearch(searchTextController.text);
      },
    ),
    SizedBox(
      width: 6.0,
    ),
    Expanded(
      child: Row(
        children: <Widget>[
            ],
          ),
        ),
      ],
    ),
  ),
);
          Expanded(
            // 3
            child: TextField(
              decoration: InputDecoration(
                border: InputBorder.none, hintText: 'Search'),
              autofocus: false,
              // 4
              textInputAction: TextInputAction.done,
              // 5
              onSubmitted: (value) {
                if (!previousSearches.contains(value)) {
                  previousSearches.add(value);
                  savePreviousSearches();
                }
              },
              controller: searchTextController,
            )),
          // 6
          PopupMenuButton<String>(
            icon: const Icon(Icons.arrow_drop_down),
            // 7
            onSelected: (String value) {
              searchTextController.text = value;
              startSearch(searchTextController.text);
            },
            itemBuilder: (BuildContext context) {
              // 8
              return previousSearches
                .map<CustomDropdownMenuItem<String>>((String value) {
                  return CustomDropdownMenuItem<String>(
                    text: value,
                    value: value,
                     callback: () {
                        setState(() {
                          previousSearches.remove(value);
                          Navigator.pop(context);
                        });
                      },
                  );
                }).toList();
                },
              ),

Adding the search functionality

Finally, you need to make the search work. Do this by adding startSearch() after _buildSearchCard():

void startSearch(String value) {
  // 1
  setState(() {
    // 2
    currentSearchList.clear();
    currentCount = 0;
    currentEndPosition = pageCount;
    currentStartPosition = 0;
    hasMore = true;
    // 3
    if (!previousSearches.contains(value)) {
      // 4
      previousSearches.add(value);
      // 5
      savePreviousSearches();
    }
  });
}

Test the app

It’s time to test the app. Because you added a new dependency, quit the running instance and run it again. You’ll see something like this:

Saving the selected tab

In this section, you’ll use saved_preferences to save the current UI tab that the user has navigated to.

import 'package:shared_preferences/shared_preferences.dart';
List<Widget> pageList = List<Widget>();
static const String prefSelectedIndexKey = "selectedIndex";
void saveCurrentIndex() async {
  // 1
  SharedPreferences prefs = await SharedPreferences.getInstance();
  // 2
  prefs.setInt(prefSelectedIndexKey, _selectedIndex);
}
void getCurrentIndex() async {
  // 1
  SharedPreferences prefs = await SharedPreferences.getInstance();
  // 2
  if (prefs.containsKey(prefSelectedIndexKey)) {
    // 3
    setState(() {
      _selectedIndex = prefs.getInt(prefSelectedIndexKey);
    });
  }
}
getCurrentIndex();
saveCurrentIndex();

Key points

  • There are multiple way to save data in an app, including to files, in shared preferences, and to a SQLite database.
  • Shared preferences are best used to store simple, key-value pairs of primitive types like strings, numbers, and booleans.
  • An example use of shared preferences is saving the currently selected tab that a user is viewing, so that the next time the user starts the app, they’re brought to the same tab.
  • The async/await keyword pair let you run asynchronous code off the main UI thread and then wait for the response. An example is getting an instance of the SharedPreferences class.
  • The shared_preferences plugin should not be used to hold sensitive data. Instead, consider using the flutter_secure_storage plugin.

Where to go from here?

In this chapter, you learned how to persist simple data types in your app using the shared_preferences plugin. If you want to learn more about Android SharedPreferences, go to https://developer.android.com/reference/kotlin/android/content/SharedPreferences?hl=en. For iOS, go to https://developer.apple.com/documentation/foundation/userdefaults.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now