1. Einführung
Zuletzt aktualisiert: 14.03.2022
FlutterFire für die geräteübergreifende Kommunikation
Da immer mehr Heimautomatisierungs-, Wearables- und persönliche Gesundheitstechnologie-Geräte online gehen, wird die geräteübergreifende Kommunikation zu einem immer wichtigeren Bestandteil der Entwicklung mobiler Apps. Die Einrichtung der geräteübergreifenden Kommunikation, z. B. die Steuerung eines Browsers über eine Telefon-App oder die Steuerung der Wiedergabe auf Ihrem Fernseher über Ihr Telefon, ist üblicherweise komplexer als das Erstellen einer normalen mobilen App .
Die Realtime Database von Firebase bietet die Presence API , mit der Nutzer den Online-/Offlinestatus ihres Geräts sehen können. Sie verwenden die Datenbank zusammen mit dem Firebase-Installationsdienst, um alle Geräte, auf denen sich ein Nutzer angemeldet hat, zu erfassen und zu verbinden. Sie verwenden Flutter, um schnell Anwendungen für mehrere Plattformen zu erstellen. Anschließend erstellen Sie einen geräteübergreifenden Prototyp, der Musik auf einem Gerät abspielt und die Musik auf einem anderen Gerät steuert.
Umfang
In diesem Codelab erstellen Sie eine einfache Fernbedienung für einen Musikplayer. Mit der Anwendung können Sie Folgendes tun:
- Sie haben einen einfachen Musikplayer für Android, iOS und das Web, der mit Flutter entwickelt wurde.
- Nutzern die Anmeldung erlauben.
- Geräte verbinden, wenn derselbe Nutzer auf mehreren Geräten angemeldet ist
- Nutzern erlauben, die Musikwiedergabe auf einem Gerät von einem anderen Gerät aus zu steuern
Aufgaben in diesem Lab
- So erstellen und führen Sie eine Flutter-Musikplayer-App aus.
- Nutzern erlauben, sich mit Firebase Auth anzumelden
- Informationen zum Verbinden von Geräten mithilfe der Firebase RTDB Presence API und des Firebase Installation Service
Voraussetzungen
- Eine Flutter-Entwicklungsumgebung. Folgen Sie der Anleitung in der Installationsanleitung für Flutter, um das Flutter-Gerät einzurichten.
- Sie benötigen mindestens Flutter-Version 2.10. Wenn Sie eine niedrigere Version haben, führen Sie
flutter upgrade.
aus. - Ein Firebase-Konto
2. Einrichtung
Startcode abrufen
Wir haben eine Musikplayer-App in Flutter entwickelt. Der Startcode befindet sich in einem Git-Repository. Zuerst müssen Sie das Repository in der Befehlszeile klonen, in den Ordner mit dem Startstatus wechseln und die Abhängigkeiten installieren:
git clone https://github.com/FirebaseExtended/cross-device-controller.git
cd cross-device-controller/starter_code
flutter pub get
App erstellen
Sie können die Anwendung mit Ihrer bevorzugten IDE erstellen oder die Befehlszeile verwenden.
Erstellen Sie die Web-App in Ihrem App-Verzeichnis mit dem Befehl flutter run -d web-server.
Sie sollten die folgende Eingabeaufforderung sehen.
lib/main.dart is being served at http://localhost:<port>
Gehe zu http://localhost:<port>
, um den Musikplayer anzusehen.
Wenn Sie mit dem Android-Emulator oder iOS-Simulator vertraut sind, können Sie die App für diese Plattformen erstellen und mit dem Befehl flutter run -d <device_name>
installieren.
Die Webanwendung sollte einen einfachen eigenständigen Musikplayer anzeigen. Prüfen, ob die Playerfunktionen wie vorgesehen funktionieren Dies ist eine einfache Musikplayer-App, die für dieses Codelab entwickelt wurde. Es kann nur der Firebase-Song Better Together abgespielt werden.
Android-Emulator oder iOS-Simulator einrichten
Wenn Sie bereits ein Android- oder iOS-Gerät für die Entwicklung haben, können Sie diesen Schritt überspringen.
Wenn Sie einen Android-Emulator erstellen möchten, laden Sie Android Studio herunter, das auch die Flutter-Entwicklung unterstützt, und folgen Sie der Anleitung unter Virtuelle Geräte erstellen und verwalten.
Zum Erstellen eines iOS-Simulators benötigen Sie eine Mac-Umgebung. Laden Sie XCode herunter und folgen Sie der Anleitung unter Simulatorübersicht > „Simulator verwenden“ > Simulator öffnen und schließen.
3. Firebase einrichten
Firebase-Projekt erstellen
Öffnen Sie http://console.firebase.google.com/ in einem Browser.
- Melden Sie sich in Firebase an.
- Klicken Sie in der Firebase Console auf Projekt hinzufügen (oder Projekt erstellen) und geben Sie als Namen für Ihr Firebase-Projekt Firebase-Cross-Device-Codelab ein.
- Klicken Sie sich durch die Optionen für die Projekterstellung. Akzeptieren Sie die Firebase-Nutzungsbedingungen, wenn Sie dazu aufgefordert werden. Überspringen Sie die Einrichtung von Google Analytics, weil Sie Analytics für diese App nicht verwenden werden.
Sie müssen die genannten Dateien nicht herunterladen oder die build.gradle-Dateien ändern. Diese konfigurieren Sie, wenn Sie FlutterFire initialisieren.
Firebase SDK installieren
Führen Sie in der Befehlszeile im Projektverzeichnis den folgenden Befehl aus, um Firebase zu installieren:
flutter pub add firebase_core
Bearbeiten Sie in der Datei pubspec.yaml
die Version für firebase_core
auf 1.13.1 oder führen Sie flutter upgrade
aus
FlutterFire initialisieren
- Wenn Sie die Firebase CLI nicht installiert haben, können Sie sie installieren, indem Sie
curl -sL https://firebase.tools | bash
ausführen. - Melde dich an, indem du
firebase login
ausführst und den Aufforderungen folgst. - Installieren Sie die FlutterFire CLI, indem Sie
dart pub global activate flutterfire_cli
ausführen. - Konfigurieren Sie die FlutterFire-Befehlszeile, indem Sie
flutterfire configure
ausführen. - Wählen Sie in der Eingabeaufforderung das Projekt aus, das Sie gerade für dieses Codelab erstellt haben, z. B. Firebase-Cross-Device-Codelab.
- Wählen Sie iOS, Android und Web aus, wenn Sie aufgefordert werden, die Unterstützung für die Konfiguration festzulegen.
- Wenn Sie nach der Apple-Paket-ID gefragt werden, geben Sie eine eindeutige Domain oder
com.example.appname
ein. Dies reicht für dieses Codelab aus.
Nach der Konfiguration wird eine firebase_options.dart
-Datei für Sie generiert, die alle für die Initialisierung erforderlichen Optionen enthält.
Fügen Sie der Datei „main.dart“ im Editor den folgenden Code hinzu, um Flutter und Firebase zu initialisieren:
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());
}
Kompilieren Sie die App mit dem folgenden Befehl:
flutter run
Sie haben noch keine UI-Elemente geändert, sodass sich das Aussehen und Verhalten der App nicht verändert hat. Jetzt haben Sie aber eine Firebase-App und können Firebase-Produkte verwenden, darunter:
- Firebase Authentication ermöglicht es Ihren Nutzern, sich in Ihrer App anzumelden.
- Firebase Realtime Database(RTDB): Sie verwenden die Presence API, um den Online-/Offlinestatus des Geräts zu verfolgen.
- Mit Firebase-Sicherheitsregeln können Sie die Datenbank schützen.
- Firebase-Installationsdienst zur Identifizierung der Geräte, auf denen sich ein einzelner Nutzer angemeldet hat.
4. Firebase Auth hinzufügen
E-Mail-Anmeldung für Firebase Authentication aktivieren
Damit sich Nutzer in der Web-App anmelden können, verwenden Sie die Anmeldemethode E-Mail-Adresse/Passwort:
- Maximieren Sie in der Firebase Console im linken Bereich das Menü Build.
- Klicken Sie auf Authentication (Authentifizierung), dann auf die Schaltfläche Get Started (Jetzt starten) und dann auf den Tab Sign-in method (Anmeldemethode).
- Klicken Sie in der Liste Anbieter für Anmeldungen auf E-Mail-Adresse/Passwort, stellen Sie den Schalter für Aktivieren auf „Ein“ und klicken Sie auf Speichern.
Firebase-Authentifizierung in Flutter konfigurieren
Führen Sie in der Befehlszeile die folgenden Befehle aus, um die erforderlichen Flutter-Pakete zu installieren:
flutter pub add firebase_auth
flutter pub add provider
Mit dieser Konfiguration können Sie jetzt den An- und Abmeldevorgang erstellen. Da sich der Authentifizierungsstatus nicht von Bildschirm zu Bildschirm ändern sollte, erstellen Sie eine application_state.dart
-Klasse, um Statusänderungen auf App-Ebene zu verfolgen, z. B. Anmeldung und Abmeldung. Weitere Informationen hierzu finden Sie in der Dokumentation zur Flutter-Statusverwaltung.
Fügen Sie Folgendes in die neue Datei application_state.dart
ein:
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();
}
}
Damit ApplicationState
beim Start der App initialisiert wird, musst du main.dart
einen Initialisierungsschritt hinzufügen:
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(),
));
}
Die Benutzeroberfläche der Anwendung sollte unverändert geblieben sein. Jetzt können Sie jedoch Nutzer anmelden und App-Status speichern.
Anmeldevorgang erstellen
In diesem Schritt arbeiten Sie am Anmelde- und Abmeldevorgang. So sieht der Ablauf aus:
- Ein abgemeldeter Nutzer startet den Anmeldevorgang, indem er rechts in der App-Leiste auf das Kontextmenü klickt.
- Der Anmeldevorgang wird in einem Dialogfeld angezeigt.
- Wenn sich der Nutzer noch nie angemeldet hat, wird er aufgefordert, ein Konto mit einer gültigen E-Mail-Adresse und einem Passwort zu erstellen.
- Wenn sich der Nutzer schon einmal angemeldet hat, wird er aufgefordert, sein Passwort einzugeben.
- Sobald der Nutzer angemeldet ist, wird durch Klicken auf das Kontextmenü die Option Abmelden angezeigt.
Das Hinzufügen des Anmeldevorgangs umfasst drei Schritte.
Erstellen Sie zuerst ein AppBarMenuButton
-Widget. Dieses Widget steuert das Pop-up des Kontextmenüs in Abhängigkeit von den loginState
eines Nutzers. Importe hinzufügen
lib/src/widgets.dart
import 'application_state.dart';
import 'package:provider/provider.dart';
import 'authentication.dart';
Hängen Sie den folgenden Code an widgets.dart.
an
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),
),
),
];
}
}
Erstellen Sie dann in derselben widgets.dart
-Klasse das SignInDialog
-Widget.
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,
),
),
]),
);
}
}
Suchen Sie im dritten Schritt das vorhandene appBar-Widget unter main.dart.
. Fügen Sie AppBarMenuButton
hinzu, damit die Option Anmelden oder Abmelden angezeigt wird.
lib/main.dart
import 'src/widgets.dart';
appBar: AppBar(
title: const Text('Music Box'),
backgroundColor: Colors.deepPurple.shade400,
actions: const <Widget>[
AppBarMenuButton(),
],
),
Führen Sie den Befehl flutter run
aus, um die App mit diesen Änderungen neu zu starten. Rechts in der App-Leiste sollte das Kontextmenü angezeigt werden. Wenn Sie darauf klicken, wird ein Anmeldedialogfeld geöffnet.
Sobald Sie sich mit einer gültigen E-Mail-Adresse und einem Passwort angemeldet haben, sollte im Kontextmenü die Option Abmelden angezeigt werden.
In der Firebase Console sollte unter Authentifizierung die E-Mail-Adresse als neuer Nutzer aufgeführt sein.
Glückwunsch! Nutzer können sich jetzt in der App anmelden.
5. Datenbankverbindung hinzufügen
Jetzt können Sie mit der Geräteregistrierung mithilfe der Firebase Presence API fortfahren.
Führen Sie in der Befehlszeile die folgenden Befehle aus, um die erforderlichen Abhängigkeiten hinzuzufügen:
flutter pub add firebase_app_installations
flutter pub add firebase_database
Datenbank erstellen
In der Firebase Console
- Gehen Sie in der Firebase Console zum Abschnitt Echtzeitdatenbank. Klicken Sie auf Datenbank erstellen.
- Wenn Sie aufgefordert werden, einen Startmodus für Ihre Sicherheitsregeln auszuwählen, wählen Sie vorerst Testmodus aus. Im Testmodus werden Sicherheitsregeln erstellt, die alle Anfragen zulassen. Sicherheitsregeln werden später hinzugefügt. Es ist wichtig, dass Sie nie in die Produktion gehen, wenn sich Ihre Sicherheitsregeln noch im Testmodus befinden.)
Die Datenbank ist vorerst leer. Sie finden Ihre databaseURL
in den Projekteinstellungen auf dem Tab Allgemein. Scrollen Sie nach unten zum Abschnitt Web-Apps.
Fügen Sie die databaseURL
zur Datei firebase_options.dart
hinzu.:
lib/firebase_options.dart
static const FirebaseOptions web = FirebaseOptions(
apiKey: yourApiKey,
...
databaseURL: 'https://<YOUR_DATABASE_URL>,
...
);
Geräte mit der RTDB Presence API registrieren
Sie möchten die Geräte eines Nutzers registrieren, wenn sie online sind. Dazu verwenden Sie Firebase-Installationen und die Firebase RTDB-Präsenz-API, um eine Liste der Onlinegeräte eines einzelnen Nutzers im Blick zu behalten. Mit dem folgenden Code können Sie dieses Ziel erreichen:
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';
}
Kehren Sie zur Befehlszeile zurück, erstellen Sie die App mit flutter run.
und führen Sie sie auf Ihrem Gerät oder in einem Browser aus
Melden Sie sich in Ihrer App als Nutzer an. Melden Sie sich auf verschiedenen Plattformen als derselbe Nutzer an.
In der Firebase Console sollten Ihre Geräte in Ihrer Datenbank unter einer Nutzer-ID aufgeführt sein.
6. Gerätestatus synchronisieren
Leitgerät auswählen
Legen Sie ein Gerät als Leader oder Controller fest, um den Status zwischen Geräten zu synchronisieren. Das Hauptgerät bestimmt die Status der Folgegeräte.
Erstellen Sie eine setLeadDevice
-Methode in application_state.dart
und erfassen Sie dieses Gerät mit dem Schlüssel active_device
in der 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;
});
}
}
Wenn Sie diese Funktion dem Kontextmenü der App-Leiste hinzufügen möchten, erstellen Sie eine PopupMenuItem
mit dem Namen Controller
, indem Sie das SignedInMenuButton
-Widget ändern. In diesem Menü können Nutzer das Hauptgerät festlegen.
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();
}
}
}
Status des Hauptgeräts in die Datenbank schreiben
Nachdem du ein Hauptgerät festgelegt hast, kannst du die Status des Hauptgeräts mit dem folgenden Code mit RTDB synchronisieren. Hängen Sie den folgenden Code an das Ende von application_state.dart.
an. Dadurch werden zwei Attribute gespeichert: der Player-Status (Wiedergabe oder Pause) und die Position des Schiebereglers.
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');
}
}
}
Außerdem musst du setActiveDeviceState
jedes Mal aufrufen, wenn sich der Spielerstatus des Controllers ändert. Nehmen Sie die folgenden Änderungen an der vorhandenen Datei player_widget.dart
vor:
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;
}
Status des Lead-Geräts aus der Datenbank auslesen
Der Status des Lead-Geräts wird in zwei Teilen gelesen und verwendet. Zuerst richten Sie einen Datenbank-Listener für den Status des leitenden Spielers in application_state
ein. Dieser Listener informiert die folgenden Geräte über einen Callback, wann der Bildschirm aktualisiert werden soll. Beachten Sie, dass Sie in diesem Schritt eine Schnittstelle OnLeadDeviceChangeCallback
definiert haben. Sie ist noch nicht implementiert. Sie implementieren diese Schnittstelle im nächsten Schritt in player_widget.dart
.
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();
});
}
}
Zweitens: Starte den Datenbank-Listener während der Initialisierung des Players in player_widget.dart
. Übergebe die Funktion _updatePlayer
, damit der Spielerstatus des Kanalmitglieds aktualisiert werden kann, wenn sich der Datenbankwert ändert.
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');
}
}
}
}
Jetzt können Sie die App testen:
- Führen Sie die App in der Befehlszeile in Emulatoren und/oder in einem Browser mit folgendem Befehl aus:
flutter run -d <device-name>
- Öffnen Sie die Apps in einem Browser, in einem iOS-Simulator oder in einem Android-Emulator. Öffnen Sie das Kontextmenü und wählen Sie eine App als Leitgerät aus. Du solltest sehen können, wie sich die Spieler der folgenden Geräte ändern, wenn das Gerät aktualisiert wird.
- Ändern Sie jetzt das Hauptgerät, spielen Sie Musik ab oder pausieren Sie sie und beobachten Sie, wie sich die Folgegeräte entsprechend aktualisieren.
Wenn die folgenden Geräte ordnungsgemäß aktualisiert werden, ist ein geräteübergreifender Controller erfolgreich erstellt. Jetzt ist nur noch ein wichtiger Schritt übrig.
7. Sicherheitsregeln aktualisieren
Wenn wir keine besseren Sicherheitsregeln schreiben, könnte jemand einen Status auf einem fremden Gerät schreiben. Bevor Sie fertig sind, aktualisieren Sie die Sicherheitsregeln für die Realtime Database, damit nur die Nutzer auf einem Gerät Lese- oder Schreibvorgänge durchführen können, die auf diesem Gerät angemeldet sind. Gehen Sie in der Firebase Console zur Echtzeitdatenbank und dann zum Tab Regeln. Fügen Sie die folgenden Regeln ein, damit nur angemeldete Nutzer ihre eigenen Gerätestatus lesen und schreiben können:
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
},
}
}
8. Glückwunsch!
Glückwunsch, Sie haben mit Flutter eine geräteübergreifende Fernbedienung erstellt.
Gutschriften
Better Together, ein Firebase-Song
- Musik von Ryan Vernon
- Lyrics und Albumcover von Marissa Christy
- Voice von JP Gomez
9. Bonus
Als zusätzliche Herausforderung können Sie Flutter FutureBuilder
verwenden, um den aktuellen Gerätetyp des Leads asynchron zur Benutzeroberfläche hinzuzufügen. Falls Sie eine Unterstützung benötigen, finden Sie sie in dem Ordner, der den Status „Fertig“ des Codelabs enthält.