1- مقدمة
تاريخ آخر تعديل: 14/03/2022
FlutterFire للتواصل على جميع الأجهزة
في ظل توفُّر عدد كبير من عمليات التشغيل الآلي للأجهزة المنزلية والأجهزة القابلة للارتداء والأجهزة التي تستخدم تكنولوجيا الصحة الشخصية على الإنترنت، أصبح التواصل بين الأجهزة جزءًا مهمًا بشكل متزايد من عملية إنشاء التطبيقات للأجهزة الجوّالة. عادةً ما يكون إعداد الاتصال على جميع الأجهزة، مثل التحكّم في المتصفّح من تطبيق الهاتف أو التحكّم في المحتوى الذي يتمّ تشغيله على التلفزيون من هاتفك، أكثر تعقيدًا من إنشاء تطبيق عادي متوافق مع الأجهزة الجوّالة .
توفّر "قاعدة بيانات Firebase في الوقت الفعلي" Presence API التي تسمح للمستخدمين بالاطّلاع على حالة اتصال أجهزتهم بالإنترنت أو عدم اتصالهم بالإنترنت. ستستخدمه مع "خدمة تثبيت Firebase" لتتبُّع وربط جميع الأجهزة التي سجّل المستخدم نفسه الدخول إليها. عليك استخدام Flutter لإنشاء تطبيقات لمنصات متعدّدة بسرعة، وبعد ذلك عليك إنشاء نموذج أولي على جميع الأجهزة لتشغيل الموسيقى على أحد الأجهزة والتحكّم في الموسيقى على جهاز آخر.
ما الذي ستنشئه
في هذا الدرس التطبيقي حول الترميز، ستتعرّف على طريقة تصميم وحدة تحكم عن بُعد بسيطة لمشغّل الموسيقى. سينفّذ تطبيقك ما يلي:
- امتلاك مشغِّل موسيقى بسيط على أجهزة Android وiOS والويب، وهو مُصمَّم باستخدام Flutter
- السماح للمستخدمين بتسجيل الدخول
- توصيل الأجهزة عندما يسجّل المستخدم نفسه الدخول على أجهزة متعددة.
- هي فئة تطبيقات تسمح للمستخدمين بالتحكّم في تشغيل الموسيقى على أحد الأجهزة من جهاز آخر.
المعلومات التي ستطّلع عليها
- كيفية إنشاء تطبيق مشغِّل موسيقى Flutter وتشغيله
- كيفية السماح للمستخدمين بتسجيل الدخول باستخدام مصادقة 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>
.
يجب أن يعرض تطبيق الويب مشغل موسيقى أساسيًا مستقلاً. تأكّد من أنّ ميزات المشغّل تعمل على النحو المطلوب. هذا تطبيق بسيط لمشغّل موسيقى تم تصميمه لهذا الدرس التطبيقي حول الترميز. يمكنها فقط تشغيل أغنية Firebase، وهي Better Together.
إعداد محاكي Android أو محاكي iOS
إذا كان لديك جهاز Android أو جهاز iOS قيد التطوير، يمكنك تخطّي هذه الخطوة.
لإنشاء محاكي Android، نزِّل تطبيق استوديو Android الذي يتوافق أيضًا مع تطوير برنامج Flutter، واتّبِع التعليمات الواردة في مقالة إنشاء الأجهزة الافتراضية وإدارتها.
لإنشاء مُحاكي iOS، ستحتاج إلى بيئة Mac. نزِّل XCode، واتَّبع التعليمات الواردة في نظرة عامة على المحاكاة > استخدام المحاكي > افتح محاكيًا وأغلقه.
3- إعداد Firebase
إنشاء مشروع على Firebase
افتح متصفّحًا للوصول إلى http://console.firebase.google.com/.
- سجِّل الدخول إلى Firebase.
- في وحدة تحكُّم Firebase، انقر على إضافة مشروع (أو إنشاء مشروع)، وأدخِل اسمًا لمشروع Firebase Firebase-Cross-Device-Codelab.
- انقر على خيارات إنشاء المشروع. وافِق على بنود Firebase إذا طُلب منك ذلك. يمكنك تخطّي عملية إعداد "إحصاءات Google" لأنّك لن تستخدم "إحصاءات Google" لهذا التطبيق.
لست بحاجة إلى تنزيل الملفات المذكورة أو تغيير ملفات Build.gradle. ويمكنك إعدادها عند إعداد FlutterFire.
تثبيت حزمة تطوير البرامج (SDK) لمنصّة Firebase
بالعودة إلى سطر الأوامر، في دليل المشروع، شغّل الأمر التالي لتثبيت Firebase:
flutter pub add firebase_core
في ملف pubspec.yaml
، عدِّل إصدار "firebase_core
" ليكون 1.13.1 على الأقل، أو شغِّل flutter upgrade
.
إعداد FlutterFire
- في حال عدم تثبيت واجهة سطر الأوامر في Firebase، يمكنك تثبيته من خلال تشغيل
curl -sL https://firebase.tools | bash
. - سجِّل الدخول عن طريق تشغيل
firebase login
واتّباع الطلبات. - تثبيت FlutterFire CLI من خلال تشغيل "
dart pub global activate flutterfire_cli
" - اضبط واجهة سطر الأوامر FlutterFire من خلال تشغيل
flutterfire configure
. - في الطلب، اختَر المشروع الذي أنشأته للتوّ لهذا الدرس التطبيقي حول الترميز، مثل Firebase-Cross-Device-Codelab.
- اختَر iOS وAndroid والويب عندما يُطلب منك اختيار دعم الإعداد.
- عندما يُطلب منك معرّف حزمة 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 في الوقت الفعلي(RTDB) ستستخدم واجهة برمجة تطبيقات التواجد لتتبُّع حالة اتصال جهازك بالإنترنت أو عدم اتصاله بالإنترنت
- ستتيح لك قواعد أمان Firebase تأمين قاعدة البيانات.
- خدمة عمليات تثبيت Firebase لتحديد الأجهزة التي سجّل مستخدم واحد الدخول إليها.
4. إضافة مصادقة Firebase
تفعيل تسجيل الدخول إلى البريد الإلكتروني لمصادقة Firebase
للسماح للمستخدمين بتسجيل الدخول إلى تطبيق الويب، ستستخدم طريقة تسجيل الدخول البريد الإلكتروني/كلمة المرور:
- في "وحدة تحكُّم Firebase"، وسِّع القائمة إنشاء في اللوحة اليمنى.
- انقر على المصادقة، ثم انقر على زر البدء ثم على علامة التبويب طريقة تسجيل الدخول.
- انقر على البريد الإلكتروني/كلمة المرور في قائمة مزوّدي خدمة تسجيل الدخول، واضبط مفتاح تفعيل على الوضع "تفعيل"، ثمّ انقر على حفظ.
ضبط مصادقة 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,
),
),
]),
);
}
}
ثالثًا، ابحث عن تطبيق 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. انقر على إنشاء قاعدة بيانات.
- إذا طُلب منك اختيار وضع بدء لقواعد الأمان، اختَر وضع الاختبار في الوقت الحالي**.** (ينشئ "وضع الاختبار" قواعد أمان تسمح بجميع الطلبات. ستضيف قواعد الأمان لاحقًا. من المهم ألّا تنتقل أبدًا إلى مرحلة الإنتاج بينما تظل قواعد الأمان في وضع الاختبار).
قاعدة البيانات فارغة في الوقت الحالي. حدِّد موقع 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 وواجهة 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"، انتقِل إلى "قاعدة بيانات الوقت الفعلي"، ثم إلى علامة التبويب القواعد. ألصِق القواعد التالية التي تسمح فقط للمستخدم الذي سجّل الدخول بقراءة حالات جهازه الخاصة وكتابتها:
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
},
}
}
8- تهانينا
تهانينا، لقد نجحت في إنشاء وحدة تحكّم عن بُعد على جميع الأجهزة باستخدام Flutter.
ائتمانات
Better Together، أغنية من Firebase
- موسيقى من تأليف "رايان فيرنون"
- كلمات الأغنية وغلاف الألبوم من تأليف "ماريسا كريستي"
- صوت JP Gomez
9- مكافأة
بالإضافة إلى ذلك، ننصحك باستخدام Flutter FutureBuilder
لإضافة نوع الجهاز الرئيسي الحالي إلى واجهة المستخدم بشكل غير متزامن. إذا كنت بحاجة إلى مساعدة، سيتم تنفيذها في المجلد الذي يحتوي على حالة اكتمال الدرس التطبيقي حول الترميز.