概要
指定した地点の方角にスマホを向けたら文字が表示されるアプリを開発中です。
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を使えばビルド後に処理を行うことができるのは知りましたが、どの処理をビルド後に行えばいいのかが分かりません。

回答1件
あなたの回答
tips
プレビュー
下記のような回答は推奨されていません。
このような回答には修正を依頼しましょう。
また依頼した内容が修正された場合は、修正依頼を取り消すようにしましょう。
2023/05/07 12:27