📱 SDK Flutter - Ondes SDK
Ce guide explique comment créer une mini-application Flutter Web qui s'exécute dans l'environnement Ondes Core.
💡 Différence avec le SDK JavaScript : Le SDK Flutter permet de développer des mini-apps en Dart/Flutter au lieu de HTML/JS, tout en bénéficiant des mêmes fonctionnalités natives.
🎯 Prérequis
- Flutter SDK 3.24+ installé
- Un appareil avec l'app Ondes Core installée
- Les deux appareils sur le même réseau WiFi (pour le développement)
📦 Installation
1. Créer un nouveau projet Flutter
2. Ajouter la dépendance ondes_sdk
Ou manuellement dans votre pubspec.yaml :
Puis exécutez :
🚀 Première Mini-App Flutter
Code minimal
Remplacez le contenu de lib/main.dart :
import 'package:flutter/material.dart';
import 'package:ondes_sdk/ondes_sdk.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// ⚠️ IMPORTANT : Attendre que le bridge soit prêt
try {
await Ondes.ensureReady();
print('✅ Ondes SDK connecté !');
} catch (e) {
print('⚠️ Mode développement (hors Ondes) : $e');
}
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Ma Mini-App',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
String _username = 'Chargement...';
@override
void initState() {
super.initState();
_loadUser();
}
Future<void> _loadUser() async {
if (!Ondes.isReady) {
setState(() => _username = 'Mode développement');
return;
}
final profile = await Ondes.user.getProfile();
setState(() {
_username = profile?.username ?? 'Non connecté';
});
}
Future<void> _showToast() async {
if (!Ondes.isReady) return;
await Ondes.ui.showToast(
message: 'Bonjour depuis Flutter ! 🎉',
type: ToastType.success,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Ma Mini-App'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Bienvenue, $_username !',
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 20),
ElevatedButton.icon(
onPressed: _showToast,
icon: const Icon(Icons.celebration),
label: const Text('Afficher un Toast'),
),
],
),
),
);
}
}
🔧 Développement et Test
Méthode 1 : Serveur de développement Flutter (Recommandé)
Étape 1 : Lancer le serveur web Flutter
📝 Explications des options : -
-d web-server: Lance un serveur web au lieu d'un navigateur ---web-port=3000: Port du serveur (modifiable) ---web-hostname=0.0.0.0: Écoute sur toutes les interfaces réseau (nécessaire pour accès depuis mobile)
Étape 2 : Trouver votre adresse IP
Notez votre IP locale (ex: 192.168.1.42).
Étape 3 : Connecter depuis Ondes Lab
- Ouvrez l'app Ondes Core sur votre mobile
- Allez dans l'onglet Lab
- Entrez l'URL :
http://192.168.1.42:3000 - Appuyez sur Lancer
🎉 Votre app Flutter s'affiche dans Ondes avec accès à toutes les APIs natives !
💡 Hot Reload : Flutter Web supporte le hot reload. Les modifications de code se reflètent automatiquement.
Méthode 2 : Build de production + serveur statique
Étape 1 : Compiler en mode release
Les fichiers sont générés dans build/web/.
Étape 2 : Lancer un serveur local
# Avec Python 3
cd build/web
python3 -m http.server 3000
# Ou avec Node.js (http-server)
npx http-server build/web -p 3000
# Ou avec PHP
cd build/web
php -S 0.0.0.0:3000
Étape 3 : Connecter depuis Ondes Lab
Même procédure que la Méthode 1.
Méthode 3 : QR Code (Plus rapide)
- Générez un QR code contenant votre URL (ex:
http://192.168.1.42:3000) - Dans Ondes Lab, appuyez sur Scanner un QR Code
- Scannez le code → L'app se lance automatiquement
🔧 Astuce : Utilisez qr-code-generator.com ou la commande
qrencode:
📚 Référence API
Initialisation
import 'package:ondes_sdk/ondes_sdk.dart';
// Attendre que le bridge soit prêt (obligatoire)
await Ondes.ensureReady();
// Vérifier si on est dans Ondes
if (Ondes.isReady) {
// Utiliser les APIs Ondes
}
🎨 UI - Interface native
// Toast
await Ondes.ui.showToast(
message: "Message affiché",
type: ToastType.success, // info, success, error, warning
);
// Alerte
await Ondes.ui.showAlert(
title: "Titre",
message: "Contenu du message",
buttonText: "OK",
);
// Confirmation
final confirmed = await Ondes.ui.showConfirm(
title: "Supprimer ?",
message: "Cette action est irréversible.",
confirmText: "Oui, supprimer",
cancelText: "Annuler",
);
if (confirmed) {
// Supprimer...
}
// Menu contextuel (Bottom Sheet)
final choice = await Ondes.ui.showBottomSheet(
title: "Actions",
items: [
BottomSheetItem(label: "Modifier", value: "edit", icon: "edit"),
BottomSheetItem(label: "Partager", value: "share", icon: "share"),
BottomSheetItem(label: "Supprimer", value: "delete", icon: "delete"),
],
);
print("Choix: $choice"); // "edit", "share", "delete" ou null
// Configurer la barre de navigation
await Ondes.ui.configureAppBar(
title: "Mon titre",
visible: true,
backgroundColor: "#2196F3",
foregroundColor: "#FFFFFF",
);
👤 User - Utilisateur
// Vérifier l'authentification
if (await Ondes.user.isAuthenticated()) {
// Récupérer le profil
final profile = await Ondes.user.getProfile();
print("Utilisateur: ${profile?.username}");
print("Email: ${profile?.email}");
print("Avatar: ${profile?.avatar}");
📱 Device - Matériel
// Retour haptique
await Ondes.device.hapticFeedback(HapticStyle.success);
// Styles: light, medium, heavy, success, warning, error
// Vibration
await Ondes.device.vibrate(200); // durée en ms
// Scanner un QR Code
try {
final code = await Ondes.device.scanQRCode();
print("Code scanné: $code");
} catch (e) {
print("Scan annulé ou refusé");
}
// Position GPS
final pos = await Ondes.device.getGPSPosition();
print("Latitude: ${pos.latitude}");
print("Longitude: ${pos.longitude}");
print("Précision: ${pos.accuracy}m");
// Infos appareil
final info = await Ondes.device.getInfo();
print("Plateforme: ${info.platform}"); // iOS, android, etc.
print("Mode sombre: ${info.isDarkMode}");
print("Écran: ${info.screenWidth}x${info.screenHeight}");
💾 Storage - Stockage persistant
// Sauvegarder des données (JSON)
await Ondes.storage.set('preferences', {
'theme': 'dark',
'notifications': true,
'language': 'fr',
});
// Récupérer des données
final prefs = await Ondes.storage.get<Map<String, dynamic>>('preferences');
print(prefs?['theme']); // 'dark'
// Lister les clés
final keys = await Ondes.storage.getKeys();
// Supprimer une clé
await Ondes.storage.remove('preferences');
// Tout effacer
await Ondes.storage.clear();
📦 App - Cycle de vie
// Infos sur la mini-app
final info = await Ondes.app.getInfo();
print("Nom: ${info.name}");
print("Version: ${info.version}");
print("Bundle ID: ${info.bundleId}");
// Récupérer le manifest
final manifest = await Ondes.app.getManifest();
// Fermer la mini-app
await Ondes.app.close();
👥 Friends - Amis
// Liste des amis
final friends = await Ondes.friends.list();
for (final friend in friends) {
print("${friend.username} - ami depuis ${friend.friendsSince}");
}
// Envoyer une demande d'ami
await Ondes.friends.request(username: 'john_doe');
// Demandes en attente (reçues)
final pending = await Ondes.friends.getPendingRequests();
print("${pending.length} demandes en attente");
// Accepter/Refuser
await Ondes.friends.accept(requestId);
await Ondes.friends.reject(requestId);
// Bloquer/Débloquer
await Ondes.friends.block(username: 'spammer');
await Ondes.friends.unblock(userId);
// Rechercher des utilisateurs
final results = await Ondes.friends.search('john');
🌍 Social - Réseau social
// Récupérer le feed
final posts = await Ondes.social.getFeed(
limit: 20,
offset: 0,
type: FeedType.main, // main, discover, video
);
// Publier un post
final post = await Ondes.social.publish(
content: "Mon premier post depuis Flutter ! 🚀",
visibility: PostVisibility.followers,
tags: ['flutter', 'ondes'],
);
// Liker / Unliker
await Ondes.social.likePost(post.uuid);
await Ondes.social.unlikePost(post.uuid);
// Commenter
await Ondes.social.addComment(post.uuid, "Super post !");
// Récupérer les commentaires
final comments = await Ondes.social.getComments(post.uuid);
// Bookmarks (favoris)
await Ondes.social.bookmarkPost(post.uuid);
final saved = await Ondes.social.getBookmarks();
// Stories
final stories = await Ondes.social.getStories();
await Ondes.social.viewStory(storyUuid);
await Ondes.social.createStory(mediaPath, duration: 5);
// Follow / Unfollow
await Ondes.social.follow(username: 'influencer');
await Ondes.social.unfollow(userId: 123);
// Sélecteur de média natif
final media = await Ondes.social.pickMedia(
multiple: true,
allowVideo: true,
maxFiles: 10,
);
for (final file in media) {
print("${file.type}: ${file.path}");
}
🔌 Websocket - Connexions temps réel
// Connexion à un serveur WebSocket
final conn = await Ondes.websocket.connect(
'ws://192.168.1.42:8080',
options: WebsocketConnectOptions(
reconnect: true, // Auto-reconnexion
timeout: 5000, // Timeout 5 secondes
),
);
print('Connecté: ${conn.id}');
// Écouter les messages entrants
Ondes.websocket.onMessage(conn.id).listen((message) {
print('Message reçu: $message');
// Parser le message si nécessaire
if (message is String && message.startsWith('<')) {
handleRobotMessage(message);
}
});
// Écouter les changements d'état
Ondes.websocket.onStatusChange(conn.id).listen((event) {
print('État: ${event.status.name}');
if (event.status == WebsocketStatus.error) {
print('Erreur: ${event.error}');
}
});
// Envoyer des messages
await Ondes.websocket.send(conn.id, '<100s50>'); // Texte
await Ondes.websocket.send(conn.id, { // JSON
'type': 'command',
'action': 'move',
});
// Obtenir le statut d'une connexion
final status = await Ondes.websocket.getStatus(conn.id);
if (status != null) {
print('URL: ${status.url}, État: ${status.status.name}');
}
// Lister toutes les connexions
final connections = await Ondes.websocket.list();
print('${connections.length} connexion(s) active(s)');
// Déconnecter
await Ondes.websocket.disconnect(conn.id);
// Déconnecter toutes les connexions
final count = await Ondes.websocket.disconnectAll();
print('$count connexion(s) fermée(s)');
📡 UDP - Découverte réseau
Le module UDP permet la découverte de périphériques et la communication réseau via sockets UDP.
// Créer un socket UDP
final socket = await Ondes.udp.bind(
options: UdpBindOptions(
port: 12345, // Port local (0 = aléatoire)
broadcast: true, // Autoriser le broadcast
),
);
print('Socket lié sur le port ${socket.port}');
// Écouter les messages entrants
Ondes.udp.onMessage(socket.id).listen((message) {
print('Reçu de ${message.address}:${message.port}');
print('Message: ${message.message}');
});
// Envoyer un message
await Ondes.udp.send(socket.id, 'DISCOVER_ROBOT', '192.168.1.100', 12345);
// Broadcast vers plusieurs adresses
final result = await Ondes.udp.broadcast(
socket.id,
'DISCOVER_ROBOT',
[
'192.168.1.255',
'192.168.4.255',
'192.168.4.1',
],
12345,
);
print('Broadcast vers ${result.results.length} adresses');
// Infos sur le socket
final info = await Ondes.udp.getInfo(socket.id);
print('Messages reçus: ${info.messagesReceived}');
// Lister tous les sockets
final sockets = await Ondes.udp.list();
print('${sockets.length} socket(s) actif(s)');
// Fermer le socket
await Ondes.udp.close(socket.id);
// Fermer tous les sockets
final closedCount = await Ondes.udp.closeAll();
print('$closedCount socket(s) fermé(s)');
Exemple : Découverte de robots IoT
Future<List<Map<String, String>>> discoverRobots() async {
final robots = <Map<String, String>>[];
final socket = await Ondes.udp.bind(
options: UdpBindOptions(port: 12345, broadcast: true),
);
final subscription = Ondes.udp.onMessage(socket.id).listen((msg) {
final match = RegExp(r'<(.+)>').firstMatch(msg.message);
if (match == null) return;
final parts = match.group(1)!.split(',');
if (parts[0] == 'DISCOVER_ROBOT') return; // Ignorer nos messages
final robot = {
'ip': parts[0],
'name': parts.length > 1 ? parts[1] : parts[0],
};
if (!robots.any((r) => r['ip'] == robot['ip'])) {
robots.add(robot);
}
});
await Ondes.udp.broadcast(
socket.id,
'DISCOVER_ROBOT',
['192.168.1.255', '192.168.4.255', '192.168.4.1'],
12345,
);
await Future.delayed(Duration(seconds: 4));
await subscription.cancel();
await Ondes.udp.close(socket.id);
return robots;
}
⚠️ Gestion des erreurs
import 'package:ondes_sdk/ondes_sdk.dart';
try {
final pos = await Ondes.device.getGPSPosition();
} on OndesException catch (e) {
switch (e.code) {
case 'PERMISSION_DENIED':
print('Permission GPS refusée');
break;
case 'AUTH_REQUIRED':
print('Connexion requise');
break;
case 'CANCELLED':
print('Action annulée');
break;
case 'NOT_FOUND':
print('Ressource introuvable');
break;
default:
print('Erreur: ${e.message}');
}
}
🎨 Bonnes pratiques
1. Toujours vérifier Ondes.isReady
Future<void> _doSomething() async {
if (!Ondes.isReady) {
// Fallback pour le développement hors Ondes
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Fonctionnalité non disponible')),
);
return;
}
// Code Ondes...
}
2. Configurer l'AppBar au démarrage
@override
void initState() {
super.initState();
_configureAppBar();
}
Future<void> _configureAppBar() async {
if (!Ondes.isReady) return;
await Ondes.ui.configureAppBar(
title: 'Mon App',
backgroundColor: '#673AB7',
foregroundColor: '#FFFFFF',
);
}
3. Gérer le cycle de vie
@override
void dispose() {
// Nettoyage si nécessaire
super.dispose();
}
Future<void> _closeApp() async {
if (Ondes.isReady) {
await Ondes.app.close();
} else {
Navigator.of(context).pop();
}
}
🚀 Publication
Une fois votre app prête, vous pouvez la publier sur le Store Ondes via le Studio :
- Compilez en mode release :
flutter build web --release - Créez un fichier
manifest.jsonà la racine debuild/web/ - Zippez le contenu du dossier
build/web/ - Uploadez via Ondes Studio (onglet Lab → Ouvrir le Studio)
Voir Guide de publication pour plus de détails.
📝 Exemple complet
Un exemple fonctionnel est disponible dans le dépôt :
cd packages/ondes_sdk/example
flutter pub get
flutter run -d web-server --web-port=3000 --web-hostname=0.0.0.0
Puis connectez-vous depuis Ondes Lab avec l'URL de votre machine.