1. Introduzione
Ultimo aggiornamento: 14/03/2022
FlutterFire per la comunicazione cross-device
Mentre assistiamo a un gran numero di dispositivi di domotica, dispositivi indossabili e tecnologici per la salute personale che entrano online, la comunicazione cross-device diventa una parte sempre più importante della creazione di applicazioni mobile. La configurazione della comunicazione tra dispositivi, 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.
Firebase Realtime Database fornisce l'API Presence che consente agli utenti di vedere lo stato online/offline del proprio dispositivo. La utilizzerai con il servizio di installazione di Firebase per monitorare e collegare tutti i dispositivi su cui lo stesso utente ha eseguito l'accesso. Utilizzerai Flutter per creare rapidamente applicazioni per più piattaforme e poi realizzerai un prototipo cross-device che riproduce la 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 media player su Android, iOS e web, creato con Flutter.
- Consenti agli utenti di accedere.
- Connetti i dispositivi su più dispositivi quando lo stesso utente ha eseguito l'accesso.
- Consenti agli utenti di controllare la riproduzione di musica su un dispositivo da un altro dispositivo.
Cosa imparerai a fare
- Come creare ed eseguire un'app per il music player 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 minima di Flutter 2.10 o successiva. Se hai una versione precedente, esegui
flutter upgrade.
- Un account Firebase.
2. Preparazione
Ottieni il codice di avvio
Abbiamo creato un'app per la musica in Flutter. Il codice di avvio si trova in un repository Git. Per iniziare, clona il repository nella riga di comando, spostati nella 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 la riga di comando.
Nella directory delle 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 player musicale.
Se hai dimestichezza 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 semplice player musicale autonomo. Assicurati che le funzionalità del player funzionino come previsto. Si tratta di una semplice app di lettore musicale progettata per questo codelab. Può riprodurre solo un brano 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 Flutter, e segui le istruzioni riportate in Creare e gestire dispositivi virtuali.
Per creare un simulatore iOS, devi avere un ambiente Mac. Scarica XCode e segui le istruzioni riportate in Panoramica del simulatore > Utilizza il simulatore > Apri e chiudi un simulatore.
3. Configurazione di Firebase
Crea un progetto Firebase
Apri un browser e vai all'indirizzo http://console.firebase.google.com/.
- Accedi a Firebase.
- Nella console Firebase, fai clic su Aggiungi progetto (o Crea un progetto) e denomina il tuo progetto Firebase Firebase-Cross-Device-Codelab.
- Fai clic sulle opzioni di creazione del progetto. Accetta i termini di Firebase, se richiesto. Salta la configurazione di Google Analytics perché non utilizzerai Analytics per questa app.
Non è necessario scaricare i file menzionati o modificare i file build.gradle. Le configurerai al momento dell'inizializzazione di FlutterFire.
Installa l'SDK Firebase
Torna alla directory del progetto della riga di comando ed 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 oppure 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 viene richiesto l'ID pacchetto Apple, digita un dominio univoco o inserisci
com.example.appname
, che va bene ai fini di questo codelab.
Una volta configurato, verrà generato un file firebase_options.dart
contenente tutte le opzioni necessarie 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 elementi dell'interfaccia utente, pertanto l'aspetto e il comportamento dell'app non sono cambiati. Ora però 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 di Firebase ti consentono di proteggere il database.
- Servizio installazioni Firebase per identificare i dispositivi a cui ha eseguito l'accesso un singolo utente.
4. Aggiungi autenticazione Firebase
Attivare l'accesso via 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 Crea nel riquadro a sinistra.
- Fai clic su Autenticazione, quindi sul pulsante Inizia e infine sulla scheda Metodo di accesso.
- Fai clic su Email/Password nell'elenco Provider di accesso, imposta l'opzione Abilita su On e fai clic su Salva.
Configurare Firebase Authentication in Flutter
Nella riga di comando, esegui questi 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 uscita. Poiché lo stato di autenticazione non dovrebbe 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 accesso e uscita. Per saperne di più, consulta la documentazione sulla gestione dello stato fluido.
Incolla il codice seguente 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, dovrai aggiungere 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, l'interfaccia utente 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 di disconnessione. Ecco come sarà il flusso:
- Un utente che non ha eseguito l'accesso 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 prima d'ora, gli verrà chiesto di creare un account utilizzando un indirizzo email e una password validi.
- Se l'utente ha già eseguito l'accesso, gli verrà chiesto di inserire la password.
- Dopo che l'utente avrà eseguito l'accesso, facendo clic sul menu contestuale verrà visualizzata l'opzione Esci.
L'aggiunta del flusso di accesso richiede tre passaggi.
Prima di tutto, crea un widget AppBarMenuButton
. Questo widget controllerà il popup del menu contestuale in base alle 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),
),
),
];
}
}
In secondo luogo, 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 appBar 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 riuscire a vedere il menu contestuale sul lato destro della barra delle app. Se fai clic, si aprirà una finestra di dialogo di accesso.
Dopo aver eseguito l'accesso con un indirizzo email e una password validi, dovresti visualizzare l'opzione Uscire nel menu contestuale.
Nella Console Firebase, in Autenticazione, dovresti riuscire a vedere 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 di Firebase. Fai clic su Crea database.
- Se ti viene chiesto di selezionare una modalità iniziale per le regole di sicurezza, seleziona per ora la modalità di test**.** La modalità di 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 in produzione con le regole di sicurezza ancora in modalità di test.)
Per il momento il database è vuoto. Individua il tuo databaseURL
in Impostazioni progetto, nella scheda Generali. Scorri verso il basso fino alla sezione App web.
Aggiungi 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 sono online. A questo scopo, utilizzerai le installazioni di Firebase e l'API Firebase RTDB Presence per tenere traccia di un elenco di dispositivi online di un singolo utente. Il seguente codice 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 e avvia l'app sul tuo dispositivo o in un browser con flutter run.
Accedi all'app come utente. Ricordati di accedere come lo stesso utente su piattaforme diverse.
Nella Console Firebase, dovresti vedere i tuoi dispositivi in un ID utente nel database.
6. Sincronizza lo stato del dispositivo
Seleziona un dispositivo principale
Per sincronizzare gli stati tra dispositivi, designa un dispositivo come leader o controller. Il dispositivo principale determinerà gli stati sui dispositivi follower.
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
denominato 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
Una volta impostato un dispositivo lead, puoi sincronizzarne gli 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 player (riproduzione o messa in 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 player del controller si aggiorna. 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
È necessario leggere e utilizzare lo stato del dispositivo lead in due parti. Innanzitutto, devi configurare un listener del database dello stato del lead player in application_state
. Questo listener indica ai dispositivi follower quando aggiornare lo schermo tramite un callback. Tieni presente che in questo passaggio hai definito un'interfaccia OnLeadDeviceChangeCallback
. Non è ancora implementata; la implementerai 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();
});
}
}
Poi, avvia il listener del database durante l'inizializzazione del player 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 è tutto pronto per testare l'app:
- Nella riga di comando, esegui l'app sugli emulatori e/o in un browser con:
flutter run -d <device-name>
- Apri le app in un browser, su un simulatore iOS o in un emulatore Android. Vai al menu contestuale e scegli un'app come dispositivo leader. Dovresti essere in grado di vedere i giocatori dei dispositivi follower cambiare man mano che il dispositivo leader si aggiorna.
- Ora cambia il dispositivo leader, riproduci o metti in pausa la musica e osserva l'aggiornamento dei dispositivi follower di conseguenza.
Se i dispositivi dei follower si aggiornano correttamente, sei riuscito a creare un controller cross-device. Manca solo un passaggio fondamentale.
7. Aggiorna regole di sicurezza
Se non creiamo regole di sicurezza migliori, qualcuno potrebbe scrivere uno stato su un dispositivo di cui non è proprietario. 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 al Realtime Database, quindi alla scheda Regole. Incolla le seguenti regole che consentono solo all'utente che ha eseguito l'accesso di leggere e scrivere i propri stati del dispositivo:
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
},
}
}
8. Complimenti!
Complimenti, hai creato un telecomando cross-device utilizzando Flutter.
Crediti
Better Together, un brano Firebase
- Musica di Ryan Vernon
- Testo e copertina dell'album di Marissa Christy
- Voce di JP Gomez
9. Bonus
Come ulteriore verifica, valuta la possibilità di utilizzare Flutter FutureBuilder
per aggiungere il tipo di dispositivo lead attuale alla UI in modo asincrono. Se hai bisogno di assistenza, questa viene implementata nella cartella contenente lo stato Completato del codelab.