提供商:ListView.builder 编辑一个元素时未更新
Provider: ListView.builder not updating on editing one element
我正在使用 Provider 包读取、编辑和删除列表中的元素。
我的 class 提供程序包含所有 3 个元素,但我只有编辑位有问题。这是代码的相关部分:
main.dart
...
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => UserProvider(),
),
ChangeNotifierProvider(
create: (context) => GenreProvider(),
),
],
child: MyApp(),
),
);
}
...
genre_provider.dart
class GenreProvider with ChangeNotifier {
static List<Map<String, dynamic>> _genres = [];
Future fetchGenres() async {
...
}
Future updateGenre(String id, String title) async {
try {
await FirebaseFirestore.instance
.collection('genres')
.doc(id)
.update({'title': title});
var updatedGenre = _genres.firstWhere((element) => element['id'] == id);
updatedGenre['title'] = title;
notifyListeners();
} catch (e) {
throw (e);
}
}
void removeGenre(String id) async {
...
}
List<Map<String, dynamic>> get getGenres {
return _genres;
}
}
genre_list.dart(函数的调用在'showEditTitleDialog'下)
class GenreListScreen extends StatefulWidget {
static const routeName = '/genre-list';
@override
_GenreListScreenState createState() => _GenreListScreenState();
}
class _GenreListScreenState extends State<GenreListScreen> {
final TextEditingController _titleController = new TextEditingController();
final GlobalKey<FormState> _keyDialogForm = new GlobalKey<FormState>();
var initialGenreTitle = '';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(title: 'List of all musical Genres'),
body: Center(
child: Consumer<GenreProvider>(
builder: (context, genreProvider, child) {
List<Map<String, dynamic>> genreList = genreProvider.getGenres;
return ListView.builder(
itemBuilder: (context, index) {
var genre = genreList[index];
return Dismissible(
key: Key(genre['id']),
background: kDismissibleContainer,
onDismissed: (_) => GenreProvider().removeGenre(genre['id']),
child: Card(
child: Row(
children: [
Expanded(
child: Text(genre['title']),
),
IconButton(
icon: Icon(Icons.edit),
onPressed: () {
_titleController.text = genre['title'];
showEditTitleDialog(context, genre);
},
),
],
),
),
);
},
itemCount: genreList.length,
);
},
),
),
);
}
Future showEditTitleDialog(BuildContext context, Map<String, dynamic> genre) {
return showDialog(
context: context,
builder: (_) => AlertDialog(
title: Form(
key: _keyDialogForm,
child: Column(
children: <Widget>[
TextFormField(
decoration: const InputDecoration(
icon: Icon(Icons.edit),
),
controller: _titleController,
maxLength: 20,
textAlign: TextAlign.center,
onSaved: (value) {
_titleController.text = value;
},
validator: (value) {
if (value.isEmpty) {
return 'Enter a genre, please.';
} else if (value.length < 2) {
return 'Too short.';
}
return null;
},
)
],
),
),
actions: <Widget>[
TextButton(
onPressed: () async {
if (_keyDialogForm.currentState.validate()) {
_keyDialogForm.currentState.save();
try {
await GenreProvider()
.updateGenre(genre['id'], _titleController.text);
} on Exception catch (_) {
showSnackBar(
text: 'Unknown error updating the genre.',
color: Colors.red,
context: context,
);
} finally {
Navigator.pop(context);
}
}
},
child: Text(
'Save',
style: TextStyle(
color: Colors.blue,
),
),
),
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text(
'Cancel',
style: TextStyle(
color: Colors.red,
),
),
),
],
),
);
}
}
所以,我可以获取整个列表,我可以使用 Dismissible 将其删除,但在编辑时没有任何变化。我看到数据库发生变化,但 UI.
上没有任何变化
编辑:我在 ListView.build 构建器方法中写了一个 'print' 语句,表明它从不更新。
我可以 'force' 使用 'setState(() {})' 重绘 UI,但那是错误的代码,我想使用正确的方法。
好像忘记了什么小东西,但我没看到。该函数有效,我在 'await' 部分之后使用 notifyListeners() ,但它没有更新 UI.
代码中缺少什么?
好的,在重新阅读您的代码后我发现了您的错误,您没有使用提供的 class,您正在重新创建它
await GenreProvider().updateGenre(genre['id'], _titleController.text); //This is not the same instance that you
您不应该创建一个新的 class GenreProvider()
,使用 Provider.of(context)
获取您的应用正在使用的
Future showEditTitleDialog(BuildContext context, Map<String, dynamic> genre) {
final genreProvider = Provider.of<GenreProvider>(context, listen: false);
//.... your code
onTap: () {
//......
await genreProvider.updateGenre(genre['id'], _titleController.text);
}
}
也在您的可忽略范围内
onDismissed: (_) => genreProvider.removeGenre(genre['id']),
您在使用 dismiss 时没有看到相同的错误是因为该方法实际上从您的数据库中删除了对象,然后 dismiss 小部件执行动画,但是如果您调试,您实际上会发现该对象保留在 _genres 中,只是隐藏它的 UI
我正在使用 Provider 包读取、编辑和删除列表中的元素。
我的 class 提供程序包含所有 3 个元素,但我只有编辑位有问题。这是代码的相关部分:
main.dart
...
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => UserProvider(),
),
ChangeNotifierProvider(
create: (context) => GenreProvider(),
),
],
child: MyApp(),
),
);
}
...
genre_provider.dart
class GenreProvider with ChangeNotifier {
static List<Map<String, dynamic>> _genres = [];
Future fetchGenres() async {
...
}
Future updateGenre(String id, String title) async {
try {
await FirebaseFirestore.instance
.collection('genres')
.doc(id)
.update({'title': title});
var updatedGenre = _genres.firstWhere((element) => element['id'] == id);
updatedGenre['title'] = title;
notifyListeners();
} catch (e) {
throw (e);
}
}
void removeGenre(String id) async {
...
}
List<Map<String, dynamic>> get getGenres {
return _genres;
}
}
genre_list.dart(函数的调用在'showEditTitleDialog'下)
class GenreListScreen extends StatefulWidget {
static const routeName = '/genre-list';
@override
_GenreListScreenState createState() => _GenreListScreenState();
}
class _GenreListScreenState extends State<GenreListScreen> {
final TextEditingController _titleController = new TextEditingController();
final GlobalKey<FormState> _keyDialogForm = new GlobalKey<FormState>();
var initialGenreTitle = '';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CustomAppBar(title: 'List of all musical Genres'),
body: Center(
child: Consumer<GenreProvider>(
builder: (context, genreProvider, child) {
List<Map<String, dynamic>> genreList = genreProvider.getGenres;
return ListView.builder(
itemBuilder: (context, index) {
var genre = genreList[index];
return Dismissible(
key: Key(genre['id']),
background: kDismissibleContainer,
onDismissed: (_) => GenreProvider().removeGenre(genre['id']),
child: Card(
child: Row(
children: [
Expanded(
child: Text(genre['title']),
),
IconButton(
icon: Icon(Icons.edit),
onPressed: () {
_titleController.text = genre['title'];
showEditTitleDialog(context, genre);
},
),
],
),
),
);
},
itemCount: genreList.length,
);
},
),
),
);
}
Future showEditTitleDialog(BuildContext context, Map<String, dynamic> genre) {
return showDialog(
context: context,
builder: (_) => AlertDialog(
title: Form(
key: _keyDialogForm,
child: Column(
children: <Widget>[
TextFormField(
decoration: const InputDecoration(
icon: Icon(Icons.edit),
),
controller: _titleController,
maxLength: 20,
textAlign: TextAlign.center,
onSaved: (value) {
_titleController.text = value;
},
validator: (value) {
if (value.isEmpty) {
return 'Enter a genre, please.';
} else if (value.length < 2) {
return 'Too short.';
}
return null;
},
)
],
),
),
actions: <Widget>[
TextButton(
onPressed: () async {
if (_keyDialogForm.currentState.validate()) {
_keyDialogForm.currentState.save();
try {
await GenreProvider()
.updateGenre(genre['id'], _titleController.text);
} on Exception catch (_) {
showSnackBar(
text: 'Unknown error updating the genre.',
color: Colors.red,
context: context,
);
} finally {
Navigator.pop(context);
}
}
},
child: Text(
'Save',
style: TextStyle(
color: Colors.blue,
),
),
),
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text(
'Cancel',
style: TextStyle(
color: Colors.red,
),
),
),
],
),
);
}
}
所以,我可以获取整个列表,我可以使用 Dismissible 将其删除,但在编辑时没有任何变化。我看到数据库发生变化,但 UI.
上没有任何变化编辑:我在 ListView.build 构建器方法中写了一个 'print' 语句,表明它从不更新。
我可以 'force' 使用 'setState(() {})' 重绘 UI,但那是错误的代码,我想使用正确的方法。
好像忘记了什么小东西,但我没看到。该函数有效,我在 'await' 部分之后使用 notifyListeners() ,但它没有更新 UI.
代码中缺少什么?
好的,在重新阅读您的代码后我发现了您的错误,您没有使用提供的 class,您正在重新创建它
await GenreProvider().updateGenre(genre['id'], _titleController.text); //This is not the same instance that you
您不应该创建一个新的 class GenreProvider()
,使用 Provider.of(context)
获取您的应用正在使用的
Future showEditTitleDialog(BuildContext context, Map<String, dynamic> genre) {
final genreProvider = Provider.of<GenreProvider>(context, listen: false);
//.... your code
onTap: () {
//......
await genreProvider.updateGenre(genre['id'], _titleController.text);
}
}
也在您的可忽略范围内
onDismissed: (_) => genreProvider.removeGenre(genre['id']),
您在使用 dismiss 时没有看到相同的错误是因为该方法实际上从您的数据库中删除了对象,然后 dismiss 小部件执行动画,但是如果您调试,您实际上会发现该对象保留在 _genres 中,只是隐藏它的 UI