質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Flutter

Flutterは、iOSとAndroidのアプリを同じコードで開発するためのフレームワークです。オープンソースで開発言語はDart。双方のプラットフォームにおける高度な実行パフォーマンスと開発効率を提供することを目的としています。

Dart

Dartは、Googleによって開発されたJavaScriptの代替となることを目的に作られた、ウェブ向けのプログラミング言語である。

Q&A

解決済

1回答

1005閲覧

FlutterでsetState() or markNeedsBuild() called during buildエラーが発生する

ekito_station

総合スコア3

Flutter

Flutterは、iOSとAndroidのアプリを同じコードで開発するためのフレームワークです。オープンソースで開発言語はDart。双方のプラットフォームにおける高度な実行パフォーマンスと開発効率を提供することを目的としています。

Dart

Dartは、Googleによって開発されたJavaScriptの代替となることを目的に作られた、ウェブ向けのプログラミング言語である。

0グッド

0クリップ

投稿2023/05/06 12:03

編集2023/05/06 12:06

概要

指定した地点の方角にスマホを向けたら文字が表示されるアプリを開発中です。

flutter_compassのイベントをStreamBuilderでListenしようとしたら、Widgetのビルド中にStateを変更してはいけないという旨のエラーが発生しました。
ただ、どの処理がビルド中に行われてStateを変更しようとしてしまっているのかかが分かりません。

解決方法についてご教授いただけると幸いです。

発生している問題・エラーメッセージ

FlutterError (setState() or markNeedsBuild() called during build. This CompassPage 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: CompassPage The widget which was currently being built when the offending call was made was: StreamBuilder<CompassEvent>)

該当のソースコード

dart

1import 'dart:async'; 2import 'package:flutter/material.dart'; 3import 'package:google_maps_flutter/google_maps_flutter.dart'; 4import 'package:geolocator/geolocator.dart'; 5import 'package:flutter_compass/flutter_compass.dart'; 6 7// 指定した地点 8LatLng markerPosition = const LatLng(35.689702, 139.700560); // 初期座標(新宿駅) 9 10// 自分のスマホの位置 11Position myPosition = Position( 12 // 初期座標(渋谷駅) 13 latitude: 35.658199, 14 longitude: 139.701625, 15 timestamp: DateTime.now(), 16 altitude: 0, 17 accuracy: 0, 18 heading: 0, 19 speed: 0, 20 speedAccuracy: 0, 21 floor: null, 22); 23 24class CompassPage extends StatefulWidget { 25 const CompassPage({Key? key}) : super(key: key); 26 27 28 State<CompassPage> createState() => _CompassPageState(); 29} 30 31class _CompassPageState extends State<CompassPage> { 32 late StreamSubscription<Position> positionStream; 33 late double? direction; 34 late double markerBearing; 35 double bearingTolerance = 5.0; 36 String distance = ''; 37 38 final LocationSettings locationSettings = const LocationSettings( 39 accuracy: LocationAccuracy.high, 40 distanceFilter: 10, 41 ); 42 43 // 指定した地点の方角を計算 44 double calcMarkerBearing(Position myPosition, LatLng markerPosition) { 45 double myLat = myPosition.latitude; 46 double myLng = myPosition.longitude; 47 double markerLat = markerPosition.latitude; 48 double markerLng = markerPosition.longitude; 49 double bearing = 50 Geolocator.bearingBetween(myLat, myLng, markerLat, markerLng); 51 if (bearing < 0.0) { 52 bearing += 360.0; 53 } 54 return bearing; 55 } 56 57 // 指定した地点までの距離を計算 58 void calcMarkerDistance(Position myPosition, LatLng markerPosition) async { 59 double myLat = myPosition.latitude; 60 double myLng = myPosition.longitude; 61 double markerLat = markerPosition.latitude; 62 double markerLng = markerPosition.longitude; 63 double distanceInMeters = 64 Geolocator.distanceBetween(myLat, myLng, markerLat, markerLng); 65 setState(() { 66 distance = distanceInMeters.round().toString(); 67 }); 68 } 69 70 // 指定した地点までの距離を表示 71 String displayPlaceDistance() { 72 calcMarkerDistance(myPosition, markerPosition); 73 return '$distance m'; 74 } 75 76 // スマホの向いている方角と指定した地点の方角の差が閾値以下かどうか確認 77 bool checkTolerance(double compassAngle, double bearing) { 78 if ((compassAngle - bearing).abs() < bearingTolerance) { 79 return true; 80 } else if ((compassAngle - bearing).abs() > 360 - bearingTolerance) { 81 return true; 82 } else { 83 return false; 84 } 85 } 86 87 88 void initState() { 89 super.initState(); 90 91 WidgetsBinding.instance.addPostFrameCallback((_) { 92 positionStream = 93 Geolocator.getPositionStream(locationSettings: locationSettings) 94 .listen((Position position) { 95 // 位置情報に対する権限が許可されたら、デバイスの位置情報を返す 96 myPosition = position; 97 }); 98 } 99 100 101 Widget build(BuildContext context) { 102 return Scaffold( 103 appBar: AppBar( 104 title: const Text('Compass Ruler'), 105 ), 106 body: StreamBuilder<CompassEvent>( 107 stream: FlutterCompass.events, 108 builder: (context, snapshot) { 109 if (snapshot.hasError) { 110 return Text('Error reading heading: ${snapshot.error}'); 111 } 112 if (snapshot.connectionState == ConnectionState.waiting) { 113 return const Center( 114 child: CircularProgressIndicator(), 115 ); 116 } 117 118 direction = snapshot.data?.heading; 119 markerBearing = calcMarkerBearing(myPosition, markerPosition); 120 if (direction == null) { 121 return const Center( 122 child: Text("Device does not have sensors."), 123 ); 124 } 125 126 return Center( 127 child: Column( 128 children: [ 129 const Icon( 130 Icons.expand_less, 131 size: 45, 132 ), 133 Opacity( 134 // スマホが指定した地点の方角を向いていたら透明度を1にして文字を表示 135 opacity: 136 checkTolerance(direction!, markerBearing) ? 1.0 : 0.0, 137 child: Column( 138 children: [ 139 Text(displayPlaceDistance(), 140 style: const TextStyle(fontSize: 45)), 141 const Text("from ther marker", 142 style: TextStyle(fontSize: 30)), 143 ], 144 )), 145 ], 146 ), 147 ); 148 }), 149 ), 150 ); 151 } 152} 153

