Comment créer votre application mobile de géolocalisation avec Kuzzle & Flutter

Exemple d'une application Open data utilisant le backend Flutter et Kuzzle.

Introduction

Dans cet article, nous allons créer une application utilisant Flutter et Kuzzle, fonctionnant sur des données ouvertes, pour voir les stations de vélos publiques de la ville de Montpellier sur une application mobile.

Pour cela, nous aurons besoin d'une partie Backend où nous récupérerons les données ouvertes chaque minute et les publierons en tant que données en temps réel. Ensuite, une application mobile affichera une carte avec un marqueur pour chaque station de vélos, avec son nombre de vélos gratuits / nombre total de vélos. Elle s'abonnera à des messages en temps réel (à l'aide de websocket) pour mettre à jour la carte lorsqu'elle sera informée d'un changement.

 

Le code source complet est disponible ici https://github.com/jenow/kuzzle-flutter

 

Pré requis 

 

Pour cela, nous utiliserons le développement d'une application Kuzzle pour la partie Backend et Flutter pour la partie mobile.
Pour que Kuzzle fonctionne correctement, il faut une instance ElasticSearch et une instance Redis.
La façon la plus simple d'exécuter ElasticSearch et Redis est d'utiliser notre application cli Kourou

 

$ npm i kourou -g 
$ kourou app:start-services

 

L'application backend

Commençon par installer Kuzzle :

 

$ npm i kuzzle

 

Ensuite, commencez à écrire notre application Backend en important kuzzle et installez-la :

 

 

const { Backend } = require('kuzzle')

const app = new Backend('bike-stations')

 

Nous devons maintenant étendre l'API native de Kuzzle pour ajouter un nouvel itinéraire, afin d'obtenir toutes les stations de vélo disponibles. Pour ce faire, nous devons définir un contrôleur avec une ou plusieurs actions :

 

app.controller.register('bike-stations', {
actions: {
get: {
handler: () => getStations()
}
}
})

 

Nous avons maintenant un nouveau contrôleur API appelé "bike-stations", exposant une action unique appelée "get", que nous utiliserons pour récupérer toutes les stations de vélos.

Pour en savoir plus sur le fonctionnement des contrôleurs dans Kuzzle, consultez ce guide : https://docs.kuzzle.io/core/2/plugins/guides/controllers

 

Maintenant, mettons en œuvre la fonction getStations. Elle doit aller chercher des données XML dans le service web open data de Montpellier, puis les convertir en JSON en utilisant la bibliothèque xml2json :

 

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)
}
})
})
})
}

 

Nous allons également mettre en place une routine qui permettra de récupérer ces données et de les envoyer en temps réel chaque minute. Nous lancerons cette routine après avoir exécuté notre instance Kuzzle.

 

app.start().then(() => {
setInterval(async () => {
const res = await getStations()
await app.sdk.realtime.publish('bike-stations', 'stations', res)
}, 60000)
}).catch(err => {
console.error(err)
})

 

Et c'est tout pour notre partie Backend. Il ne nous reste plus qu'à la faire fonctionner.

 

$ node index.js

 

L'application mobile

Maintenant, écrivons notre application de flutter. Nous allons d'abord ajouter les dépendances que sont le SDK Kuzzle Dart et la carte flutter.

 

dependencies:
flutter_map: 0.10.1+1
kuzzle: 2.0.1

 

Le modèle

 

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,
});

 

Cela représentera une station de vélos avec leur emplacement, le nombre de vélos gratuits et le nombre total de vélos.

 

Ajoutons également une méthode d'aide pour pouvoir facilement ajouter cette station à notre carte en renvoyant un widget marqueur avec la bonne localisation et les bonnes 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),
)),
],
),
),
);
}

 

La carte

 

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,
),
),
],
);
}
}

 

Ce widget représentera notre carte avec la liste des stations de vélo.

Vous pouvez voir que nous ajoutons nos stations de vélos à notre liste de marqueurs en utilisant notre méthode précédemment écrite toWidget.

 

Le coeur

 

Il est temps d'écrire notre application de base en utilisant notre modèle et notre carte précédemment écrits dans notre fichier main.dart.

 

La méthode principale

main.dart:

void main() {
runApp(MyApp());
}

 

MyApp class

 

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'),
);
}
}

 

Jusqu'à présent rien de bien compliqué, c'est la base pour créer une application de flutter.

Nous allons maintenant écrire le cœur de notre application en appelant notre route API Kuzzle personnalisée créée précédemment pour obtenir la liste des stations de vélos et ensuite nous abonner à notre collection pour être mis à jour de tout changement concernant ces stations de vélos.

 

  1. Installer kuzzle
  2. Se connecter à  Kuzzle
  3. Obtenir un station de vélos
  4. Tout afficher

Pour installer Kuzzle, il suffit d'appeler le constructeur en lui passant un protocole comme paramètre.

 

kuzzle = Kuzzle(
WebSocketProtocol(
Uri(
scheme: 'ws',
host: 'my-kuzzle-host',
port: 7512,
),
),
);

 

 

Même chose pour se connecter à notre instance kuzzle, il suffit d'appeler la méthode de connexion

 

kuzzle.connect();

 

 

Appelons maintenant notre nouvelle route API pour obtenir toutes les stations de vélos. Pour ce faire, nous appellerons la méthode de requête pour effectuer une requête une fois que nous serons connectés à notre instance Kuzzle.

 

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);
});
}
});
});

 

Nous construisons donc ici un KuzzleRequest qui représentera une demande à notre contrôleur personnalisé et une action. Une fois récupérées, nous les ajouterons à une liste de BikeStation. Nous faisons cela dans un gestionnaire de setState, de sorte que notre carte mettra automatiquement à jour tous nos marqueurs (nous verrons comment nous installons notre carte dans l'étape suivante)

 

Pour l'instant, nous avons la logique nécessaire pour nous connecter et faire en sorte que les stations de vélos se trouvent dans le fornt-end. La classe MyHomePage devrait ressembler à ça pour l'instant :

 

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),
);
}
}

 

 

Nous devrions maintenant avoir une carte avec des marqueurs à chaque station de vélo

 

 

5 - Mise a jour en temps réel

La dernière étape consiste à recevoir les messages publiés par le backend chaque minute pour mettre à jour nos stations de vélos.

Pour cela, il nous suffit de nous inscrire à notre index/collection où nous publions notre 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);
});
}
});

 

Pour chaque message que nous recevons, nous effaçons les stations que nous avons déjà et nous mettons celles qui sont reçues. Le setState indique automatiquement à Flutter de rendre nos stations.

 

C'est fini

 

Si vous voulez en savoir plus sur Kuzzle visitez notre site web.

Si vous avez des questions, n'hésitez pas à vous joindre à notre discorde à l'adresse    http://join.discord.kuzzle.io

Vous pouvez trouver plus d'informations sur notre SDK Javascript ici et plus sur notre SDK Dart ici.
Pour en savoir plus sur notre moteur en temps réel, veuillez visiter

https://docs.kuzzle.io/core/1/guides/cookbooks/realtime-api/introduction/.

 

Vous pouvez trouver tout le code source sur https://github.com/jenow/kuzzle-flutter

Kevin Blondel

Postes associés