Firebase Cross Device Codelab

1. הקדמה

עדכון אחרון: 2022-03-14

FlutterFire לתקשורת בין מכשירים

ככל שאנו עדים למספר רב של מכשירים לבישים וטכנולוגיות בריאות אישיות באוטומציה ביתית המגיעים לאינטרנט, תקשורת חוצת מכשירים הופכת לחלק חשוב יותר ויותר בבניית יישומים ניידים. הגדרת תקשורת חוצת מכשירים כגון שליטה בדפדפן מאפליקציית טלפון, או שליטה במה שמנגן בטלוויזיה מהטלפון שלך, היא באופן מסורתי מורכבת יותר מבניית אפליקציה רגילה לנייד.

מסד הנתונים בזמן אמת של Firebase מספק את Presence API המאפשר למשתמשים לראות את מצב המכשיר שלהם מקוון/לא מקוון; תשתמש בו עם שירות התקנות Firebase כדי לעקוב ולחבר את כל המכשירים שבהם אותו משתמש נכנס. תשתמש ב-Flutter כדי ליצור במהירות יישומים עבור פלטפורמות מרובות, ולאחר מכן תבנה אב טיפוס חוצה מכשירים שמתנגן מוזיקה במכשיר אחד ושולטת במוזיקה במכשיר אחר!

מה שתבנה

בקוד מעבד זה, תבנה שלט רחוק פשוט של נגן מוזיקה. האפליקציה שלך תעשה:

  • קבל נגן מוזיקה פשוט באנדרואיד, iOS ואינטרנט, שנבנה עם Flutter.
  • אפשר למשתמשים להיכנס.
  • חבר מכשירים כאשר אותו משתמש מחובר במספר מכשירים.
  • אפשר למשתמשים לשלוט בהשמעת מוזיקה במכשיר אחד ממכשיר אחר.

7f0279938e1d3ab5.gif

מה תלמד

  • כיצד לבנות ולהפעיל אפליקציית נגן מוזיקה של Flutter.
  • כיצד לאפשר למשתמשים להיכנס באמצעות Firebase Auth.
  • כיצד להשתמש ב-Firebase RTDB Presence API ובשירות ההתקנה של Firebase כדי לחבר התקנים.

מה אתה צריך

  • סביבת פיתוח של Flutter. עקוב אחר ההוראות במדריך ההתקנה של Flutter כדי להגדיר אותו.
  • נדרשת גרסת Flutter מינימלית של 2.10 ומעלה. אם יש לך גרסה נמוכה יותר, הפעל flutter upgrade.
  • חשבון Firebase.

2. מתחילים להתקין

קבל את קוד ההתחלה

יצרנו אפליקציית נגן מוזיקה בפלוטר. קוד ההתחלה נמצא בריפו של Git. כדי להתחיל, בשורת הפקודה, שכפל את ה-repo, עבור לתיקיה עם מצב ההתחלה, והתקן תלות:

git clone https://github.com/FirebaseExtended/cross-device-controller.git

cd cross-device-controller/starter_code

flutter pub get

בנה את האפליקציה

אתה יכול לעבוד עם ה-IDE המועדף עליך כדי לבנות את האפליקציה, או להשתמש בשורת הפקודה.

בספריית האפליקציה שלך, בנה את האפליקציה לאינטרנט עם הפקודה flutter run -d web-server. אתה אמור להיות מסוגל לראות את ההנחיה הבאה.

lib/main.dart is being served at http://localhost:<port>

בקר בכתובת http://localhost:<port> כדי לראות את נגן המוזיקה.

אם אתה מכיר את אמולטור אנדרואיד או סימולטור iOS, אתה יכול לבנות את האפליקציה עבור אותן פלטפורמות ולהתקין אותה עם הפקודה flutter run -d <device_name> .

