简体   繁体   中英

Flutter: Using 2 nested StreamBuilders where one of them is not working as expected

I'm building and App where I'm using 2 StreamBuilders (one inside another). The outer one consumes an Stream<List<User>> and render that list. The inner one consumes Stream<User> where I can check if the user is favorite or not.

Here is the code:

users_page.dart

@override
Widget build(BuildContext context) {
return Scaffold(
  child: StreamBuilder<List<User>>(
  stream: userBloc.outList,
  initialData: [],
  builder: (BuildContext context, AsyncSnapshot<List<User>> snapshot) {
    final List<User> users = snapshot.data;
    return buildList(users);
  })
}


Widget buildList(List<User> users) {
  return ListView.builder(
    itemCount: users.length,
    itemBuilder: (BuildContext context, int index) {
       final User user = users[index];
       return ListTile(
         title: Text('${user.firstName}'),
         trailing: buildFavoriteButton(user));
  });
}

Widget buildFavoriteButton(User user) {
User oldUser = user;
return StreamBuilder<User>(
  stream: userBloc.outFavorite,
  initialData: oldUser,
  builder: (BuildContext context, AsyncSnapshot<User> snapshot) {
    final User newUser = snapshot.data;
    if (oldUser.id == newUser.id) {
      oldUser = newUser;
    }
    return IconButton(
      icon: Icon(Icons.favorite, color: oldUser.isFavorite ? Colors.red : Colors.blueGrey),
      onPressed: () {
        print('onPressed: This is called once');
        userBloc.inFavorite.add(newUser);
      });
  });
}

users_block.dart

class UserBloc {
  final Repository _repository = Repository();

  // More variables like the BehaviourSubject for outList and so on ...

  final BehaviorSubject<User> _userFavoriteSubject = BehaviorSubject<User>();
  Stream<User> _outFavorite = Stream.empty();
  Stream<User> get outFavorite => _outFavorite;
  Sink<User> get inFavorite => _userFavoriteSubject;

  UserBloc() {
    _outFavorite = _userFavoriteSubject.switchMap<User>((user) {
      print('userBloc: This is called N times')
      return user.isFavorite ? _repository.removeFromFavorite(user) : _repository.saveAsFavorite(user);
    });
  }
}

The outer stream is called once and the onPressed method is called once as well (as expected).

But the problem I'm having is when I press the Icon: userBloc prints N times (where N is the number of rows in the list), like I would pressed the Icon N times.

So the log is:

print: onPressed: This is called once
print: userBloc: This is called N times
print: userBloc: This is called N times
...
print: userBloc: This is called N times

In this case the action (pressing the icon) is executed once, but userBloc gets N inputs.

Why this is happening and how can I solve this problem?

Thanks in advance!

I made a test where I defined:

Widget buildBody() {
return Column(
  children: <Widget>[
    StreamBuilder<int>(
      stream: userBloc.outState,
      initialData: 0,
      builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
        print("Builder 1");
        print("Snapshot 1: " + snapshot.data.toString());
        return (IconButton(
            icon: Icon(Icons.favorite, color: Colors.red),
            onPressed: () {
              print("onPressed 1");
              userBloc.inEvents.add(1);
            }));
      },
    ),
    StreamBuilder<int>(
      stream: userBloc.outState,
      initialData: 0,
      builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
        print("Builder 2");
        print("Snapshot 2: " + snapshot.data.toString());
        return (IconButton(
            icon: Icon(Icons.favorite, color: Colors.red),
            onPressed: () {
              print("onPressed 2");
              userBloc.inEvents.add(2);
            }));
      },
    )
  ],
);

And the stream:

_outState = _userSubject.switchMap<int>(
  (integer) {
    print("Input (sink): " + integer.toString());
    return doSomething(integer);
  },
);

When I run this code and click the IconButton 1, this is the output:

I/flutter ( 3912): Builder 1
I/flutter ( 3912): Snapshot 1: 0
I/flutter ( 3912): Builder 2
I/flutter ( 3912): Snapshot 2: 0
I/flutter ( 3912): onPressed 1
I/flutter ( 3912): Input (sink): 1
I/flutter ( 3912): Input (sink): 1
I/flutter ( 3912): Builder 1
I/flutter ( 3912): Snapshot 1: 1
I/flutter ( 3912): Builder 2
I/flutter ( 3912): Snapshot 2: 1

As you can see the print "Input (sink): 1" is shown twice. So for any input to the sink the code inside subject is executed n times, depending on the amount of StreamBuilders subscribed to the stream.

Is this behaviour okay, or is it a bug?

I know that the builder function should be call twice because any change in the stream is forwarded to all StreamBuilder subscribed, but the code inside the subject should be call twice too?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM