1. מבוא
העדכון האחרון: 14 במרץ 2022
FlutterFire לתקשורת בין מכשירים
ככל שיותר מכשירים של אוטומציה ביתית, מכשירים לבישים וטכנולוגיות בריאות אישיות מתחברים לאינטרנט, התקשורת בין מכשירים הופכת לחלק חשוב יותר ויותר בפיתוח אפליקציות לנייד. הגדרה של תקשורת בין מכשירים, כמו שליטה בדפדפן מאפליקציית טלפון או שליטה בהפעלה בטלוויזיה מהטלפון, היא בדרך כלל מורכבת יותר מאשר פיתוח של אפליקציה רגילה לנייד .
מסד הנתונים בזמן אמת של Firebase מספק את Presence API שמאפשר למשתמשים לראות את הסטטוס של המכשיר שלהם (מחובר או לא מחובר לאינטרנט). תשתמשו בו עם שירות ההתקנות של Firebase כדי לעקוב אחרי כל המכשירים שבהם אותו משתמש מחובר ולחבר אותם. תשתמשו ב-Flutter כדי ליצור במהירות אפליקציות למספר פלטפורמות, ואז תיצרו אב טיפוס חוצה מכשירים שמפעיל מוזיקה במכשיר אחד ושולט במוזיקה במכשיר אחר.
מה תפַתחו
בשיעור הזה תלמדו איך ליצור שלט פשוט לנגינת מוזיקה. האפליקציה שלכם:
- יש נגן מוזיקה פשוט ב-Android, ב-iOS ובאינטרנט, שנבנה באמצעות Flutter.
- המשתמשים יוכלו להיכנס לחשבון.
- שיוך מכשירים כשאותו משתמש מחובר לכמה מכשירים.
- אפליקציות שמאפשרות למשתמשים לשלוט בהשמעת מוזיקה במכשיר אחד ממכשיר אחר.
מה תלמדו
- איך ליצור ולהפעיל אפליקציה של נגן מוזיקה ב-Flutter.
- איך מאפשרים למשתמשים להיכנס באמצעות Firebase Auth.
- איך משתמשים ב-Firebase RTDB Presence API וב-Firebase Installation Service כדי לחבר מכשירים.
מה צריך
- סביבת פיתוח של Flutter. פועלים לפי ההוראות במדריך ההתקנה של Flutter כדי להגדיר אותו.
- נדרשת גרסת Flutter מינימלית של 2.10 ואילך. אם יש לכם גרסה ישנה יותר, מריצים את
flutter upgrade.
- חשבון Firebase.
2. תהליך ההגדרה
קבלת קוד לתחילת הדרך
יצרנו אפליקציית נגן מוזיקה ב-Flutter. קוד ההתחלה נמצא במאגר Git. כדי להתחיל, בשורת הפקודה, משכפלים את מאגר הנתונים, עוברים לתיקייה עם מצב ההתחלה ומתקינים את התלות:
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>
.
אם אתם מכירים את האמולטור של Android או את הסימולטור של iOS, אתם יכולים ליצור את האפליקציה לפלטפורמות האלה ולהתקין אותה באמצעות הפקודה flutter run -d <device_name>
.
אפליקציית האינטרנט אמורה להציג נגן מוזיקה בסיסי עצמאי. מוודאים שתכונות הנגן פועלות כמצופה. זוהי אפליקציית נגן מוזיקה פשוטה שנוצרה במיוחד ל-Codelab הזה. הוא יכול להשמיע רק שיר של Firebase, Better Together.
הגדרה של אמולטור Android או סימולטור iOS
אם כבר יש לכם מכשיר Android או מכשיר iOS לפיתוח, אתם יכולים לדלג על השלב הזה.
כדי ליצור אמולטור של Android, מורידים את Android Studio, שתומך גם בפיתוח של Flutter, ופועלים לפי ההוראות במאמר יצירה וניהול של מכשירים וירטואליים.
כדי ליצור סימולטור iOS, תצטרכו סביבת Mac. מורידים את XCode ופועלים לפי ההוראות במאמר סקירה כללית של Simulator > שימוש ב-Simulator > פתיחה וסגירה של סימולטור.
3. הגדרת Firebase
יצירת פרויקט Firebase
- נכנסים למסוף Firebase באמצעות חשבון Google.
- לוחצים על הלחצן כדי ליצור פרויקט חדש, ואז מזינים שם לפרויקט (לדוגמה,
Firebase-Cross-Device-Codelab
).
- לוחצים על המשך.
- אם מוצגת בקשה לעשות זאת, קוראים ומאשרים את התנאים של Firebase, ואז לוחצים על המשך.
- (אופציונלי) מפעילים את העזרה מבוססת-AI במסוף Firebase (שנקראת Gemini ב-Firebase).
- ב-codelab הזה לא צריך להשתמש ב-Google Analytics, ולכן משביתים את האפשרות Google Analytics.
- לוחצים על יצירת פרויקט, מחכים שהפרויקט יוקצה ולוחצים על המשך.
התקנת Firebase SDK
חוזרים לשורת הפקודה, בספריית הפרויקט, ומריצים את הפקודה הבאה כדי להתקין את Firebase:
flutter pub add firebase_core
בקובץ pubspec.yaml
, עורכים את הגרסה של firebase_core
כך שתהיה לפחות 1.13.1, או מריצים את הפקודה flutter upgrade
הפעלת FlutterFire
- אם לא התקנתם את Firebase CLI, אתם יכולים להתקין אותו על ידי הרצת הפקודה
curl -sL https://firebase.tools | bash
. - מתחברים על ידי הפעלת הפקודה
firebase login
ופועלים לפי ההנחיות. - מריצים את הפקודה
dart pub global activate flutterfire_cli
כדי להתקין את FlutterFire CLI. - מגדירים את FlutterFire CLI על ידי הרצת הפקודה
flutterfire configure
. - בהודעה שמופיעה, בוחרים את הפרויקט שיצרתם בשביל ה-codelab הזה, למשל Firebase-Cross-Device-Codelab.
- בוחרים באפשרות iOS, Android ואתר כשמוצגת בקשה לבחור תמיכה בהגדרה.
- כשמוצגת בקשה להזין את מזהה החבילה של Apple, מקלידים דומיין ייחודי או מזינים
com.example.appname
, וזה בסדר למטרת ה-Codelab הזה.
אחרי ההגדרה, ייווצר קובץ 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); תשתמשו ב-Presence API כדי לעקוב אחרי הסטטוס של המכשיר (מחובר או לא מחובר לאינטרנט)
- כללי האבטחה של Firebase מאפשרים לאבטח את מסד הנתונים.
- שירות ההתקנות של Firebase כדי לזהות את המכשירים שמשתמש יחיד נכנס אליהם.
4. הוספת Firebase Auth
הפעלת כניסה באמצעות אימייל ב-Firebase Authentication
כדי לאפשר למשתמשים להיכנס לאפליקציית האינטרנט, צריך להשתמש בשיטת הכניסה אימייל/סיסמה:
- ב-Firebase Console, מרחיבים את התפריט Build בחלונית הימנית.
- לוחצים על אימות, ואז על הלחצן תחילת העבודה, ואז על הכרטיסייה שיטת הכניסה.
- ברשימה ספקי כניסה, לוחצים על אימייל/סיסמה, מעבירים את המתג הפעלה למצב מופעל ואז לוחצים על שמירה.
הגדרת אימות ב-Firebase ב-Flutter
בשורת הפקודה, מריצים את הפקודות הבאות כדי להתקין את חבילות ה-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(),
));
}
שוב, ממשק המשתמש של האפליקציה אמור להישאר זהה, אבל עכשיו אפשר לאפשר למשתמשים להיכנס ולשמור את מצבי האפליקציה.
יצירת תהליך כניסה
בשלב הזה תעבדו על תהליך הכניסה והיציאה. כך ייראה התהליך:
- משתמש שלא מחובר יתחיל את תהליך הכניסה בלחיצה על תפריט ההקשר
בצד שמאל של סרגל האפליקציות.
- תהליך הכניסה יוצג בתיבת דו-שיח.
- אם המשתמש מעולם לא נכנס לחשבון, הוא יתבקש ליצור חשבון באמצעות כתובת אימייל וסיסמה תקינות.
- אם המשתמש כבר נכנס לחשבון בעבר, הוא יתבקש להזין את הסיסמה שלו.
- אחרי שהמשתמש מתחבר, לחיצה על תפריט ההקשר תציג את האפשרות יציאה.
הוספת תהליך כניסה לחשבון מתבצעת בשלושה שלבים.
קודם כול, יוצרים ווידג'ט 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,
),
),
]),
);
}
}
בשלב השלישי, מוצאים את הווידג'ט הקיים של סרגל האפליקציות ב-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
כדי להפעיל מחדש את האפליקציה עם השינויים האלה. תפריט ההקשר אמור להופיע בצד שמאל של סרגל האפליקציות. לחיצה עליו תפתח תיבת דו-שיח לכניסה לחשבון.
אחרי שתיכנסו לחשבון עם כתובת אימייל וסיסמה תקינות, אמורה להופיע בתפריט ההקשר האפשרות יציאה מהחשבון.
במסוף Firebase, בקטע אימות, כתובת האימייל אמורה להופיע כמשתמש חדש.
כל הכבוד! המשתמשים יכולים עכשיו להיכנס לאפליקציה.
5. הוספת חיבור למסד נתונים
עכשיו אפשר להמשיך לרישום המכשיר באמצעות Firebase Presence API.
בשורת הפקודה, מריצים את הפקודות הבאות כדי להוסיף את יחסי התלות הנדרשים:
flutter pub add firebase_app_installations
flutter pub add firebase_database
יצירת מסד נתונים
במסוף Firebase,
- עוברים לקטע Realtime Database במסוף Firebase. לוחצים על יצירת מסד נתונים.
- אם מוצגת בקשה לבחור מצב התחלתי לכללי האבטחה, בוחרים באפשרות מצב בדיקה לעת עתה**.** (מצב הבדיקה יוצר כללי אבטחה שמאפשרים לכל הבקשות לעבור. תוכלו להוסיף כללי אבטחה בהמשך. חשוב לא להעביר אף פעם את כללי האבטחה למצב ייצור כשהם עדיין במצב בדיקה).
מסד הנתונים ריק כרגע. אפשר למצוא את databaseURL
בהגדרות הפרויקט, בכרטיסייה כללי. גוללים למטה לקטע אפליקציות אינטרנט.
הוספת databaseURL
לקובץ firebase_options.dart
:
lib/firebase_options.dart
static const FirebaseOptions web = FirebaseOptions(
apiKey: yourApiKey,
...
databaseURL: 'https://<YOUR_DATABASE_URL>,
...
);
רישום מכשירים באמצעות RTDB Presence API
אתם רוצים לרשום את המכשירים של המשתמש כשהם מופיעים אונליין. כדי לעשות את זה, תשתמשו ב-Firebase Installations וב-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, המכשירים שלכם אמורים להופיע תחת מזהה משתמש אחד במסד הנתונים.
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');
}
}
}
}
עכשיו אפשר לבדוק את האפליקציה:
- בשורת הפקודה, מריצים את האפליקציה באמולטורים ו/או בדפדפן באמצעות:
flutter run -d <device-name>
- פותחים את האפליקציות בדפדפן, בסימולטור iOS או באמולטור Android. עוברים לתפריט ההקשר ובוחרים אפליקציה אחת שתהיה המכשיר הראשי. אמורה להיות לכם אפשרות לראות את השינויים בנגנים של המכשירים העוקבים כשהמכשיר המוביל מתעדכן.
- עכשיו משנים את המכשיר הראשי, מפעילים או מפסיקים את המוזיקה ורואים שהמכשירים המשניים מתעדכנים בהתאם.
אם המכשירים המשניים מתעדכנים כמו שצריך, סימן שהצלחתם ליצור בקר חוצה מכשירים. נותר רק עוד שלב אחד חשוב.
7. עדכון כללי האבטחה
אם לא נכתוב כללי אבטחה טובים יותר, מישהו יוכל לכתוב מצב למכשיר שלא בבעלותו. לכן, לפני שמסיימים, צריך לעדכן את כללי האבטחה של מסד הנתונים בזמן אמת כדי לוודא שרק המשתמש שמחובר למכשיר יכול לקרוא או לכתוב בו. במסוף Firebase, עוברים אל Realtime Database ואז אל הכרטיסייה Rules (כללים). מדביקים את הכללים הבאים שמאפשרים רק למשתמשים מחוברים לקרוא ולכתוב את מצבי המכשירים שלהם:
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
},
}
}
8. כל הכבוד!
כל הכבוד, יצרתם בהצלחה שלט רחוק שפועל בכמה מכשירים באמצעות Flutter!
זיכויים
Better Together, a Firebase Song
- מוזיקה מאת Ryan Vernon
- מילים ושער אלבום מאת Marissa Christy
- קריינות: JP Gomez
9. בונוס
כדי להוסיף אתגר, אפשר להשתמש ב-Flutter FutureBuilder
כדי להוסיף את סוג המכשיר הנוכחי של הלידים לממשק המשתמש באופן אסינכרוני. אם אתם צריכים עזרה, אפשר למצוא אותה בתיקייה שמכילה את המצב הסופי של ה-codelab.