30天计划第13天-本地存储(SharedPreferences)

-
-
2025-11-16 04:44
  • 学习内容

    1. 安装 SharedPreferences 依赖

    2. 本地存储操作:存数据(setString/setInt)、取数据(getString)、删数据(remove

  • 实践任务

    1. 优化登录页:登录成功后,本地存储 “用户名”

    2. App 启动时,读取本地存储:若有用户名则直接进入首页,否则进入登录页

一、SharedPreferences简介与环境准备

1. 什么是SharedPreferences

SharedPreferences是一个简单的、异步的、键值对(Key-Value)存储方案。它适用于存储少量的、简单的数据类型,例如:

  • 用户偏好设置(如主题、语言)。

  • 登录令牌(Token)或用户名。

  • 应用状态(如:是否首次启动)。

注意:它不适合存储大量数据或敏感信息(如:密码)。对于密码等敏感数据,应使用更安全的存储方案,如:flutter_secure_storage。

数据存储位置:

  • Android:SharedPreferences会存储在应用的私有SharedPreferences目录下的XML文件中。

  • iOS:会存储在应用的NSUserDefaults中。

2. 安装SharedPreferences依赖

在pubspec.yaml文件中添加shared_preferences依赖:

dependencies:
  flutter:
    sdk: flutter
  # 添加 shared_preferences 依赖
  shared_preferences: ^2.5.3  # 请使用 pub.dev 上的最新版本

在终端执行命令安装依赖:

flutter pub get

在需要使用的文件中导入:

import 'package:shared_preferences/shared_preferences.dart';

二、SharedPreferences基础操作

SharedPreferences的所有操作都是异步的,因此需要使用async/await。

1. 获取SharedPreferences实例

在进行任何操作之前,你需要先获取一个SharedPreferences的实例。通常,我们会用一个静态方法getInstance()来获取。

final prefs = await SharedPreferences.getInstance();

为了方便,可以创建一个工具类来封装这些操作。

推荐实践:创建一个存储工具类

创建一个新文件:lib/services/storage_service.dart

import 'package:shared_preferences/shared_preferences.dart';

class StorageService {
  // 私有构造函数
  StorageService._();

  //单例模式
  static final StorageService instance = StorageService._();

  // SharedPreferences实例
  late final SharedPreferences _prefs;

  // 初始化,在应用启动时调用
  Future<void> init()  async {
    _prefs = await SharedPreferences.getInstance();
  }

  // --- 通用方法 ---

  // 存储数据
  Future<bool> setValue(String key, dynamic value) async {
    if (value is String) {
      return await _prefs.setString(key, value);
    } else if (value is int) {
      return await _prefs.setInt(key, value);
    } else if (value is bool) {
      return await _prefs.setBool(key, value);
    } else if (value is double) {
      return await _prefs.setDouble(key, value);
    }

    throw UnsupportedError('不支持的类型');
  }

  // 获取数据
  T? getValue<T>(String key) {
    try {
      if (T == String) {
        return _prefs.getString(key) as T?;
      } else if (T == int) {
        return _prefs.getInt(key) as T?;
      } else if (T == bool) {
        return _prefs.getBool(key) as T?;
      } else if (T == double) {
        return _prefs.getDouble(key) as T?;
      }
    } catch (e) {
      return null;
    }

    throw UnsupportedError('不支持的类型');
  }

  // 删除数据
  Future<bool> remove(String key) async {
    return await _prefs.remove(key);
  }

  // 清空所有数据
  Future<bool> clear() async {
    return await _prefs.clear();
  }

  // --- 业务相关方法 ---
}

使用工具类的好处:

  • 集中管理:所有存储操作都在一个地方,便于维护和修改。
  • 代码简洁:在业务逻辑中调用时更加清晰。
  • 单例模式:确保整个应用中使用的是同一个SharedPreferences实例。

2. 在main.dart中初始化

为了确保StoreService在应用的其他部分使用之间已经初始化,最好在main函数中进行初始化。

import 'services/storage_service.dart';

void main() async {
  // 确保Flutter绑定已初始化
  WidgetsFlutterBinding.ensureInitialized();
  
  // 初始化存储服务
  await StorageService.instance.init();
  
  runApp(const App());
}

三、综合实战

1. 任务需求

  • 登录成功后:在SharedPreferences中存储用户名。
  • 应用启动时:LoginPage在构建时检查SharedPreferences中是否存在用户名。
    • 如果存在:自动跳转到HomePage。
    • 如果不存在:显示正常的登录界面。
  • 退出登录时:从SharedPreferences 中删除用户名,并跳转回LoginPage。

2. 代码实现

  • 首页(home_page.dart)

    // /pages/home_page.dart
    import 'package:flutter/material.dart';
    import 'login_page.dart';
    import '../services/storage_services.dart';
    
    
    class HomePage extends StatefulWidget {
      final String username;
    
      const HomePage({super.key, required this.username});
    
      @override
      State<HomePage> createState() =>  _HomePageState();
    }
    
    class _HomePageState extends State<HomePage> {
      Future<void> _handleLogout() async {
        // 退出登录时,删除本地存储的用户名
        await StorageService.instance.removeUsername();
    
        // 跳转到登录页
        if (mounted) {
          Navigator.pushReplacement(
            context,
            MaterialPageRoute(builder: (context) => const LoginPage()));
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('首页'),
            actions: [
              IconButton(
                icon: const Icon(Icons.logout, color: Colors.black,),
                onPressed: _handleLogout,
                tooltip: '退出登录',
              )
            ],
          ),
          body: Center(
            child: Text(
              '欢迎回来,${widget.username}!',
              style: Theme.of(context).textTheme.headlineMedium,
              textAlign: TextAlign.center,
            ),
          ),
        );
      }
    }
  • 登录页(login_page.dart)

    // /pages/login_page.dart
    import 'package:flutter/material.dart';
    import 'home_page.dart';
    import '../services/storage_services.dart';
    
    
    class LoginPage extends StatefulWidget {
      const LoginPage({super.key});
    
      @override
      State<LoginPage> createState() => _LoginPageState();
    }
    
    class _LoginPageState extends State<LoginPage> {
      final TextEditingController _usernameController  = TextEditingController();
      final TextEditingController _passwordCOntroller = TextEditingController();
      final _formKey = GlobalKey<FormState>();
    
      // 标记是否正在检查登录状态
      bool _isCheckingLoginStats = true;
    
      @override
      void initState() {
        super.initState();
    
        // 页面初始化时,检查是否已登录
        _checkAutoLogin();
      }
    
      // 检查是否可以自动登录
      Future<void> _checkAutoLogin() async {
        // 从本地存储获取用户名
        String? username = StorageService.instance.getUsername();
    
        if (username != null && username.isNotEmpty) {
          // 如果存在用户名,则自动跳转到首页
          if (mounted) { // 确保Widget还在树中
            // 延迟到当前帧构建完成后再执行路由跳转
            WidgetsBinding.instance.addPostFrameCallback((_) {
              Navigator.pushReplacement(
                context,
                MaterialPageRoute(builder: (context) => HomePage(username: username)));
            });
          }
        } else {
          // 如果不存在,则显示登录界面
          setState(() {
            _isCheckingLoginStats = false;
          });
        }
      }
    
      Future<void> _handleLogin() async {
        if (_formKey.currentState!.validate()) {
          String username = _usernameController.text;
          String password = _passwordCOntroller.text;
    
          // 这里可以添加真实的登录验证逻辑
          if (password.isNotEmpty) {
            // 登录成功,存储用户名
            await StorageService.instance.saveUsername(username);
    
            // 跳转到首页
            if (mounted) {
              Navigator.pushReplacement(
                context,
                MaterialPageRoute(builder: (context) => HomePage(username: username)),
              );
            }
          } else {
            if (mounted) {
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(content: Text('密码不能为空'))
              );
            }
          }
        }
      }
    
      @override
      Widget build(BuildContext context) {
        // 在检查登录状态时,可以显示一个加载指示器
        if (_isCheckingLoginStats) {
          return const Scaffold(
            body: Center(child: CircularProgressIndicator(),),
          );
        }
    
        return Scaffold(
          appBar: AppBar(title: const Text('登录'),),
          body: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Form(
              key: _formKey,
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  TextFormField(
                    controller: _usernameController,
                    decoration: const InputDecoration(
                      labelText: '用户名',
                      hintText: '请输入用户名',
                    ),
                    validator: (value) {
                      if (value == null || value.isEmpty) {
                        return '请输入用户名';
                      }
                      return null;
                    },
                  ),
                  const SizedBox(height: 16,),
                  TextFormField(
                    controller: _passwordCOntroller,
                    obscureText: true,
                    decoration: const InputDecoration(
                      labelText: '密码',
                      hintText: '请输入密码'
                    ),
                    validator: (value) {
                      if (value == null || value.isEmpty) {
                        return '请输入密码';
                      }
                      return null;
                    },
                  ),
                  const SizedBox(height: 32),
                  ElevatedButton(
                    onPressed: _handleLogin,
                    child: const Text('登录'),
                  )
                ],
              ),
            ),
          ),
        );
      }
    }
  • 存储工具类(storage_services.dart)

    // /services/storage_services.dart
    import 'package:shared_preferences/shared_preferences.dart';
    
    class StorageService {
      // 私有构造函数
      StorageService._();
    
      //单例模式
      static final StorageService instance = StorageService._();
    
      // SharedPreferences实例
      late final SharedPreferences _prefs;
    
      // 初始化,在应用启动时调用
      Future<void> init()  async {
        _prefs = await SharedPreferences.getInstance();
      }
    
      // --- 通用方法 ---
    
      // 存储数据
      Future<bool> setValue(String key, dynamic value) async {
        if (value is String) {
          return await _prefs.setString(key, value);
        } else if (value is int) {
          return await _prefs.setInt(key, value);
        } else if (value is bool) {
          return await _prefs.setBool(key, value);
        } else if (value is double) {
          return await _prefs.setDouble(key, value);
        }
    
        throw UnsupportedError('不支持的类型');
      }
    
      // 获取数据
      T? getValue<T>(String key) {
        try {
          if (T == String) {
            return _prefs.getString(key) as T?;
          } else if (T == int) {
            return _prefs.getInt(key) as T?;
          } else if (T == bool) {
            return _prefs.getBool(key) as T?;
          } else if (T == double) {
            return _prefs.getDouble(key) as T?;
          }
        } catch (e) {
          return null;
        }
    
        throw UnsupportedError('不支持的类型');
      }
    
      // 删除数据
      Future<bool> remove(String key) async {
        return await _prefs.remove(key);
      }
    
      // 清空所有数据
      Future<bool> clear() async {
        return await _prefs.clear();
      }
    
      // --- 业务相关方法 ---
    
      // 存储用户名
      Future<bool> saveUsername(String username) async {
        return await setValue('username', username);
      }
    
      // 获取用户名
      String? getUsername() {
        return getValue<String>('username');
      }
    
      // 删除用户名
      Future<bool> removeUsername() async {
        return await remove('username');
      }
    }
  • 主文件(main.dart)

    import 'package:flutter/material.dart';
    import 'services/storage_services.dart';
    import 'pages/login_page.dart';
    
    
    void main() async {
      // 确保Flutter绑定已初始化
      WidgetsFlutterBinding.ensureInitialized();
    
      // 初始化存储服务
      await StorageService.instance.init();
    
      runApp(const App());
    }
    
    class App extends StatelessWidget{
      const App({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'SharedPreferences Demo',
          theme: ThemeData(primarySwatch: Colors.yellow),
          home: const LoginPage(), // 初始页面设为登录页,它判断是否需要自动登录。
        );
      }
    }
  • 说明
    • mounted是State类的属性,只有在 StatefulWidget 的 State 子类中才能直接使用。
    • 在 StatelessWidget 中,用 ModalRoute.of(context)?.isCurrent ?? false 替代 mounted,判断当前页面是否还在栈顶(是否有效)。

3. 运行效果

说明:登录后,退出程序,下次再打开,依旧会保持登录状态。


目录