📱 Flutter for OpenHarmony 第三方库实战:本地存储 SharedPreferences 完整实现(记住登录状态)✨

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net


写在前面

哈喽大家好!我还是那个正在学习 Flutter for OpenHarmony 的大一学生~
上两篇文章,我们已经完整实现了:

  1. Provider 状态管理(用户/商品/购物车数据统一管理)
  2. 下拉刷新 + 上拉加载更多(列表分页加载)

今天我们继续完成任务清单里的 第2个任务:本地存储(SharedPreferences),实现 App 开发中最常用的「记住登录状态」功能,让用户下次打开应用时不用重复登录,直接进入首页!

整篇文章依旧是小白友好、步骤超详细、代码可直接复制、鸿蒙真机验证通过,完全符合征文规范,写完就能直接发布~


一、本篇你能学到什么?🎯

  • 如何在 Flutter 项目中引入 shared_preferences 依赖
  • 封装通用本地存储服务,统一管理所有存储逻辑
  • 实现「记住我」登录状态持久化,重启应用自动登录
  • 结合 Provider 状态管理,实现数据与存储的联动
  • 鸿蒙平台本地存储权限与适配注意事项
  • 完整的错误处理与状态恢复逻辑
  • 可直接复用到你的电商/社区/工具类鸿蒙项目中

二、技术栈与环境说明 🧰

  • Flutter 版本:3.16.0-ohos(鸿蒙适配稳定版)
  • OpenHarmony SDK:API 10
  • 状态管理:provider: ^6.1.2(上篇已集成)
  • 本地存储:shared_preferences: ^2.2.2(已兼容鸿蒙)
  • 开发工具:VS Code + DevEco Studio 4.0
  • 测试设备:OpenHarmony 开发板 / 模拟器

三、为什么要做本地存储?🤔

很多刚入门的同学会觉得:“登录状态存内存里不就行了?”
但真实场景下,这样会有很多问题:

  1. App 重启后登录状态丢失,用户每次打开都要重新登录,体验极差
  2. 应用被系统后台杀死后,状态无法恢复
  3. 无法实现「记住我」「自动登录」这类用户友好的功能
  4. 无法存储用户偏好设置(如主题、语言、上次浏览位置)

shared_preferences 是 Flutter 生态中最常用的轻量级本地存储方案,完美适配 OpenHarmony 平台,专门用来存储简单的键值对数据,比如登录状态、用户偏好、配置项等,对新手非常友好!


四、功能整体设计思路 📝

我们要实现一个完整的「记住登录状态」流程:

  1. 首次登录时,根据用户勾选的「记住我」选项,决定是否保存登录信息
  2. 应用启动时,读取本地存储的登录状态,如果存在且勾选了「记住我」,则自动恢复登录
  3. 用户退出登录时,清除本地存储的登录信息
  4. 所有存储逻辑封装成统一的 StorageService,和业务代码解耦
  5. 结合 Provider,实现状态变化与本地存储的自动同步

五、完整代码实现(超详细,逐行讲解)🚀

第一步:添加 shared_preferences 依赖

打开项目根目录的 pubspec.yaml 文件,在 dependencies 中添加依赖:

dependencies:
  flutter:
    sdk: flutter
  provider: ^6.1.2          # 上篇已添加的状态管理库
  dio: ^5.4.0               # 网络请求库
  shared_preferences: ^2.2.2 # 鸿蒙兼容的本地存储库

添加完成后,在终端执行依赖拉取命令:

flutter pub get

💡 鸿蒙适配注意:shared_preferences: ^2.2.2 及以上版本已通过 OpenHarmony TPC 社区适配验证,可直接使用,无需额外修改平台代码。


第二步:封装通用本地存储服务

lib/ 目录下新建 services 文件夹,创建 storage_service.dart 文件,统一封装所有本地存储逻辑:

import 'package:shared_preferences/shared_preferences.dart';

/// 本地存储服务封装类
/// 统一管理所有键值对存储逻辑,避免业务代码直接操作 SharedPreferences
class StorageService {
  // 单例模式,全局唯一实例
  static final StorageService _instance = StorageService._internal();
  factory StorageService() => _instance;
  StorageService._internal();

  late SharedPreferences _prefs;

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

  // ------------------- 登录状态相关存储 -------------------
  static const String _keyIsLoggedIn = "is_logged_in";
  static const String _keyRememberMe = "remember_me";
  static const String _keyUsername = "username";
  static const String _keyUserId = "user_id";

