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

Flutter中Provider实现收藏列表的两类问题:图标变色与显示异常

Flutter Provider收藏列表功能问题修复

问题描述

我用Flutter的Provider实现收藏列表功能,需求为:ListView展示从JSON获取的数据,每个ListTile包含标题(数据内容)、前置ID、后置收藏图标,点击图标时该图标变为红色。目前存在两个问题:

  • 点击单个收藏图标时,所有图标短暂变红后恢复,无法单独维持红色状态;
  • 点击列表图标跳转至收藏列表页面时,页面未正确显示收藏项,而是显示「Instance of 'Food'」。

相关代码

首页代码

import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/container.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:note/Designs/Favorite_List.dart';
import 'package:note/Models/Favorite_List_Provider.dart';
import 'package:note/Models/Food_Models.dart';
import 'package:provider/provider.dart';

import '../Services/Fetch_Data.dart';
import 'Details_DesignOfDesignOne.dart';

class DesignOne extends StatefulWidget {
  const DesignOne({super.key, required this.Image, required widget.Desc});
  final String Image;
  final String Desc;
  @override
  State<DesignOne> createState() => _DesignOneState();
}

class _DesignOneState extends State<DesignOne> {
  late Future<Food> futureFood;
  @override
  void initState() {
    super.initState();
    futureFood = fetchData(widget.Image, widget.Desc);
  }

  @override
  Widget build(BuildContext context) {
    bool ischecked = false;
    final provider = favorite.of(context);
    return Scaffold(
      backgroundColor: Colors.grey.shade200,
      appBar: AppBar(
        title: Text('Design one'),
        actions: [
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: InkWell(
              onTap: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => FavoriteScreen(Desc: '', Image: ''),
                  ),
                );
              },
              child: Icon(
                Icons.list,
                size: 30,
              ),
            ),
          )
        ],
      ),
      body: Consumer<favorite>(
          builder: (BuildContext context, favorite value, child) {
        return Center(
          child: FutureBuilder<Food>(
            future: fetchData(widget.Image, widget.Desc),
            builder: (context, snapshot) {
              if (snapshot.hasData) {
                return ListView.builder(
                    shrinkWrap: true,
                    itemCount: snapshot.data!.categories.length,
                    itemBuilder: (contxt, index) {
                      final fav = snapshot.data!;
                      return InkWell(
                        onTap: () {
                          Navigator.push(
                            context,
                            MaterialPageRoute(
                              builder: (context) => DetailsDo(
                                Desc: snapshot.data!.categories[index]
                                    .strCategoryDescription,
                                Image: snapshot
                                    .data!.categories[index].strCategoryThumb,
                              ),
                            ),
                          );
                        },
                        child: Card(
                          elevation: 2,
                          shape: RoundedRectangleBorder(
                            side: BorderSide(color: Colors.white70, width: 1),
                            borderRadius: BorderRadius.circular(50),
                          ),
                          child: ListTile(
                            title: Text(
                              snapshot.data!.categories[index].strCategory
                                  .toString(),
                              style: GoogleFonts.montserrat(
                                fontSize: 20,
                                fontWeight: FontWeight.w600,
                                fontStyle: FontStyle.italic,
                              ),
                            ),
                            leading: CircleAvatar(
                              backgroundColor:
                                  Color.fromARGB(213, 255, 251, 251),
                              child: Text(
                                snapshot.data!.categories[index].idCategory
                                    .toString(),
                                style: GoogleFonts.montserrat(
                                    fontSize: 20,
                                    fontWeight: FontWeight.w600,
                                    fontStyle: FontStyle.italic,
                                    color: Color.fromARGB(255, 148, 148, 135)),
                              ),
                            ),
                            trailing: IconButton(
                              icon: (provider.isExist(fav))
                                  ? const Icon(Icons.favorite,
                                      color: Colors.red)
                                  : const Icon(Icons.favorite_border),
                              onPressed: () {
                                provider.toggleFavorite(fav);
                              },
                            ),
                          ),
                        ),
                      );
                    });
              } else if (snapshot.hasError) {
                return Text('${snapshot.error}');
              }
              return const CircularProgressIndicator();
            },
          ),
        );
      }),
    );
  }
}

收藏列表页面代码

import 'dart:ui';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:note/Models/Favorite_List_Provider.dart';
import 'package:provider/provider.dart';

import '../Models/Food_Models.dart';
import '../Services/Fetch_Data.dart';

class FavoriteScreen extends StatefulWidget {
  const FavoriteScreen({super.key, required widget.Image, required widget.Desc});
  final String Image;
  final String Desc;

  @override
  State<FavoriteScreen> createState() => _FavoriteScreenState();
}

