Reputation: 1536
I am building an app with flutter and the provider pattern. I have particular one ViewModel, that gets provided with Provider.of<AddressBookModel>(context)
.
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<AddressBookViewModel>(
builder:(_) => AddressBookViewModel(),
child: Scaffold(
body: _getBody(context);
}
Widget _getBody(BuildContext context) {
AddressBookViewModel vm = Provider.of<AddressBookViewModel>(context);
// AddressBookViewModel holds a list of contact objects
// (id, name, street, starred etc.)
List<Contact> contacts = vm.contacts;
return ListView.builder(
itemCount: contacts.length,
itemBuilder: (context, index) => ListTile(
title: Text(contacts[index].name),
trailing: contacts[index].starred
? Icon(Icons.star))
: null,
/**
* Changing one object rebuilds and redraws the whole list
*/
onLongPress: () => vm.toggleStarred(contacts[index]);
));
}
}
And the respective ViewModel
class AddressBookViewModel with ChangeNotifier {
final List<Contact> contacts;
AddressBookViewModel({this.contacts = []});
void toggleStarred(Contact contact) {
int index = contacts.indexOf(contact);
// the contact object is immutable
contacts[index] = contact.copy(starred: !contact.starred);
notifyListeners();
}
}
The problem I am facing is, once I am changing one contact object in the list with toggleStarred()
,
the provider is rebuilding and redrawing the whole list. This is not necessary in my opinion, as only
the one entry needs to be rebuild. Is there any way to have a provider that is only responsible
for one list item? Or any other way to solve this problem?
Upvotes: 0
Views: 141
Reputation: 276911
When working with lists, you'll want to have a "provider" for each item of your list and extract the list item into a constant – especially if the data associated to your item is immutable.
Instead of:
final contactController = Provider.of<ContactController>(context);
return ListView.builder(
itemCount: contactController.contacts.length,
builder: (_, index) {
reurn Text(contactController.contacts[index].name);
}
)
Prefer:
final contactController = Provider.of<ContactController>(context);
return ListView.builder(
itemCount: contactController.contacts.length,
builder: (_, index) {
reurn Provider.value(
value: contactController.contacts[index],
child: const ContactItem(),
);
}
)
Where ContactItem
is a StatelessWidget
that typically look like so:
class ContactItem extends StatelessWidget {
const ContactItem({Key key}): super(key: key);
@override
Widget build(BuildContext context) {
return Text(Provider.of<Contact>(context).name);
}
}
Upvotes: 1
Reputation: 8599
class Contact with ChangeNotifier { }
bool starred;
void toggleStarred() {
starred = !starred;
notifyListeners();
}
class Contact with ChangeNotifier {
final String name;
bool starred;
Contact(this.name, this.starred);
void toggleStarred() {
starred = !starred;
notifyListeners();
}
}
class ContactView extends StatelessWidget {
Widget build(BuildContext context) {
return ListTile();
}
}
(context, index) {
return ChangeNotifierProvider.value(
value: contacts[index],
child: ContactView(),
);
final contact = Provider.of<Contact>(context);
onLongPress: () => contact.toggleStarred(),
class ContactView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final contact = Provider.of<Contact>(context);
print("building ListTile item with contact " + contact.name);
return ListTile(
title: Text(contact.name),
trailing: contact.starred ? Icon(Icons.star) : null,
onLongPress: () => contact.toggleStarred(),
);
}
}
Steps[5] Code Changes Review :
return ListView.builder(
itemCount: contacts.length,
itemBuilder: (context, index) {
print("building ListView item with index $index");
return ChangeNotifierProvider.value(
value: contacts[index],
child: ContactView(),
);
},
);
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider<AddressBookViewModel>(
builder: (context) => AddressBookViewModel(),
child: HomeScreen(),
),
);
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<AddressBookViewModel>(
builder: (context) => AddressBookViewModel(),
child: MaterialApp(
home: Scaffold(
body: _getBody(context),
),
),
);
}
Widget _getBody(BuildContext context) {
AddressBookViewModel vm = Provider.of<AddressBookViewModel>(context);
final contacts = vm.contacts;
return ListView.builder(
itemCount: contacts.length,
itemBuilder: (context, index) {
print("building ListView item with index $index");
return ChangeNotifierProvider.value(
value: contacts[index],
child: ContactView(),
);
},
);
}
}
// product_item.dart
class ContactView extends StatelessWidget {
@override
Widget build(BuildContext context) {
final contact = Provider.of<Contact>(context);
print("building ListTile item with contact " + contact.name);
return ListTile(
title: Text(contact.name),
trailing: contact.starred ? Icon(Icons.star) : null,
onLongPress: () => contact.toggleStarred(),
);
}
}
class AddressBookViewModel with ChangeNotifier {
final contacts = [
Contact("Contact A", false),
Contact("Contact B", false),
Contact("Contact C", false),
Contact("Contact D", false),
];
void addcontacts(Contact contact) {
contacts.add(contact);
notifyListeners();
}
}
class Contact with ChangeNotifier {
final String name;
bool starred;
Contact(this.name, this.starred);
void toggleStarred() {
starred = !starred;
notifyListeners();
}
}
Upvotes: 1