אפליקציית האינטרנט אמורה להציג נגן מוזיקה עצמאי בסיסי. ודא שתכונות הנגן פועלות כמתוכנן. זוהי אפליקציית נגן מוזיקה פשוטה המיועדת עבור מעבדת קוד זה. זה יכול להשמיע רק שיר של Firebase, Better Together .

הגדר אמולטור אנדרואיד או סימולטור iOS

אם כבר יש לך מכשיר אנדרואיד או מכשיר iOS לפיתוח, תוכל לדלג על שלב זה.

כדי ליצור אמולטור אנדרואיד, הורד את Android Studio שתומך גם בפיתוח Flutter, ופעל לפי ההוראות ביצירה וניהול מכשירים וירטואליים .

כדי ליצור סימולטור iOS, תזדקק לסביבת Mac. הורד את XCode , ובצע את ההוראות בסקירת הסימולטור > השתמש בסימולטור > פתח וסגור סימולטור .

3. הגדרת Firebase

צור פרויקט Firebase

פתח דפדפן אל http://console.firebase.google.com/ .

  1. היכנס ל- Firebase .
  2. במסוף Firebase, לחץ על הוסף פרויקט (או צור פרויקט ), ושם לפרויקט Firebase שלך ​​Firebase-Cross-Device-Codelab .
  3. לחץ על אפשרויות יצירת הפרויקט. קבל את תנאי Firebase אם תתבקש. דלג על הגדרת Google Analytics, כי לא תשתמש ב-Analytics עבור האפליקציה הזו.

אין צורך להוריד את הקבצים שהוזכרו או לשנות קבצי build.gradle. אתה תגדיר אותם כאשר אתה מאתחל את FlutterFire.

התקן Firebase SDK

בחזרה בשורת הפקודה, בספריית הפרויקט, הפעל את הפקודה הבאה כדי להתקין את Firebase:

flutter pub add firebase_core

בקובץ pubspec.yaml , ערוך את הגרסה עבור firebase_core לגרסה 1.13.1 לפחות, או הפעל את flutter upgrade

אתחול FlutterFire

  1. אם לא התקנת את Firebase CLI, תוכל להתקין אותו על ידי הפעלת curl -sL https://firebase.tools | bash .
  2. היכנס על ידי הפעלת firebase login וביצוע ההנחיות.
  3. התקן את FlutterFire CLI על ידי הפעלת dart pub global activate flutterfire_cli .
  4. הגדר את FlutterFire CLI על-ידי הפעלת flutterfire configure .
  5. בהנחיה, בחר את הפרויקט שיצרת זה עתה עבור מעבד הקוד הזה, משהו כמו Firebase-Cross-Device-Codelab .
  6. בחר iOS , Android ו- Web כאשר תתבקש לבחור תמיכה בתצורה.
  7. כשתתבקש להזין את מזהה החבילה של Apple , הקלד דומיין ייחודי, או הזן com.example.appname , וזה בסדר עבור מעבדת הקוד הזה.

לאחר ההגדרה, ייווצר עבורך קובץ firebase_options.dart המכיל את כל האפשרויות הנדרשות לאתחול.

בעורך שלך, הוסף את הקוד הבא לקובץ main.dart שלך כדי לאתחל את Flutter ו-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());
}

הרכיב את האפליקציה עם הפקודה:

flutter run

עדיין לא שינית רכיבי ממשק משתמש, כך שהמראה וההתנהגות של האפליקציה לא השתנו. אבל עכשיו יש לך אפליקציית Firebase, ואתה יכול להתחיל להשתמש במוצרי Firebase, כולל:

  • אימות Firebase , המאפשר למשתמשים שלך להיכנס לאפליקציה שלך.
  • Firebase Realtime Database (RTDB) ; תשתמש בממשק API של נוכחות כדי לעקוב אחר מצב המכשיר המקוון/לא מקוון
  • כללי האבטחה של Firebase יאפשרו לך לאבטח את מסד הנתונים.
  • שירות התקנות Firebase כדי לזהות את המכשירים שמשתמש בודד נכנס אליהם.

