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

如何使用SharedPreferences解码对象列表并持久化收藏餐品?

餐品收藏状态持久化问题修复

问题背景

已基于json_serializable实现Meal模型,通过FavouritesCubit结合SharedPreferences实现餐品收藏操作,但应用重启后收藏状态丢失,需解决Meal列表的编码/解码持久化问题。

现有代码

Meal模型(meal.dart)

import 'dart:convert';

import 'package:json_annotation/json_annotation.dart';
part 'meal.g.dart';

List<Meal> mealExtFromJson(String str) =>
    List<Meal>.from(jsonDecode(str).map((x) => Meal.fromJson(x)));
String mealExtToJson(List<Meal> data) =>
    json.encode(List<dynamic>.from(data.map((x) => x.toJson())));

enum Complexity {
  @JsonValue(203)
  simple,
  @JsonValue(204)
  challenging,
  @JsonValue(205)
  hard,
}

enum Affordability {
  @JsonValue(200)
  affordable,
  @JsonValue(201)
  pricey,
  @JsonValue(202)
  luxurious,
}

@JsonSerializable()
class Meal {
  final String id;
  final List<String> categories;
  final String title;
  final String imageUrl;
  final List<String> ingredients;
  final List<String> steps;
  final int duration;
  final Complexity complexity;
  final Affordability affordability;
  final bool isGlutenFree;
  final bool isLactoseFree;
  final bool isVegan;
  final bool isVegetarian;

  const Meal({
    required this.id,
    required this.categories,
    required this.title,
    required this.imageUrl,
    required this.ingredients,
    required this.steps,
    required this.duration,
    required this.complexity,
    required this.affordability,
    required this.isGlutenFree,
    required this.isLactoseFree,
    required this.isVegan,
    required this.isVegetarian,
  });
  factory Meal.fromJson(Map<String, dynamic> json) => _$MealFromJson(json);

  Map<String, dynamic> toJson() => _$MealToJson(this);
}

FavouritesCubit(favourites_cubit.dart)

import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:meals_app/models/dummy_data.dart';
import 'package:meals_app/models/meal.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'favourites_state.dart';

class FavouritesCubit extends Cubit<FavouritesState> {
  final String favouriteDataPref = 'favouriteData';
  List<Meal> favourites = [];
  SharedPreferences? _prefs;

  FavouritesCubit() : super(const FavouritesInitial()) {
    SharedPreferences.getInstance().then((prefs) {
      _prefs = prefs;
      loadSavedFavourites(prefs);
    });
  }

  void toggleFavourite(String newMealId) {
    final existingIndex = favourites.indexWhere((meal) => meal.id == newMealId);
    if (existingIndex == -1) {
      favourites.add(
          DummyData().mealsData.firstWhere((meal) => meal.id == newMealId));
    } else {
      favourites.removeAt(existingIndex);
    }

    unawaited(saveFavourites(favourites).onError(
      (error, stackTrace) => print(stackTrace),
    ));
    emit(
      FavouritesLoaded(
        favouritedMeals: favourites,
      ),
    );
  }

  Future<void> saveFavourites(List<Meal> favouritedList) async {
    favourites = favouritedList;
    await _prefs?.setString(favouriteDataPref, mealExtToJson(favouritedList));
  }

  loadSavedFavourites(SharedPreferences prefs) async {
    String? favouritedList = prefs.getString(favouriteDataPref);
    try {
      if (favouritedList != null) {
        List<Meal> savedFavs = mealExtFromJson(favouritedList);
        favourites = savedFavs;
        return;
      }
    } catch (e) {
      print(e);
    }
  }
}

问题分析

  1. 加载数据后未触发状态更新:loadSavedFavourites方法加载完收藏数据后,没有调用emit更新UI状态,导致重启后UI无法感知已保存的收藏列表。
  2. SharedPreferences初始化异步风险:构造函数中通过then处理初始化,可能存在操作执行时_prefs仍为null的情况,导致保存失败。
  3. 错误处理不完整:保存和加载过程中的异常未妥善处理,可能导致数据丢失或状态不一致。

修复方案

修改后的FavouritesCubit代码

import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:meals_app/models/dummy_data.dart';
import 'package:meals_app/models/meal.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'favourites_state.dart';

class FavouritesCubit extends Cubit<FavouritesState> {
  final String favouriteDataPref = 'favouriteData';
  List<Meal> favourites = [];
  SharedPreferences? _prefs;

  FavouritesCubit() : super(const FavouritesInitial()) {
    _initPreferences();
  }

  // 统一初始化SharedPreferences并加载收藏数据
  Future<void> _initPreferences() async {
    _prefs = await SharedPreferences.getInstance();
    await _loadSavedFavourites();
  }

  void toggleFavourite(String newMealId) {
    final existingIndex = favourites.indexWhere((meal) => meal.id == newMealId);
    if (existingIndex == -1) {
      favourites.add(
          DummyData().mealsData.firstWhere((meal) => meal.id == newMealId));
    } else {
      favourites.removeAt(existingIndex);
    }

    unawaited(_saveFavourites().onError(
      (error, stackTrace) => print('保存收藏失败: $error\n$stackTrace'),
    ));
    emit(FavouritesLoaded(favouritedMeals: List.from(favourites)));
  }

  // 保存收藏列表到SharedPreferences
  Future<void> _saveFavourites() async {
    if (_prefs == null) {
      throw Exception('SharedPreferences未完成初始化');
    }
    await _prefs!.setString(favouriteDataPref, mealExtToJson(favourites));
  }

  // 加载已保存的收藏列表并更新状态
  Future<void> _loadSavedFavourites() async {
    try {
      final favouritedList = _prefs?.getString(favouriteDataPref);
      if (favouritedList != null) {
        favourites = mealExtFromJson(favouritedList);
      }
      emit(FavouritesLoaded(favouritedMeals: List.from(favourites)));
    } catch (e) {
      print('加载收藏数据失败: $e');
      emit(const FavouritesLoaded(favouritedMeals: []));
    }
  }
}

关键修复点

  1. 统一初始化流程:用_initPreferences异步方法处理SharedPreferences初始化和数据加载,避免异步回调导致的状态不一致。
  2. 加载后触发状态更新:_loadSavedFavourites方法在加载完成(或出错)后,通过emit更新UI状态,确保重启后收藏列表能正确显示。
  3. 空安全处理:保存数据时先检查_prefs是否为空,避免空指针异常。
  4. 状态数据不可变:emit时传递List.from(favourites)创建新列表,确保Bloc状态的不可变性。
  5. 增强错误日志:为保存和加载过程添加更清晰的错误提示,便于排查问题。

额外验证步骤

确保已运行build_runner生成序列化代码:

flutter pub run build_runner build

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 14:45:31