如何解决更改一个 Provider 中的属性会将另一个 Provider 中的属性更改为 List Flutter 更新 - 我实际上发现它是一个 Flutter Issue
更新 - 我实际上发现它是一个 Flutter Issue。
我有两个提供者,一个是 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);
}
}
}
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,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,)
..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,onpressed: () {
closeDrawer();
},)
: IconButton(
icon: Icon(
Icons.menu,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,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,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,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,)
],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,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,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,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),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,controller: _descriptionController,decoration: Inputdecoration(
hintText:
MyLocalizations.of(context).entryScreenEnterDescription,style: TextStyle(fontSize: 18),_buildTagList(),SizedBox(
height: 3,Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,children: <Widget>[
Text(
MyLocalizations.of(context).entryScreenImages,Consumer<EntryProvider>(
builder: (context,child) => ImageList(
entryProvider.getEntry.images,_removeImage,_showPicker,_showImageDialog,SizedBox(
height: 5,);
}
}
解决方法
是的,对象是通过引用传递的。因此,您正在修改同一个对象。由于 there is no reflection in Flutter,您无法真正自动制作副本。
解决此问题的一种方法是实现您自己的 copyWith 方法。例如,这就是 Flutter 在样式的内部所做的。
更新:需要注意的是 List 和 Map 也是通过引用传递的。因此,您需要在自己的 copyWith
实现中使用 List.from 或 spread operator。
示例:
Entry(
images: images ?? List.from(this.images),);
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。