'ListView not displaying item with BLoC pattern

I'm a flutter beginner and I'm currently working on an app that let me take a photo and then update a ListView to display the photo with a little text description under. Both are placed inside a Card. I already managed to make it work properly. Recently I dived into BLoC pattern and I tried to implement it on this app. The problemn is that the ListView doesn't display what goes out of the stream even though snapshot contain the data added.

Here is my main :

class MyApp extends StatelessWidget {
  final LandscapeBloc _landscapeBloc = LandscapeBloc();
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      routes: <String,WidgetBuilder>{
        '/CameraPage': (BuildContext context)=> CameraScreen(cameras,_landscapeBloc),
      },
      initialRoute: '/',
      home: HomePage(_landscapeBloc)
    );
  }
} 

Here is my code for the HomePage that contain the ListView that is exposed to the streamOutput

class HomePage extends StatefulWidget {
  final LandscapeBloc _landscapeBloc;

  @override
  _HomePageState createState() {
    return _HomePageState();
  }

  HomePage(this._landscapeBloc);
}

class _HomePageState extends State<HomePage> {
  List<LandscapeCard> list = [];



  @override
    void dispose() {
      widget._landscapeBloc.dispose();
      super.dispose();
    }



  @override
  Widget build(BuildContext context) {
    return Scaffold(
            body: StreamBuilder(
            stream: widget._landscapeBloc.landscapeCardStream,
            initialData: list,
            builder: (BuildContext context,
                AsyncSnapshot<List<LandscapeCard>> snapshot) {
              print("[snaphot]" + snapshot.data.toString());
              return (Column(children: <Widget>[
                Flexible(
                  child: ListView.builder(
                      physics: const AlwaysScrollableScrollPhysics(),
                      itemCount: snapshot.data.length,
                      itemBuilder: (BuildContext context, int index) {
                        final LandscapeCard item = snapshot.data[index];
                        return Dismissible(
                            key: Key(item.landscapeImagePath),
                            onDismissed: (direction) {
                              widget._landscapeBloc.landscapeEvent
                                  .add(DeleteEvent(index));
                              Scaffold.of(context).showSnackBar(
                                  SnackBar(content: Text("item suprrimé")));
                            },
                            child: item);
                      },
                      padding: const EdgeInsets.all(10.0)),
                ),
              ]));
            }));
  }
  } 

I know that the StreamBuilder receive the data cause when I print(snapshot) the list Contain the added photo.

I input data here;

class CardBuilder extends StatefulWidget {

  final String _path;
  final LandscapeBloc landscapeBloc;

  CardBuilder(this._path, this._landscapeBloc);

@override
  _CardBuilderState createState() {
    // TODO: implement createState
    return _CardBuilderState();
  }


}


class _CardBuilderState extends State<CardBuilder> {
  //TODO: stateful widget pour mis a jour UI quand on tape un texte


  final TextEditingController descController = TextEditingController();


  @override
    void dispose() {
      widget.landscapeBloc.dispose();
      descController.dispose();
      super.dispose();
    }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: Colors.white,
        body: SingleChildScrollView(
          child: Column(children: <Widget>[
            Align(
              alignment: Alignment.centerLeft,
              child: IconButton(
                  icon: Icon(
                    Icons.arrow_back,
                    color: Colors.black,
                  ),
                  iconSize: 35.00,
                  onPressed: widget._onPressedBack),
            ),
            DisplayMedia(widget._path),
            Divider(
              color: Colors.black,
              height: 5,
            ),
            TextField(
              autofocus: false,
              controller: descController,
              maxLines: 5,
              decoration: InputDecoration(
                  filled: false,
                  fillColor: Colors.black12,
                  border: InputBorder.none,
                  hintText: "put a description"),
            ),
            Divider(
              color: Colors.black,
              height: 5,
            ),
            IconButton(
                icon: Icon(Icons.check_circle, color: Colors.black),
                iconSize: 35.00,
                onPressed: () {
                  final LandscapeCard landscapeCard= LandscapeCard(descController.text,widget._path);
                  widget._landscapeBloc.landscapeEvent.add(AddEvent(landscapeCard));
                  Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(
                    builder: (BuildContext context) => HomePage(widget._landscapeBloc)
                  ),(Route route)=> route==null);
                })
          ]),
        ));
  }
}

here is the BLoc:

class LandscapeBloc  {
  List<LandscapeCard> list = [];
  final StreamController _listLandscapeStateController = StreamController<List<LandscapeCard>>.broadcast();


  StreamSink<List<LandscapeCard>> get _landscapeSink => _listLandscapeStateController.sink;


  Stream<List<LandscapeCard>> get landscapeCardStream => _listLandscapeStateController.stream;

  final StreamController<ManagingListEvent> _listLandscapeEventController = StreamController<ManagingListEvent>.broadcast();


  StreamSink<ManagingListEvent> get landscapeEvent => _listLandscapeEventController.sink;

  LandscapeBloc(){
    _listLandscapeEventController.stream.listen(onManageEvent);
  }

  void onManageEvent(ManagingListEvent event){
    if(event is DeleteEvent){
      list.removeAt(event.indexDelete);
    }
    else{
      list.add(event.landscapeCardAdd);
    }
    _landscapeSink.add(list);
    print("[onManageEvent]"+event.landscapeCardAdd.landscapeImagePath+"   "+event.landscapeCardAdd.landscapeDesc);
  }

  void dispose(){
    _listLandscapeEventController.close();
    _listLandscapeStateController.close();
  }
} 

and finally the ManageListEvent Class

abstract class ManagingListEvent{
  int _indexS;
  LandscapeCard _landscapeCardS;;

  int get indexDelete => _indexS;

  LandscapeCard get landscapeCardAdd  => _landscapeCardS;
}

class DeleteEvent extends ManagingListEvent{

  DeleteEvent(int index){
    super._indexS=index;
  }
}

class AddEvent extends ManagingListEvent{  

  AddEvent(LandscapeCard landscape){
    super._landscapeCardS=landscape;
  }
} 

Basically I want to Navigate to a new Page where I have the photo that i've just Take, add a little description, put it in the sink and then navigate back to HomePage. But the ListView is not rerendered with the new widgets even though it receive it through the snapshot. Thanks in advance for your answers and your time



Solution 1:[1]

The reason why the ListView wasn't updated is because the Stream wasn't populated before the StreamBuilder is rendered on Screen. The StreamBuilder will only rebuild if there's change detected on Stream. You can populate the Stream on initState() to have the data ready to be displayed.

@override
void initState(){
  super.initState();

  // Fetch initial List data for Stream
  widget._landscapeBloc. fetchLandscapeCardList();
}

On LandscapeBloc, you can add the helper to fetch the initial List.

fetchLandscapeCardList() {
  List<LandscapeCard> initList = // Fetch initial data
  _listLandscapeStateController.sink.add(initList);
}

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 Omatt