gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-12-04 00:51:42 31 4
gpt4 key购买 nike

更新 - 我实际上发现它是 Flutter Issue .


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

我的问题是,当我编辑条目而不保存它时,我可以看到 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);
}
}
}
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;
}
}
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,
);
}
}
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),
),
),
)
],
),
),
onWillPop: () {
Provider.of<EntryProvider>(context, listen: false)
.clearEntryContext();
Provider.of<EmojiListProvider>(context, listen: false)
.setEmojiList();
Navigator.pop(context);
return;
},
);
},
),
),
);
}

void _showFormError(String errorText) {
final snackBar = SnackBar(
backgroundColor: Colors.red[400],
content: Text(errorText),
);
}
}

class EntryScreenData extends StatefulWidget {
final Entry entry;
List<Object> images;

EntryScreenData(this.entry);

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

class _EntryScreenDataState extends State<EntryScreenData> {
final SettingsDataModel settingsDataModel =
SettingsDataModel.fromJson(jsonDecode(sharedPrefs.settingsData));
final _titleController = TextEditingController();
final _descriptionController = TextEditingController();
final Geolocator geolocator = Geolocator()..forceAndroidLocationManager;

DateTime datePicked;

@override
void dispose() {
_titleController.dispose();
_descriptionController.dispose();
super.dispose();
}

@override
void initState() {
if (widget.entry.weather == null) {
widget.entry.weather = 'Sunny';
}

_titleController.value = TextEditingValue(
text: widget.entry.title != null ? widget.entry.title : '',
selection: TextSelection.collapsed(
offset: widget.entry.title != null ? widget.entry.title.length : 0,
),
);

_descriptionController.value = TextEditingValue(
text: widget.entry.description != null ? widget.entry.description : '',
selection: TextSelection.collapsed(
offset: widget.entry.description != null
? widget.entry.description.length
: 0,
),
);

widget.entry.entryDate != null
? datePicked = widget.entry.entryDate
: datePicked = DateTime.now();

widget.entry.tags != null
? widget.entry.tags = widget.entry.tags
: widget.entry.tags = List<dynamic>();

super.initState();
}

Future<String> getImage(int type) async {
PickedFile pickedImage = await ImagePicker().getImage(
source: type == 1 ? ImageSource.camera : ImageSource.gallery,
imageQuality: 50);
return pickedImage.path;
}

_imgFromCamera() async {
final imagePath = await getImage(1);
Provider.of<EntryProvider>(context, listen: false)
.addImageToEntry(imagePath);
}

// HERE FOR INSTANCE IS WHERE I@M MAKING A CHANGE TO THE ENTRY THAT SHOWS ON THE LIST
_imgFromGallery() async {
final imagePath = await getImage(2);
Provider.of<EntryProvider>(context, listen: false)
.addImageToEntry(imagePath);
}


Widget _buildTagList() {
return Container(
height: 71,
margin: EdgeInsets.only(top: 5, bottom: 5),
child: Column(
children: <Widget>[
Container(
alignment: Alignment.topLeft,
child: Text(MyLocalizations.of(context).entryScreenTags,
style: TextStyle(fontSize: 18)),
),
Consumer<EntryProvider>(
builder: (context, entryProvider, child) => CreateHashtags(
entryProvider.getEntry.tags,
_addTag,
_removeTag,
),
),
],
),
);
}

void _addTag(String tagText) {
Provider.of<EntryProvider>(context, listen: false).addTagToEntry(tagText);
}

void _removeTag(int index) {
Provider.of<EntryProvider>(context, listen: false).removeTagAt(index);
}

void _removeImage(int index) {
Provider.of<EntryProvider>(context, listen: false).removeImageAt(index);
}

@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomPadding: true,
body: Container(
alignment: Alignment.topCenter,
color: Colors.white,
padding: EdgeInsets.only(
left: 20,
right: 20,
),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
EntryMetaTags(widget.entry, _getAddressFromLatLng),
SizedBox(
height: 10,
),
Container(
alignment: Alignment.topLeft,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
InkWell(
onTap: _presentDatePicker,
child: Text(
DateFormat.yMMMd().format(
widget.entry.entryDate != null
? widget.entry.entryDate
: DateTime.now(),
),
style: TextStyle(fontSize: 24),
),
),
if (widget.entry.id != null)
IconButton(
onPressed: () {
_showDeleteDialog(context);
},
icon: Icon(
Icons.delete,
color: Theme.of(context).primaryColor,
),
),
],
),
),
Container(
alignment: Alignment.topLeft,
child: TextFormField(
onSaved: (String title) {
Provider.of<EntryProvider>(context, listen: false)
.getEntry
.title = title;
},
textCapitalization: TextCapitalization.sentences,
controller: _titleController,
decoration: InputDecoration(
hintText: MyLocalizations.of(context).entryScreenEnterTitle,
contentPadding: EdgeInsets.all(0),
border: InputBorder.none,
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(width: 0, color: Colors.white),
),
),
style: TextStyle(fontSize: 20),
),
),
Container(
height: 190,
margin: EdgeInsets.only(top: 5),
alignment: Alignment.topLeft,
child: TextFormField(
onSaved: (String description) {
Provider.of<EntryProvider>(context, listen: false)
.getEntry
.description = description;
},
validator: (description) {
if (description.isEmpty) {
return MyLocalizations.of(context)
.entryScreenEnterDescriptionWarn;
}

return null;
},
maxLines: 8,
keyboardType: TextInputType.text,
textCapitalization: TextCapitalization.sentences,
controller: _descriptionController,
decoration: InputDecoration(
hintText:
MyLocalizations.of(context).entryScreenEnterDescription,
contentPadding: EdgeInsets.all(0),
border: InputBorder.none,
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(width: 0, color: Colors.white),
),
),
style: TextStyle(fontSize: 18),
),
),
_buildTagList(),
SizedBox(
height: 3,
),
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
MyLocalizations.of(context).entryScreenImages,
style: TextStyle(fontSize: 18),
),
],
),
),
Consumer<EntryProvider>(
builder: (context, entryProvider, child) => ImageList(
entryProvider.getEntry.images,
_removeImage,
_showPicker,
_showImageDialog,
),
),
SizedBox(
height: 5,
),
],
),
),
),
);
}
}

最佳答案

是的,对象是通过引用传递的。因此,您正在修改同一个对象。由于there is no reflection in Flutter ,您无法真正自动制作副本。

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

更新:重要的是要注意 List 和 Map 也是通过引用传递的。因此,您需要使用 List.fromspread operator在您自己的 copyWith 实现中。

例子:

Entry(
images: images ?? List.from(this.images),
);

关于flutter - 更改一个 Provider 中的属性会将另一个 Provider 中的属性更改为 List Flutter,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65845812/

31 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com