Example of an Open data app using Flutter and Kuzzle backend.
In this article we will create an app using Flutter and Kuzzle, operating on open data, to see public bike stations of the city of Montpellier on a mobile app.
For this we will need a backend part where we will retrieve the open data every minute and publish them as realtime data. Then a mobile app will display a map with a marker for each bike station, with its number of free bikes / total bikes. It'll subscribe to realtime messages (using websocket) to update the map when notified about a change.
Full source code here https://github.com/jenow/kuzzle-flutter
For this we will use develop a Kuzzle application for the backend part and Flutter for the mobile app part.
In order for Kuzzle to work properly it needs an ElasticSearch and a Redis instance.
The easiest way to run an ElasticSearch and a Redis would be to use our cli Kourou
$ npm i kourou -g $ kourou app:start-services
So let’s start by installing kuzzle:
$ npm i kuzzle
Then start writing our backend application by importing kuzzle and instantiate it:
const { Backend } = require('kuzzle')
const app = new Backend('bike-stations')
Now we need to extend the Kuzzle native API to add a new route, for getting all available bike stations. To do this we need to define a controller with one or multiple actions:
app.controller.register('bike-stations', {
actions: {
get: {
handler: () => getStations()
}
}
})
Now we have a new API controller named “bike-stations”, exposing a single action named “get”, which we will use to retrieve all bike stations.
To know more about how controllers work in Kuzzle, see this guide: https://docs.kuzzle.io/core/2/plugins/guides/controllers
Now let’s implement the getStations function. It needs to fetch XML data from the open data Montpellier web service, then convert it to JSON using the xml2json library:
const getStations = () => {
return new Promise((resolve, reject) => {
https.get('https://data.montpellier3m.fr/sites/default/files/ressources/TAM_MMM_VELOMAG.xml',
res => {
let body = '';
res.on('data', (chunk) => (body += chunk.toString()));
res.on('error', reject);
res.on('end', () => {
if (res.statusCode >= 200 && res.statusCode <= 299) {
resolve(parser.toJson(body, {
coerce: true,
object: true
}))
} else {
reject('Request failed. status: ' + res.statusCode + ', body: ' + body)
}
})
})
})
}
Also what we will do is to run a routine which will fetch these data and send them as a realtime message every minute. We will run that after we run our Kuzzle instance.
app.start().then(() => {
setInterval(async () => {
const res = await getStations()
await app.sdk.realtime.publish('bike-stations', 'stations', res)
}, 60000)
}).catch(err => {
console.error(err)
})
And that’s all for our backend part. Now we just need to run it
$ node index.js
Now let’s write our flutter app. First we will add the dependencies which are the Kuzzle Dart SDK and the flutter map.
dependencies:
flutter_map: 0.10.1+1
kuzzle: 2.0.1
bike_station.dart:
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong/latlong.dart';
class BikeStation {
final double lat;
final double lng;
final int total;
final int free;
BikeStation({
this.lat,
this.lng,
this.total,
this.free,
});
This will represent a bike station with their location, number of free bikes and number of total bike.
Let’s also add a helper method to be easily able to add this station to our map by returning a marker widget with the right location and informations:
Marker toWidget() => Marker(
width: 80.0,
height: 80.0,
point: LatLng(this.lat, this.lng),
builder: (ctx) => Container(
child: Stack(
children: <Widget>[
Container(
alignment: Alignment.center,
child: new Container(
width: 35.0,
height: 35.0,
decoration: new BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
),
),
Container(
alignment: Alignment.center,
child: Text(
'${this.free}/${this.total}',
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold,
fontSize: 13.0),
)),
],
),
),
);
}
map.dart
class Map extends StatefulWidget {
final List<BikeStation> stations;
Map(this.stations);
@override
MapState createState() {
return MapState(stations);
}
}
class MapState extends State<Map> {
List<BikeStation> stations;
List<Marker> _markers = List<Marker>();
MapState(this.stations);
@override
void initState() {
super.initState();
}
Widget build(BuildContext context) {
for (BikeStation s in stations) {
_markers.add(s.toWidget());
}
return FlutterMap(
options: MapOptions(
center: LatLng(43.6100166, 3.8518451),
zoom: 14.0,
maxZoom: 18,
),
children: <Widget>[
TileLayerWidget(
options: TileLayerOptions(
urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
subdomains: ['a', 'b', 'c'],
),
),
MarkerLayerWidget(
options: MarkerLayerOptions(
markers: _markers,
),
),
],
);
}
}
This widget will represent our map with the list of bike stations.
You can see that we add our bike stations to our marker list by using our previously written method toWidget.
It’s time to write our core application by using our model and map previously written in our main.dart file.
main.dart:
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Kuzzle & Flutter',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Kuzzle & Flutter Demo'),
);
}
}
Until now nothing fancy, this is the basics to create a flutter app.
We will now write the core of our application by calling our previously created custom Kuzzle API route to get the list of bike stations and then also subscribe to our collection to be updated of any changes about those bike stations.
To instantiate Kuzzle simply call the constructor by passing a protocol as parameter
kuzzle = Kuzzle(
WebSocketProtocol(
Uri(
scheme: 'ws',
host: 'my-kuzzle-host',
port: 7512,
),
),
);
Same to connect to our kuzzle instance simply call the connect method
kuzzle.connect();
Now let’s call our new API route to get all the bike stations. To do this we will call the query method to do a request once we are connected to our Kuzzle instance.
kuzzle.connect().then((_) {
kuzzle.query(KuzzleRequest(
controller: 'bike-stations',
action: 'get'
)).then((res) {
for (dynamic station in res.result['vcs']['sl']['si']) {
setState(() {
BikeStation bs = BikeStation(
lat: station['la'],
lng: station['lg'],
total: station['to'],
free: station['fr'],
);
stations.add(bs);
});
}
});
});
So here we build a KuzzleRequest which will represent a request to our custom controller and action. Once retrieved we will add them to a list of BikeStation. We do this inside a setState handler so our map will automatically update all our markers (we will see how we instanciate our map in the next step)
For now we have the logic to connect and get bike stations to our front-end. The class MyHomePage should look like this for now:
main.dart
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<BikeStation> stations = List<BikeStation>();
Kuzzle kuzzle;
@override
void initState() {
kuzzle = Kuzzle(
WebSocketProtocol(
Uri(
scheme: 'ws',
host: 'my-kuzzle-host',
port: 7512,
),
),
);
kuzzle.connect().then((_) {
kuzzle.query(KuzzleRequest(
controller: 'bike-stations',
action: 'get'
)).then((res) {
for (dynamic station in res.result['vcs']['sl']['si']) {
setState(() {
BikeStation bs = BikeStation(
lat: station['la'],
lng: station['lg'],
total: station['to'],
free: station['fr'],
);
stations.add(bs);
});
}
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: new Map(this.stations),
);
}
}
Now we should have a map with markers at each bike stations
The last step is to receive the messages published by the backend every minute to update our bike stations.
For this we simply need to subscribe to our index/collection where we publish our message.
kuzzle.realtime.subscribe('bike-stations', 'stations', {}, (message) {
stations.clear();
for (dynamic station in message.result['_source']['vcs']['sl']['si']) {
setState(() {
BikeStation bs = BikeStation(lat: station['la'], lng: station['lg'], total: station['to'], free: station['fr']);
stations.add(bs);
});
}
});
For every message we receive we clear the stations we already have and put the ones received. The setState will automatically tells flutter to re render our stations.
If you want to know more about Kuzzle please visit our website.
If you have any questions feel free to join our discord at http://join.discord.kuzzle.io
You can find more information about our Javascript SDK here and more about our Dart SDK here.
To know more about our realtime engine please visit https://docs.kuzzle.io/core/1/guides/cookbooks/realtime-api/introduction/.
You can find all the source code at https://github.com/jenow/kuzzle-flutter