1. Introduction
Dernière mise à jour : 14/03/2022
FlutterFire pour la communication multi-appareils
Avec l'arrivée en ligne d'un grand nombre d'appareils domotiques, de wearables et de technologies de santé personnelle, la communication inter-appareils devient un élément de plus en plus important dans la création d'applications mobiles. La configuration de la communication entre appareils, comme le contrôle d'un navigateur à partir d'une application mobile ou le contrôle de ce qui est lu sur votre téléviseur à partir de votre téléphone, est traditionnellement plus complexe que la création d'une application mobile normale .
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 Firebase Installations pour suivre et connecter tous les appareils sur lesquels le même utilisateur s'est connecté. Vous utiliserez Flutter pour créer rapidement des applications pour plusieurs plates-formes, puis vous créerez un prototype multi-appareils qui lit de la musique sur un appareil et la contrôle sur un autre.
Objectifs de l'atelier
Dans cet atelier de programmation, vous allez créer une télécommande simple pour un lecteur de musique. Cette application pourra :
- Créez un lecteur de musique simple sur Android, iOS et le Web avec Flutter.
- Autoriser les utilisateurs à se connecter.
- Associez des appareils lorsque le même utilisateur est connecté sur plusieurs appareils.
- Les applications de cette catégorie permettent aux utilisateurs de contrôler la lecture de musique sur un appareil à partir d'un autre appareil.
Points abordés
- Créer et exécuter une application de lecteur de musique Flutter.
- Découvrez comment autoriser les utilisateurs à se connecter avec Firebase Auth.
- Découvrez comment utiliser l'API Presence Firebase RTDB et le service d'installation Firebase pour connecter des appareils.
Prérequis
- Un environnement de développement Flutter. Suivez les instructions du guide d'installation de Flutter pour le configurer.
- Une version minimale de Flutter 2.10 ou ultérieure est requise. 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 lecteur de musique dans Flutter. Le code de démarrage se trouve dans un dépôt Git. Pour commencer, clonez le dépôt sur la ligne de commande, accédez au dossier avec 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
Compiler l'application
Vous pouvez utiliser votre IDE préféré pour créer l'application ou utiliser la ligne de commande.
Dans le répertoire de votre application, créez l'application pour le Web avec la commande flutter run -d web-server.
. L'invite suivante devrait s'afficher.
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 avec 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'un seul titre Firebase, Better Together.
Configurer un émulateur Android ou un simulateur iOS
Si vous disposez déjà d'un appareil Android ou iOS pour le 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, puis suivez les instructions de Créer et gérer des appareils virtuels.
Pour créer un simulateur iOS, vous aurez besoin d'un environnement Mac. Téléchargez Xcode, puis suivez les instructions de Présentation du simulateur > Utiliser le simulateur > Ouvrir et fermer un simulateur.
3. Configurer Firebase
Créer un projet Firebase
- Connectez-vous à la console Firebase à l'aide de votre compte Google.
- Cliquez sur le bouton pour créer un projet, puis saisissez un nom de projet (par exemple,
Firebase-Cross-Device-Codelab
).
- Cliquez sur Continuer.
- Si vous y êtes invité, lisez et acceptez les Conditions d'utilisation de Firebase, puis cliquez sur Continuer.
- (Facultatif) Activez l'assistance IA dans la console Firebase (appelée "Gemini dans Firebase").
- Pour cet atelier de programmation, vous n'avez pas besoin de Google Analytics. Désactivez donc l'option Google Analytics.
- Cliquez sur Créer un projet, attendez que votre projet soit provisionné, puis cliquez sur Continuer.
Installer le SDK Firebase
De retour 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, choisissez 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 de bundle Apple, saisissez un domaine unique ou
com.example.appname
, ce qui convient pour cet atelier de programmation.
Une fois configuré, un fichier firebase_options.dart
contenant toutes les options requises pour l'initialisation sera généré pour vous.
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 encore modifié aucun élément de l'UI. 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 Presence pour suivre l'état en ligne/hors connexion des appareils.
- Les règles de sécurité Firebase vous permettent de sécuriser la base de données.
- Service Firebase Installations pour identifier les appareils sur lesquels un même utilisateur 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 Email/Password (Adresse e-mail/Mot de passe) :
- Dans la console Firebase, développez le menu Créer dans le panneau de gauche.
- Cliquez sur Authentification, puis sur le bouton Commencer, puis 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
Sur 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
Avec cette configuration, vous pouvez maintenant 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, comme la connexion et la déconnexion. Pour en savoir plus, consultez la documentation 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
sera initialisé au démarrage de l'application, vous devez 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 permettre aux utilisateurs de se connecter et d'enregistrer les états de l'application.
Créer un flux de connexion
Dans cette étape, vous allez travailler sur le flux de connexion et de déconnexion. Voici à quoi ressemblera le flux :
- Un utilisateur déconnecté lancera le flux de connexion en cliquant sur le menu contextuel
sur le côté droit de la barre d'application.
- Le processus de connexion s'affiche dans une boîte de dialogue.
- Si l'utilisateur ne s'est jamais connecté auparavant, il sera invité à créer un compte à l'aide d'une adresse e-mail et d'un mot de passe valides.
- Si l'utilisateur s'est déjà connecté, il sera invité à saisir son mot de passe.
- Une fois l'utilisateur connecté, l'option Se déconnecter s'affiche lorsqu'il clique sur le menu contextuel.
L'ajout d'un flux de connexion nécessite trois étapes.
Commencez par créer un widget AppBarMenuButton
. Ce widget contrôle le pop-up du menu contextuel en fonction du loginState
de l'utilisateur. Ajouter 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 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 devrait s'afficher sur le côté droit de la barre d'application. Si vous cliquez dessus, une boîte de dialogue de connexion s'affiche.
Une fois que vous vous êtes connecté avec une adresse e-mail et un mot de passe valides, l'option Se déconnecter devrait s'afficher dans le menu contextuel.
Dans la console Firebase, sous Authentication (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 à une base de données
Vous êtes maintenant prêt à passer à l'enregistrement de l'appareil à 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 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 Mode test pour le moment**.** (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é ultérieurement. Il est important de ne jamais passer en production tant que vos règles de sécurité sont encore en mode test.)
La base de données est vide pour le moment. Recherchez 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 Presence RTDB
Vous souhaitez enregistrer les appareils d'un utilisateur lorsqu'ils apparaissent en ligne. Pour ce faire, vous allez tirer parti de Firebase Installations et de l'API Firebase RTDB Presence pour suivre la liste des appareils en ligne d'un même utilisateur. Le code suivant vous aidera à 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';
}
De retour sur 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 compte sur les différentes plates-formes.
Dans la console Firebase, vos appareils devraient s'afficher sous un seul ID utilisateur dans votre base de données.
6. Synchroniser l'état de l'appareil
Sélectionnez un appareil principal
Pour synchroniser les états entre les appareils, désignez-en un comme appareil principal ou contrôleur. L'appareil principal détermine l'état des appareils secondaires.
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 un PopupMenuItem
appelé 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.
. Cela commencera à stocker deux attributs : 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 lecteur 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
Pour lire et utiliser l'état de l'appareil principal, deux parties sont nécessaires. Tout d'abord, vous devez configurer un écouteur de base de données de l'état du lecteur principal dans application_state
. Cet écouteur indiquera aux appareils suiveurs quand 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 implémenterez 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 lecteur 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 :
- Sur 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, sur un simulateur iOS ou sur un émulateur Android. Accédez au menu contextuel et choisissez une application comme appareil principal. Vous devriez voir les lecteurs des appareils suiveurs changer à mesure que l'appareil principal se met à jour.
- Modifiez maintenant l'appareil principal, lancez ou mettez en pause la musique, et observez la mise à jour des appareils secondaires en conséquence.
Si les appareils suiveurs se mettent à jour correctement, vous avez réussi à créer un contrôleur multi-appareils. Il ne reste plus qu'une étape essentielle.
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é de la base de données en temps réel pour vous assurer que seul l'utilisateur connecté à un appareil peut lire ou écrire des données sur cet appareil. Dans la console Firebase, accédez à Realtime Database, puis à l'onglet Règles. Collez les règles suivantes pour autoriser uniquement les utilisateurs connectés à lire et à écrire les états de leurs propres appareils :
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
},
}
}
8. Félicitations !
Félicitations, vous avez réussi à créer 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
Pour un défi supplémentaire, envisagez d'utiliser Flutter FutureBuilder
pour ajouter le type d'appareil principal actuel à l'UI de manière asynchrone. Si vous avez besoin d'aide, elle est implémentée dans le dossier contenant l'état final de l'atelier de programmation.