Flutter Bloc is not updating the state/ not working

Wai Yan Hein picture Wai Yan Hein · Apr 21, 2020 · Viewed 8.1k times · Source

I am developing a mobile application using Flutter. I am using the flutter block package, https://pub.dev/packages/flutter_bloc for managing and setting up the bloc. But when the state change it is not updating the widgets or views.

I have a bloc class file called home_bloc.dart with the following implementation.

class HomeEvent {
  static const int FETCH_ARTICLES = 1;
  static const int TOGGLE_IS_FILTERING = 2;
  int _event = 0;
  String _filterKeyword = "";

  int get event => _event;

  void set event(int event) {
    this._event = event;
  }

  String get filterKeyword => _filterKeyword;

  void set filterKeyword(String filterKeyword) {
    this._filterKeyword = filterKeyword;
  }
}

class HomeBloc extends Bloc<HomeEvent, HomeState> {
  Repository _repository = Repository();
  HomeState state = HomeState();

  @override
  HomeState get initialState => state;

  @override
  Stream<HomeState> mapEventToState(HomeEvent event) async* {
    switch (event.event) {
      case HomeEvent.FETCH_ARTICLES:
        {
          List<dynamic> articles = List<dynamic>();
          fetchArticles(filter: event.filterKeyword).listen((dynamic article) {
            articles.add(article);
          });
          state = state.copyWith(articles: articles);
          break;
        }
      case HomeEvent.TOGGLE_IS_FILTERING:
        {
          state.isFiltering = ! state.isFiltering;
          state = state.copyWith();
          break;
        }
      default:
        {
          state = state.initial();
          break;
        }
    }

    yield state;
  }

  Stream<dynamic> fetchArticles({String filter = ""}) async* {
    List<dynamic> list = (this.state.articles.length > 0)
        ? this.state.articles
        : await _repository.getArticles();
    if (filter.isNotEmpty) {
      for (var article in list) {
        if (article is String) {
          yield article;
        } else if (article.title.contains(filter)) {
          yield article;
        }
      }
    } else {
      for (var article in list) {
        yield article;
      }
    }
  }
}

class HomeState {
  bool _isFiltering = false;
  List<dynamic> _articles = List<dynamic>();

  bool get isFiltering => _isFiltering;

  void set isFiltering(bool isFiltering) {
    this._isFiltering = isFiltering;
  }

  List<dynamic> get articles => _articles;

  void set articles(List<dynamic> list) {
    this._articles = list;
  }

  HomeState initial() {
    HomeState state = HomeState();
    state.isFiltering = false;
    state.articles = List<dynamic>();

    return state;
  }

  HomeState copyWith({ bool isFiltering, List<dynamic> articles }) {
    HomeState state = HomeState();
    state.isFiltering = isFiltering != null? isFiltering: this._isFiltering;
    state.articles = articles!=null && articles.length > 0? articles: this._articles;

    return state;
  }
}

This is my repository class returning dummy data.

class Repository {
  Future<List<dynamic>> getArticles() async {
    List<dynamic> list = List<dynamic>();
    list.add("A");

    Article article1 = Article();
    article1.id = 1;
    article1.title = "A start is born";
    list.add(article1);

    Article article2 = Article();
    article2.id = 2;
    article2.title = "Asking for help";
    list.add(article2);

    Article article3 = Article();
    article3.id = 3;
    article3.title = "Angel is comming";
    list.add(article3);

    list.add("B");

    Article article4 = Article();
    article4.id = 4;
    article4.title = "Baby Boss";
    list.add(article4);

    Article article5 = Article();
    article5.id = 5;
    article5.title = "Beginner guide to Staying at Home";
    list.add(article5);

    list.add("C");

    Article article6 = Article();
    article6.id = 6;
    article6.title = "Care each other";
    list.add(article6);

    Article article7 = Article();
    article7.id = 7;
    article7.title = "Controlling the world";
    list.add(article7);

    Article article8 = Article();
    article8.id = 8;
    article8.title = "Chasing the dream";
    list.add(article8);

    return list;
  }
}

This is my HomePage widget

class HomePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _HomePageState();
  }
}