4. הוסף Firebase Auth

אפשר כניסה בדוא"ל עבור אימות Firebase

כדי לאפשר למשתמשים להיכנס לאפליקציית האינטרנט, תשתמש בשיטת הכניסה לדוא"ל/סיסמה :

  1. במסוף Firebase, הרחב את תפריט Build בחלונית השמאלית.
  2. לחץ על אימות ולאחר מכן לחץ על הלחצן התחל , ולאחר מכן על הכרטיסייה שיטת כניסה .
  3. לחץ על דואר אלקטרוני/סיסמה ברשימת ספקי הכניסה , הגדר את המתג הפעל למצב מופעל ולאחר מכן לחץ על שמור . 58e3e3e23c2f16a4.png

הגדר את אימות Firebase ב-Flutter

בשורת הפקודה, הפעל את הפקודות הבאות כדי להתקין את חבילות הפלטר הדרושות:

flutter pub add firebase_auth

flutter pub add provider

עם תצורה זו, כעת תוכל ליצור את זרימת הכניסה והיציאה. מכיוון שמצב האימות לא אמור להשתנות ממסך למסך, תיצור מחלקה application_state.dart כדי לעקוב אחר שינויים במצב רמת האפליקציה, כגון כניסה והתנתק. למידע נוסף על כך בתיעוד ניהול מצב Flutter .

הדבק את הדברים הבאים בקובץ 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();
  }
}

כדי לוודא ApplicationState יאתחל עם הפעלת האפליקציה, תוסיף שלב אתחול ל- 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(),
  ));
}

שוב, ממשק המשתמש של האפליקציה היה צריך להישאר זהה, אבל עכשיו אתה יכול לאפשר למשתמשים להיכנס ולשמור מצבי אפליקציה.

צור זרימת כניסה

בשלב זה, תעבדו על זרימת הכניסה והיציאה. כך תיראה הזרימה:

  1. משתמש מנותק יתחיל את זרימת הכניסה על ידי לחיצה על תפריט ההקשר 71fcc1030a336423.png בצד ימין של סרגל האפליקציות.
  2. זרימת הכניסה תוצג בתיבת דו-שיח.
  3. אם המשתמש מעולם לא נכנס לפני כן, הוא יתבקש ליצור חשבון באמצעות כתובת דוא"ל חוקית וסיסמה.
  4. אם המשתמש נכנס בעבר, הוא יתבקש להזין את הסיסמה שלו.
  5. לאחר שהמשתמש נכנס, לחיצה על תפריט ההקשר תציג אפשרות יציאה .

c295f6fa2e1d40f3.png

הוספת זרימת הכניסה דורשת שלושה שלבים.

קודם כל, צור ווידג'ט של AppBarMenuButton . ווידג'ט זה ישלוט בתפריט הקופץ של תפריט ההקשר בהתאם ל- loginState של משתמש. הוסף את היבוא

lib/src/widgets.dart

import 'application_state.dart';
import 'package:provider/provider.dart';
import 'authentication.dart';

הוסף את הקוד הבא ל- 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),
        ),
      ),
    ];
  }
}

שנית, באותה מחלקה widgets.dart , צור את הווידג'ט 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,
          ),
        ),
      ]),
    );
  }
}

שלישית, מצא את הווידג'ט הקיים של appBar ב- main.dart. הוסף את AppBarMenuButton כדי להציג את האפשרות כניסה או יציאה .

lib/main.dart

import 'src/widgets.dart';
appBar: AppBar(
  title: const Text('Music Box'),
  backgroundColor: Colors.deepPurple.shade400,
  actions: const <Widget>[
    AppBarMenuButton(),
  ],
),