  /// 保存登录状态
  Future<void> saveLoginState({
    required bool isLoggedIn,
    required bool rememberMe,
    required String username,
    required String userId,
  }) async {
    await _prefs.setBool(_keyIsLoggedIn, isLoggedIn);
    await _prefs.setBool(_keyRememberMe, rememberMe);
    await _prefs.setString(_keyUsername, username);
    await _prefs.setString(_keyUserId, userId);
  }

  /// 清除登录状态(退出登录时调用)
  Future<void> clearLoginState() async {
    await _prefs.remove(_keyIsLoggedIn);
    await _prefs.remove(_keyRememberMe);
    await _prefs.remove(_keyUsername);
    await _prefs.remove(_keyUserId);
  }

  /// 获取保存的登录状态
  bool get isLoggedIn => _prefs.getBool(_keyIsLoggedIn) ?? false;

  /// 获取是否勾选了「记住我」
  bool get rememberMe => _prefs.getBool(_keyRememberMe) ?? false;

  /// 获取保存的用户名
  String get savedUsername => _prefs.getString(_keyUsername) ?? "";

  /// 获取保存的用户ID
  String get savedUserId => _prefs.getString(_keyUserId) ?? "";

  // ------------------- 其他通用存储方法 -------------------
  /// 保存字符串
  Future<void> setString(String key, String value) async {
    await _prefs.setString(key, value);
  }

  /// 获取字符串
  String? getString(String key) {
    return _prefs.getString(key);
  }

  /// 保存布尔值
  Future<void> setBool(String key, bool value) async {
    await _prefs.setBool(key, value);
  }

  /// 获取布尔值
  bool? getBool(String key) {
    return _prefs.getBool(key);
  }

  /// 删除指定键
  Future<void> remove(String key) async {
    await _prefs.remove(key);
  }

  /// 清空所有存储数据
  Future<void> clearAll() async {
    await _prefs.clear();
  }
}

💡 设计亮点:

  • 采用单例模式,全局唯一存储实例,避免重复初始化
  • 统一封装键名,避免业务代码中出现魔法字符串
  • 所有存储操作封装成方法,业务代码直接调用,后续如果更换存储方案,只需修改这里即可
  • 预留了通用存储方法,后续可以扩展存储用户偏好、设置等数据

第三步:修改 UserProvider,支持登录状态持久化

打开 lib/providers/user_provider.dart 文件,修改用户状态管理类,加入本地存储联动逻辑:

import 'package:flutter/foundation.dart';
import '../services/storage_service.dart';

// 用户模型类
class User {
  final String id;
  final String username;
  final String avatar;

  User({
    required this.id,
    required this.username,
    required this.avatar,
  });
}

class UserProvider extends ChangeNotifier {
  User? _currentUser;
  bool _isLoggedIn = false;
  bool _rememberMe = false; // 记住我选项状态

  User? get currentUser => _currentUser;
  bool get isLoggedIn => _isLoggedIn;
  bool get rememberMe => _rememberMe;

  /// 应用启动时恢复登录状态
  Future<void> restoreLoginState() async {
    final storage = StorageService();
    // 如果勾选了记住我,且保存了登录状态
    if (storage.rememberMe && storage.isLoggedIn) {
      _currentUser = User(
        id: storage.savedUserId,
        username: storage.savedUsername,
        avatar: "https://picsum.photos/200",
      );
      _isLoggedIn = true;
      _rememberMe = true;
      notifyListeners();
    }
  }

  /// 登录方法,支持记住我选项
  Future<bool> login(String username, String password, bool rememberMe) async {
    try {
      // 模拟网络请求延迟
      await Future.delayed(const Duration(seconds: 1));

      // 测试账号:test / 123456
      if (username == "test" && password == "123456") {
        _currentUser = User(
          id: "1001",
          username: username,
          avatar: "https://picsum.photos/200",
        );
        _isLoggedIn = true;
        _rememberMe = rememberMe;

        // 如果勾选了记住我,保存登录状态
        if (rememberMe) {
          await StorageService().saveLoginState(
            isLoggedIn: true,
            rememberMe: true,
            username: username,
            userId: "1001",
          );
        } else {
          // 不勾选记住我,清除之前的保存状态
          await StorageService().clearLoginState();
        }

        notifyListeners();
        return true;
      } else {
        return false;
      }
    } catch (e) {
      if (kDebugMode) {
        print("登录出错:$e");
      }
      return false;
    }
  }

  /// 退出登录方法,清除本地存储状态
  Future<void> logout() async {
    _currentUser = null;
    _isLoggedIn = false;
    _rememberMe = false;
    // 清除本地存储的登录信息
    await StorageService().clearLoginState();
    notifyListeners();
  }

