1. מבוא
עדכון אחרון:14 במרץ 2022
FlutterFire לתקשורת בין מכשירים
אנחנו עדים לעלייה במספר המכשירים שמחוברים לאינטרנט ומשמשים לאוטומציה של הבית, למכשירים לבישים ולטכנולוגיות בריאות אישיות. לכן, תקשורת בין מכשירים הופכת לחלק חשוב יותר ויותר בתהליך פיתוח אפליקציות לנייד. הגדרת תקשורת בין מכשירים שונים, כמו שליטה בדפדפן מאפליקציה בטלפון או שליטה במה שמופעל בטלוויזיה מהטלפון, היא תהליך מורכב יותר מאשר פיתוח אפליקציה רגילה לנייד.
מסד הנתונים בזמן אמת של Firebase מספק את Presence API שמאפשר למשתמשים לראות את סטטוס המכשיר שלהם (אונליין או אופליין). תוכלו להשתמש בו עם שירות ההתקנות של Firebase כדי לעקוב אחרי כל המכשירים שבהם אותו משתמש נכנס לחשבון ולקשר ביניהם. נשתמש ב-Flutter כדי ליצור במהירות אפליקציות למספר פלטפורמות, ולאחר מכן נבנה אב טיפוס למכשירים שונים שמפעיל מוזיקה במכשיר אחד ומאפשר לשלוט במוזיקה במכשיר אחר.
מה תפַתחו
בקודלאב הזה תלמדו ליצור שלט רחוק פשוט לנגן מוזיקה. האפליקציה שלך:
- נגן מוזיקה פשוט ל-Android, ל-iOS ולאינטרנט, שנוצר באמצעות Flutter.
- מאפשרים למשתמשים להיכנס.
- חיבור מכשירים כאשר אותו משתמש מחובר לחשבון במספר מכשירים.
- לאפשר למשתמשים לשלוט בהפעלת המוזיקה במכשיר אחד ממכשיר אחר.
מה תלמדו
- איך ליצור ולהפעיל אפליקציית נגן מוזיקה של Flutter.
- איך מאפשרים למשתמשים להיכנס לחשבון באמצעות Firebase Auth
- איך משתמשים ב-Firebase RTDB Presence API ובשירות ההתקנה של Firebase כדי לחבר מכשירים.
מה צריך להכין
- סביבת פיתוח של Flutter. פועלים לפי ההוראות במדריך להתקנתFlutter כדי להגדיר אותו.
- נדרשת גרסה 2.10 ומעלה של Flutter. אם הגרסה שלך ישנה יותר, מריצים את
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 הזה. אפשר להפעיל בה רק את השיר Better Together.
איך מגדירים אמולטור של Android או סימולטור של iOS
אם כבר יש לכם מכשיר Android או מכשיר iOS לצורכי פיתוח, אתם יכולים לדלג על השלב הזה.
כדי ליצור אמולטור של Android, צריך להוריד את Android Studio שתומך גם בפיתוח Flutter, ולפעול לפי ההוראות במאמר יצירה וניהול של מכשירים וירטואליים.
כדי ליצור סימולטור של iOS, נדרשת סביבה של Mac. מורידים את XCode ופועלים לפי ההוראות שבקטע סקירה כללית של הסימולטור > שימוש בסימולטור > פתיחה וסגירה של סימולטור.
3. הגדרת Firebase
יצירת פרויקט Firebase
פותחים דפדפן בכתובת http://console.firebase.google.com/.
- נכנסים אל Firebase.
- במסוף Firebase, לוחצים על הוספת פרויקט (או על יצירת פרויקט) ונותנים לפרויקט Firebase את השם Firebase-Cross-Device-Codelab.
- לוחצים על האפשרויות ליצירת פרויקט. מאשרים את התנאים של 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
- אם ה-CLI של Firebase לא מותקן, אפשר להתקין אותו על ידי הרצת
curl -sL https://firebase.tools | bash
. - כדי להתחבר, מריצים את
firebase login
ופועלים לפי ההנחיות. - מתקינים את ה-CLI של FlutterFire על ידי הפעלת
dart pub global activate flutterfire_cli
. - מגדירים את ה-CLI של FlutterFire על ידי הרצת
flutterfire configure
. - בהודעה שמופיעה, בוחרים את הפרויקט שיצרתם עכשיו עבור ה-Codelab הזה, כמו Firebase-Cross-Device-Codelab.
- בוחרים באפשרויות iOS, Android ואתר כשמוצגת בקשה לבחור תמיכה בתצורה.
- כשמתבקשים להזין את מזהה החבילה של Apple, מקלידים דומיין ייחודי או מזינים את הערך
com.example.appname
, שמתאים למטרות של ה-codelab הזה.
לאחר ההגדרה, ייווצר קובץ firebase_options.dart
שיכיל את כל האפשרויות הנדרשות לאתחול.
כדי לאתחל את Flutter ו-Firebase, בעורך שלך, יש להוסיף את הקוד הבא לקובץ Main.dart:
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 (RTDB): תשתמשו ב-API של נוכחות כדי לעקוב אחרי סטטוס המכשיר אופליין/אונליין
- כללי אבטחה של Firebase יאפשרו לכם לאבטח את מסד הנתונים.
- שירות התקנות של Firebase כדי לזהות את המכשירים שבהם משתמש אחד נכנס לחשבון.
4. הוספת אימות מ-Firebase
הפעלת כניסה לאימייל לצורך אימות ב-Firebase
כדי לאפשר למשתמשים להיכנס לאפליקציית האינטרנט, צריך להשתמש בשיטת הכניסה באמצעות אימייל/סיסמה:
- במסוף Firebase, מרחיבים את התפריט Build בחלונית הימנית.
- לוחצים על Authentication (אימות) ואז לוחצים על הלחצן Get Started (תחילת העבודה) ואז על הכרטיסייה Sign-in method (שיטת הכניסה).
- לוחצים על אימייל/סיסמה ברשימה ספקי כניסה, מעבירים את המתג הפעלה למצב מופעל ולוחצים על שמירה.
הגדרת אימות ב-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),
),
),
];
}
}
בשלב השני, יוצרים את הווידג'ט SignInDialog
באותה כיתה widgets.dart
.
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
כדי להפעיל מחדש את האפליקציה עם השינויים האלה. תפריט ההקשר אמור להופיע בצד שמאל של סרגל האפליקציות. לחיצה עליו תעביר אתכם לתיבת דו-שיח לכניסה.
אחרי שתיכנסו באמצעות כתובת אימייל חוקית וסיסמה, האפשרות יציאה אמורה להופיע בתפריט ההקשר.
בקטע אימות במסוף Firebase, אתם אמורים לראות את כתובת האימייל של משתמש חדש.
כל הכבוד! עכשיו המשתמשים יכולים להיכנס לאפליקציה
5. הוספת חיבור למסד הנתונים
עכשיו אתם מוכנים להמשיך לרישום המכשיר באמצעות Firebase Presence API.
בשורת הפקודה, מריצים את הפקודות הבאות כדי להוסיף את יחסי התלות הנדרשים:
flutter pub add firebase_app_installations
flutter pub add firebase_database
יצירת מסד נתונים
במסוף Firebase,
- עוברים לקטע Realtime Database במסוף Firebase. לוחצים על Create Database.
- אם מוצגת בקשה לבחור מצב התחלה לכללי האבטחה, צריך לבחור באפשרות מצב בדיקה בינתיים**.** (במצב בדיקה נוצרים כללי אבטחה שמאפשרים את כל הבקשות. כללי אבטחה יתווספו בהמשך. חשוב לעולם לא לעבור לסביבת הייצור כשכללי האבטחה עדיין נמצאים במצב בדיקה).
מסד הנתונים ריק כרגע. מאתרים את databaseURL
בקטע Project settings (הגדרות הפרויקט), בכרטיסייה General (כללי). גוללים למטה לקטע אפליקציות אינטרנט.
הוספת 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
. ה-listener הזה יודיע למכשירים שעוקבים אחריכם מתי לעדכן את המסך באמצעות קריאה חוזרת (callback). שימו לב שהגדרתם ממשק 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. עדכון כללי אבטחה
אם לא נכתוב כללי אבטחה טובים יותר, מישהו יוכל לכתוב מצב למכשיר שהוא לא הבעלים שלו. לכן, לפני שמסיימים, כדאי לעדכן את כללי האבטחה של Realtime Database כדי לוודא שרק המשתמשים שנכנסו למכשיר יכולים לקרוא או לכתוב בו. במסוף Firebase, עוברים למסד הנתונים 'זמן אמת' ואז לכרטיסייה כללים. מדביקים את הכללים הבאים כדי לאפשר רק למשתמשים שמחוברים לחשבון לקרוא ולכתוב את מצבי המכשיר שלהם:
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
},
}
}
8. כל הכבוד!
מזל טוב, יצרתם שלט רחוק למכשירים שונים באמצעות Flutter!
זיכויים
Better Together, שיר של Firebase
- מוזיקה של ריאן ורנון
- מילות השיר ועטיפת האלבום מאת Marissa Christy
- קול של JP גומז
9. בונוס
כאתגר נוסף, כדאי להשתמש ב-Flutter FutureBuilder
כדי להוסיף לממשק המשתמש באופן אסינכרוני את סוג המכשיר הנוכחי להשארת פרטים. אם אתם זקוקים לעזרה, היא מיושמת בתיקייה שמכילה את המצב הסופי של ה-Codelab.