class _HomePageState extends State<HomePage> {
  IconData _searchIcon = Icons.search;
  Widget _appBarTitle;
  HomeBloc _homeBloc;

  @override
  void initState() {
    super.initState();
    this._homeBloc = BlocProvider.of(context);
    WidgetsBinding.instance.addPostFrameCallback((_) => this.fetchArticles());
    WidgetsBinding.instance
        .addPostFrameCallback((_) => this.buildAppBarTitle());
  }

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<HomeBloc, HomeState>(
      builder: (context, state) {
        return Scaffold(
          appBar: AppBar(title: Text("Home"),),
          body: Container(
            child: buildListView(context, state),
          ),
        );
      },
    );
  }

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

  void buildAppBarTitle() {
    this.setState(() {
      if (_searchIcon == Icons.search) {
        this._appBarTitle = Text("Home");
      } else {
        this._appBarTitle = TextField(
          onChanged: (String inputValue) {
            debugPrint("Search term has changed $inputValue");
            //homeBloc.fetchArticles(filter: inputValue);
          },
          style: TextStyle(
            color: Colors.white,
          ),
          decoration: InputDecoration(
            hintText: "Search",
          ),
        );
      }
    });
  }

  Widget buildAppBarSearchIcon() {
    return IconButton(
        icon: Icon(
          _searchIcon,
          color: Colors.white,
        ),
        onPressed: () {
          if (this._searchIcon == Icons.search) {
            //display the search text field and close icons

            this.setState(() {
              this._searchIcon = Icons.close;
              this.buildAppBarTitle();
              //homeBloc.toggleFiltering();
            });
          } else {
            this.fetchArticles();
            this.setState(() {
              this._searchIcon = Icons.search;
              this.buildAppBarTitle();
              //homeBloc.toggleFiltering();
            });
          }
        });
  }

  Widget buildListView(
      BuildContext context, HomeState state) {
    if (state.articles.length > 0) {
      var listView = ListView.builder(
          itemCount: state.articles.length,
          itemBuilder: (context, index) {
            var item = state.articles[index];

            if (item is String) {
              return buildListFirstInitialView(item);
            }

            Article article = item as Article;

            return buildListArticleView(article);
          });

      return listView;
    } else {
      return Center(
        child: Text("No resources found."),
      );
    }
  }

  Widget buildListFirstInitialView(String initial) {
    return ListTile(
      title: Text(initial),
    );
  }

  Widget buildListArticleView(Article article) {
    return ListTile(
      title: Text(article.title),
    );
  }

  Widget buildBottomNavigationBar() {
    return BottomNavigationBar(
        currentIndex: 0,
        onTap: (int position) {},
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            title: Text('Home'),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.settings),
            title: Text('Settings'),
          ),
        ]);
  }

  void fetchArticles({String filter = ""}) {
    HomeEvent event = HomeEvent();
    event.event = HomeEvent.FETCH_ARTICLES;
    _homeBloc.add(event);
  }
}

As you can see this is my HomePage widget is doing. It will fetch the articles after the widget is built. Then the list view will be updated with the dummy data.

This is my main.dart file.

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        // This is the theme of your application.
        //
        // Try running your application with "flutter run". You'll see the
        // application has a blue toolbar. Then, without quitting the app, try
        // changing the primarySwatch below to Colors.green and then invoke
        // "hot reload" (press "r" in the console where you ran "flutter run",
        // or simply save your changes to "hot reload" in a Flutter IDE).
        // Notice that the counter didn't reset back to zero; the application
        // is not restarted.
        primarySwatch: Colors.blue,
      ),
      home: BlocProvider(
        create: (context) => HomeBloc(),
        child: HomePage(),
      ),
    );
  }
}

When I run my app, it is not updating the list view with the dummy data. Instead, it is always showing the message for no records found.

enter image description here

Why is it not working?

Answer

Ricardo Mu&#241;oz picture Ricardo Muñoz · Nov 13, 2020

I had the same issue and solved this problem changing:

yield status; 

to

yield status.toList();

At the end of my mapEventToState() method.

And probably you had to make for all yield that is passing a List.

If it worked for you let me know