  /// 更新记住我选项状态(未登录时勾选/取消勾选)
  void setRememberMe(bool value) {
    _rememberMe = value;
    notifyListeners();
  }
}

💡 关键修改说明:

  1. 新增 restoreLoginState() 方法,应用启动时从本地存储恢复登录状态
  2. 修改 login() 方法,增加 rememberMe 参数,根据用户选择决定是否保存登录信息
  3. 修改 logout() 方法,退出登录时清除本地存储的登录状态
  4. 新增 setRememberMe() 方法,支持用户在登录页面修改记住我选项

第四步:修改 main.dart,初始化存储服务

打开 lib/main.dart 文件,在应用启动时初始化存储服务:

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'providers/user_provider.dart';
import 'providers/product_provider.dart';
import 'providers/cart_provider.dart';
import 'services/storage_service.dart';
import 'pages/home_page.dart';

void main() async {
  // 确保 Flutter 绑定初始化完成,再调用异步方法
  WidgetsFlutterBinding.ensureInitialized();
  // 初始化本地存储服务
  await StorageService().init();
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => UserProvider()),
        ChangeNotifierProvider(create: (_) => ProductProvider()),
        ChangeNotifierProvider(create: (_) => CartProvider()),
      ],
      child: MaterialApp(
        title: '鸿蒙电商 Demo',
        theme: ThemeData(primarySwatch: Colors.blue),
        home: const HomePage(),
        debugShowCheckedModeBanner: false,
      ),
    );
  }
}

💡 注意:WidgetsFlutterBinding.ensureInitialized() 必须在 main() 开头调用,否则无法在 main() 中执行异步初始化方法。


第五步:修改登录页面,加入记住我选项

打开 lib/pages/home_page.dart 文件,修改登录页面,添加「记住我」复选框:

// 在登录页面的 build 方法中,添加记住我复选框
Column(
  children: [
    // 用户名输入框
    TextField(
      controller: _usernameController,
      decoration: const InputDecoration(
        labelText: '用户名',
        border: OutlineInputBorder(),
      ),
    ),
    const SizedBox(height: 16),
    // 密码输入框
    TextField(
      controller: _passwordController,
      obscureText: true,
      decoration: const InputDecoration(
        labelText: '密码',
        border: OutlineInputBorder(),
      ),
    ),
    const SizedBox(height: 8),
    // 记住我复选框
    Consumer<UserProvider>(
      builder: (context, userProvider, child) {
        return Row(
          children: [
            Checkbox(
              value: userProvider.rememberMe,
              onChanged: (value) {
                userProvider.setRememberMe(value ?? false);
              },
            ),
            const Text('记住我(下次自动登录)'),
          ],
        );
      },
    ),
    const SizedBox(height: 16),
    // 登录按钮
    SizedBox(
      width: double.infinity,
      child: ElevatedButton(
        onPressed: _isLoading
            ? null
            : () async {
                setState(() {
                  _isLoading = true;
                });
                // 调用登录方法,传入记住我选项
                final success = await context.read<UserProvider>().login(
                      _usernameController.text,
                      _passwordController.text,
                      context.read<UserProvider>().rememberMe,
                    );
                setState(() {
                  _isLoading = false;
                });
                if (success) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(content: Text('登录成功!')),
                  );
                } else {
                  ScaffoldMessenger.of(context).showSnackBar(
                    const SnackBar(content: Text('用户名或密码错误,请用 test / 123456 测试')),
                  );
                }
              },
        child: _isLoading
            ? const CircularProgressIndicator(color: Colors.white)
            : const Text('登录'),
      ),
    ),
  ],
)

同时,在 HomePageinitState 中,添加恢复登录状态的逻辑:


void initState() {
  super.initState();
  // 页面初始化时,恢复登录状态
  WidgetsBinding.instance.addPostFrameCallback((_) {
    context.read<UserProvider>().restoreLoginState();
  });
}

第六步:鸿蒙平台适配注意事项

shared_preferences 在 OpenHarmony 平台上的适配非常简单,无需额外配置权限,即可正常使用。但有两个小细节需要注意:

  1. 数据存储位置:在鸿蒙设备上,shared_preferences 的数据会存储在应用私有目录下,其他应用无法访问,安全可靠
  2. 数据持久化:应用卸载时,本地存储的数据会被自动清除,符合鸿蒙系统的安全规范
  3. 模拟器与真机一致性:鸿蒙模拟器和真机上的存储行为完全一致,无需额外适配

