Satyajyoti Biswas
Satyajyoti Biswas

Reputation: 927

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', 'Jhuma', 'Mondal'];

  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 ?

Upvotes: 3

Views: 3314

Answers (3)

user16054919
user16054919

Reputation: 91

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,
       )

Upvotes: 9

blokberg
blokberg

Reputation: 994

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),
    },
);

Upvotes: 0

Thierry
Thierry

Reputation: 8383

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();
  }

Upvotes: 1

Related Questions