1. Introduction
Dernière mise à jour : 14/03/2022
FlutterFire pour la communication entre appareils
Alors que nous assistons à la mise en ligne d'un grand nombre d'appareils domotiques, d'accessoires connectés et de technologies de santé personnelles, la communication inter-appareil devient de plus en plus importante dans le développement d'applications mobiles. Il est généralement plus complexe de mettre en place une communication inter-appareil, telle que contrôler un navigateur depuis une application pour téléphone ou ce qui est diffusé sur votre téléviseur depuis votre téléphone .
Firebase Realtime Database fournit l'API Presence, qui permet aux utilisateurs de voir l'état en ligne/hors connexion de leur appareil. Vous l'utiliserez avec le service d'installations Firebase pour suivre et connecter tous les appareils sur lesquels le même utilisateur s'est connecté. Vous utiliserez Flutter afin de créer rapidement des applications pour plusieurs plates-formes, puis vous développerez un prototype multi-appareil qui diffusera de la musique sur un appareil et la contrôlera sur un autre.
Objectifs de l'atelier
Dans cet atelier de programmation, vous allez créer une télécommande simple pour lecteur de musique. Cette application pourra :
- disposer d'un lecteur de musique simple sur Android, iOS et le Web, créé avec Flutter ;
- Autorisez les utilisateurs à se connecter.
- Connexion des appareils lorsque le même utilisateur est connecté sur plusieurs appareils.
- Autorisez les utilisateurs à contrôler la lecture de musique sur un appareil depuis un autre appareil.
Points abordés
- Créer et exécuter une application de lecteur de musique Flutter
- Autoriser les utilisateurs à se connecter avec Firebase Auth.
- Utiliser l'API RTDB Presence et le service d'installation Firebase pour connecter des appareils
Prérequis
- Un environnement de développement Flutter Pour le configurer, suivez les instructions du guide d'installation de Flutter.
- Vous devez disposer de Flutter 2.10 ou version ultérieure. Si vous disposez d'une version antérieure, exécutez
flutter upgrade.
. - Un compte Firebase
2. Configuration
Télécharger le code de démarrage
Nous avons créé une application de lecture de musique dans Flutter. Le code de démarrage se trouve dans un dépôt Git. Pour commencer, clonez le dépôt dans la ligne de commande, accédez au dossier contenant l'état de départ et installez les dépendances :
git clone https://github.com/FirebaseExtended/cross-device-controller.git
cd cross-device-controller/starter_code
flutter pub get
Créer l'application
Vous pouvez utiliser votre IDE préféré pour créer l'application ou la ligne de commande.
Dans le répertoire de votre application, créez-la pour le Web à l'aide de la commande flutter run -d web-server.
. Vous devriez voir l'invite suivante.
lib/main.dart is being served at http://localhost:<port>
Accédez à http://localhost:<port>
pour afficher le lecteur de musique.
Si vous connaissez l'émulateur Android ou le simulateur iOS, vous pouvez créer l'application pour ces plates-formes et l'installer à l'aide de la commande flutter run -d <device_name>
.
L'application Web doit afficher un lecteur de musique autonome de base. Assurez-vous que les fonctionnalités du lecteur fonctionnent comme prévu. Il s'agit d'une application de lecteur de musique simple conçue pour cet atelier de programmation. Il ne peut lire qu'une chanson Firebase, Better Together.
Configurer un émulateur Android ou un simulateur iOS
Si vous disposez déjà d'un appareil Android ou iOS à des fins de développement, vous pouvez ignorer cette étape.
Pour créer un émulateur Android, téléchargez Android Studio, qui est également compatible avec le développement Flutter, et suivez les instructions de la section Créer et gérer des appareils virtuels.
Pour créer un simulateur iOS, vous avez besoin d'un environnement Mac. Téléchargez Xcode, puis suivez les instructions de la section Présentation du simulateur > Utiliser le simulateur > Ouvrir et fermer un simulateur.
3. Configurer Firebase
Créer un projet Firebase
Ouvrez http://console.firebase.google.com/ dans un navigateur.
- Connectez-vous à Firebase.
- Dans la console Firebase, cliquez sur Ajouter un projet (ou Créer un projet), puis nommez votre projet Firebase Firebase-Cross-Device-Codelab.
- Cliquez sur les options souhaitées. Si vous y êtes invité, acceptez les conditions d'utilisation de Firebase. Ignorez la configuration de Google Analytics, car vous n'utiliserez pas Analytics pour cette application.
Vous n'avez pas besoin de télécharger les fichiers mentionnés ni de modifier les fichiers build.gradle. Vous les configurerez lorsque vous initialiserez FlutterFire.
Installer le SDK Firebase
Sur la ligne de commande, dans le répertoire du projet, exécutez la commande suivante pour installer Firebase:
flutter pub add firebase_core
Dans le fichier pubspec.yaml
, définissez la version de firebase_core
sur au moins 1.13.1 ou exécutez flutter upgrade
.
Initialiser FlutterFire
- Si la CLI Firebase n'est pas installée, vous pouvez l'installer en exécutant
curl -sL https://firebase.tools | bash
. - Connectez-vous en exécutant
firebase login
et en suivant les instructions. - Installez la CLI FlutterFire en exécutant
dart pub global activate flutterfire_cli
. - Configurez la CLI FlutterFire en exécutant
flutterfire configure
. - À l'invite, sélectionnez le projet que vous venez de créer pour cet atelier de programmation, par exemple Firebase-Cross-Device-Codelab.
- Sélectionnez iOS, Android et Web lorsque vous êtes invité à choisir la compatibilité de la configuration.
- Lorsque vous êtes invité à saisir l'ID du bundle Apple, saisissez un domaine unique ou saisissez
com.example.appname
(ce qui convient à cet atelier de programmation).
Une fois la configuration effectuée, un fichier firebase_options.dart
contenant toutes les options requises pour l'initialisation sera généré automatiquement.
Dans votre éditeur, ajoutez le code suivant à votre fichier main.dart pour initialiser Flutter et Firebase:
lib/main.dart
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
runApp(const MyMusicBoxApp());
}
Compilez l'application avec la commande suivante:
flutter run
Vous n'avez pas encore modifié d'éléments d'interface utilisateur. L'apparence et le comportement de l'application n'ont donc pas changé. Vous disposez désormais d'une application Firebase et vous pouvez commencer à utiliser les produits Firebase, y compris:
- Firebase Authentication, qui permet à vos utilisateurs de se connecter à votre application.
- Firebase Realtime Database(RTDB) : vous utiliserez l'API de présence pour suivre l'état en ligne/hors connexion des appareils.
- Les règles de sécurité Firebase vous permettront de sécuriser la base de données.
- Service d'installation Firebase pour identifier les appareils sur lesquels un utilisateur unique s'est connecté.
4. Ajouter Firebase Auth
Activer la connexion par e-mail pour Firebase Authentication
Pour permettre aux utilisateurs de se connecter à l'application Web, vous allez définir la méthode de connexion Adresse e-mail/Mot de passe :
- Dans la console Firebase, développez le menu Build (Compiler) dans le panneau de gauche.
- Cliquez sur Authentification, puis sur le bouton Commencer et enfin sur l'onglet Méthode de connexion.
- Cliquez sur Adresse e-mail/Mot de passe dans la liste Fournisseurs de connexion, mettez le bouton bascule Activer en position activée, puis cliquez sur Enregistrer.
Configurer Firebase Authentication dans Flutter
Dans la ligne de commande, exécutez les commandes suivantes pour installer les packages Flutter nécessaires:
flutter pub add firebase_auth
flutter pub add provider
Cette configuration vous permet désormais de créer le flux de connexion et de déconnexion. Étant donné que l'état d'authentification ne doit pas changer d'un écran à l'autre, vous allez créer une classe application_state.dart
pour suivre les changements d'état au niveau de l'application, tels que la connexion et la déconnexion. Pour en savoir plus, consultez la documentation sur la gestion de l'état Flutter.
Collez ce qui suit dans le nouveau fichier application_state.dart
:
lib/src/application_state.dart
import 'package:firebase_auth/firebase_auth.dart'; // new
import 'package:firebase_core/firebase_core.dart'; // new
import 'package:flutter/material.dart';
import '../firebase_options.dart';
import 'authentication.dart';
class ApplicationState extends ChangeNotifier {
ApplicationState() {
init();
}
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loginState = ApplicationLoginState.loggedIn;
} else {
_loginState = ApplicationLoginState.loggedOut;
}
notifyListeners();
});
}
ApplicationLoginState _loginState = ApplicationLoginState.loggedOut;
ApplicationLoginState get loginState => _loginState;
String? _email;
String? get email => _email;
void startLoginFlow() {
_loginState = ApplicationLoginState.emailAddress;
notifyListeners();
}
Future<void> verifyEmail(
String email,
void Function(FirebaseAuthException e) errorCallback,
) async {
try {
var methods =
await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);
if (methods.contains('password')) {
_loginState = ApplicationLoginState.password;
} else {
_loginState = ApplicationLoginState.register;
}
_email = email;
notifyListeners();
} on FirebaseAuthException catch (e) {
errorCallback(e);
}
}
Future<void> signInWithEmailAndPassword(
String email,
String password,
void Function(FirebaseAuthException e) errorCallback,
) async {
try {
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: email,
password: password,
);
} on FirebaseAuthException catch (e) {
errorCallback(e);
}
}
void cancelRegistration() {
_loginState = ApplicationLoginState.emailAddress;
notifyListeners();
}
Future<void> registerAccount(
String email,
String displayName,
String password,
void Function(FirebaseAuthException e) errorCallback) async {
try {
var credential = await FirebaseAuth.instance
.createUserWithEmailAndPassword(email: email, password: password);
await credential.user!.updateDisplayName(displayName);
} on FirebaseAuthException catch (e) {
errorCallback(e);
}
}
void signOut() {
FirebaseAuth.instance.signOut();
}
}
Pour vous assurer que ApplicationState
est initialisé au démarrage de l'application, vous allez ajouter une étape d'initialisation à main.dart
:
lib/main.dart
import 'src/application_state.dart';
import 'package:provider/provider.dart';
void main() async {
...
runApp(ChangeNotifierProvider(
create: (context) => ApplicationState(),
builder: (context, _) => const MyMusicBoxApp(),
));
}
Encore une fois, l'interface utilisateur de l'application devrait être restée la même, mais vous pouvez désormais autoriser les utilisateurs à se connecter et à enregistrer les états de l'application.
Créer un flux de connexion
Au cours de cette étape, vous allez suivre le processus de connexion et de déconnexion. Voici à quoi ressemblera le flux:
- Un utilisateur déconnecté lance le flux de connexion en cliquant sur le menu contextuel à droite de la barre d'application.
- Le flux de connexion s'affiche dans une boîte de dialogue.
- Si l'utilisateur ne s'est jamais connecté auparavant, il est invité à créer un compte avec une adresse e-mail et un mot de passe valides.
- S'il s'est déjà connecté auparavant, il sera invité à saisir son mot de passe.
- Une fois l'utilisateur connecté, l'option Déconnexion s'affiche lorsque l'utilisateur clique sur le menu contextuel.
L'ajout d'un flux de connexion se déroule en trois étapes.
Commencez par créer un widget AppBarMenuButton
. Ce widget contrôle le menu contextuel pop-up en fonction de l'loginState
de l'utilisateur. Ajoutez les importations.
lib/src/widgets.dart
import 'application_state.dart';
import 'package:provider/provider.dart';
import 'authentication.dart';
Ajoutez le code suivant à widgets.dart.
.
lib/src/widgets.dart
class AppBarMenuButton extends StatelessWidget {
const AppBarMenuButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Consumer<ApplicationState>(
builder: (context, appState, child) {
if (appState.loginState == ApplicationLoginState.loggedIn) {
return SignedInMenuButton(buildContext: context);
}
return SignInMenuButton(buildContext: context);
},
);
}
}
class SignedInMenuButton extends StatelessWidget {
const SignedInMenuButton({Key? key, required this.buildContext})
: super(key: key);
final BuildContext buildContext;
@override
Widget build(BuildContext context) {
return PopupMenuButton<String>(
onSelected: _handleSignedInMenu,
color: Colors.deepPurple.shade300,
itemBuilder: (context) => _getMenuItemBuilder(),
);
}
List<PopupMenuEntry<String>> _getMenuItemBuilder() {
return [
const PopupMenuItem<String>(
value: 'Sign out',
child: Text(
'Sign out',
style: TextStyle(color: Colors.white),
),
)
];
}
Future<void> _handleSignedInMenu(String value) async {
switch (value) {
case 'Sign out':
Provider.of<ApplicationState>(buildContext, listen: false).signOut();
break;
}
}
}
class SignInMenuButton extends StatelessWidget {
const SignInMenuButton({Key? key, required this.buildContext})
: super(key: key);
final BuildContext buildContext;
@override
Widget build(BuildContext context) {
return PopupMenuButton<String>(
onSelected: _signIn,
color: Colors.deepPurple.shade300,
itemBuilder: (context) => _getMenuItemBuilder(context),
);
}
Future<void> _signIn(String value) async {
return showDialog<void>(
context: buildContext,
builder: (context) => const SignInDialog(),
);
}
List<PopupMenuEntry<String>> _getMenuItemBuilder(BuildContext context) {
return [
const PopupMenuItem<String>(
value: 'Sign in',
child: Text(
'Sign in',
style: TextStyle(color: Colors.white),
),
),
];
}
}
Ensuite, dans la même classe widgets.dart
, créez le widget SignInDialog
.
lib/src/widgets.dart
class SignInDialog extends AlertDialog {
const SignInDialog({Key? key}) : super(key: key);
@override
AlertDialog build(BuildContext context) {
return AlertDialog(
content: Column(mainAxisSize: MainAxisSize.min, children: [
Consumer<ApplicationState>(
builder: (context, appState, _) => Authentication(
email: appState.email,
loginState: appState.loginState,
startLoginFlow: appState.startLoginFlow,
verifyEmail: appState.verifyEmail,
signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
cancelRegistration: appState.cancelRegistration,
registerAccount: appState.registerAccount,
signOut: appState.signOut,
),
),
]),
);
}
}
Troisièmement, recherchez le widget appBar existant dans main.dart.
Ajoutez le AppBarMenuButton
pour afficher l'option Se connecter ou Se déconnecter.
lib/main.dart
import 'src/widgets.dart';
appBar: AppBar(
title: const Text('Music Box'),
backgroundColor: Colors.deepPurple.shade400,
actions: const <Widget>[
AppBarMenuButton(),
],
),
Exécutez la commande flutter run
pour redémarrer l'application avec ces modifications. Le menu contextuel doit s'afficher à droite de la barre d'application. Si vous cliquez dessus, une boîte de dialogue de connexion s'ouvre.
Une fois que vous vous êtes connecté avec une adresse e-mail valide et un mot de passe, vous devriez voir l'option Se déconnecter dans le menu contextuel.
Dans la console Firebase, sous Authentification, vous devriez voir l'adresse e-mail listée en tant que nouvel utilisateur.
Félicitations ! Les utilisateurs peuvent désormais se connecter à l'application.
5. Ajouter une connexion à la base de données
Vous êtes maintenant prêt à enregistrer des appareils à l'aide de l'API Firebase Presence.
Dans la ligne de commande, exécutez les commandes suivantes pour ajouter les dépendances nécessaires:
flutter pub add firebase_app_installations
flutter pub add firebase_database
Créer une base de données
Dans la console Firebase,
- Accédez à la section Realtime Database (Base de données en temps réel) de la console Firebase. Cliquez sur Créer une base de données.
- Si vous êtes invité à sélectionner un mode de démarrage pour vos règles de sécurité, sélectionnez Test Mode (Mode test) pour l'instant**.** (Le mode test crée des règles de sécurité qui autorisent toutes les requêtes. Vous ajouterez des règles de sécurité plus tard. Il est important de ne jamais passer en production avec vos règles de sécurité en mode test.)
La base de données est vide pour le moment. Localisez votre databaseURL
dans Paramètres du projet, sous l'onglet Général. Faites défiler la page jusqu'à la section Applications Web.
Ajoutez votre databaseURL
au fichier firebase_options.dart
:
lib/firebase_options.dart
static const FirebaseOptions web = FirebaseOptions(
apiKey: yourApiKey,
...
databaseURL: 'https://<YOUR_DATABASE_URL>,
...
);
Enregistrer des appareils à l'aide de l'API RTDB Presence
Vous souhaitez enregistrer les appareils d'un utilisateur lorsqu'ils apparaissent en ligne. Pour ce faire, vous utiliserez les installations Firebase et l'API Firebase RTDB Presence pour suivre la liste des appareils en ligne d'un seul utilisateur. Le code suivant permet d'atteindre cet objectif:
lib/src/application_state.dart
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:firebase_app_installations/firebase_app_installations.dart';
class ApplicationState extends ChangeNotifier {
String? _deviceId;
String? _uid;
Future<void> init() async {
...
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loginState = ApplicationLoginState.loggedIn;
_uid = user.uid;
_addUserDevice();
}
...
});
}
Future<void> _addUserDevice() async {
_uid = FirebaseAuth.instance.currentUser?.uid;
String deviceType = _getDevicePlatform();
// Create two objects which we will write to the
// Realtime database when this device is offline or online
var isOfflineForDatabase = {
'type': deviceType,
'state': 'offline',
'last_changed': ServerValue.timestamp,
};
var isOnlineForDatabase = {
'type': deviceType,
'state': 'online',
'last_changed': ServerValue.timestamp,
};
var devicesRef =
FirebaseDatabase.instance.ref().child('/users/$_uid/devices');
FirebaseInstallations.instance
.getId()
.then((id) => _deviceId = id)
.then((_) {
// Use the semi-persistent Firebase Installation Id to key devices
var deviceStatusRef = devicesRef.child('$_deviceId');
// RTDB Presence API
FirebaseDatabase.instance
.ref()
.child('.info/connected')
.onValue
.listen((data) {
if (data.snapshot.value == false) {
return;
}
deviceStatusRef.onDisconnect().set(isOfflineForDatabase).then((_) {
deviceStatusRef.set(isOnlineForDatabase);
});
});
});
}
String _getDevicePlatform() {
if (kIsWeb) {
return 'Web';
} else if (Platform.isIOS) {
return 'iOS';
} else if (Platform.isAndroid) {
return 'Android';
}
return 'Unknown';
}
Revenez à la ligne de commande, compilez et exécutez l'application sur votre appareil ou dans un navigateur avec flutter run.
.
Dans votre application, connectez-vous en tant qu'utilisateur. N'oubliez pas de vous connecter avec le même nom d'utilisateur sur différentes plates-formes.
Dans la console Firebase, vos appareils devraient apparaître sous un seul ID utilisateur dans votre base de données.
6. Synchroniser l'état de l'appareil
Sélectionner un appareil pour prospects
Pour synchroniser les états entre les appareils, désignez un appareil comme responsable ou contrôleur. L'appareil principal détermine les états des appareils suiveurs.
Créez une méthode setLeadDevice
dans application_state.dart
et suivez cet appareil avec la clé active_device
dans RTDB :
lib/src/application_state.dart
bool _isLeadDevice = false;
String? leadDeviceType;
Future<void> setLeadDevice() async {
if (_uid != null && _deviceId != null) {
var playerRef =
FirebaseDatabase.instance.ref().child('/users/$_uid/active_device');
await playerRef
.update({'id': _deviceId, 'type': _getDevicePlatform()}).then((_) {
_isLeadDevice = true;
});
}
}
Pour ajouter cette fonctionnalité au menu contextuel de la barre d'application, créez une PopupMenuItem
appelée Controller
en modifiant le widget SignedInMenuButton
. Ce menu permettra aux utilisateurs de définir l'appareil principal.
lib/src/widgets.dart
class SignedInMenuButton extends StatelessWidget {
const SignedInMenuButton({Key? key, required this.buildContext})
: super(key: key);
final BuildContext buildContext;
List<PopupMenuEntry<String>> _getMenuItemBuilder() {
return [
const PopupMenuItem<String>(
value: 'Sign out',
child: Text(
'Sign out',
style: TextStyle(color: Colors.white),
),
),
const PopupMenuItem<String>(
value: 'Controller',
child: Text(
'Set as controller',
style: TextStyle(color: Colors.white),
),
)
];
}
void _handleSignedInMenu(String value) async {
switch (value) {
...
case 'Controller':
Provider.of<ApplicationState>(buildContext, listen: false)
.setLeadDevice();
}
}
}
Écrire l'état de l'appareil principal dans la base de données
Une fois que vous avez défini un appareil principal, vous pouvez synchroniser ses états avec RTDB à l'aide du code suivant. Ajoutez le code suivant à la fin de application_state.dart.
. Deux attributs seront alors stockés : l'état du lecteur (lecture ou pause) et la position du curseur.
lib/src/application_state.dart
Future<void> setLeadDeviceState(
int playerState, double sliderPosition) async {
if (_isLeadDevice && _uid != null && _deviceId != null) {
var leadDeviceStateRef =
FirebaseDatabase.instance.ref().child('/users/$_uid/active_device');
try {
var playerSnapshot = {
'id': _deviceId,
'state': playerState,
'type': _getDevicePlatform(),
'slider_position': sliderPosition
};
await leadDeviceStateRef.set(playerSnapshot);
} catch (e) {
throw Exception('updated playerState with error');
}
}
}
Enfin, vous devez appeler setActiveDeviceState
chaque fois que l'état du joueur du contrôleur est mis à jour. Apportez les modifications suivantes au fichier player_widget.dart
existant :
lib/player_widget.dart
import 'package:provider/provider.dart';
import 'application_state.dart';
void _onSliderChangeHandler(v) {
...
// update player state in RTDB if device is active
Provider.of<ApplicationState>(context, listen: false)
.setLeadDeviceState(_playerState.index, _sliderPosition);
}
Future<int> _pause() async {
...
// update DB if device is active
Provider.of<ApplicationState>(context, listen: false)
.setLeadDeviceState(_playerState.index, _sliderPosition);
return result;
}
Future<int> _play() async {
var result = 0;
// update DB if device is active
Provider.of<ApplicationState>(context, listen: false)
.setLeadDeviceState(PlayerState.PLAYING.index, _sliderPosition);
if (_playerState == PlayerState.PAUSED) {
result = await _audioPlayer.resume();
return result;
}
...
}
Future<int> _updatePositionAndSlider(Duration tempPosition) async {
...
// update DB if device is active
Provider.of<ApplicationState>(context, listen: false)
.setLeadDeviceState(_playerState.index, _sliderPosition);
return result;
}
Lire l'état de l'appareil principal à partir de la base de données
Deux parties permettent de lire et d'utiliser l'état de l'appareil principal. Tout d'abord, vous devez configurer un écouteur de base de données de l'état du joueur principal dans application_state
. Cet écouteur indiquera aux appareils abonnés à quel moment mettre à jour l'écran via un rappel. Notez que vous avez défini une interface OnLeadDeviceChangeCallback
à cette étape. Elle n'est pas encore implémentée. Vous allez implémenter cette interface dans player_widget.dart
à l'étape suivante.
lib/src/application_state.dart
// Interface to be implemented by PlayerWidget
typedef OnLeadDeviceChangeCallback = void Function(
Map<dynamic, dynamic> snapshot);
class ApplicationState extends ChangeNotifier {
...
OnLeadDeviceChangeCallback? onLeadDeviceChangeCallback;
Future<void> init() async {
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
_loginState = ApplicationLoginState.loggedIn;
_uid = user.uid;
_addUserDevice().then((_) => listenToLeadDeviceChange());
}
...
});
}
Future<void> listenToLeadDeviceChange() async {
if (_uid != null) {
var activeDeviceRef =
FirebaseDatabase.instance.ref().child('/users/$_uid/active_device');
activeDeviceRef.onValue.listen((event) {
final activeDeviceState = event.snapshot.value as Map<dynamic, dynamic>;
String activeDeviceKey = activeDeviceState['id'] as String;
_isLeadDevice = _deviceId == activeDeviceKey;
leadDeviceType = activeDeviceState['type'] as String;
if (!_isLeadDevice) {
onLeadDeviceChangeCallback?.call(activeDeviceState);
}
notifyListeners();
});
}
}
Ensuite, démarrez l'écouteur de base de données lors de l'initialisation du lecteur dans player_widget.dart
. Transmettez la fonction _updatePlayer
afin que l'état du joueur suiveur puisse être mis à jour chaque fois que la valeur de la base de données change.
lib/player_widget.dart
class _PlayerWidgetState extends State<PlayerWidget> {
@override
void initState() {
...
Provider.of<ApplicationState>(context, listen: false)
.onLeadDeviceChangeCallback = updatePlayer;
}
void updatePlayer(Map<dynamic, dynamic> snapshot) {
_updatePlayer(snapshot['state'], snapshot['slider_position']);
}
void _updatePlayer(dynamic state, dynamic sliderPosition) {
if (state is int && sliderPosition is double) {
try {
_updateSlider(sliderPosition);
final PlayerState newState = PlayerState.values[state];
if (newState != _playerState) {
switch (newState) {
case PlayerState.PLAYING:
_play();
break;
case PlayerState.PAUSED:
_pause();
break;
case PlayerState.STOPPED:
case PlayerState.COMPLETED:
_stop();
break;
}
_playerState = newState;
}
} catch (e) {
if (kDebugMode) {
print('sync player failed');
}
}
}
}
Vous êtes maintenant prêt à tester l'application:
- Dans la ligne de commande, exécutez l'application sur des émulateurs et/ou dans un navigateur avec
flutter run -d <device-name>
. - Ouvrez les applications dans un navigateur, dans un simulateur iOS ou dans un émulateur Android. Dans le menu contextuel, choisissez une application comme appareil principal. Vous devriez voir les joueurs des appareils suiveurs changer à mesure que l'appareil leader est mis à jour.
- Modifiez maintenant l'appareil maître, lancez ou mettez en pause la musique, et observez les appareils suiveurs se mettre à jour en conséquence.
Si les appareils abonnés se mettent à jour correctement, cela signifie que vous avez réussi à créer un contrôleur inter-appareil. Il ne reste qu'une étape cruciale.
7. Mettre à jour les règles de sécurité
Si nous n'écrivons pas de meilleures règles de sécurité, quelqu'un pourrait écrire un état sur un appareil qui ne lui appartient pas. Avant de terminer, mettez à jour les règles de sécurité Realtime Database pour vous assurer que seuls les utilisateurs connectés à un appareil peuvent lire ou écrire des données sur un appareil. Dans la console Firebase, accédez à Realtime Database, puis à l'onglet Règles. Collez les règles suivantes, qui n'autorisent qu'un utilisateur connecté à lire et à écrire ses propres états d'appareil :
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
},
}
}
8. Félicitations !
Félicitations, vous avez créé une télécommande multi-appareils à l'aide de Flutter.
Crédits
Better Together, une chanson Firebase
- Musique de Ryan Vernon
- Paroles et pochette de l'album par Marissa Christy
- Voix de JP Gomez
9. Bonus
Autre défi : vous pouvez utiliser Flutter FutureBuilder
pour ajouter le type d'appareil actuel à l'UI de manière asynchrone. Si vous avez besoin d'aide, elle est implémentée dans le dossier contenant l'état terminé de l'atelier de programmation.