六、运行与真机测试 ✅

  1. 执行命令拉取依赖

    flutter pub get
    
  2. 连接鸿蒙开发板 / 打开模拟器

  3. 运行项目

    flutter run
    

    在这里插入图片描述

  4. 测试完整流程:

    • ✅ 首次登录:输入 test / 123456,勾选「记住我」,点击登录,登录成功
    • ✅ 退出应用:完全关闭应用,重新打开,直接显示已登录状态,无需重新输入账号密码
    • ✅ 退出登录:点击退出登录,本地存储的登录状态被清除,下次打开需要重新登录
    • ✅ 不勾选记住我:登录后关闭应用,重新打开需要重新登录
    • ✅ 鸿蒙真机运行:存储状态稳定,重启设备后依然能恢复登录

七、核心原理通俗讲解(小白必看)💡

很多同学会好奇:shared_preferences 到底是怎么实现本地存储的?
其实原理非常简单:

  1. shared_preferences 会把数据以键值对的形式,写入设备本地的 XML 文件中
  2. 应用启动时,读取这个 XML 文件,把数据加载到内存中
  3. 我们通过封装好的 setBoolgetString 等方法,读写这些键值对
  4. 数据会持久化保存在设备上,直到应用被卸载或主动清除数据

而我们实现的「记住登录状态」流程,就是把用户的登录信息和记住我选项,通过 shared_preferences 保存到本地,应用启动时再读取出来,自动恢复登录状态。


八、我踩过的巨坑(帮你少走弯路)⚠️

坑1:main() 中初始化存储服务报错

  • 表现:运行时红屏报错 ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized
  • 原因:main() 中没有调用 WidgetsFlutterBinding.ensureInitialized(),就执行了异步初始化方法
  • 解决:在 main() 开头添加 WidgetsFlutterBinding.ensureInitialized();

坑2:登录状态保存后,重启应用无法恢复

  • 表现:勾选记住我登录后,重启应用还是未登录状态
  • 原因:restoreLoginState() 方法没有调用,或者调用时机不对
  • 解决:在 HomePageinitState 中,用 addPostFrameCallback 包裹调用,确保页面渲染完成后再恢复状态

坑3:退出登录后,下次打开还是登录状态

  • 表现:点击退出登录,状态变成未登录,但重启应用又恢复了登录状态
  • 原因:logout() 方法中没有调用 clearLoginState() 清除本地存储
  • 解决:在 logout() 方法中,添加 await StorageService().clearLoginState();

坑4:鸿蒙设备上存储数据丢失

  • 表现:模拟器上正常,真机上重启应用数据丢失
  • 原因:部分低版本鸿蒙系统,应用后台杀死后会清除临时数据
  • 解决:使用 shared_preferences: ^2.2.2 及以上版本,该版本已修复鸿蒙真机存储丢失的问题

九、功能扩展方向(进阶必学)🚀

如果你想做得更完善,还可以继续扩展:

  1. 存储用户设置:比如主题模式、语言设置、消息推送开关
  2. 存储用户浏览历史:记录用户上次浏览的商品、页面位置
  3. 存储购物车数据:用户退出应用后,购物车商品不会丢失
  4. 存储本地缓存数据:比如商品列表数据,无网络时也能查看
  5. 实现数据加密:敏感数据(如密码、token)加密后再存储,提升安全性

十、总结 🎉

这篇文章我们完整实现了 Flutter + OpenHarmony 平台下的本地存储功能,完成了 App 开发中最常用的「记住登录状态」场景:

  • ✅ 引入并适配鸿蒙的 shared_preferences 依赖
  • ✅ 封装通用本地存储服务,统一管理所有存储逻辑
  • ✅ 结合 Provider 状态管理,实现登录状态的持久化
  • ✅ 鸿蒙真机完美运行,重启应用自动登录
  • ✅ 完整的错误处理与状态恢复逻辑

整个代码结构清晰、易于维护,完全可以直接用在你的鸿蒙征文、课程设计、大创项目、实战 Demo 中。

作为一名正在学习 Flutter 鸿蒙开发的大一学生,我深刻感受到:Flutter 生态真的太完善了,几乎所有常见的功能,都有成熟的三方库和方案,适配鸿蒙也非常简单,学习成本低、产出效率高。


下一篇预告

接下来我还会继续完成任务清单里的其他任务:

  • 路由管理 go_router(规范化页面跳转)
  • 图片缓存 cached_network_image(优化鸿蒙性能)
  • 依赖注入 get_it(解耦业务逻辑)
  • 购物车完整功能实现

想看哪个内容,欢迎在评论区告诉我~

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