更改一个 Provider 中的属性会将另一个 Provider 中的属性更改为 List Flutter

Smi*_*Kmc 3 flutter flutter-provider

更新 - 我实际上发现这是一个Flutter 问题


我有两个提供者,一个是 EntriesProvider,另一个是 EntryProvider。我在创建条目时使用我的 EntryProvider 和我的 EntriesProvider 来加载保存到数据库的所有条目。我遇到了一个问题,我认为这可能是我对如何使用 Providers 的理解。一旦我将我的数据库数据加载到我的 EntriesProvider 中,我就会将该数据加载到 ListView 中。单击某个项目后,我将该列表中的条目传递到我的视图中以进行填充和编辑。

我的问题是,当我在不保存的情况下编辑条目时,我可以看到 ListView 中发生的更改,这不是我想要的。我尝试清除 EntryProvider,因为我认为属于它的数据与 EntriesProvider 是分开的。但是现在我在尝试了很多事情之后不知道了。当我只要求 EntryProvider 更新其侦听器时,为什么要更新列表?

class EntryProvider extends ChangeNotifier {
  Entry _entry;
  BuildContext context;

  EntryProvider();

  Entry get getEntry {
    return _entry;
  }

  void setEntryContext(Entry entryToBeSet, BuildContext context) {
    this._entry = entryToBeSet;
    this.context = context;
    notifyListeners();
  }

  void clearEntryContext() {
    this._entry = null;
    this.context = null;
    notifyListeners();
  }

  void addImageToEntry(String imagePath) {
    getEntry.images.add(imagePath);
    notifyListeners();
  }

  void removeImageAt(int index) {
    getEntry.images.removeAt(index);
    notifyListeners();
  }

  void addTagToEntry(String tagText) {
    getEntry.tags.add(tagText);
    notifyListeners();
  }

  void removeTagAt(int index) {
    getEntry.tags.removeAt(index);
    notifyListeners();
  }

