I'm using the Provider package to Read, Edit and Delete elements from a list.
My class provider contains all 3 elements, but I'm only having trouble with the Editing bit. Here is the relevant part of the code:
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 (The calling of the function is under '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,
),
),
),
],
),
);
}
}
So, I can fetch the whole list, I can remove it with the Dismissible, but on Editing nothing changes. I see the database changes, but nothing on the UI.
Edit: I wrote a 'print' statement inside the ListView.build builder method, showing that it nevers updates.
I can 'force' the redrawing of the UI with 'setState(() {})', but that's bad code and I want to use the proper methods.
Seems like I'm forgetting something small, but I fail to see it. The function works, I use notifyListeners() after the 'await' section, but it is not updating the UI.
What I am missing on the code?
Ok after re reading your code I found your error, you're not using the class that is being provided, you're creating it anew
await GenreProvider().updateGenre(genre['id'], _titleController.text); //This is not the same instance that you
You shouldn't create a new class GenreProvider()
, use Provider.of(context)
to get the one your app is using
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);
}
}
Also in your dismissible
onDismissed: (_) => genreProvider.removeGenre(genre['id']),
The fact that you're not seeing the same error when using dismiss is that the method actually deletes the object from your DB and then dismiss widget does the animation, but if you debug you'll actually find that the object remains in _genres, is just the UI that hidden it