1. Introduzione
Ultimo aggiornamento: 14/03/2022
FlutterFire per la comunicazione cross-device
Con l'aumento del numero di dispositivi di domotica, indossabili e per la salute personale che si connettono a internet, la comunicazione cross-device diventa una parte sempre più importante della creazione di applicazioni mobile. La configurazione della comunicazione cross-device, ad esempio il controllo di un browser da un'app per smartphone o il controllo della riproduzione sulla TV dallo smartphone, è tradizionalmente più complessa rispetto alla creazione di una normale app mobile .
Realtime Database di Firebase fornisce l'API Presence , che consente agli utenti di visualizzare lo stato online/offline del proprio dispositivo. La utilizzerai con il servizio Firebase Installations per monitorare e connettere tutti i dispositivi su cui è stato eseguito l'accesso dallo stesso utente. Utilizzerai Flutter per creare rapidamente applicazioni per più piattaforme, quindi creerai un prototipo cross-device che riproduce musica su un dispositivo e la controlla su un altro.
Cosa creerai
In questo codelab, creerai un semplice telecomando per il lettore musicale. La tua app sarà in grado di:
- Avere un semplice lettore musicale su Android, iOS e web, creato con Flutter.
- Consenti agli utenti di accedere.
- Collega i dispositivi quando lo stesso utente ha eseguito l'accesso su più dispositivi.
- Consente agli utenti di controllare la riproduzione musicale su un dispositivo da un altro dispositivo.
Cosa imparerai a fare
- Come creare ed eseguire un'app lettore musicale Flutter.
- Come consentire agli utenti di accedere con Firebase Auth.
- Come utilizzare l'API Firebase RTDB Presence e il servizio di installazione Firebase per connettere i dispositivi.
Che cosa ti serve
- Un ambiente di sviluppo Flutter. Segui le istruzioni nella guida all'installazione di Flutter per configurarlo.
- È richiesta una versione di Flutter pari ad almeno 2.10 o successive. Se hai una versione precedente, esegui
flutter upgrade.
- Un account Firebase.
2. Configurazione
Recupera il codice di avvio
Abbiamo creato un'app di riproduzione musicale in Flutter. Il codice iniziale si trova in un repository Git. Per iniziare, nella riga di comando clona il repository, sposta la cartella con lo stato iniziale e installa le dipendenze:
git clone https://github.com/FirebaseExtended/cross-device-controller.git
cd cross-device-controller/starter_code
flutter pub get
Crea l'app
Puoi utilizzare il tuo IDE preferito per creare l'app o utilizzare la riga di comando.
Nella directory dell'app, crea l'app per il web con il comando flutter run -d web-server.
. Dovresti visualizzare il seguente prompt.
lib/main.dart is being served at http://localhost:<port>
Visita http://localhost:<port>
per visualizzare il lettore musicale.
Se hai familiarità con l'emulatore Android o il simulatore iOS, puoi creare l'app per queste piattaforme e installarla con il comando flutter run -d <device_name>
.
L'app web dovrebbe mostrare un lettore musicale di base autonomo. Assicurati che le funzionalità del player funzionino come previsto. Si tratta di una semplice app di riproduzione musicale progettata per questo codelab. Può riprodurre solo un brano di Firebase, Better Together.
Configurare un emulatore Android o un simulatore iOS
Se hai già un dispositivo Android o iOS per lo sviluppo, puoi saltare questo passaggio.
Per creare un emulatore Android, scarica Android Studio, che supporta anche lo sviluppo di Flutter, e segui le istruzioni riportate in Creare e gestire dispositivi virtuali.
Per creare un simulatore iOS, è necessario un ambiente Mac. Scarica Xcode e segui le istruzioni riportate in Panoramica del simulatore > Utilizzare il simulatore > Aprire e chiudere un simulatore.
3. Configura Firebase
Crea un progetto Firebase
- Accedi alla console Firebase utilizzando il tuo Account Google.
- Fai clic sul pulsante per creare un nuovo progetto, quindi inserisci un nome per il progetto (ad esempio
Firebase-Cross-Device-Codelab
).
- Fai clic su Continua.
- Se richiesto, leggi e accetta i termini di Firebase, quindi fai clic su Continua.
- (Facoltativo) Attiva l'assistenza AI nella console Firebase (denominata "Gemini in Firebase").
- Per questo codelab non hai bisogno di Google Analytics, quindi disattiva l'opzione Google Analytics.
- Fai clic su Crea progetto, attendi il provisioning del progetto, poi fai clic su Continua.
Installa l'SDK Firebase
Di nuovo nella riga di comando, nella directory del progetto, esegui questo comando per installare Firebase:
flutter pub add firebase_core
Nel file pubspec.yaml
, modifica la versione di firebase_core
in modo che sia almeno 1.13.1 o esegui flutter upgrade
Inizializza FlutterFire
- Se non hai installato l'interfaccia a riga di comando di Firebase, puoi farlo eseguendo
curl -sL https://firebase.tools | bash
. - Accedi eseguendo
firebase login
e seguendo le istruzioni. - Installa l'interfaccia a riga di comando FlutterFire eseguendo
dart pub global activate flutterfire_cli
. - Configura l'interfaccia a riga di comando FlutterFire eseguendo
flutterfire configure
. - Al prompt, scegli il progetto che hai appena creato per questo codelab, ad esempio Firebase-Cross-Device-Codelab.
- Seleziona iOS, Android e Web quando ti viene chiesto di scegliere il supporto per la configurazione.
- Quando ti viene chiesto l'ID bundle Apple, digita un dominio univoco o inserisci
com.example.appname
, che va bene per lo scopo di questo codelab.
Una volta configurato, verrà generato un file firebase_options.dart
contenente tutte le opzioni richieste per l'inizializzazione.
Nell'editor, aggiungi il seguente codice al file main.dart per inizializzare Flutter e 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());
}
Compila l'app con il comando:
flutter run
Non hai ancora modificato alcun elemento dell'interfaccia utente, quindi l'aspetto e il comportamento dell'app non sono cambiati. Ora hai un'app Firebase e puoi iniziare a utilizzare i prodotti Firebase, tra cui:
- Firebase Authentication, che consente agli utenti di accedere alla tua app.
- Firebase Realtime Database(RTDB): utilizzerai l'API Presence per monitorare lo stato online/offline del dispositivo
- Le regole di sicurezza Firebase ti consentono di proteggere il database.
- Servizio Firebase Installations per identificare i dispositivi a cui ha eseguito l'accesso un singolo utente.
4. Aggiungi Firebase Auth
Abilitare l'accesso con indirizzo email per Firebase Authentication
Per consentire agli utenti di accedere all'app web, utilizzerai il metodo di accesso Email/password:
- Nella console Firebase, espandi il menu Build nel riquadro a sinistra.
- Fai clic su Autenticazione, quindi sul pulsante Inizia e sulla scheda Metodo di accesso.
- Fai clic su Email/Password nell'elenco Provider di accesso, imposta l'opzione Attiva su ON e poi fai clic su Salva.
Configurare Firebase Authentication in Flutter
Nella riga di comando, esegui i seguenti comandi per installare i pacchetti Flutter necessari:
flutter pub add firebase_auth
flutter pub add provider
Con questa configurazione, ora puoi creare il flusso di accesso e disconnessione. Poiché lo stato di autenticazione non deve cambiare da una schermata all'altra, creerai una classe application_state.dart
per tenere traccia delle modifiche dello stato a livello di app, ad esempio l'accesso e la disconnessione. Scopri di più in merito nella documentazione relativa alla gestione dello stato di Flutter.
Incolla il seguente codice nel nuovo file 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();
}
}
Per assicurarti che ApplicationState
venga inizializzato all'avvio dell'app, aggiungi un passaggio di inizializzazione a 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(),
));
}
Anche in questo caso, la UI dell'applicazione dovrebbe essere rimasta invariata, ma ora puoi consentire agli utenti di accedere e salvare gli stati dell'app.
Crea un flusso di accesso
In questo passaggio, lavorerai al flusso di accesso e disconnessione. Ecco come si presenta il flusso:
- Un utente che ha eseguito la disconnessione avvia il flusso di accesso facendo clic sul menu contestuale
sul lato destro della barra delle app.
- Il flusso di accesso verrà visualizzato in una finestra di dialogo.
- Se l'utente non ha mai eseguito l'accesso, gli verrà chiesto di creare un account utilizzando un indirizzo email valido e una password.
- Se l'utente ha già eseguito l'accesso, gli verrà chiesto di inserire la password.
- Una volta effettuato l'accesso, se l'utente fa clic sul menu contestuale, viene visualizzata l'opzione Esci.
L'aggiunta del flusso di accesso richiede tre passaggi.
Innanzitutto, crea un widget AppBarMenuButton
. Questo widget controllerà il popup del menu contestuale in base al loginState
di un utente. Aggiungi le importazioni
lib/src/widgets.dart
import 'application_state.dart';
import 'package:provider/provider.dart';
import 'authentication.dart';
Aggiungi il seguente codice a 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),
),
),
];
}
}
Secondo, nella stessa classe widgets.dart
, crea il 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,
),
),
]),
);
}
}
Terzo, trova il widget della barra delle app esistente in main.dart.
Aggiungi AppBarMenuButton
per visualizzare l'opzione Accedi o Esci.
lib/main.dart
import 'src/widgets.dart';
appBar: AppBar(
title: const Text('Music Box'),
backgroundColor: Colors.deepPurple.shade400,
actions: const <Widget>[
AppBarMenuButton(),
],
),
Esegui il comando flutter run
per riavviare l'app con queste modifiche. Dovresti visualizzare il menu contestuale sul lato destro della barra delle app. Se fai clic, si aprirà una finestra di dialogo di accesso.
Una volta eseguito l'accesso con un indirizzo email e una password validi, dovresti visualizzare l'opzione Esci nel menu contestuale.
Nella Console Firebase, in Autenticazione, dovresti visualizzare l'indirizzo email elencato come nuovo utente.
Complimenti! Ora gli utenti possono accedere all'app.
5. Aggiungi connessione al database
Ora puoi passare alla registrazione del dispositivo utilizzando l'API Firebase Presence.
Nella riga di comando, esegui questi comandi per aggiungere le dipendenze necessarie:
flutter pub add firebase_app_installations
flutter pub add firebase_database
Crea un database
Nella console Firebase,
- Vai alla sezione Realtime Database della console Firebase. Fai clic su Crea database.
- Se ti viene chiesto di selezionare una modalità di avvio per le regole di sicurezza, seleziona per ora Modalità test**.** La modalità test crea regole di sicurezza che consentono il passaggio di tutte le richieste. Aggiungerai le regole di sicurezza in un secondo momento. È importante non passare mai alla produzione con le regole di sicurezza ancora in modalità test.)
Per il momento il database è vuoto. Individua il tuo databaseURL
in Impostazioni progetto, nella scheda Generale. Scorri verso il basso fino alla sezione App web.
Aggiungere databaseURL
al file firebase_options.dart
:
lib/firebase_options.dart
static const FirebaseOptions web = FirebaseOptions(
apiKey: yourApiKey,
...
databaseURL: 'https://<YOUR_DATABASE_URL>,
...
);
Registrare i dispositivi utilizzando l'API RTDB Presence
Vuoi registrare i dispositivi di un utente quando vengono visualizzati online. Per farlo, sfrutterai Firebase Installations e l'API Firebase RTDB Presence per tenere traccia di un elenco di dispositivi online di un singolo utente. Il seguente codice ti aiuterà a raggiungere questo obiettivo:
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';
}
Torna alla riga di comando, compila ed esegui l'app sul tuo dispositivo o in un browser con flutter run.
Nella tua app, accedi come utente. Ricordati di accedere come lo stesso utente su piattaforme diverse.
Nella console Firebase, dovresti vedere i tuoi dispositivi visualizzati con un ID utente nel database.
6. Sincronizzare lo stato del dispositivo
Seleziona un dispositivo principale
Per sincronizzare gli stati tra i dispositivi, designa un dispositivo come leader o controller. Il dispositivo principale determinerà gli stati sui dispositivi secondari.
Crea un metodo setLeadDevice
in application_state.dart
e monitora questo dispositivo con la chiave active_device
in 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;
});
}
}
Per aggiungere questa funzionalità al menu contestuale della barra delle app, crea un PopupMenuItem
chiamato Controller
modificando il widget SignedInMenuButton
. Questo menu consente agli utenti di impostare il dispositivo principale.
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();
}
}
}
Scrivere lo stato del dispositivo principale nel database
Dopo aver impostato un dispositivo principale, puoi sincronizzare i relativi stati con RTDB utilizzando il seguente codice. Aggiungi il seguente codice alla fine di application_state.dart.
In questo modo, inizierai a memorizzare due attributi: lo stato del lettore (riproduzione o pausa) e la posizione del cursore.
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');
}
}
}
Infine, devi chiamare setActiveDeviceState
ogni volta che lo stato del giocatore del controller viene aggiornato. Apporta le seguenti modifiche al file player_widget.dart
esistente:
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;
}
Leggere lo stato del dispositivo principale dal database
Per leggere e utilizzare lo stato del dispositivo principale, sono necessarie due parti. Innanzitutto, devi configurare un listener del database dello stato del giocatore principale in application_state
. Questo listener comunicherà ai dispositivi follower quando aggiornare lo schermo tramite un callback. Tieni presente che in questo passaggio hai definito un'interfaccia OnLeadDeviceChangeCallback
. Non è ancora implementata. Implementerai questa interfaccia in player_widget.dart
nel passaggio successivo.
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();
});
}
}
In secondo luogo, avvia l'ascoltatore del database durante l'inizializzazione del giocatore in player_widget.dart
. Passa la funzione _updatePlayer
in modo che lo stato del giocatore follower possa essere aggiornato ogni volta che il valore del database cambia.
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');
}
}
}
}
Ora puoi testare l'app:
- Nella riga di comando, esegui l'app su emulatori e/o in un browser con:
flutter run -d <device-name>
- Apri le app in un browser, in un simulatore iOS o in un emulatore Android. Vai al menu contestuale e scegli un'app da impostare come dispositivo principale. Dovresti vedere i lettori dei dispositivi follower cambiare man mano che il dispositivo leader si aggiorna.
- Ora cambia il dispositivo principale, riproduci o metti in pausa la musica e osserva l'aggiornamento dei dispositivi secondari.
Se i dispositivi follower si aggiornano correttamente, hai creato un controller cross-device. Manca solo un passaggio fondamentale.
7. Aggiorna le regole di sicurezza
A meno che non scriviamo regole di sicurezza migliori, qualcuno potrebbe scrivere uno stato su un dispositivo che non possiede. Prima di terminare, aggiorna le regole di sicurezza del database in tempo reale per assicurarti che gli unici utenti che possono leggere o scrivere su un dispositivo siano quelli che hanno eseguito l'accesso al dispositivo. Nella console Firebase, vai a Realtime Database, quindi alla scheda Regole. Incolla le seguenti regole che consentono solo all'utente che ha eseguito l'accesso di leggere e scrivere gli stati dei propri dispositivi:
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
},
}
}
8. Complimenti!
Congratulazioni, hai creato correttamente un telecomando cross-device utilizzando Flutter.
Crediti
Better Together, una canzone di Firebase
- Musiche di Ryan Vernon
- Testi e copertina dell'album di Marissa Christy
- Voce di JP Gomez
9. Bonus
Come ulteriore sfida, valuta la possibilità di utilizzare Flutter FutureBuilder
per aggiungere in modo asincrono il tipo di dispositivo principale corrente alla UI. Se hai bisogno di assistenza, è implementata nella cartella contenente lo stato finale del codelab.