הפעל את הפקודה flutter run כדי להפעיל מחדש את האפליקציה עם השינויים האלה. אתה אמור להיות מסוגל לראות את תפריט ההקשר 71fcc1030a336423.png בצד ימין של סרגל האפליקציות. לחיצה עליו תעביר אותך לתיבת דו-שיח כניסה.

לאחר שתיכנס עם כתובת דוא"ל חוקית וסיסמה, אתה אמור להיות מסוגל לראות אפשרות יציאה בתפריט ההקשר.

במסוף Firebase, תחת אימות , אתה אמור להיות מסוגל לראות את כתובת הדוא"ל הרשומה כמשתמש חדש.

888506c86a28a72c.png

מזל טוב! משתמשים יכולים כעת להיכנס לאפליקציה!

5. הוסף חיבור למסד נתונים

כעת אתה מוכן לעבור לרישום מכשירים באמצעות ממשק ה-API של Firebase Presence.

בשורת הפקודה, הפעל את הפקודות הבאות כדי להוסיף את התלות הדרושות:

flutter pub add firebase_app_installations

flutter pub add firebase_database

צור מסד נתונים

במסוף Firebase,

  1. נווט לקטע מסד נתונים בזמן אמת של מסוף Firebase . לחץ על צור מסד נתונים .
  2. אם תתבקש לבחור מצב התחלה עבור כללי האבטחה שלך, בחר מצב בדיקה לעת עתה**.** (מצב בדיקה יוצר כללי אבטחה המאפשרים לעבור את כל הבקשות. אתה תוסיף כללי אבטחה מאוחר יותר. חשוב לעולם לא ללכת לייצור עם כללי האבטחה שלך עדיין במצב בדיקה.)

מסד הנתונים ריק לעת עתה. אתר את databaseURL שלך בהגדרות פרוייקט , תחת הכרטיסייה כללי . גלול מטה למקטע יישומי אינטרנט .

1b6076f60a36263b.png

הוסף את databaseURL שלך לקובץ firebase_options.dart :

lib/firebase_options.dart

 static const FirebaseOptions web = FirebaseOptions(
    apiKey: yourApiKey,
    ...
    databaseURL: 'https://<YOUR_DATABASE_URL>,
    ...
  );

רשום מכשירים באמצעות RTDB Presence API

אתה רוצה לרשום את המכשירים של משתמש כשהם מופיעים באינטרנט. כדי לעשות זאת, תנצל את התקנות Firebase ואת Firebase RTDB Presence API כדי לעקוב אחר רשימה של מכשירים מקוונים ממשתמש יחיד. הקוד הבא יעזור להשיג מטרה זו:

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';
  }

חזרה לשורת הפקודה, בנה והפעל את האפליקציה במכשיר שלך או בדפדפן עם flutter run.

באפליקציה שלך, היכנס כמשתמש. זכור להיכנס כאותו משתמש בפלטפורמות שונות.

במסוף Firebase , אתה אמור לראות את המכשירים שלך מופיעים תחת מזהה משתמש אחד במסד הנתונים שלך.

5bef49cea3564248.png

6. סנכרון מצב המכשיר

בחר התקן מוביל

כדי לסנכרן מצבים בין מכשירים, הגדר מכשיר אחד כמוביל, או הבקר. ההתקן המוביל יכתיב את המצבים במכשירי העוקבים.

צור שיטה setLeadDevice ב- application_state.dart , ועקוב אחר מכשיר זה עם המפתח active_device ב-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;
      });
    }
  }

כדי להוסיף פונקציונליות זו לתפריט ההקשר של שורת האפליקציה, צור PopupMenuItem בשם Controller על ידי שינוי הווידג'ט SignedInMenuButton . תפריט זה יאפשר למשתמשים להגדיר את ההתקן המוביל.

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();
    }
  }
}

כתוב את מצב ההתקן המוביל למסד הנתונים