  Future<void> saveEntry() async {
    if (getEntry.id != null) {
      await Provider.of<EntriesProvider>(context, listen: false)
          .updateEntry(getEntry);
    } else {
      await Provider.of<EntriesProvider>(context, listen: false)
          .addEntry(getEntry);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)
class EntriesProvider extends ChangeNotifier {
  List<Entry> _entries = [];

  EntriesProvider(this._entries);

  UnmodifiableListView<Entry> get entries => UnmodifiableListView(_entries);

  int get length => _entries.length;

  List<Entry> get getEntriesSortedByDateReversed {
    List<Entry> entriesCopy = entries;
    entriesCopy.sort((a, b) => a.entryDate.compareTo(b.entryDate));

    return entriesCopy.reversed.toList();
  }

  List<Entry> getEntries(DateTime dateTime) {
    List<Entry> entriesToBeSorted = entries
        .where(
          (entry) => DateFormat.yMMMd().format(entry.entryDate).contains(
                DateFormat.yMMMd().format(dateTime),
              ),
        )
        .toList();

    entriesToBeSorted.sort((a, b) {
      return a.entryDate.compareTo(b.entryDate);
    });

    return entriesToBeSorted;
  }
}
Run Code Online (Sandbox Code Playgroud)
class JournalListView extends StatefulWidget {
  bool isDrawerOpen;
  final TransformData transformData;

  JournalListView(this.isDrawerOpen, this.transformData);

  @override
  _JournalListScreenState createState() => _JournalListScreenState();
}

class _JournalListScreenState extends State<JournalListView> {
  List<Entry> entries = [];
  List<Entry> filteredEntries = [];
  DateTime dateTimeSet;

  AppDataModel appDataModel;

  @override
  void initState() {
    super.initState();
    dateTimeSet = DateTime.now();
  }

  Widget _buildEntryList(BuildContext context) {
    return Consumer<EntriesProvider>(builder: (context, entryModel, child) {
      print(entryModel.entries);
      List<Entry> entries = entryModel.getEntries(dateTimeSet);
      return Container(
        constraints: BoxConstraints(
          maxHeight: 650,
          maxWidth: double.infinity,
        ),
        child: Container(
          child: entries.length > 0
              ? ListView.builder(
                  itemCount: entries.length,
                  padding: EdgeInsets.all(2.0),
                  itemBuilder: (context, index) {
                    return InkWell(
                      onTap: () {
                        if (widget.isDrawerOpen) {
                          closeDrawer();
                        } else {
                          Navigator.of(context).push(
                            PageRouteBuilder(
                                transitionDuration: Duration(milliseconds: 650),
                                pageBuilder:
                                    (context, animation, secondaryAnimation) {
                                  final Entry copiedEntry = entries[index]
                                      .copyWith(
                                          id: entries[index].id,
                                          title: entries[index].title,
                                          description:
                                              entries[index].description,
                                          entryDate: entries[index].entryDate,
                                          feelingOnEntry:
                                              entries[index].feelingOnEntry,
                                          images: entries[index].images,
                                          location: entries[index].location,
                                          tags: entries[index].tags,
                                          time: entries[index].time,
                                          weather: entries[index].weather);
                                  Provider.of<EntryProvider>(context, listen: false)
        .setEntryContext(entry, context);
                                  return JournalEntryView(copiedEntry);
                                }),
                          );
                        }
                      },
                      child: Hero(
                        tag: '${entries[index].entryDate}${entries[index].id}',
                        child: _buildEntryLayout(context, entries[index]),
                      ),
                    );
                  },
                )
              : JournalEmpty(
                  'lib/assets/emojis/empty-folder.png',
                  MyLocalizations.of(context).journalListEmpty,
                ),
        ),
      );
    });
  }

  Widget _buildEntryLayout(BuildContext context, Entry entry) {
    int entryLayout = appDataModel.entryLayout;
    Widget entryLayoutWidget;

    switch (entryLayout) {
      case 1:
        entryLayoutWidget = EntryCard1(entry);
        break;
      case 2:
        entryLayoutWidget = EntryCard2(entry);
        break;
      default:
        entryLayoutWidget = EntryCard1(entry);
        break;
    }

    return entryLayoutWidget;
  }

  Widget _buildCalenderStrip(BuildContext context) {
    return Container(
      height: 64,
      margin: const EdgeInsets.all(2.0),
      child: Consumer<EntriesProvider>(
        builder: (context, entryModel, child) {
          return Calendarro(
              startDate: DateUtils.getFirstDayOfMonth(DateTime(2020, 09)),
              endDate: DateUtils.getLastDayOfCurrentMonth(),
              selectedSingleDate: DateTime.now(),
              displayMode: DisplayMode.WEEKS,
              dayTileBuilder: CustomDayBuilder(entryModel.entries),
              onTap: (datetime) {
                if (widget.isDrawerOpen) {
                  closeDrawer();
                }
                setState(() {
                  dateTimeSet = datetime;
                });
              });
        },
      ),
    );
  }

  Widget _buildSearchEntryWidget(BuildContext context) {
    return Consumer<EntriesProvider>(builder: (context, entries, child) {
      return IconButton(
        onPressed: () => showSearch(
          context: context,
          delegate: SearchPage<Entry>(
            items: entries.entries,
            searchLabel: MyLocalizations.of(context).journalListSearchEntries,
            suggestion: Center(
              child: Text(MyLocalizations.of(context).journalListFilterEntries),
            ),
            failure: JournalEmpty(
              'lib/assets/emojis/no_items.png',
              MyLocalizations.of(context).journalListNoEntriesFound,
            ),
            filter: (entry) {
              List<String> filterOn = List<String>();
              filterOn.add(entry.title);
              if (entry.tags != null) {
                entry.tags.forEach((tag) => filterOn.add(tag));
              }
              return filterOn;
            },
            builder: (entry) => InkWell(
              onTap: () {
                Navigator.of(context).push(
                  MaterialPageRoute(
                    builder: (context) => JournalEntryView(entry),
                  ),
                );
              },
              child: EntryCard1(
                entry,
              ),
            ),
          ),
        ),
        icon: Icon(
          Icons.search,
          size: 30,
          color: Theme.of(context).primaryColor,
        ),
      );
    });
  }

  void closeDrawer() {
    setState(() {
      widget.transformData.xOffset = 0;
      widget.transformData.yOffset = 0;
      widget.transformData.scaleFactor = 1;
      widget.isDrawerOpen = false;
    });
  }

  bool isDateChoosenValid() {
    return dateTimeSet.compareTo(DateTime.now()) < 1;
  }

  @override
  Widget build(BuildContext context) {
    appDataModel = Provider.of<AppDataProvider>(context).appDataModel;

    return AnimatedContainer(
      transform: Matrix4.translationValues(
        widget.transformData.xOffset,
        widget.transformData.yOffset,
        0,
      )
        ..scale(widget.transformData.scaleFactor)
        ..rotateY(widget.isDrawerOpen ? -0.5 : 0),
      duration: Duration(milliseconds: 250),
      decoration: BoxDecoration(
        color: Colors.grey[200],
        borderRadius: BorderRadius.circular(
          widget.isDrawerOpen ? 25 : 0.0,
        ),
      ),
      child: GestureDetector(
        onTap: () {
          if (widget.isDrawerOpen) {
            closeDrawer();
          }
        },
        child: ClipRRect(
          borderRadius: BorderRadius.circular(25),
          child: Scaffold(
              body: Column(
                children: [
                  SizedBox(
                    height: 30,
                  ),
                  Container(
                    margin: EdgeInsets.symmetric(horizontal: 20),
                    child: Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        widget.isDrawerOpen
                            ? IconButton(
                                icon: Icon(
                                  Icons.arrow_back,
                                  size: 30,
                                  color: Theme.of(context).primaryColor,
                                ),
                                onPressed: () {
                                  closeDrawer();
                                },
                              )
                            : IconButton(
                                icon: Icon(
                                  Icons.menu,
                                  size: 30,
                                  color: Theme.of(context).primaryColor,
                                ),
                                onPressed: () {
                                  setState(() {
                                    widget.transformData.xOffset = 260;
                                    widget.transformData.yOffset = 150;
                                    widget.transformData.scaleFactor = 0.7;
                                    widget.isDrawerOpen = true;
                                  });
                                }),
                        Column(
                          crossAxisAlignment: CrossAxisAlignment.end,
                          children: [
                            Text(
                              Constants.APP_NAME,
                              style: TextStyle(
                                fontSize: 28,
                                color: Theme.of(context).primaryColor,
                                fontWeight: FontWeight.w500,
                              ),
                            ),
                          ],
                        ),
                        _buildSearchEntryWidget(context)
                      ],
                    ),
                  ),
                  SizedBox(
                    height: 5,
                  ),
                  _buildCalenderStrip(context),
                  _buildEntryList(context),
                ],
              ),
              floatingActionButtonLocation:
                  FloatingActionButtonLocation.endFloat,
              floatingActionButton: isDateChoosenValid()
                  ? OpenContainer(
                      transitionDuration: Duration(milliseconds: 600),
                      closedBuilder: (BuildContext c, VoidCallback action) =>
                          FloatingActionButton(
                        onPressed: null,
                        child: Icon(
                          Icons.edit,
                          size: 30,
                        ),
                        tooltip:
                            MyLocalizations.of(context).journalListAddEntry,
                        backgroundColor: isDateChoosenValid()
                            ? Theme.of(context).primaryColor
                            : Colors.grey[500],
                        elevation: 8.0,
                      ),
                      closedShape: RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(100)),
                      openBuilder: (BuildContext c, VoidCallback action) {
                        final entry = Entry(
                          entryDate: dateTimeSet,
                          images: List<Object>(),
                          tags: List<String>(),
                        );
                        return JournalEntryView(entry);
                      },
                      tappable: isDateChoosenValid(),
                    )
                  : SizedBox()),
        ),
      ),
    );
  }
}

