The goal is to produce google map markers using clusters from information in memory. Currently, I'm downloading LATLNG points from Firebase into local memory. Next the goal is to display these collection of point on a map in a Flutter app using the Google Maps clustering feature. To achieve this, there is a dependency called clustering_google_maps 0.0.4+2 which allows for access to data from a local database (SQLite) or from local memory.
The developer recommends with large marker sets in the thousands it's best to use a local database (SQLite). In my case, I only have 20 - 40 total markers. Can someone help provide a solution to explain how one can use the data from local memory to display on the Google Map?
Quick Example from the Repo
class HomeScreen extends StatefulWidget {
final List<LatLngAndGeohash> list;
HomeScreen({Key key, this.list}) : super(key: key);
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
ClusteringHelper clusteringHelper;
final CameraPosition initialCameraPosition =
CameraPosition(target: LatLng(0.000000, 0.000000), zoom: 0.0);
Set<Marker> markers = Set();
void _onMapCreated(GoogleMapController mapController) async {
print("onMapCreated");
if (widget.list == null) {
clusteringHelper.database = await AppDatabase.get().getDb();
}
clusteringHelper.updateMap();
}
updateMarkers(Set<Marker> markers) {
setState(() {
this.markers = markers;
});
}
#override
void initState() {
if (widget.list != null) {
initMemoryClustering();
} else {
initDatabaseClustering();
}
super.initState();
}
// For db solution
initDatabaseClustering() {
clusteringHelper = ClusteringHelper.forDB(
dbGeohashColumn: FakePoint.dbGeohash,
dbLatColumn: FakePoint.dbLat,
dbLongColumn: FakePoint.dbLong,
dbTable: FakePoint.tblFakePoints,
updateMarkers: updateMarkers,
);
}
// For memory solution
initMemoryClustering() {
clusteringHelper = ClusteringHelper.forMemory(
list: widget.list,
updateMarkers: updateMarkers,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Clustering Example"),
),
body: GoogleMap(
onMapCreated: _onMapCreated,
initialCameraPosition: initialCameraPosition,
markers: markers,
onCameraMove: (newPosition) => clusteringHelper.onCameraMove(newPosition, forceUpdate: false),
onCameraIdle: clusteringHelper.onMapIdle,
),
floatingActionButton: FloatingActionButton(
child:
widget.list == null ? Icon(Icons.content_cut) : Icon(Icons.update),
onPressed: () {
if (widget.list == null) {
clusteringHelper.whereClause = "WHERE ${FakePoint.dbLat} > 42.6";
clusteringHelper.updateMap();
} else {
clusteringHelper.updateMap();
}
},
),
);
}
}
From the docs
MEMORY TECHNIQUE
To work properly you must have a list of LatLngAndGeohash object. LatLngAndGeohash is a simple object with Location and Geohash property, the last is generated automatically; you need only location of the point.
For this solution you must use the MEMORY constructor of
ClusteringHelper:
ClusteringHelper.forMemory(...);
To test this feature you can clone the giandifra/clustering_google_maps repo. Then modify the splash.dart file using the following code. when calling the HomeScreen from splash.dart
List<LatLngAndGeohash> markerList = new List<LatLngAndGeohash>();
#override
void initState() {
super.initState();
markerList.add(LatLngAndGeohash(LatLng(34.0522, -118.2437)));
markerList.add(LatLngAndGeohash(LatLng(34.0522, -118.2647)));
markerList.add(LatLngAndGeohash(LatLng(34.0522, -118.2467)));
markerList.add(LatLngAndGeohash(LatLng(34.0522, -118.2487)));
markerList.add(LatLngAndGeohash(LatLng(34.0522, -118.2707)));
}
#override
Widget build(BuildContext context) {
return Scaffold(
....
HomeScreen(list: markerList)
Related
Below is the code I wrote to extract data from MySQL database with flutter rest API. However, I am getting the following error while printing the data I received to the mobile screen. How can I fix it?
LateError (LateInitializationError: Field 'user Data' has not been initialized.)
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Material App',
home: main1(),
);
}
}
class main1 extends StatefulWidget {
main1({Key? key}) : super(key: key);
#override
State<main1> createState() => _main1State();
}
class _main1State extends State<main1> {
late List userData;
late Map data;
#override
void initState() {
// TODO: implement initState
loaddata();
super.initState();
}
void loaddata() async {
var url = "http://192.168.1.106/server/data.php";
var res = await http.get(Uri.parse(url));
data = json.decode(res.body);
userData = data["data"];
print(userData);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Fake Friends"),
backgroundColor: Colors.green,
),
body: ListView.builder(
itemCount: userData == null ? 0 : userData.length,
itemBuilder: (BuildContext context, int index) {
return Container(
child: Text(userData[index]["projename"]),
);
}),
);
}
}
<?php
require_once("dbconfig.php");
$json["error"] = false;
$json["errmsg"] = "";
$json["data"] = array();
//Fetch 3 rows from actor table
$sql = "SELECT * FROM projeinfo";
$res = mysqli_query($db, $sql);
$numrows = mysqli_num_rows($res);
if($numrows > 0){
//check if there is any data
$namelist = array();
while($array = mysqli_fetch_assoc($res)){
array_push($json["data"], $array);
//push fetched array to $json["data"]
}
}else{
$json["error"] = true;
$json["errmsg"] = "No any data to show.";
}
mysqli_close($db);
header('Content-Type: application/json');
// tell browser that its a json data
echo json_encode($json);
?>
If you set a variable to late it cannot be null. So in your build function it assumes, that it is initialized, but it isn't, because load data is a future and the result is ready after the build function gets called first.
2 possible ways.
1.
List? userData;
Map? data;
Use a FutureBuilder
And: You don't call setState in your loadData(), so it does not get rebuild if the data is available. For the first approach, this is necessary!
How can i pass the Map data from that list to others screens being StatefulWidget or StatelessWidget, and why it don´t work like the one screen example?
The Api Part here:
Future pokeinfo(int position) async {
var dio = Dio();
Response response;
response =
await dio.get('https://pokeapi.co/api/v2/pokemon/${position.toString()}');
Map json = jsonDecode(response.toString());
return json;
}
The function part here:
bool widgetVisible = false;
List<PokeList> elements = [];
void showWidget() {
createList();
setState(() {
widgetVisible = !widgetVisible;
});
}
#override
void initState() {
super.initState();
}
#override
void dispose() {
super.dispose();
}
void createList() async {
List<PokeList> _elements = [];
for (int i = 1; i < 50; i++) {
Map currentData = await pokeinfo(i);
_elements.add(PokeList(currentData));
}
setState(() {
elements = _elements;
});
How it works in one screen:
Map data;
PokeList(Map this.data);
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => PokemonPage()),
);
you need to use one of state management packages, However google recommend provider package.
with provider you can make request in provider class
class PokeProvider extends ChangeNotifier {
Map json;
Future pokeinfo(int position) async {
var dio = Dio();
Response response;
response = await dio
.get('https://pokeapi.co/api/v2/pokemon/${position.toString()}');
Map json = jsonDecode(response.toString());
return json;
}
}
then store it's data inside a variable that every screen will listen on change of it, like json
then expose that provider to whole app
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => PokeProvider(),
child: const MyApp(),
),
);
}
then read it from anywhere inside application with
PokeProvider pokeProvider = Provider.of<PokeProvider>(context, listen: false);
pokeProvider.json; // now you got your map
be carful of listen: false if your data will change ui directly so make listen: true
for more information about provider package check the link blew contain everything you need
ChangeNotifierProvider example
Thank you all who tried to help me, I got the problem and how to solve it. I will put the solution here to someone who has the same problem. A simple thing to change actually:
Remove the specific screen from List.
bool widgetVisible = false;
List elements = [];
void showWidget() {
createList();
setState(() {
widgetVisible = !widgetVisible;
});
}
Inside the function you just need to change List, repeating what was shown previously.
void createList() async {
List _elements = [];
for (int i = 1; i < 50; i++) {
Map currentData = await pokeinfo(i);
_elements.add(PokeList(currentData));
}
And finally, you only have to put inside the screen class. Like that:
Class ScreenNameHere extends StatelessWidget{
Map data;
ScreenNameHere(Map this.data);
/*
#override
Widget build(BuildContext context) {
return ScreenBody();
}
*/
}
I am calling an API and utilising the return Json data to post markers on a map
I have the Call returning a ListView fine on a separate application
I tried to implement my Future builder from my other application calling the same HTTP, with some modifications.
I am now getting errors from the Future Builder construction after trying to implement a Future Builder, which I have been told will fix the previous error as it is mandatory, to the best of my beginners ability!
I am pulling the stations.place.location.lat & stations.place.location.lng Json to use in GeoCoordinates to place the marker from my API call
Here is the Dart code I am using, any guidance is appreciated. I will exclude code which is irrelevant to this issue.
The Future Builder (of which there is some rough code left in my example below) needs to go into my Main.dart
void main() {
SdkContext.init(IsolateOrigin.main);
runApp(MaterialApp(home: MyApp()));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Future<Stations> stations;
#override
void initState() {
stations = API_Call().fetchStations();
super.initState();
}
BuildContext _context;
MapMarkerExample _mapMarkerExample;
#override
Widget build(BuildContext context) {
_context = context;
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Example 1'),
),
body: Container(
child:
FutureBuilder<Stations>(
future: stations,
builder: (BuildContext context, AsyncSnapshot<Stations> snapshot) {
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
else
return Stack(
children: [
HereMap(onMapCreated: _onMapCreated),
Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
button('Call', _anchoredMapMarkersButtonClicked),
button('Clear', _clearButtonClicked),
],
),
],
),
],
);
}
);
}
}
)
)
)
);
}
...
api_manager.dart
class MapMarkerExample{
void showAnchoredMapMarkers() {
var stations;
stations = API_Call().fetchStations();
for (Station stations in stations) {
GeoCoordinates geoCoordinates = GeoCoordinates (stations.place.location.lat, stations.place.location.lng);
}
GeoCoordinates geoCoordinates = stations.coordinates;
_addPOIMapMarker(geoCoordinates, 1);
}
...
api_call.dart
class API_Call {
Future<Stations> fetchStations() async {
var client = http.Client();
final response = await client.get(
'https://transit.hereapi.com/v8/stations?in=x,-x&return=transport&apiKey=MY_API_KEY');
if (response.statusCode == 200) {
return Stations.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to load stations');
}
}
}
If your service is returning a list, you should handle it as List.
List<Station> stations = [];
final responseData = json.decode(response.body);
stations = responseData.map((model) => Station.fromJson(model)).toList();
And don't forget the itemLength value for ListView.builder()
I am having the nested json where I want parse the worksheetData and display the list of worksheetdata in separate cards. I have tried used online tool parse but when I print the data it throws an error called "type 'List' is not a subtype of type 'Map"
#Update
Below is the home.dart file where I am getting the data error
Home.dart
class WorkSheet extends StatefulWidget {
const WorkSheet({Key key}) : super(key: key);
#override
_WorkSheetState createState() => new _WorkSheetState();
}
class _WorkSheetState extends State<WorkSheet> {
Future<String> loadSheetDataFromAssets() async {
return await DefaultAssetBundle.of(context)
.loadString('assets/example.json');
}
Future loadSheetData() async {
String jsonString = await loadSheetDataFromAssets();
final jsonResponse = json.decode(jsonString);
SheetData sheetData = new SheetData.fromJson(jsonResponse);
print('PName : ${sheetData.projectName}');
print('Worksheet : ${sheetData.worksheetData}');
print(sheetData);
}
#override
void initState() {
super.initState();
loadSheetData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Work sheet data'),
),
body: FutureBuilder(
future: loadSheetData(),
builder: (context, snapshot){
if(snapshot.data == null){
return Center(child: Text(snapshot.error));
}else{
return Card(child: Text(snapshot.data.toString()));
}
}
)
);
}
}
You could use some external tools to generate your models like quicktype
Or any of the approaches described in the official documentation doc
Make sure your class classes you want to convert fromJson are annotated with #JsonSerializable(). Please follow flutter documentation for this https://flutter.dev/docs/development/data-and-backend/json
This with Autoatically convert all your nested classes that are declared with #JsonSerializable() but if you have to convert a list from Json, you have to write some extra code like this below
Map jsonObject = json.decode(jsonString);
Iterable list = json.decode(jsonObject['worksheetData']);
List<WorksheetData> datasheet = list.map((f) => WorksheetData.fromJson(f)).toList();
What is the best way to serialize a list of data from Firebase? Firebase provides an object with a list of properties for the list which makes it more challenging to come up with a good conversion technique.
How would you serialize this data from Firebase:
{
"-KiRg_F-qC59xxlfZ6ej": {
"first":"Brandon",
"last":"Donnelson"
},
"-KiRgmsISBsJSWfXhrdD": {
"first":"Danny",
"last":"Kirk"
}
}
What I came up with — see _loadData()) —:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Text(
'click',
),
),
floatingActionButton: new FloatingActionButton(
onPressed: _test,
tooltip: 'Increment',
child: new Icon(Icons.add),
),);
}
void _test() {
_loadData();
}
_loadData() async {
String url = 'https://dev-xxxxxxx.firebaseio.com/names.json';
var httpClient = createHttpClient();
var response = await httpClient.read(url);
print('response=' + response);
// response = {
// "-KiRg_F-qC59xxlfZ6ej":{"first":"Brandon","last":"Donnelson"},
// "-KiRgmsISBsJSWfXhrdD":{"first":"Danny","last":"Kirk"}
// }
NamesData namesData = new NamesData(JSON.decode(response));
print("names.len=" + namesData.names.length.toString());
}
}
class NamesData {
final List<NameData> names = new List();
NamesData(Map data) {
data.values.forEach((Map map) => names.add(new NameData.fromJson(map)));
}
}
class NameData {
String first;
String last;
NameData.fromJson(Map map) {
first = map['first'];
last = map['last'];
}
}
I found the JSON decoder has a better method for instantiating classes with the reviver function. This feels much better, but I think I can do better.
_loadData() async {
String url = 'https://dev-xxxxxxx.firebaseio.com/names.json';
var httpClient = createHttpClient();
var response = await httpClient.read(url);
print('response=' + response);
// response = {
// "-KiRg_F-qC59xxlfZ6ej":{"first":"Brandon","last":"Donnelson"},
// "-KiRgmsISBsJSWfXhrdD":{"first":"Danny","last":"Kirk"}
// }
var extendedJson = new JsonCodec(reviver: _reviver);
var o = extendedJson.decode(response);
print('end');
}
// https://github.com/dart-lang/sdk/blob/master/tests/lib/convert
// /json_toEncodable_reviver_test.dart
_reviver(key, value) {
if (value != null && value is Map && key.toString().contains("-")) {
return new NameData2(value);
}
return value;
}
}
class NameData2 {
String first;
String last;
NameData2(Map map) {
first = map['first'];
last = map['last'];
}
}
I personally like writing a tiny Codec sometimes:
DartPad example
import 'dart:convert';
void main() {
final decoder = const FirebaseNamesDecoder();
print(decoder.convert(exampleFirebaseData));
}
class NamedData {
final String id;
final String firstName;
final String lastName;
const NamedData(this.id, this.firstName, this.lastName);
#override
String toString() => '$NamedData {$id: $firstName $lastName}';
}
class FirebaseNamesDecoder extends Converter<Map, Iterable<NamedData>> {
const FirebaseNamesDecoder();
#override
Iterable<NamedData> convert(Map<String, Map> raw) {
return raw.keys.map((id) => new NamedData(id, raw[id]['first'], raw[id]['last']));
}
}
final exampleFirebaseData = {
"-KiRg_F-qC59xxlfZ6ej": {
"first":"Brandon",
"last":"Donnelson"
},
"-KiRgmsISBsJSWfXhrdD": {
"first":"Danny",
"last":"Kirk"
}
};
Results in:
(
NamedData {-KiRg_F-qC59xxlfZ6ej: Brandon Donnelson},
NamedData {-KiRgmsISBsJSWfXhrdD: Danny Kirk}
)
Dart 2 needs modification to the overridden method:
Iterable<NamedData> convert(Map<dynamic,dynamic> raw) {
return raw.keys
.map((id) => new NamedData(id, raw[id]['first'], raw[id]['last']));
}
Serializing JSON manually using dart:convert
Basic JSON serialization in Flutter is very simple. Flutter has a built-in dart:convert library which includes a straightforward JSON encoder and decoder.
The following sample JSON implements a simple user model.
{"name":"John Smith","email":"john#example.com"}
With dart:convert, you can serialize this JSON model in two ways.
1) Serializing JSON inline
Map<String, dynamic> user = jsonDecode(jsonString);
print('Howdy, ${user['name']}!');
print('We sent the verification link to ${user['email']}.');
2) Serializing JSON inside model classes
class User
{
final Stringname;
final Stringemail;
User(this.name,this.email);
User.fromJson(Map<String,dynamic>json):name=json['name'],email=json['email'];
Map<String,dynamic>toJson()=>
{
'name':name,
'email':email,
};
}
The responsibility of the decoding logic is now moved inside the model itself. With this new approach, you can decode a user easily.
Map userMap = jsonDecode(jsonString);
var user = User.fromJson(userMap);
print('Howdy, ${user.name}!');
print('We sent the verification link to ${user.email}.');
I would recommend using json_serializable it is developed by google developers and it can handle the boilerplate code easily.