1. Einleitung
Letzte Aktualisierung: 14.03.2022
FlutterFire für geräteübergreifende Kommunikation
Da wir erleben, dass eine große Anzahl von Heimautomatisierungs-, tragbaren und persönlichen Gesundheitstechnologiegeräten online geht, wird die geräteübergreifende Kommunikation zu einem immer wichtigeren Bestandteil bei der Entwicklung mobiler Anwendungen. Das Einrichten einer geräteübergreifenden Kommunikation, beispielsweise die Steuerung eines Browsers über eine Telefon-App oder die Steuerung der Wiedergabe auf Ihrem Fernseher über Ihr Telefon, ist traditionell komplexer als die Erstellung einer normalen mobilen App.
Die Echtzeitdatenbank von Firebase stellt die Presence-API bereit, mit der Benutzer den Online-/Offline-Status ihres Geräts sehen können. Sie verwenden es mit dem Firebase-Installationsdienst, um alle Geräte zu verfolgen und zu verbinden, auf denen sich derselbe Benutzer angemeldet hat. Sie verwenden Flutter, um schnell Anwendungen für mehrere Plattformen zu erstellen, und erstellen dann einen geräteübergreifenden Prototyp, der abgespielt werden kann Musik auf einem Gerät und steuert die Musik auf einem anderen!
Was Sie bauen werden
In diesem Codelab erstellen Sie eine einfache Fernbedienung für einen Musikplayer. Ihre App wird:
- Verfügen Sie über einen einfachen Musikplayer für Android, iOS und das Internet, der mit Flutter erstellt wurde.
- Benutzern erlauben, sich anzumelden.
- Verbinden Sie Geräte, wenn derselbe Benutzer auf mehreren Geräten angemeldet ist.
- Ermöglichen Sie Benutzern, die Musikwiedergabe auf einem Gerät von einem anderen Gerät aus zu steuern.
Was Sie lernen werden
- So erstellen und führen Sie eine Flutter-Musikplayer-App aus.
- So erlauben Sie Benutzern die Anmeldung mit Firebase Auth.
- So verwenden Sie die Firebase RTDB Presence API und den Firebase Installation Service zum Verbinden von Geräten.
Was du brauchen wirst
- Eine Flutter-Entwicklungsumgebung. Befolgen Sie zur Einrichtung die Anweisungen im Flutter-Installationshandbuch .
- Es ist mindestens eine Flutter-Version von 2.10 oder höher erforderlich. Wenn Sie eine niedrigere Version haben, führen Sie
flutter upgrade.
- Ein Firebase-Konto.
2. Erste Schritte
Holen Sie sich den Startercode
Wir haben in Flutter eine Musik-Player-App erstellt. Der Startercode befindet sich in einem Git-Repo. Klonen Sie zunächst in der Befehlszeile das Repo, verschieben Sie es in den Ordner mit dem Startstatus und installieren Sie Abhängigkeiten:
git clone https://github.com/FirebaseExtended/cross-device-controller.git
cd cross-device-controller/starter_code
flutter pub get
Erstellen Sie die App
Sie können mit Ihrer bevorzugten IDE arbeiten, um die App zu erstellen, oder die Befehlszeile verwenden.
Erstellen Sie in Ihrem App-Verzeichnis die App für das Web mit dem Befehl flutter run -d web-server.
Sie sollten die folgende Eingabeaufforderung sehen können.
lib/main.dart is being served at http://localhost:<port>
Besuchen Sie http://localhost:<port>
, um den Musikplayer anzuzeigen.
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 Web-App sollte einen einfachen eigenständigen Musikplayer anzeigen. Stellen Sie sicher, dass die Player-Funktionen wie vorgesehen funktionieren. Dies ist eine einfache Musik-Player-App, die für dieses Codelab entwickelt wurde. Es kann nur ein Firebase-Song, Better Together, abgespielt werden.
Richten Sie einen Android-Emulator oder einen iOS-Simulator ein
Wenn Sie bereits ein Android- oder iOS-Gerät für die Entwicklung haben, können Sie diesen Schritt überspringen.
Um einen Android-Emulator zu erstellen, laden Sie Android Studio herunter, das auch die Flutter-Entwicklung unterstützt, und befolgen Sie die Anweisungen unter Erstellen und Verwalten virtueller Geräte .
Um einen iOS-Simulator zu erstellen, benötigen Sie eine Mac-Umgebung. Laden Sie XCode herunter und folgen Sie den Anweisungen unter Simulatorübersicht > Simulator verwenden > Simulator öffnen und schließen .
3. Firebase einrichten
Erstellen Sie ein Firebase-Projekt
Öffnen Sie einen Browser zu http://console.firebase.google.com/ .
- Melden Sie sich bei Firebase an.
- Klicken Sie in der Firebase-Konsole auf Projekt hinzufügen (oder Projekt erstellen ) und nennen Sie Ihr Firebase-Projekt Firebase-Cross-Device-Codelab .
- Klicken Sie sich durch die Projekterstellungsoptionen. Akzeptieren Sie die Firebase-Bedingungen, wenn Sie dazu aufgefordert werden. Überspringen Sie die Einrichtung von Google Analytics, da Sie Analytics für diese App nicht verwenden werden.
Sie müssen die genannten Dateien nicht herunterladen oder die build.gradle-Dateien ändern. Sie konfigurieren sie, wenn Sie FlutterFire initialisieren.
Installieren Sie das Firebase SDK
Zurück in der Befehlszeile im Projektverzeichnis führen Sie 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
so, dass sie mindestens 1.13.1 ist, 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ührencurl -sL https://firebase.tools | bash
. - Melden Sie sich an, indem Sie
firebase login
ausführen und den Anweisungen folgen. - Installieren Sie die FlutterFire-CLI, indem Sie
dart pub global activate flutterfire_cli
ausführen. - Konfigurieren Sie die FlutterFire-CLI, indem Sie
flutterfire configure
ausführen. - Wählen Sie an der Eingabeaufforderung das Projekt aus, das Sie gerade für dieses Codelab erstellt haben, etwa Firebase-Cross-Device-Codelab .
- Wählen Sie iOS , Android und Web aus, wenn Sie aufgefordert werden, die Konfigurationsunterstützung auszuwählen.
- Wenn Sie zur Eingabe der Apple-Bundle-ID aufgefordert werden, geben Sie eine eindeutige Domäne ein oder geben Sie
com.example.appname
ein, was für den Zweck dieses Codelabs in Ordnung ist.
Nach der Konfiguration wird für Sie eine Datei firebase_options.dart
generiert, die alle für die Initialisierung erforderlichen Optionen enthält.
Fügen Sie in Ihrem Editor den folgenden Code zu Ihrer main.dart-Datei 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 Befehl:
flutter run
Sie haben noch keine UI-Elemente geändert, sodass sich das Aussehen und Verhalten der App nicht geändert hat. Aber jetzt haben Sie eine Firebase-App und können Firebase-Produkte verwenden, darunter:
- Firebase-Authentifizierung , die es Ihren Benutzern ermöglicht, sich bei Ihrer App anzumelden.
- Firebase-Echtzeitdatenbank (RTDB) ; Sie verwenden die Präsenz-API, um den Online-/Offline-Status des Geräts zu verfolgen
- Mit den Firebase-Sicherheitsregeln können Sie die Datenbank sichern.
- Firebase-Installationsdienst zur Identifizierung der Geräte, bei denen sich ein einzelner Benutzer angemeldet hat.
4. Fügen Sie Firebase Auth hinzu
Aktivieren Sie die E-Mail-Anmeldung für die Firebase-Authentifizierung
Um Benutzern die Anmeldung bei der Web-App zu ermöglichen, verwenden Sie die Anmeldemethode E-Mail/Passwort :
- Erweitern Sie in der Firebase-Konsole das Build- Menü im linken Bereich.
- Klicken Sie auf Authentifizierung und dann auf die Schaltfläche „Erste Schritte“ und dann auf die Registerkarte „Anmeldemethode“ .
- Klicken Sie in der Liste der Anmeldeanbieter auf E-Mail/Passwort , stellen Sie den Schalter „Aktivieren“ auf „Ein“ und klicken Sie dann auf „Speichern“ .
Konfigurieren Sie die Firebase-Authentifizierung in Flutter
Führen Sie in der Befehlszeile die folgenden Befehle aus, um die erforderlichen Flatterpakete zu installieren:
flutter pub add firebase_auth
flutter pub add provider
Mit dieser Konfiguration können Sie nun den Anmelde- und Abmeldeablauf 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. Anmelden und Abmelden. 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();
}
}
Um sicherzustellen, dass ApplicationState
beim Start der App initialisiert wird, fügen Sie main.dart
einen Initialisierungsschritt hinzu:
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(),
));
}
Auch hier hätte die Benutzeroberfläche der Anwendung dieselbe bleiben sollen, aber jetzt können Sie Benutzern erlauben, sich anzumelden und App-Status zu speichern.
Erstellen Sie einen Anmeldeablauf
In diesem Schritt arbeiten Sie am Anmelde- und Abmeldevorgang. So wird der Ablauf aussehen:
- Ein abgemeldeter Benutzer leitet den Anmeldevorgang ein, indem er auf das Kontextmenü klickt auf der rechten Seite der App-Leiste.
- Der Anmeldevorgang wird in einem Dialog angezeigt.
- Wenn sich der Benutzer noch nie angemeldet hat, wird er aufgefordert, ein Konto mit einer gültigen E-Mail-Adresse und einem Passwort zu erstellen.
- Wenn sich der Benutzer bereits angemeldet hat, wird er aufgefordert, sein Passwort einzugeben.
- Sobald der Benutzer angemeldet ist, wird durch Klicken auf das Kontextmenü die Option „Abmelden“ angezeigt.
Das Hinzufügen eines Anmeldeablaufs erfordert drei Schritte.
Erstellen Sie zunächst ein AppBarMenuButton
-Widget. Dieses Widget steuert das Kontextmenü-Popup abhängig vom loginState
eines Benutzers. Fügen Sie die Importe hinzu
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.
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),
),
),
];
}
}
Zweitens erstellen Sie 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,
),
),
]),
);
}
}
Drittens suchen Sie das vorhandene appBar-Widget in main.dart.
Fügen Sie den AppBarMenuButton
hinzu, um die Option „Anmelden “ oder „Abmelden“ anzuzeigen.
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
, um die App mit diesen Änderungen neu zu starten. Sie sollten das Kontextmenü sehen können auf der rechten Seite der App-Leiste. Wenn Sie darauf klicken, gelangen Sie zu einem Anmeldedialog.
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-Konsole sollten Sie unter Authentication die E-Mail-Adresse sehen, die als neuer Benutzer aufgeführt ist.
Glückwunsch! Benutzer können sich jetzt bei 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
Erstellen Sie eine Datenbank
In der Firebase-Konsole
- Navigieren Sie zum Abschnitt „Echtzeitdatenbank“ der Firebase-Konsole . Klicken Sie auf Datenbank erstellen .
- Wenn Sie aufgefordert werden, einen Startmodus für Ihre Sicherheitsregeln auszuwählen, wählen Sie zunächst den Testmodus aus**.** (Im Testmodus werden Sicherheitsregeln erstellt, die alle Anforderungen durchlassen. Sicherheitsregeln werden später hinzugefügt. Es ist wichtig, dass Sie niemals damit in die Produktion gehen Ihre Sicherheitsregeln befinden sich noch im Testmodus.)
Die Datenbank ist vorerst leer. Suchen Sie Ihre databaseURL
in den Projekteinstellungen auf der Registerkarte „Allgemein“ . Scrollen Sie nach unten zum Abschnitt „Web-Apps“ .
Fügen Sie Ihre databaseURL
zur Datei firebase_options.dart
hinzu :
lib/firebase_options.dart
static const FirebaseOptions web = FirebaseOptions(
apiKey: yourApiKey,
...
databaseURL: 'https://<YOUR_DATABASE_URL>,
...
);
Registrieren Sie Geräte mithilfe der RTDB Presence API
Sie möchten die Geräte eines Benutzers registrieren, wenn diese online angezeigt werden. Dazu nutzen Sie Firebase-Installationen und die Firebase RTDB Presence API, um den Überblick über eine Liste der Online-Geräte eines einzelnen Benutzers zu behalten. Der folgende Code hilft dabei, dieses Ziel zu 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';
}
Zurück in der Befehlszeile erstellen Sie die App und führen sie auf Ihrem Gerät oder in einem Browser mit flutter run.
Melden Sie sich in Ihrer App als Benutzer an. Denken Sie daran, sich auf verschiedenen Plattformen als derselbe Benutzer anzumelden.
In der Firebase-Konsole sollten Ihre Geräte unter einer Benutzer-ID in Ihrer Datenbank angezeigt werden.
6. Gerätestatus synchronisieren
Wählen Sie ein Lead-Gerät aus
Um Zustände zwischen Geräten zu synchronisieren, bestimmen Sie ein Gerät als Leiter oder Controller. Das führende Gerät diktiert die Zustände auf den Folgegeräten.
Erstellen Sie eine setLeadDevice
Methode in application_state.dart
und verfolgen Sie dieses Gerät mit dem Schlüssel 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;
});
}
}
Um diese Funktionalität zum Kontextmenü der App-Leiste hinzuzufügen, erstellen Sie ein PopupMenuItem
namens Controller
, indem Sie das SignedInMenuButton
Widget ändern. In diesem Menü können Benutzer das Leitgerä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();
}
}
}
Schreiben Sie den Status des führenden Geräts in die Datenbank
Sobald Sie ein Leitgerät festgelegt haben, können Sie die Zustände des Leitgeräts mit dem folgenden Code mit RTDB synchronisieren. Hängen Sie den folgenden Code an das Ende von application_state.dart.
Dadurch werden zwei Attribute gespeichert: der Player-Status (Wiedergabe oder Pause) und die Schiebereglerposition.
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');
}
}
}
Und schließlich müssen Sie setActiveDeviceState
immer dann aufrufen, wenn der Player-Status des Controllers aktualisiert wird. 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;
}
Lesen Sie den Status des führenden Geräts aus der Datenbank
Es gibt zwei Teile zum Lesen und Verwenden des Status des Hauptgeräts. Zunächst möchten Sie in application_state
einen Datenbank-Listener für den Status des Hauptspielers einrichten. Dieser Listener teilt den Folgegeräten über einen Rückruf mit, wann der Bildschirm aktualisiert werden soll. Beachten Sie, dass Sie in diesem Schritt eine OnLeadDeviceChangeCallback
Schnittstelle definiert haben. Es ist noch nicht implementiert; Sie werden diese Schnittstelle im nächsten Schritt in player_widget.dart
implementieren.
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 starten Sie den Datenbank-Listener während der Player-Initialisierung in player_widget.dart
. Übergeben Sie die Funktion _updatePlayer
, damit der Status des Follower-Players 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 auf Emulatoren und/oder in einem Browser aus mit:
flutter run -d <device-name>
- Öffnen Sie die Apps in einem Browser, auf einem iOS-Simulator oder einem Android-Emulator. Gehen Sie zum Kontextmenü und wählen Sie eine App als Führungsgerät aus. Sie sollten sehen können, wie sich die Player der Follower-Geräte ändern, wenn das Leader-Gerät aktualisiert wird.
- Wechseln Sie nun das Leader-Gerät, spielen Sie Musik ab oder pausieren Sie sie und beobachten Sie, wie die Follower-Geräte entsprechend aktualisiert werden.
Wenn die Folgegeräte ordnungsgemäß aktualisiert werden, ist es Ihnen gelungen, einen geräteübergreifenden Controller zu erstellen. Es bleibt nur noch ein entscheidender Schritt.
7. Sicherheitsregeln aktualisieren
Wenn wir keine besseren Sicherheitsregeln schreiben, könnte jemand einen Zustand auf ein Gerät schreiben, das ihm nicht gehört! Aktualisieren Sie also, bevor Sie fertig sind, die Echtzeitdatenbank-Sicherheitsregeln, um sicherzustellen, dass der einzige Benutzer, der auf einem Gerät lesen oder schreiben kann, der Benutzer ist, der bei diesem Gerät angemeldet ist. Navigieren Sie in der Firebase-Konsole zur Echtzeitdatenbank und dann zur Registerkarte „Regeln“ . Fügen Sie die folgenden Regeln ein, die es nur angemeldeten Benutzern ermöglichen, ihre eigenen Gerätezustände zu lesen und zu schreiben:
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
},
}
}
8. Herzlichen Glückwunsch!
Herzlichen Glückwunsch, Sie haben mit Flutter erfolgreich eine geräteübergreifende Fernbedienung erstellt!
Credits
Better Together, ein Firebase-Song
- Musik von Ryan Vernon
- Text und Albumcover von Marissa Christy
- Stimme von JP Gomez
9. Bonus
Erwägen Sie als zusätzliche Herausforderung die Verwendung von Flutter FutureBuilder
, um den aktuellen Lead-Gerätetyp asynchron zur Benutzeroberfläche hinzuzufügen. Wenn Sie Hilfe benötigen, finden Sie diese in dem Ordner, der den fertigen Status des Codelabs enthält.