How to listen for change within a list using flutter provider?
Asked Answered
F

4

22

I have a provider that is listening for changes to a playerList class:

import 'package:flutter/material.dart';
import 'package:scam_artist/constants/PlayerColors.dart';
import 'package:scam_artist/models/Player.dart';

class PlayerList with ChangeNotifier {
  List<Player> _players = [];

  List<Player> get players {
    return [..._players];
  }

  void addPlayer(Key key, String name, int index) {
    _players.add(
        new Player(key: key, color: PlayerColors.colors[index], name: name));
    notifyListeners();
  }

  void editPlayerName(String newName, int index) {
    _players[index].name = newName;
    notifyListeners();
  }

  void editPlayerColor(Color newColor, int index) {
    _players[index].color = newColor;
    notifyListeners();
  }
}

However, when I call a function to change a value to one of the Player objects (change name for example), the list doesn't update the object with new data.

Do I need another provider for the Player class? If so, how do I make the PlayerList provider listen for changes in the Player provider?

Doing a little research, I'm thinking ProxyProvider might be what I'm looking for, but I'm not sure how to implement it.

Here's my Player class if that is helpful:

import 'package:flutter/material.dart';

class Player {
  // id will be for database if implemented
  String uid;
  Key key;
  String name;

  //position is the order where the player plays
  int position;
  Color color;
  bool isTurn;
  bool isFakeArtist;
  bool isWinner;

  Player({this.key, this.name, this.color});
}

And this is where I create the ChangeNotifierProvider:

import 'package:flutter/material.dart';
import 'package:scam_artist/UserListener.dart';
import 'package:scam_artist/models/user.dart';
import 'package:scam_artist/providers/PlayerList.dart';
import 'package:scam_artist/services/AuthService.dart';
import 'package:provider/provider.dart';
import 'package:scam_artist/views/Lobby.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        StreamProvider<User>.value(value: AuthService().user),
        ChangeNotifierProvider(create: (context) => PlayerList())
      ],
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        home: UserListener(),
        routes: {Lobby.routeName: (ctx) => Lobby()},
      ),
    );
  }
}
Fumy answered 31/5, 2020 at 20:8 Comment(1)
I am having the same exact issue. Have you managed to fix it? I have tried sending the notifyListeners() to each object of the list and call it there when I perform my update as well but the behavior still remains. Any ideas? Also, I am currently using ProxyProvider for other things, and that works correctly, but that is for making one provider listen to changes of another provider, I don't know how to create a dynamic list of providers (Player) to use them that way.Kreindler
S
7

There are multiple things wrong here.

1. Fix your PlayerList declaration line.

//CORRECT
class PlayerList extends ChangeNotifier {...}
//WRONG
class PlayerList with ChangeNotifier {...}

4. Fix your players getter line.

Use this:

List<Player> get players => _players;

3. You need more Conceptual Knowledge on Provider.


Basically, Provider makes your PlayerList accessible from anywhere within the Widget Tree below where you have provided it. For example, you are providing from the top of your MaterialApp. So you can access it in your HomePage or Lobby.

To access your PlayerList, you have to use a Consumer widget or Selector widget, but for your case, Consumer is enough. Selector is for advanced usage.

Here is the code for reading live values from your PlayerList.

class Lobby extends StatelessWidget {

  @override
  Widget build(BuildContext context) => Scaffold(
    body: _body(),
  );

  _body() => Container(
    child: Consumer<PlayerList>(
      builder: (BuildContext context, PlayerList bloc) => 
          ListView.builder(
            itemCount: bloc.players.length,
            itemBuilder: 
               (BuildContext context, int index) => Text(bloc.players[index].name),
          ),
    ),

  );

}
Splutter answered 25/1, 2021 at 9:23 Comment(1)
this does not answer the question, it seems they want the list model to listen for changes made on the items (player model)Macruran
S
1

I am having exactly the same problem.

One way I solved it (not the prettiest though) is to have a method in the ChangeNotifier to return a specific object and then watch for changes, e.g.

// in ChangeNotifier

Player getPlayer(int index) => _players[index];

and then in the widget have

context.watch<PlayerList>().getPlayer(index).updateName("newName")
Shebashebang answered 22/8, 2021 at 9:30 Comment(0)
H
1

If you are modifying from parent PlayerList then this is the way because parent is already notifying in your case

import 'package:flutter/material.dart';
class Player with ChangeNotifier {
  Player({String name, Color color}) {
    this.color = color;
    this.name = name;
  }

  String uid;
  Key key;
  String _name;

  String get name => _name;

  set name(String value) {
    _name = value;
    notifyListeners();
  }

  int position;
  Color _color;
  bool isTurn;
  bool isFakeArtist;
  bool isWinner;

  Color get color => _color;

  set color(Color value) {
    _color = value;
    notifyListeners();
  }
}

If you are modifying from within Player and want parent PlayerList to notify

import 'package:flutter/material.dart';
class Player with ChangeNotifier {
  Player({String name, Color color, this.playerList}) {
    this.color = color;
    this.name = name;
  }

  final PlayerList playerList;
  String uid;
  Key key;
  String _name;

  String get name => _name;

  set name(String value) {
    _name = value;
    notifyListeners();
    playerList.notifyListeners();
  }

  int position;
  Color _color;
  bool isTurn;
  bool isFakeArtist;
  bool isWinner;

  Color get color => _color;

  set color(Color value) {
    _color = value;
    notifyListeners();
    playerList.notifyListeners();
  }
}
Harpist answered 15/3, 2022 at 18:47 Comment(0)
A
-1

Just change your _players accessor and you should be good.

  List<Player> get players {
    return _players;
  }
Affusion answered 31/1, 2021 at 2:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.