Flutter Bloc is not updating the state/ not working
Asked Answered
H

5

11

I am developing a mobile application using Flutter. I am using the flutter bloc 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.

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?

Hartsfield answered 21/4, 2020 at 4:20 Comment(0)
D
7

your state class is not equatable ... Dart can't tell when the state has changed

You must use: import 'package:equatable/equatable.dart';

I also don't think you should SET the state property on your bloc class. You should only yield it and let the bloc update it...

please check the docs as I could be wrong

Dinky answered 3/11, 2020 at 6:5 Comment(1)
You were right!Cheeseparing
L
6

Bloc will never change If the state didn't change, you might be confused that you assigned a state, but the fact is that Bloc is a Stream, you'll need to yield a state instead of assigning It directly.

Hopefully, you had implemented a copyWith, so you could do It as below:

yield state.copyWith(articles: articles)

For keeping your structure, you could still use:

// state = state.copyWith(articles: articles);
newState = state.copyWith(articles: articles);
...
yield newState;

Because state variable is used by Bloc, you must use another variable to prevent Bloc from comparing the same variable (state == state will always true, so the state never changed.)

Localism answered 21/4, 2020 at 6:9 Comment(2)
Hi, thanks for posting the answer. I tried. But it still does not work.Hartsfield
Seems to have serveral problems in your code, you did a strange thing in FETCH_ARTICLES, you'll never get the value because you listen a stream that's not filled before passing the state.copyWith(articles: articles). That's your second issue. It is recommended to print every steps to debug in Bloc, so you'll find the problem that's related or unrelated to Bloc. Use await for If you really want to use stream with Bloc.Localism
F
5

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

Fumigate answered 13/11, 2020 at 19:45 Comment(0)
R
1

For Equatable users...

If you use Equatable for your state classes (this package), ensure that you have overridden the props method on each of your state classes to return the specific fields on your object you want to be considered when checking object equality.

For example, we consider Results (one of my states) which extends UrlState (parent state class the Cubit/Bloc deals with) which in turn extends Equatable.

class Results extends UrlState {
  final String dataEntry;
  final List<String> jsonStats;
  Results(this.dataEntry, this.jsonStats);

  // NOT HAVING THIS METHOD OVERRIDDEN MEANS 2 `RESULT` OBJECTS
  // THAT HAVE DIFFERENT FIELD VALUES ARE CONSIDERED "THE SAME".
  //
  // THIS WILL CAUSE A LOT OF UNEXPECTED BEHAVIOUR WITH BLOC/CUBIT.
  @override
  List<Object?> get props => [dataEntry, jsonStats]; // We only consider two results to be the same if both these fields are the same for the 2 objects.
}
Robert answered 7/5, 2023 at 19:43 Comment(0)
D
0

For Equatable users mixing in Iterable

It will default to ONLY comparing the contents of the Iterable<>, not the othe fields, despite these fields being in the props array: so override operator == and hashCode manually in this case, or remove the iterable mixin.

Dr answered 8/4 at 15:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.