You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter Web:刷新浏览器后如何防止ValueNotifier被清除?

Flutter Web刷新后保留ValueNotifier状态的解决方案

ValueNotifier是内存级别的状态管理,浏览器刷新相当于重启Flutter应用,内存中的状态会被完全清除;而SharedPreferences这类持久化存储是存在浏览器本地的,所以不会丢失。要解决这个问题,核心思路是把ValueNotifier的状态和持久化存储绑定,实现状态的自动保存与恢复。

方案1:封装带持久化的ValueNotifier子类

自定义一个继承自ValueNotifier的类,在初始化时从存储读取状态,状态变化时自动保存到存储。

代码实现

import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';

class PersistedValueNotifier<T> extends ValueNotifier<T> {
  final String storageKey;
  final SharedPreferences prefs;
  final T Function(dynamic) deserialize;
  final dynamic Function(T) serialize;

  PersistedValueNotifier({
    required this.storageKey,
    required this.prefs,
    required T initialValue,
    required this.deserialize,
    required this.serialize,
  }) : super(initialValue) {
    // 初始化时从存储加载状态
    _loadState();
  }

  Future<void> _loadState() async {
    final storedData = prefs.get(storageKey);
    if (storedData != null) {
      value = deserialize(storedData);
    }
  }

  @override
  set value(T newValue) {
    super.value = newValue;
    // 状态变化时自动保存到存储
    _saveState(newValue);
  }

  Future<void> _saveState(T value) async {
    final data = serialize(value);
    if (data is String) {
      await prefs.setString(storageKey, data);
    } else if (data is int) {
      await prefs.setInt(storageKey, data);
    } else if (data is bool) {
      await prefs.setBool(storageKey, data);
    } else if (data is double) {
      await prefs.setDouble(storageKey, data);
    } else if (data is List<String>) {
      await prefs.setStringList(storageKey, data);
    }
  }
}

使用示例

1. 初始化SharedPreferences并创建持久化Notifier

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  final prefs = await SharedPreferences.getInstance();
  
  runApp(MyApp(prefs: prefs));
}

class MyApp extends StatelessWidget {
  final SharedPreferences prefs;

  const MyApp({super.key, required this.prefs});

  @override
  Widget build(BuildContext context) {
    // 示例:持久化计数器状态
    final counterNotifier = PersistedValueNotifier<int>(
      storageKey: 'app_counter',
      prefs: prefs,
      initialValue: 0,
      deserialize: (val) => val as int,
      serialize: (val) => val,
    );

    // 示例:持久化自定义对象(如User)
    final userNotifier = PersistedValueNotifier<User>(
      storageKey: 'current_user',
      prefs: prefs,
      initialValue: User('Guest', 20),
      deserialize: (val) => User.fromJson(jsonDecode(val as String)),
      serialize: (user) => jsonEncode(user.toJson()),
    );

    return MaterialApp.router(
      routerConfig: GoRouter(
        routes: [
          GoRoute(
            path: '/',
            builder: (context, state) => HomePage(
              counterNotifier: counterNotifier,
              userNotifier: userNotifier,
            ),
          ),
        ],
      ),
    );
  }
}

// 自定义User类示例
class User {
  final String name;
  final int age;

  User(this.name, this.age);

  factory User.fromJson(Map<String, dynamic> json) => User(json['name'], json['age']);

  Map<String, dynamic> toJson() => {'name': name, 'age': age};
}

2. 在页面中使用

class HomePage extends StatelessWidget {
  final PersistedValueNotifier<int> counterNotifier;
  final PersistedValueNotifier<User> userNotifier;

  const HomePage({
    super.key,
    required this.counterNotifier,
    required this.userNotifier,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ValueListenableBuilder<int>(
              valueListenable: counterNotifier,
              builder: (context, count, _) => Text('计数器:$count'),
            ),
            const SizedBox(height: 20),
            ValueListenableBuilder<User>(
              valueListenable: userNotifier,
              builder: (context, user, _) => Text('当前用户:${user.name},${user.age}岁'),
            ),
          ],
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: () => counterNotifier.value++,
            child: const Icon(Icons.add),
          ),
          const SizedBox(height: 10),
          FloatingActionButton(
            onPressed: () => userNotifier.value = User('New User', 25),
            child: const Icon(Icons.person),
          ),
        ],
      ),
    );
  }
}

方案2:全局单例状态管理

把ValueNotifier放在全局单例中,在单例初始化时从存储恢复状态,同时监听状态变化自动保存。

代码实现

import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';

class AppGlobalState {
  static final AppGlobalState _instance = AppGlobalState._internal();
  late SharedPreferences _prefs;

  // 定义全局的ValueNotifier
  late ValueNotifier<int> counter;
  late ValueNotifier<User> currentUser;

  factory AppGlobalState() => _instance;

  AppGlobalState._internal();

  // 初始化方法,必须在runApp前调用
  Future<void> init() async {
    _prefs = await SharedPreferences.getInstance();
    _initCounter();
    _initCurrentUser();
  }

  void _initCounter() {
    final storedCount = _prefs.getInt('counter') ?? 0;
    counter = ValueNotifier(storedCount);
    // 监听变化,自动保存
    counter.addListener(() {
      _prefs.setInt('counter', counter.value);
    });
  }

  void _initCurrentUser() {
    final storedUser = _prefs.getString('current_user');
    final initialUser = storedUser != null 
        ? User.fromJson(jsonDecode(storedUser)) 
        : User('Guest', 20);
    
    currentUser = ValueNotifier(initialUser);
    currentUser.addListener(() {
      _prefs.setString('current_user', jsonEncode(currentUser.value.toJson()));
    });
  }
}

// User类同方案1
class User {
  final String name;
  final int age;

  User(this.name, this.age);

  factory User.fromJson(Map<String, dynamic> json) => User(json['name'], json['age']);

  Map<String, dynamic> toJson() => {'name': name, 'age': age};
}

使用示例

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // 初始化全局状态
  await AppGlobalState().init();
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: GoRouter(
        routes: [
          GoRoute(
            path: '/',
            builder: (context, state) => const HomePage(),
          ),
        ],
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    final globalState = AppGlobalState();
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ValueListenableBuilder<int>(
              valueListenable: globalState.counter,
              builder: (context, count, _) => Text('计数器:$count'),
            ),
            const SizedBox(height: 20),
            ValueListenableBuilder<User>(
              valueListenable: globalState.currentUser,
              builder: (context, user, _) => Text('当前用户:${user.name},${user.age}岁'),
            ),
          ],
        ),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: [
          FloatingActionButton(
            onPressed: () => globalState.counter.value++,
            child: const Icon(Icons.add),
          ),
          const SizedBox(height: 10),
          FloatingActionButton(
            onPressed: () => globalState.currentUser.value = User('New User', 25),
            child: const Icon(Icons.person),
          ),
        ],
      ),
    );
  }
}

核心原理

浏览器刷新会销毁Flutter应用的所有内存对象,包括ValueNotifier。通过将状态持久化到浏览器本地存储(SharedPreferences本质是浏览器的localStorage),在应用重启(刷新后)时从存储中读取数据并恢复到ValueNotifier,同时监听状态变化实时保存,就能实现刷新后状态不丢失的效果。

内容的提问来源于stack exchange,提问作者farhad rezvani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 05:43:14