לאחר שהגדרתם התקן לידים, תוכלו לסנכרן את מצבי ההתקן המוביל ל-RTDB עם הקוד הבא. הוסף את הקוד הבא לסוף של application_state.dart. פעולה זו תתחיל לאחסן שתי תכונות: מצב הנגן (הפעל או השהייה) ומיקום המחוון.

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');
      }
    }
  }

ולבסוף, עליך להתקשר setActiveDeviceState בכל פעם שמצב הנגן של הבקר מתעדכן. בצע את השינויים הבאים בקובץ player_widget.dart הקיים:

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;
  }

קרא את מצב ההתקן המוביל ממסד הנתונים

ישנם שני חלקים לקריאה ולהשתמש במצב התקן המוביל. ראשית, ברצונך להגדיר מאזין מסד נתונים של מצב הנגן המוביל ב- application_state . מאזין זה יגיד למכשירי העוקבים מתי לעדכן את המסך באמצעות התקשרות חוזרת. שים לב שהגדרת ממשק OnLeadDeviceChangeCallback בשלב זה. זה עדיין לא מיושם; תוכל ליישם את הממשק הזה ב- 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();
      });
    }
  }

שנית, הפעל את מאזין מסד הנתונים במהלך אתחול הנגן ב- player_widget.dart . העבר את הפונקציה _updatePlayer כך שניתן יהיה לעדכן את מצב הנגן העוקב בכל פעם שערך מסד הנתונים משתנה.

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');
        }
      }
    }
  }

עכשיו אתה מוכן לבדוק את האפליקציה:

  1. בשורת הפקודה, הפעל את האפליקציה על אמולטורים ו/או בדפדפן עם: flutter run -d <device-name>
  2. פתח את האפליקציות בדפדפן, בסימולטור iOS או אמולטור אנדרואיד. עבור לתפריט ההקשר, בחר אפליקציה אחת שתהיה התקן המוביל. אתה אמור להיות מסוגל לראות את השחקנים של מכשירי העוקבים משתנים ככל שהמכשיר המוביל מתעדכן.
  3. כעת שנה את מכשיר המוביל, הפעל או השהה מוזיקה, וצפה במכשירי העוקבים המתעדכנים בהתאם.

אם מכשירי העוקבים מתעדכנים כראוי, הצלחת ליצור בקר חוצה מכשירים. נותר רק צעד אחד מכריע.

7. עדכן את כללי האבטחה

אלא אם כן נכתוב כללי אבטחה טובים יותר, מישהו יכול לכתוב מצב למכשיר שאינו בבעלותו! אז לפני שתסיים, עדכן את כללי האבטחה של מסד נתונים בזמן אמת כדי לוודא שהמשתמשים היחידים שיכולים לקרוא או לכתוב למכשיר הוא המשתמש שנכנס לאותו מכשיר. במסוף Firebase, נווט אל מסד הנתונים בזמן אמת ולאחר מכן אל הכרטיסייה כללים . הדבק את הכללים הבאים המאפשרים רק למשתמש מחובר לקרוא ולכתוב את מצבי המכשיר שלו:

{
  "rules": {
    "users": {
           "$uid": {
               ".read": "$uid === auth.uid",
               ".write": "$uid === auth.uid"
           }
    },
  }
}

8. מזל טוב!

bcd986f7106d892b.gif

מזל טוב, בנית בהצלחה שלט רחוק בין מכשירים באמצעות Flutter!

נקודות זכות

Better Together, שיר של Firebase

  • מוזיקה מאת ריאן ורנון
  • מילים ועטיפה של האלבום מאת Marissa Christy
  • קול מאת JP Gomez

9. בונוס

כאתגר נוסף, שקול להשתמש ב-Flutter FutureBuilder כדי להוסיף את סוג התקן הלידים הנוכחי לממשק המשתמש באופן אסינכרוני. אם אתה צריך סיוע, הוא מיושם בתיקייה המכילה את המצב המוגמר של מעבדת הקוד.

מסמכי עזר והשלבים הבאים