class _FavoriteScreenState extends State<FavoriteScreen> {
  @override
  Widget build(BuildContext context) {
    final provider = favorite.of(context);
    final favr = provider.favoriteList;
    return Scaffold(
        appBar: AppBar(
          title: Text("Favorites"),
          centerTitle: true,
        ),
        body: Consumer<favorite>(
          builder: (context, favorite value, child) {
            return FutureBuilder<Food>(
                future: fetchData(widget.Image, widget.Desc),
                builder: (context, snapshot) {
                  if (snapshot.hasData) {
                    return ListView.builder(
                        shrinkWrap: true,
                        itemCount: favr.length,
                        itemBuilder: (context, index) {
                          final fav = favr;
                          return ListView(shrinkWrap: true, children: [
                            ListTile(
                              title: Text(fav[index].toString()),
                            ),
                          ]);
                        });
                  }
                  return const CircularProgressIndicator();
                });
          },
        ));
  }
}

Provider代码

import 'Food_Models.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class favorite extends ChangeNotifier {
  List<Food> _favoriteList = [];
  List<Food> get favoriteList => _favoriteList;

  void toggleFavorite(Food fav) {
    final isExist = _favoriteList.contains(fav);
    if (isExist) {
      _favoriteList.remove(fav);
    } else {
      _favoriteList.add(fav);
    }
    notifyListeners();
  }

  bool isExist(Food fav) {
    final isExist = _favoriteList.contains(fav);
    return isExist;
  }

  void clearFavorite() {
    _favoriteList = [];
    notifyListeners();
  }

  static favorite of(
    BuildContext context, {
    bool listen = true,
  }) {
    return Provider.of<favorite>(
      context,
      listen: listen,
    );
  }
}

问题修复方案

问题1:收藏图标状态异常修复

问题根源:

  1. 你将整个Food对象(而非单个分类项)传入收藏方法,导致所有图标同时触发状态变化;
  2. build方法中重复调用fetchData,每次状态更新都会重新加载数据,重置图标状态。

修复步骤:

  1. 使用初始化时的futureFood替代build中重复调用的fetchData;
  2. 针对单个分类项(snapshot.data!.categories[index])进行收藏判断和操作;
  3. 确保分类模型类重写==和hashCode,让contains能正确识别对象。

修改后的首页核心代码:

// 使用初始化的futureFood,避免重复请求
child: FutureBuilder<Food>(
  future: futureFood,
  builder: (context, snapshot) {
    if (snapshot.hasData) {
      return ListView.builder(
          shrinkWrap: true,
          itemCount: snapshot.data!.categories.length,
          itemBuilder: (contxt, index) {
            // 获取单个分类项
            final category = snapshot.data!.categories[index];
            return InkWell(
              ...
              child: Card(
                ...
                child: ListTile(
                  ...
                  trailing: IconButton(
                    // 针对单个分类项判断收藏状态
                    icon: (provider.isExist(category))
                        ? const Icon(Icons.favorite, color: Colors.red)
                        : const Icon(Icons.favorite_border),
                    onPressed: () {
                      // 传入单个分类项进行收藏切换
                      provider.toggleFavorite(category);
                    },
                  ),
                ),
              ),
            );
          });
    }
    ...
  },
),

分类模型重写示例:

class Category {
  final String idCategory;
  final String strCategory;
  // 其他属性...

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is Category &&
          runtimeType == other.runtimeType &&
          idCategory == other.idCategory;

  @override
  int get hashCode => idCategory.hashCode;
}

问题2:收藏列表显示异常修复

问题根源:

  1. 直接调用对象的toString(),未访问具体属性;
  2. 多余的FutureBuilder嵌套,且嵌套ListView导致布局异常。

修复步骤:

  1. 移除FutureBuilder,直接使用Provider中的收藏数据;
  2. 访问收藏项的具体属性(如strCategory)进行展示;
  3. 取消ListView嵌套,直接返回ListTile。

修改后的收藏列表页面代码:

import 'package:flutter/material.dart';
import 'package:note/Models/Favorite_List_Provider.dart';
import 'package:provider/provider.dart';

import '../Models/Food_Models.dart';

class FavoriteScreen extends StatefulWidget {
  const FavoriteScreen({super.key, required this.Image, required widget.Desc});
  final String Image;
  final String Desc;

  @override
  State<FavoriteScreen> createState() => _FavoriteScreenState();
}

class _FavoriteScreenState extends State<FavoriteScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Favorites"),
        centerTitle: true,
      ),
      body: Consumer<favorite>(
        builder: (context, favorite value, child) {
          // 空状态提示
          if (value.favoriteList.isEmpty) {
            return const Center(child: Text("暂无收藏"));
          }
          return ListView.builder(
            shrinkWrap: true,
            itemCount: value.favoriteList.length,
            itemBuilder: (context, index) {
              final category = value.favoriteList[index];
              return ListTile(
                title: Text(category.strCategory),
                leading: CircleAvatar(
                  child: Text(category.idCategory),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 00:40:41