Firebase Cross Device Codelab

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.

7f0279938e1d3ab5.gif

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/ .

  1. Melden Sie sich bei Firebase an.
  2. Klicken Sie in der Firebase-Konsole auf Projekt hinzufügen (oder Projekt erstellen ) und nennen Sie Ihr Firebase-Projekt Firebase-Cross-Device-Codelab .
  3. 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

  1. Wenn Sie die Firebase-CLI nicht installiert haben, können Sie sie installieren, indem Sie curl -sL https://firebase.tools | bash ausführen curl -sL https://firebase.tools | bash .
  2. Melden Sie sich an, indem Sie firebase login ausführen und den Anweisungen folgen.
  3. Installieren Sie die FlutterFire-CLI, indem Sie dart pub global activate flutterfire_cli ausführen.
  4. Konfigurieren Sie die FlutterFire-CLI, indem Sie flutterfire configure ausführen.
  5. Wählen Sie an der Eingabeaufforderung das Projekt aus, das Sie gerade für dieses Codelab erstellt haben, etwa Firebase-Cross-Device-Codelab .
  6. Wählen Sie iOS , Android und Web aus, wenn Sie aufgefordert werden, die Konfigurationsunterstützung auszuwählen.
  7. 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 :

  1. Erweitern Sie in der Firebase-Konsole das Build- Menü im linken Bereich.
  2. Klicken Sie auf Authentifizierung und dann auf die Schaltfläche „Erste Schritte“ und dann auf die Registerkarte „Anmeldemethode“ .
  3. Klicken Sie in der Liste der Anmeldeanbieter auf E-Mail/Passwort , stellen Sie den Schalter „Aktivieren“ auf „Ein“ und klicken Sie dann auf „Speichern“ . 58e3e3e23c2f16a4.png

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:

  1. Ein abgemeldeter Benutzer leitet den Anmeldevorgang ein, indem er auf das Kontextmenü klickt 71fcc1030a336423.png auf der rechten Seite der App-Leiste.
  2. Der Anmeldevorgang wird in einem Dialog angezeigt.
  3. 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.
  4. Wenn sich der Benutzer bereits angemeldet hat, wird er aufgefordert, sein Passwort einzugeben.
  5. Sobald der Benutzer angemeldet ist, wird durch Klicken auf das Kontextmenü die Option „Abmelden“ angezeigt.

c295f6fa2e1d40f3.png

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 71fcc1030a336423.png 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.

888506c86a28a72c.png

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

  1. Navigieren Sie zum Abschnitt „Echtzeitdatenbank“ der Firebase-Konsole . Klicken Sie auf Datenbank erstellen .
  2. 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“ .

1b6076f60a36263b.png

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.

5bef49cea3564248.png

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:

  1. Führen Sie die App in der Befehlszeile auf Emulatoren und/oder in einem Browser aus mit: flutter run -d <device-name>
  2. Ö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.
  3. 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!

bcd986f7106d892b.gif

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.

Referenzdokumente und nächste Schritte