Day 17: Giving Your App a Memory

Day 17: Local Data Storage:
Day 17: Giving Your App a Memory | 30-Day Flutter Blog

Day 17: Giving Your App a Memory

An app that forgets everything when you close it isn’t very smart. Today, we’ll go beyond the basics and truly understand how to give our app a reliable memory using local storage.

Why Does an App Need a Memory?

Imagine you’ve customized your favorite weather app: you’ve set your home city to Lahore, chosen Celsius over Fahrenheit, and enabled severe weather alerts. You close the app and open it an hour later to find it has reset completely—it thinks you’re in Karachi, shows Fahrenheit, and all your alerts are off. Infuriating, right? That’s an app with amnesia.

Local storage is the part of the app’s “brain” that lives on the user’s device. It’s what allows the app to remember information between sessions, creating a persistent and personalized experience. Today, we’ll master the two fundamental tools Flutter gives us for this: one for quick, simple “sticky notes” (`shared_preferences`) and another for finding the “filing cabinet” to store larger files (`path_provider`).

The Sticky Note: `shared_preferences`

Think of `shared_preferences` as your app’s junk drawer or a set of digital sticky notes. It’s designed for storing small, simple pieces of data in a **key-value** format. You give your data a unique name (the “key”) and store the information (the “value”). It’s incredibly fast for what it does, but it’s not meant for heavy lifting.

When to Use It:

  • User settings (dark mode, language preference, notification toggles).
  • A simple high score in a game.
  • A flag to check if a user has seen the onboarding tutorial.
  • A small authentication token.

When NOT to Use It:

  • Storing large amounts of data (e.g., a whole list of articles).
  • Complex, structured data (like a list of user objects).
  • Sensitive information like passwords or private keys.

Live Simulation: Saving App Settings

Interactive Settings Panel:

The Code Explained:

import 'package:shared_preferences/shared_preferences.dart';

// To Save Data
_savePrefs() async {
  // 1. Get the singleton instance of SharedPreferences.
  final prefs = await SharedPreferences.getInstance();
  
  // 2. Save values using specific types (setString, setBool, etc.)
  await prefs.setString('username', _username);
  await prefs.setBool('darkMode', _isDarkMode);
  await prefs.setInt('launchCount', _count);
}

// To Load Data
_loadPrefs() async {
  final prefs = await SharedPreferences.getInstance();
  setState(() {
    // 3. Use the null-coalescing operator '??' to provide a
    //    default value if the key doesn't exist yet.
    _username = prefs.getString('username') ?? 'Guest';
    _isDarkMode = prefs.getBool('darkMode') ?? false;
    _count = prefs.getInt('launchCount') ?? 0;
  });
}

The Filing Cabinet Assistant: `path_provider`

When sticky notes aren’t enough and you need to save an actual file—like a downloaded PDF, a user-created drawing, or a JSON log file—you need a filing cabinet. The problem is, the location of this cabinet is different on every operating system. You can’t just save a file to `C:\Users\MyStuff` because that path doesn’t exist on iOS or Android.

This is where `path_provider` becomes essential. Crucially, `path_provider` does NOT save any data. It’s an assistant that simply tells you the correct, platform-specific *address* (the file path) where you’re allowed to store certain types of files. Once you have that address, you use other tools (like `dart:io`) to actually create and write to the file.

Live Simulation: Finding Storage Locations

Find a Directory:

Click a button to see typical file paths and their purpose.

The Code Explained:

import 'package:path_provider/path_provider.dart';
import 'dart:io'; // Needed to use the File class

// For files the user generates that should be kept.
_getDocsPath() async {
  final directory = await getApplicationDocumentsDirectory();
  return directory.path;
}

// For temporary cache files the OS can delete.
_getTempPath() async {
  final directory = await getTemporaryDirectory();
  return directory.path;
}

// How you'd use it to save a file:
_saveLogFile() async {
  // 1. Get the path from the provider.
  final path = await _getDocsPath();
  // 2. Create a File object at that location.
  final file = File('$path/user_log.txt');
  // 3. Write to the file using dart:io.
  await file.writeAsString('User logged in at ${DateTime.now()}');
}

Which One Should I Use? A Quick Guide

It can be confusing at first. Here’s a simple cheat sheet to help you decide which tool is right for the job.

Feature `shared_preferences` `path_provider` + `dart:io`
Analogy 📝 Sticky Notes 🗄️ Filing Cabinet
Data Type Simple key-value pairs (String, bool, int, double) Any kind of file (text, JSON, images, PDF, etc.)
Data Size Small (a few KBs) Small to very large (MBs or GBs)
Primary Use Case Storing user settings and simple flags. Saving user-generated content or large cached data.
Example `prefs.setBool(‘darkMode’, true);` `File(‘$path/profile.jpg’).writeAsBytes(…)`
Share