試したこと

https://coneta.jp/article/adab563b-1d6a-4ab8-be8f-b3f9df6df132/
こちらのサイトで、WidgetsBinding.instance.addPostFrameCallbackを使えばビルド後に処理を行うことができるのは知りましたが、どの処理をビルド後に行えばいいのかが分かりません。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

ベストアンサー

calcMarkerDistanceからasyncとsetStateを外せばエラーは出ないようです。

dart

1 // 指定した地点までの距離を計算 2 void calcMarkerDistance(Position myPosition, LatLng markerPosition) { 3 double myLat = myPosition.latitude; 4 double myLng = myPosition.longitude; 5 double markerLat = markerPosition.latitude; 6 double markerLng = markerPosition.longitude; 7 double distanceInMeters = 8 Geolocator.distanceBetween(myLat, myLng, markerLat, markerLng); 9 distance = distanceInMeters.round().toString(); 10 }

後、上記の様にすればaddPostFrameCallbackを入れる必要はないです。

推測するに

  • calcMarkerDistanceで何らかの理由でasyncを入れた。
  • これによりdistanceが表示させようと思ったときに確定していない。
  • そのため、setStateで更新処理を行おうとした。

といった理由で、提示された実装になってしまったのではないでしょうか。

今回のはasyncにする必要がなかったのでそれを外し、同期処理としてdistanceが確定するのでsetStateを外したという感じになります。

投稿2023/05/07 04:46

ta.fu

総合スコア1667

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ekito_station

2023/05/07 12:27

ご指摘の通り、calcMarkerDistanceからasyncとsetStateを外すとエラーが解消されました。 確かにasyncにする必要がありませんでしたね。 大変助かりました。ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問