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

Flutter中使用shared_preferences本地保存对象列表失败求助

解决Flutter红外应用本地持久化数据丢失问题

问题描述

我开发了一款可发送红外(IR)信号的Flutter应用,支持自行添加IR信号,信号存储在名为Item的对象列表中。希望将信号名称与内容在设备本地持久化,但添加新信号后重启应用,数据会丢失,应用恢复初始状态。相关代码如下:

import 'package:flutter/material.dart';
import 'package:ir_sensor_plugin/ir_sensor_plugin.dart';
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:async';
import 'dart:convert';

class Item {
  String buttonName = "", hexCode = "";
  bool isSelected = false;

  Item({
    required this.buttonName,
    required this.hexCode,
  });

  // Method to convert the Item object to a JSON object
  Map<String, dynamic> toJson() {
    return {
      'buttonName': buttonName,
      'hexCode': hexCode,
      'isSelected': isSelected,
    };
  }

  // Method to convert a JSON object to an Item object
  static Item fromJson(Map<String, dynamic> json) {
    return Item(
      buttonName: json['buttonName'],
      hexCode: json['hexCode'],
    );
  }
}

class IrRemoteEmulator extends StatefulWidget {
  const IrRemoteEmulator({Key? key}) : super(key: key);

  @override
  State<IrRemoteEmulator> createState() => _IrRemoteEmulatorState();
}

class _IrRemoteEmulatorState extends State<IrRemoteEmulator> {
  String buttonName = "";
  TextEditingController _buttonName = TextEditingController();
  String hexCode = "";
  TextEditingController _hexCode = TextEditingController();

  List<Item> items = [
    Item(
        buttonName: "Samsung Power",
        hexCode:
            "0000 006d 0022 0003 00a9 00a8 0015 003f 0015 003f 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 003f 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0040 0015 0015 0015 003f 0015 003f 0015 003f 0015 003f 0015 003f 0015 003f 0015 0702 00a9 00a8 0015 0015 0015 0e6e"),
    Item(
        buttonName: "Prowise Power",
        hexCode:
            "0000 006d 0022 0003 00a9 00a8 0015 003f 0015 003f 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 003f 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0040 0015 0015 0015 003f 0015 003f 0015 003f 0015 003f 0015 003f 0015 003f 0015 0702 00a9 00a8 0015 0015 0015 0e6e")
  ];

  // Method to save the items list to the local storage
  Future<void> saveList(List<Item> items) async {
    // Get the instance of the shared preferences
    final prefs = await SharedPreferences.getInstance();

    // Convert the items list to a JSON string
    var itemsJson = json.encode(items);

    // Save the JSON string to the local storage
    prefs.setString('items', itemsJson);
    debugPrint("Items saved");
  }

  Future<List<Item>> loadList() async {
    // Get the instance of the shared preferences
    final prefs = await SharedPreferences.getInstance();

    // Retrieve the JSON string from the local storage
    var itemsJson = prefs.getString('items') ?? '';

    // Convert the JSON string to a List<Item> object
    var items = json.decode(itemsJson);
    debugPrint("Items loaded");
    return items;
  }

  String selectedHexcode =
      "0000 006d 0022 0003 00a9 00a8 0015 003f 0015 003f 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 003f 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0040 0015 0015 0015 003f 0015 003f 0015 003f 0015 003f 0015 003f 0015 003f 0015 0702 00a9 00a8 0015 0015 0015 0e6e";

  String _platformVersion = 'Unkown';
  bool _hasIrEmitter = false;
  String _getCarrierFrequencies = 'Unknown';