class CustomDayBuilder extends DayTileBuilder {
  final List<Entry> entries;
  CustomDayBuilder(this.entries);

  @override
  Widget build(BuildContext context, DateTime date, onTap) {
    Entry entry = entries.firstWhere(
      (entryInEntries) => DateFormat.yMMMd()
          .format(entryInEntries.entryDate)
          .contains(DateFormat.yMMMd().format(date)),
      orElse: () => Entry(),
    );
    return CustomDateTile(
      date: date,
      entry: entry,
      calendarroState: Calendarro.of(context),
      onTap: onTap,
    );
  }
}
Run Code Online (Sandbox Code Playgroud)
class JournalEntryView extends StatefulWidget {
  final Entry entry;

  JournalEntryView(this.entry);

  @override
  _JournalEntryScreenState createState() => _JournalEntryScreenState();
}

class _JournalEntryScreenState extends State<JournalEntryView> {
  GlobalKey _scaffoldKey = GlobalKey<ScaffoldState>();

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

  @override
  Widget build(BuildContext context) {
    Entry entry = widget.entry;
    Provider.of<EntryProvider>(context, listen: false)
        .setEntryContext(entry, context);
    return Hero(
      tag: '${entry.entryDate}${entry.id}',
      child: Form(
        child: Builder(
          builder: (ctx) {
            return WillPopScope(
              child: Scaffold(
                key: _scaffoldKey,
                resizeToAvoidBottomPadding: true,
                backgroundColor: Theme.of(context).primaryColor,
                appBar: AppBar(
                  actionsIconTheme: IconThemeData(color: Colors.white),
                  iconTheme: IconThemeData(color: Colors.white),
                  actions: <Widget>[
                    IconButton(
                      onPressed: () async {
                        Form.of(ctx).save();
                        if (!Form.of(ctx).validate()) {
                          return;
                        }

                        if (Provider.of<EmojiListProvider>(context,
                                    listen: false)
                                .getChosenFeeling ==
                            null) {
                          _showFormError(
                            MyLocalizations.of(context).journalEntryNeedMood,
                          );
                          return;
                        } else {
                          entry.feelingOnEntry = entry.getFeeling(
                              Provider.of<EmojiListProvider>(context,
                                      listen: false)
                                  .getChosenFeeling
                                  .url);
                        }

                        if (entry.time == null) {
                          entry.time = DateFormat.Hm().format(DateTime.now());
                        }

                        entry.weather = 'Sunny';
                        Provider.of<EntryProvider>(context, listen: false)
                            .saveEntry();
                        Navigator.of(context).pop();
                      },
                      padding: EdgeInsets.only(right: 16),
                      icon: Icon(
                        Icons.save,
                        color: Colors.white,
                        size: 25,
                      ),
                    )
                  ],
                  backgroundColor: Theme.of(context).primaryColor,
                  elevation: 0.0,
                  shadowColor: Theme.of(context).primaryColor,
                  bottomOpacity: 0.0,
                ),
                body: Stack(
                  children: <Widget>[
                    Column(
                      children: <Widget>[
                        Expanded(
                          child: Container(
                            color: Theme.of(context).primaryColor,
                            alignment: Alignment.topCenter,
                            child: Container(
                              child: Column(
                                children: [
                                  Container(
                                    margin:
                                        EdgeInsets.only(left: 20, bottom: 5),
                                    child: Text(
                                      MyLocalizations.of(context)
                                          .journalEntryFeeling,
                                      style: TextStyle(
                                        color: Colors.white,
                                        fontSize: 22,
                                        fontWeight: FontWeight.bold,
                                      ),
                                    ),
                                    alignment: Alignment.topLeft,
                                  ),
                                  FeelingsList(entry.feelingOnEntry),
                                ],
                              ),
                            ),
                          ),
                        ),
                      ],
                    ),
                    Container(
                      alignment: Alignment.bottomCenter,
                      padding: EdgeInsets.only(top: 115),
                      child: Container(
                        width: double.infinity,
                        child: ClipRRect(
                          borderRadius: BorderRadius.only(
                            topLeft: Radius.circular(80),
                          ),
                          child: EntryScreenData(entry),
                        ),
                      ),
                    )
                  ],
                ),
              ),
             

Riw*_*wen 6

是的,对象是通过引用传递的。因此,您正在修改同一个对象。由于Flutter 中没有反射,因此您无法真正自动制作副本。

解决此问题的一种方法是实现您自己的copyWith方法。例如,在样式的情况下,这就是 Flutter 在内部所做的。

更新:重要的是要注意 List 和 Map 也是通过引用传递的。因此,你需要为使用List.from传播运营商在自己的执行copyWith

例子:

Entry(
 images: images ?? List.from(this.images),
);
Run Code Online (Sandbox Code Playgroud)