Hello,
I am new to sfMaps. I trying to connect sfMap to use dynamic data from api endpoint. I receive the following error:
Exception has occurred.
LateError (LateInitializationError: Field '_shapeSource@60361165' has not been initialized.)
Appreciate your advice to resolve this issue, Please find my code below:
class _MapTestState extends State<MapTest> {
late MapShapeSource _shapeSource;
String get firstURL =>
'http://…;
late List<MapModel> _data;
Future<void> fetchData() async {
String basicAuth = 'Basic ${base64Encode(
utf8.encode('$username:$password'),
)}';
http.Response firstResponse = await http.get(
Uri.parse(firstURL),
headers: <String, String>{'authorization': basicAuth},
);
if (firstResponse.statusCode == 200) {
final json1 = jsonDecode(firstResponse.body);
setState(() {
final List<dynamic> data1 = json1['rows'];
_data = data1.map(getMapData).toList();
//_shapeSource = [];
_shapeSource = MapShapeSource.asset(
'assets/maps/json/vn.json',
shapeDataField: "name",
dataCount: _data.length,
primaryValueMapper: (int index) {
return _data[index].name!;
},
shapeColorValueMapper: (int index) {
return _data[index].count;
},
shapeColorMappers: [
const MapColorMapper(from: 0, to: 100, color: Colors.amberAccent),
const MapColorMapper(
from: 101, to: 200, color: Colors.orangeAccent),
const MapColorMapper(from: 201, to: 400, color: Colors.redAccent),
],
);
});
} else {
throw Exception('Failed to load data');
}
}
@override
void initState() {
_data = [];
//_shapeSource = [];
super.initState();
fetchData();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: const Text('Map Test'),
actions: <Widget>[
IconButton(
icon: const Icon(Icons.settings),
onPressed: () {
// do something
},
),
],
),
backgroundColor: Colors.white,
body: Column(children: <Widget>[
const Padding(
padding: EdgeInsets.all(5),
child: Text(
'Total Confirmed Cases',
softWrap: true,
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(2),
child: SfMaps(layers: [
MapShapeLayer(
zoomPanBehavior: zoomPanBehavior,
color: const Color.fromARGB(255, 206, 250, 208),
source: _shapeSource!,
showDataLabels: true,
),
]),
),
)
]),
);
}
}
class MapModel {
final String? name;
final double? count;
String? color;
MapModel({this.name, this.count, this.color});
}
MapModel getMapData(dynamic row) {
final MapModel mapModel = MapModel(
name: row[1],
count: double.tryParse(row[2]) ?? 0,
);
final String color;
if (row[2] == 0) {
color = "#daf2b9";
} else if (double.tryParse(row[2])! >= 1 && double.tryParse(row[2])! <= 100) {
color = "#54de03";
} else if (double.tryParse(row[2])! >= 101 &&
double.tryParse(row[2])! <= 200) {
color = "#97d806";
} else {
color = "#d2d009";
}
mapModel.color = color;
return mapModel;
}
Hi Sao,
We would like to inform you that the mentioned late exception occurs when we access the late variable before initializing it. Unfortunately, we are unable to replicate the mentioned exception on our end because the provided code snippet is not in a runnable condition as it requires a username and password.
However, based on the provided code snippet, we understand that you are fetching data from the API. If the data is fetched successfully, you initialize the shapeSource variable using the MapShapeSource.asset method. If the data is not fetched successfully, then the shapeSource variable is assigned directly to the source property in the MapShapeLayer before it is initialized, which causes the late exception.
To avoid this late exception, please consider the following points:
If you have any further queries, please feel free to reach out to us.
Regards,
Hari Hara Sudhan K.
Dear Hari Hara Sudhan K.
1 - Following your number 2 recommendation to make shapesource nullable, and Exception error has occured
2 - Following your #3 recommendation, won't work either because I fetch data from api which is available only after the initState()
and cause "
Exception has occurred.
_AssertionError ('package:syncfusion_flutter_maps/src/layer/shape_layer.dart': Failed assertion: line 168 pos 16: '(primaryValueMapper != null && dataCount > 0) ||
primaryValueMapper == null': is not true.)"
This would made me conclude that sfMap work best with static data provided in the iniState but not with dynamic data obtain from API though late binding.
Do you have recommendation to make sfMap work with data from API (late binding)?
Best regrads
Sao
Hi Sao,
As already mentioned, the late exception occurs when you try to access the late variable before it has been initialized. Based on the provided code snippet, we understand that you are attempting to fetch data from the API and set the _shapeSource within the fetchData method, which is called asynchronously. Therefore, when the initState method is executed, it does not waits for the asynchronous fetchData method to complete and tries to access the _shapeSource assigned to the source property in the MapShapeLayer before it has been initialized. This leads to the mentioned late exception.
Additionally, the mentioned assertion error (primaryValueMapper != null && dataCount > 0) ||primaryValueMapper == null': is not true.) will occurs when dataCount is zero while the primaryValueMapper property has been given. As the primaryValueMapper property will return the primary value for the every data in the data source collection, the value of the dataCount must be greater than zero when the primaryValueMapper property is given.
However, you can use the FutureBuilder widget to wait for the fetchData method to complete by calling the fetchData method in the future property and by rendering the SfMaps in builder property only when the connection state is completed. Otherwise, you can render an empty container or loading widget.
Kindly refer the code snippet below.
|
class MapTest extends StatefulWidget { const MapTest({Key? key}) : super(key: key);
@override State<MapTest> createState() => _MapTestState(); }
class _MapTestState extends State<MapTest> { late MapShapeSource _shapeSource;
String get firstURL => 'http://...'; // Replace with your actual API endpoint URL
late List<MapModel> _data;
Future<void> fetchData() async { String basicAuth = 'Basic ${base64Encode( utf8.encode('$username:$password'), )}';
http.Response firstResponse = await http.get( Uri.parse(firstURL), headers: <String, String>{'authorization': basicAuth}, );
if (firstResponse.statusCode == 200) { final json1 = jsonDecode(firstResponse.body);
setState(() { final List<dynamic> data1 = json1['rows']; _data = data1.map(getMapData).toList();
_shapeSource = MapShapeSource.asset( 'assets/maps/json/vn.json', // Replace with your shapefile data shapeDataField: "name", dataCount: _data.length, primaryValueMapper: (int index) { return _data[index].name!; }, shapeColorValueMapper: (int index) { return _data[index].count; }, shapeColorMappers: [ const MapColorMapper(from: 0, to: 100, color: Colors.amberAccent), const MapColorMapper( from: 101, to: 200, color: Colors.orangeAccent), const MapColorMapper(from: 201, to: 400, color: Colors.redAccent), ], ); }); } else { throw Exception('Failed to load data'); } }
@override void initState() { _data = []; super.initState(); }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( centerTitle: true, title: const Text('Map Test'), actions: <Widget>[ IconButton( icon: const Icon(Icons.settings), onPressed: () { // do something }, ), ], ), backgroundColor: Colors.white, body: Column( children: <Widget>[ const Padding( padding: EdgeInsets.all(5), child: Text( 'Total Confirmed Cases', softWrap: true, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), ), Expanded( child: FutureBuilder<void>( future: fetchData(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { return Padding( padding: const EdgeInsets.all(2), child: SfMaps( layers: [ MapShapeLayer( color: const Color.fromARGB(255, 206, 250, 208), source: _shapeSource, showDataLabels: true, ), ], ), ); } else { return Container(); } }, ), ), ], ), ); } }
class MapModel { final String? name; final double? count; String? color;
MapModel({this.name, this.count, this.color}); }
MapModel getMapData(dynamic row) { final MapModel mapModel = MapModel( name: row[1], count: double.tryParse(row[2]) ?? 0, );
final String color; if (row[2] == 0) { color = "#daf2b9"; } else if (double.tryParse(row[2])! >= 1 && double.tryParse(row[2])! <= 100) { color = "#54de03"; } else if (double.tryParse(row[2])! >= 101 && double.tryParse(row[2])! <= 200) { color = "#97d806"; } else { color = "#d2d009"; }
mapModel.color = color; return mapModel; } |
You can modify the shared code snippet according to your needs and if you have further queries, please get back to us.
Regards,
Hari Hara Sudhan. K.
Dear Hari Hara Sudhan,
Thank you very much for your solution. However, after adopting solution you provide,
1 - Map is not shown on page
2 - When page is close, the following error occurred
"Exception has occurred.
FlutterError (setState() called after dispose(): _MapTestSixDynamicState#9b3c9(lifecycle state: defunct, not mounted)
This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback.
The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the tree.
This error might indicate a memory leak if setState() is being called because another object is retaining a reference to this State object after it has been removed from the tree. To avoid memory leaks, consider breaking the reference to this object during dispose().)".
Could you please advice?
Sao
Hi Sao,
Query 1 : Map is not shown on page.
As mentioned in the earlier response, we have used the FutureBuilder widget to render the SfMaps widget only when the data has been fetched from the fetchData method or else we have returned an empty container. Therefore, we suspect that the data has not been fetched properly from the fetchData method and the empty container has been rendered in the else portion.
Query 2 : When page is close, the following error occurred.
Unfortunately, we have missed to mention avoiding the setState method inside the fetchData method as we are using the FutureBuilder to render the SfMaps only when the data has been fetched. Here, we have modified the sample by returning the _shapeSource from the fetchData method to render the SfMaps only when the _shapeSource is initialized or else we will render an empty container based on the snapshot.hasData condition.
Kindly refer the modified code snippet below:
|
class _MapTestState extends State<MapTest> { late MapShapeSource _shapeSource;
String get firstURL => 'http://...'; // Replace with your actual API endpoint URL
late List<MapModel> _data;
Future<MapShapeSource> fetchData() async { String username = 'YourUsername'; // Replace with your username String password = 'YourPassword'; // Replace with your password String basicAuth = 'Basic ${base64Encode( utf8.encode('$username:$password'), )}';
http.Response firstResponse = await http.get( Uri.parse(firstURL), headers: <String, String>{'authorization': basicAuth}, );
if (firstResponse.statusCode == 200) { final json1 = jsonDecode(firstResponse.body); final List<dynamic> data1 = json1['rows'];
_data = data1.map(getMapData).toList();
_shapeSource = MapShapeSource.asset( 'assets/maps/json/vn.json', // Replace with your shapefile data shapeDataField: "name", dataCount: _data.length,
primaryValueMapper: (int index) { return _data[index].name!; },
shapeColorValueMapper: (int index) { return _data[index].count; },
shapeColorMappers: [ const MapColorMapper(from: 0, to: 100, color: Colors.amberAccent), const MapColorMapper(from: 101, to: 200, color: Colors.orangeAccent), const MapColorMapper(from: 201, to: 400, color: Colors.redAccent), ], ); return _shapeSource; } else { throw Exception('Failed to load data'); } }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( centerTitle: true, title: const Text('Map Test'), actions: <Widget>[ IconButton( icon: const Icon(Icons.settings), onPressed: () { // do something }, ), ], ), backgroundColor: Colors.white, body: Column( children: <Widget>[ const Padding( padding: EdgeInsets.all(5), child: Text( 'Total Confirmed Cases', softWrap: true, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), ), Expanded( child: FutureBuilder<MapShapeSource>( future: fetchData(), builder: (context, snapshot) { if (snapshot.hasData) { return Padding( padding: const EdgeInsets.all(2), child: SfMaps( layers: [ MapShapeLayer( color: const Color.fromARGB(255, 206, 250, 208), source: _shapeSource, showDataLabels: true, ), ], ), ); } else { return Container(); } }, ), ), ], ), ); } }
class MapModel { final String? name;
final double? count;
String? color;
MapModel({this.name, this.count, this.color}); }
MapModel getMapData(dynamic row) { final MapModel mapModel = MapModel( name: row[1], count: double.tryParse(row[2]) ?? 0, );
final String color;
if (row[2] == 0) { color = "#daf2b9"; } else if (double.tryParse(row[2])! >= 1 && double.tryParse(row[2])! <= 100) { color = "#54de03"; } else if (double.tryParse(row[2])! >= 101 && double.tryParse(row[2])! <= 200) { color = "#97d806"; } else { color = "#d2d009"; }
mapModel.color = color;
return mapModel; } |
We suggest you check with the modified code snippet and if the mentioned exception occurs further, we require some detailed clarifications regrading the below mentioned points.
With that additional information, it will be very useful for us to provide the best possible solution from our end.
Regards,
Hari Hara Sudhan. K.
Dear Hari Hara Sudhan. K.
Thank you for providing this working solution.
Sao
Hi Sao,
Most Welcome. Kindly get back to us if you have further queries. We are always happy to assist you.
Regards,
Hari Hara Sudhan. K.