  @override
  void initState() {
    super.initState();

    // Load the saved items list from the local storage
    loadList().then((savedItems) {
      // Set the items list to the loaded list
      setState(() {
        items = savedItems;
      });
    });

    initPlatformState();
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
    String platformVersion;
    bool hasIrEmitter;
    String getCarrierFrequencies;

    // Platform messages may fail, so we use a try/catch PlatformException.
    try {
      platformVersion = await IrSensorPlugin.platformVersion;
      hasIrEmitter = await IrSensorPlugin.hasIrEmitter;
      getCarrierFrequencies = await IrSensorPlugin.getCarrierFrequencies;
    } on PlatformException {
      platformVersion = 'Failed to get data in a platform.';
      hasIrEmitter = false;
      getCarrierFrequencies = 'None';
    }

    // If the widget was removed from the tree while the asynchronous platform
    // message was in flight, we want to discard the reply rather than calling
    // setState to update our non-existent appearance.
    if (!mounted) return;

    setState(() {
      _platformVersion = platformVersion;
      _hasIrEmitter = hasIrEmitter;
      _getCarrierFrequencies = getCarrierFrequencies;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: Column(
            children: [
              const SizedBox(
                height: 25,
              ),
              const SizedBox(
                height: 15.0,
              ),
              Container(
                decoration:
                    BoxDecoration(border: Border.all(color: Colors.white)),
                child: Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Column(
                    children: [
                      FloatingActionButton.large(
                        onPressed: () async {
                          final String result =
                              await IrSensorPlugin.transmitString(
                                  pattern: selectedHexcode);
                          debugPrint('Emitting  List Int Signal: $result');
                        },
                        child: const Icon(Icons.power_settings_new_outlined),
                      ),
                    ],
                  ),
                ),
              ),
              Expanded(
                child: SizedBox(
                  height: 200,
                  child: ListView.builder(
                    itemCount: items.length,
                    itemBuilder: (context, index) {
                      return ListTile(
                        onTap: () {
                          items[index].isSelected = true;
                          selectedHexcode = items[index].hexCode;
                          debugPrint(items[index].hexCode);
                          setState(() {});
                        },
                        onLongPress: () {
                          items.remove(items[index]);
                          saveList(items);
                          setState(() {});
                        },
                        title: Text(items[index].buttonName),
                        leading: Icon((items[index].hexCode == selectedHexcode)
                            ? Icons.circle
                            : Icons.circle_outlined),
                      );
                    },
                  ),
                ),
              ),
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton.extended(
          onPressed: () => showDialog(
            context: context,
            builder: (context) => AlertDialog(
              title: const Text("Add IR Hexcode"),
              content: Column(
                children: <Widget>[
                  TextFormField(
                    controller: _buttonName,
                    decoration: InputDecoration(
                        labelText: "Remotebutton name",
                        hintText: "Ac Powerbutton",
                        hintStyle: TextStyle(color: Colors.grey[300])),
                  ),
                  TextFormField(
                    controller: _hexCode,
                    decoration: InputDecoration(
                        labelText: "Hexcode",
                        hintText:
                            "0000 006d 0022 0003 00a9 00a8 0015 003f 0015 003f 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 003f 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0040 0015 0015 0015 003f 0015 003f 0015 003f 0015 003f 0015 003f 0015 003f 0015 0702 00a9 00a8 0015 0015 0015 0e6e",
                        hintStyle: TextStyle(color: Colors.grey[300])),
                  ),
                ],
              ),
              actions: [
                TextButton(
                  child: const Text("Cancel"),
                  onPressed: () => Navigator.pop(context),
                ),
                TextButton(
                  child: const Text("Ok"),
                  onPressed: () {
                    if (_hexCode.text.isEmpty || _buttonName.text.isEmpty) {
                      debugPrint("${(items.length)}");
                    } else {
                      items.add(Item(
                        buttonName: _buttonName.text,
                        hexCode: _hexCode.text,
                      ));
                      saveList(items);
                      debugPrint("${(items)}");
                    }
                    Navigator.pop(context);

                    setState(() {});
                    return;
                  },
                ),
              ],
            ),
          ),
          label: const Text("Add IR Hexcode"),
          icon: const Icon(Icons.add),
        ),
      ),
    );
  }
}

问题根源

  1. JSON序列化错误:saveList中直接调用json.encode(items),但json.encode无法自动将自定义Item对象序列化为JSON,导致存储的内容无效。
  2. 反序列化逻辑缺失:loadList中json.decode(itemsJson)返回的是原始JSON数组(List),没有转为Item对象,直接赋值给items会导致类型不匹配,最终加载失败。
  3. 空值处理不当:首次启动时SharedPreferences中无items数据,json.decode('')会抛出异常,导致加载逻辑失败,应用继续使用硬编码的初始列表。

修复方案

1. 修正saveList方法

手动遍历Item列表,调用每个对象的toJson()方法转为JSON Map后再序列化:

Future<void> saveList(List<Item> items) async {
  final prefs = await SharedPreferences.getInstance();
  // 将每个Item转为JSON Map,再组成列表
  final itemsJsonList = items.map((item) => item.toJson()).toList();
  final itemsJson = json.encode(itemsJsonList);
  await prefs.setString('items', itemsJson);
  debugPrint("Items saved");
}

2. 完善loadList方法

添加空值判断和异常捕获,将解析后的JSON数组转为Item对象列表,无数据时返回默认初始列表:

Future<List<Item>> loadList() async {
  final prefs = await SharedPreferences.getInstance();
  final itemsJson = prefs.getString('items');

  // 无存储数据时返回默认初始列表
  if (itemsJson == null || itemsJson.isEmpty) {
    return [
      Item(
          buttonName: "Samsung Power",
          hexCode:
              "0000 006d 0022 0003 00a9 00a8 0015 003f 0015 003f 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 003f 0015 003f 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 0015 003f 0015 0015 0015
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 12:15:40