'Provider - Selector not updating UI for list items
I have a ListView consists of several ListTiles which have a trailing icon. The color of icon should change from transparent to green based on user tap. However the UI is not updating on user interaction.
The ServiceModel is like this.
class ProviderService extends ChangeNotifier {
final List<String> totalNames = ['Somesh', 'Tarulata', 'Indranil', 'Satyajyoti', 'Biswas', 'Sajal', 'Kumar', 'Slipa', 'Sonam', 'Neelam'];
List<String> _selectedNames = [];
List<String> get selectedNames => _selectedNames;
void updateselectedNames(String name) {
bool isExists = _selectedNames.contains(name);
if (isExists)
_selectedNames.remove(name);
else
_selectedNames.add(name);
notifyListeners();
}
}
The ListView goes like this.
class Members extends StatelessWidget {
@override
Widget build(BuildContext context) {
ProviderService plService = Provider.of<ProviderService>(context, listen: false);
return Scaffold(
body: SafeArea(
child: Selector<ProviderService, List<String>>(
selector: (_, service) => service.selectedNames,
builder: (context, selNames, child) {
if (plService.totalNames.isEmpty) return child;
return ListView.separated(
shrinkWrap: true,
itemBuilder: (context, index) {
String _name = plService.totalNames[index];
return ListTile(
title: Text('$_name'),
trailing: Icon(
Icons.check_circle,
color: selNames.contains(_name) ? Colors.lightGreen : Colors.transparent,
),
onTap: () {
plService.updateselectedNames(_name),
print(selNames);
},
);
},
separatorBuilder: (_, __) => Divider(),
itemCount: plService.totalNames.length,
);
},
child: Center(
child: Text('No names have been found', textAlign: TextAlign.center),
),
),
),
);
}
}
and of course the main.dart is like this.
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: ChangeNotifierProvider(
create: (context) => ProviderService(),
child: Members(),
),
);
}
}
Even though the list selectedNames updated, the UI remains same. What's going on wrong here ?
Solution 1:[1]
You may add the shouldRebuild parameter of Selector and return true?like this:
Selector<ProviderService, List<String>>(
selector: (_, service) => service.selectedNames,
builder: (context, selNames, child) {...},
shouldRebuild: (previous, next) => true,
)
Solution 2:[2]
When you use a Selector, you have to make sure that the selected object is immutable.
Selector<ProviderService, List<String>>(
selector: (_, service) => service.selectedNames,
builder: (context, selNames, child) { ...},
),
builder will only get called once because your selectedNames object always stays the same. You are removing and adding items in the same array Object.
So, you should instead provide a new array in your updateselectedNames:
void updateselectedNames(String name) {
_selectedNames = _selectedNames.contains(name)
? _selectedNames.where((item) => item != name).toList()
: [..._selectedNames, name];
notifyListeners();
}
Solution 3:[3]
My way would be like this for your scenario.
class ProviderService extends ChangeNotifier {
final List<Name> totalNames = [
Name(name: 'Somesh', isTransparent: false),
Name(name: 'Tarulata', isTransparent: false),
];
List<Name> _selectedNames = [];
List<Name> get selectedNames => _selectedNames;
void updateselectedNames(int index) {
var exist = _isExist(totalNames[index]);
if(exist){
_selectedNames.remove(totalNames[index]);
} else {
_selectedNames.add(totalNames[index]);
}
totalNames[index].isTransparent = !totalNames[index].isTransparent;
notifyListeners();
}
bool _isExist(Name name) {
var filter = _selectedNames.singleWhere(
(element) => element.name == name.name,
orElse: () => null,
);
return filter != null;
}
}
class Name {
String name;
bool isTransparent;
Name({this.name, this.isTransparent});
}
And you can use Selector in ListView for every ListTile
Selector<ProviderService, Name>(
selector: (_, service) => service.totalNames[index],
builder: (context, name, child) {
return ListTile(
title: Text('${name.name}'),
trailing: Icon(
Icons.check_circle,
color: !name.isTransparent ? Colors.lightGreen : Colors.transparent,
),
onTap: () {
plService.updateselectedNames(index),
},
);
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | user16054919 |
| Solution 2 | Thierry |
| Solution 3 | blokberg |
