Flutter Bloc не обновляет состояние / не работает

Разрабатываю мобильное приложение на Flutter. Я использую пакет блока flutter, https://pub.dev/packages/flutter_bloc для управления и настройки блока. Но при изменении состояния виджеты и представления не обновляются.

У меня есть файл класса блока с именем home_bloc.dart со следующей реализацией.

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;
  }
}

Это мой класс репозитория, возвращающий фиктивные данные.

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;
  }
}

Это мой виджет главной страницы

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);
  }
}

Как видите, это делает мой виджет HomePage. Он получит статьи после того, как виджет будет построен. Затем представление списка будет обновлено фиктивными данными.

Это мой файл main.dart.

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(),
      ),
    );
  }
}

Когда я запускаю свое приложение, оно не обновляет представление списка фиктивными данными. Вместо этого всегда отображается сообщение, если записи не найдены.

Почему не работает?

4 ответа

Ваш класс состояния не равен... Дарт не может сказать, когда состояние изменилось

Вы должны использовать: import 'package:equatable/equatable.dart';

Я также не думаю, что вам следует УСТАНОВИТЬ свойство состояния в своем классе блока. Вы должны только отдать его и позволить блоку обновить его...

пожалуйста, проверьте документацию, так как я могу ошибаться

Bloc никогда не изменится. Если состояние не изменилось, вы можете быть сбиты с толку, что вы назначили состояние, но факт в том, чтоBloc это Streamвам нужно yield состояние вместо того, чтобы назначать его напрямую.

Надеюсь, вы реализовали copyWith, чтобы вы могли сделать это, как показано ниже:

yield state.copyWith(articles: articles)

Для сохранения структуры вы все равно можете использовать:

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

Так как state переменная используется Bloc, вы должны использовать другую переменную, чтобы предотвратить Bloc от сравнения одной и той же переменной (state == state всегда будет истинным, поэтому состояние никогда не менялось.)

У меня была такая же проблема, и я решил эту проблему, изменив ее:

yield status; 

к

yield status.toList();

В конце моего mapEventToState() метод.

И, вероятно, вам нужно было сделать все, что передает List.

Если это сработало для вас, дайте мне знать

Для пользователей Equatable...

Если вы используетеEquatableдля ваших классов состояний (этот пакет ) убедитесь, что вы переопределилиpropsдля каждого из ваших классов состояний, чтобы вернуть определенные поля вашего объекта, которые вы хотите учитывать при проверке равенства объектов.

Например, мы считаемResults(одно из моих состояний), которое простираетсяUrlState(класс родительского состояния, с которым имеет дело Cubit/Bloc), который, в свою очередь,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.
}
Другие вопросы по тегам