Flutter

[Flutter]FlutterでsetState() or markNeedsBuild() called during build.が発生した場合のトラブルシューティング

エラー内容

チャットのような画面を持ったアプリを作っていて、setState() or markNeedsBuild() called during build.が発生していました。

このエラーは直訳すると、「buildしている間にsetState()markNeedsBuild()を呼ぶんじゃない」と怒られているところです。原因を調べてみました。

======== Exception caught by foundation library ====================================================
The following assertion was thrown while dispatching notifications for ChatState:
setState() or markNeedsBuild() called during build.

This _InheritedProviderScope<ChatState> widget cannot be marked as needing to build because the framework is already in the process of building widgets.  A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was: _InheritedProviderScope<ChatState>
  listening to value
The widget which was currently being built when the offending call was made was: ChatView
  dirty
  dependencies: [_InheritedProviderScope<ChatState>]
  state: _ChatViewState#1daf4
When the exception was thrown, this was the stack: 
#0      Element.markNeedsBuild.<anonymous closure> (package:flutter/src/widgets/framework.dart:4138:11)
#1      Element.markNeedsBuild (package:flutter/src/widgets/framework.dart:4153:6)
#2      _InheritedProviderScopeElement.markNeedsNotifyDependents (package:provider/src/inherited_provider.dart:496:5)
#3      ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:243:25)
#4      ChatState._readChats (package:app/states/chat_state.dart:34:5)

 

原因

チャットのデータはSQLiteで保存していて、そのデータはwith ChangeNotifierを付与したChatStateクラスで管理しています。チャットのデータを読み込んだり更新したりしたときにnotifyListeners()を呼び出して、データの更新を通知しています。ウィジェットの初期表示用にselectAll()メソッドを用意してあります。

class ChatState extends ChangeNotifier {
  Future<List<ChatHistory>> chats;

 (途中略)
  void selectAll() {
    _readChats();
  }
  void _readChats() {
    chats = DbAccess.getChats();
    notifyListeners();
  }
}

一方でチャット風な画面を構成するWidgetにおいて、SQLiteに保存されている過去のデータを初期表示するためにdidChangeDependencies()においてデータを全て読み込む処理を行なっていました。

didChangeDependencies()内で読んでいるのは、チャットのデータを管理しているクラスがwith ChangeNotifierであり、表示側はChangeNotifierProviderでデータの更新を管理させているためです。先程説明したselectAll()メソッドを使用しています。

@override
void didChangeDependencies() {
  _chatState = Provider.of<ChatState>(context);
  _chatState.selectAll();
}

ChangeNotifierProvider使っててなんでStatefulWidget使ってるんだという点はさておいて。。。

ここでselectAll()メソッドをよく見てみますと、中で_readChat()メソッドを読んでいてnotifyListeners()を呼んでいることがわかります。

今回の原因はここのようでして、つまりbuildが完了していないのにChatStateクラスからnotifyListeners()の通知が飛んできたので上記エラーが発生したようでした。

対処内容

今回はselectAll()メソッドでnotifyListeners()が呼ばれないようにしました。

もっとも、上でも書いたようにStatefulWidget使いつつChangeNotifierProviderも使ってるのはやはりおかしい気がするので、その点も直していけたらなと思います。

 

-Flutter

© 2024 かずのアプリときどきキャンプ飯 Powered by AFFINGER5