1. Giới thiệu
Cập nhật lần cuối: 2022-03-14
FlutterFire để liên lạc giữa các thiết bị
Khi chúng ta chứng kiến một số lượng lớn các thiết bị công nghệ sức khỏe cá nhân, thiết bị đeo và tự động hóa gia đình xuất hiện trực tuyến, giao tiếp giữa các thiết bị ngày càng trở thành một phần quan trọng trong việc xây dựng các ứng dụng di động. Thiết lập liên lạc giữa các thiết bị, chẳng hạn như điều khiển trình duyệt từ ứng dụng điện thoại hoặc kiểm soát nội dung phát trên TV từ điện thoại, theo truyền thống phức tạp hơn so với việc xây dựng một ứng dụng di động thông thường.
Cơ sở dữ liệu thời gian thực của Firebase cung cấp API hiện diện cho phép người dùng xem trạng thái trực tuyến/ngoại tuyến của thiết bị; bạn sẽ sử dụng nó với Dịch vụ cài đặt Firebase để theo dõi và kết nối tất cả các thiết bị mà cùng một người dùng đã đăng nhập. Bạn sẽ sử dụng Flutter để nhanh chóng tạo ứng dụng cho nhiều nền tảng và sau đó bạn sẽ xây dựng một nguyên mẫu thiết bị chéo để phát nhạc trên một thiết bị và điều khiển nhạc trên thiết bị khác!
Những gì bạn sẽ xây dựng
Trong lớp học lập trình này, bạn sẽ xây dựng một bộ điều khiển từ xa của trình phát nhạc đơn giản. Ứng dụng của bạn sẽ:
- Có trình phát nhạc đơn giản trên Android, iOS và web, được xây dựng bằng Flutter.
- Cho phép người dùng đăng nhập.
- Kết nối thiết bị khi cùng một người dùng đăng nhập trên nhiều thiết bị.
- Cho phép người dùng điều khiển việc phát lại nhạc trên thiết bị này từ thiết bị khác.
Bạn sẽ học được gì
- Cách xây dựng và chạy ứng dụng trình phát nhạc Flutter.
- Cách cho phép người dùng đăng nhập bằng Firebase Auth.
- Cách sử dụng API hiện diện Firebase RTDB và Dịch vụ cài đặt Firebase để kết nối các thiết bị.
Những gì bạn cần
- Một môi trường phát triển Flutter. Làm theo hướng dẫn trong hướng dẫn cài đặt Flutter để thiết lập.
- Cần có phiên bản Flutter tối thiểu từ 2.10 trở lên. Nếu bạn có phiên bản thấp hơn, hãy chạy
flutter upgrade.
- Tài khoản Firebase.
2. Bắt đầu thiết lập
Lấy mã khởi đầu
Chúng tôi đã tạo một ứng dụng trình phát nhạc trong Flutter. Mã khởi động nằm trong kho lưu trữ Git. Để bắt đầu, trên dòng lệnh, sao chép repo, di chuyển vào thư mục có trạng thái bắt đầu và cài đặt các phần phụ thuộc:
git clone https://github.com/FirebaseExtended/cross-device-controller.git
cd cross-device-controller/starter_code
flutter pub get
Xây dựng ứng dụng
Bạn có thể làm việc với IDE yêu thích của mình để xây dựng ứng dụng hoặc sử dụng dòng lệnh.
Trong thư mục ứng dụng của bạn, hãy xây dựng ứng dụng cho web bằng lệnh flutter run -d web-server.
Bạn sẽ có thể nhìn thấy lời nhắc sau đây.
lib/main.dart is being served at http://localhost:<port>
Truy cập http://localhost:<port>
để xem trình phát nhạc.
Nếu đã quen với trình mô phỏng Android hoặc trình mô phỏng iOS, bạn có thể xây dựng ứng dụng cho các nền tảng đó và cài đặt nó bằng lệnh flutter run -d <device_name>
.
Ứng dụng web sẽ hiển thị một trình phát nhạc độc lập cơ bản. Đảm bảo các tính năng của trình phát đang hoạt động như dự định. Đây là một ứng dụng trình phát nhạc đơn giản được thiết kế cho lớp học lập trình này. Nó chỉ có thể phát một bài hát của Firebase, Better Together .
Thiết lập trình giả lập Android hoặc trình mô phỏng iOS
Nếu bạn đã có thiết bị Android hoặc thiết bị iOS để phát triển, bạn có thể bỏ qua bước này.
Để tạo trình giả lập Android, hãy tải xuống Android Studio cũng hỗ trợ phát triển Flutter và làm theo hướng dẫn trong Tạo và quản lý thiết bị ảo .
Để tạo trình giả lập iOS, bạn sẽ cần môi trường Mac. Tải xuống XCode và làm theo hướng dẫn trong Tổng quan về trình mô phỏng > Sử dụng trình mô phỏng > Mở và đóng trình mô phỏng .
3. Thiết lập Firebase
Tạo dự án Firebase
Mở trình duyệt tới http://console.firebase.google.com/ .
- Đăng nhập vào Firebase .
- Trong bảng điều khiển Firebase, hãy nhấp vào Thêm dự án (hoặc Tạo dự án ) và đặt tên cho dự án Firebase của bạn là Firebase-Cross-Device-Codelab .
- Nhấp qua các tùy chọn tạo dự án. Chấp nhận các điều khoản của Firebase nếu được nhắc. Bỏ qua việc thiết lập Google Analytics vì bạn sẽ không sử dụng Analytics cho ứng dụng này.
Bạn không cần tải xuống các tệp được đề cập hoặc thay đổi tệp build.gradle. Bạn sẽ định cấu hình chúng khi khởi tạo FlutterFire.
Cài đặt SDK Firebase
Quay lại dòng lệnh, trong thư mục dự án, chạy lệnh sau để cài đặt Firebase:
flutter pub add firebase_core
Trong file pubspec.yaml
sửa phiên bản cho firebase_core
tối thiểu là 1.13.1 hoặc chạy flutter upgrade
Khởi tạo FlutterFire
- Nếu chưa cài đặt Firebase CLI, bạn có thể cài đặt nó bằng cách chạy
curl -sL https://firebase.tools | bash
. - Đăng nhập bằng cách chạy
firebase login
và làm theo lời nhắc. - Cài đặt FlutterFire CLI bằng cách chạy
dart pub global activate flutterfire_cli
. - Định cấu hình FlutterFire CLI bằng cách chạy
flutterfire configure
. - Khi được nhắc, hãy chọn dự án bạn vừa tạo cho lớp học lập trình này, chẳng hạn như Firebase-Cross-Device-Codelab .
- Chọn iOS , Android và Web khi bạn được nhắc chọn hỗ trợ cấu hình.
- Khi được nhắc nhập ID gói Apple , hãy nhập một miền duy nhất hoặc nhập
com.example.appname
, điều này phù hợp với mục đích của lớp học lập trình này.
Sau khi được định cấu hình, tệp firebase_options.dart
sẽ được tạo cho bạn chứa tất cả các tùy chọn cần thiết để khởi tạo.
Trong trình chỉnh sửa của bạn, hãy thêm mã sau vào tệp main.dart để khởi chạy Flutter và 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());
}
Biên dịch ứng dụng bằng lệnh:
flutter run
Bạn chưa thay đổi bất kỳ thành phần giao diện người dùng nào nên giao diện và hoạt động của ứng dụng không thay đổi. Nhưng bây giờ bạn đã có ứng dụng Firebase và có thể bắt đầu sử dụng các sản phẩm Firebase, bao gồm:
- Xác thực Firebase , cho phép người dùng đăng nhập vào ứng dụng của bạn.
- Cơ sở dữ liệu thời gian thực Firebase (RTDB) ; bạn sẽ sử dụng API hiện diện để theo dõi trạng thái trực tuyến/ngoại tuyến của thiết bị
- Quy tắc bảo mật Firebase sẽ cho phép bạn bảo mật cơ sở dữ liệu.
- Dịch vụ cài đặt Firebase để xác định các thiết bị mà một người dùng đã đăng nhập.
4. Thêm xác thực Firebase
Cho phép đăng nhập email để xác thực Firebase
Để cho phép người dùng đăng nhập vào ứng dụng web, bạn sẽ sử dụng phương thức đăng nhập Email/Mật khẩu :
- Trong bảng điều khiển Firebase, hãy mở rộng menu Build ở bảng điều khiển bên trái.
- Nhấp vào Xác thực rồi nhấp vào nút Bắt đầu , sau đó nhấp vào tab Phương thức đăng nhập .
- Nhấp vào Email/Mật khẩu trong danh sách Nhà cung cấp dịch vụ đăng nhập , đặt nút Bật sang vị trí bật rồi nhấp vào Lưu .
Định cấu hình xác thực Firebase trong Flutter
Trên dòng lệnh, chạy các lệnh sau để cài đặt các gói rung cần thiết:
flutter pub add firebase_auth
flutter pub add provider
Với cấu hình này, giờ đây bạn có thể tạo luồng đăng nhập và đăng xuất. Vì trạng thái xác thực không nên thay đổi từ màn hình này sang màn hình khác nên bạn sẽ tạo lớp application_state.dart
để theo dõi các thay đổi trạng thái cấp ứng dụng, chẳng hạn như đăng nhập và đăng xuất. Tìm hiểu thêm về điều này trong tài liệu quản lý trạng thái Flutter .
Dán phần sau vào tệp application_state.dart
mới:
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();
}
}
Để đảm bảo ApplicationState
sẽ được khởi tạo khi ứng dụng khởi động, bạn sẽ thêm bước khởi tạo vào 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(),
));
}
Một lần nữa, giao diện người dùng ứng dụng lẽ ra vẫn giữ nguyên nhưng giờ đây bạn có thể cho phép người dùng đăng nhập và lưu trạng thái ứng dụng.
Tạo luồng đăng nhập
Ở bước này, bạn sẽ thực hiện quy trình đăng nhập và đăng xuất. Đây là dòng chảy sẽ trông như thế nào:
- Người dùng đã đăng xuất sẽ bắt đầu luồng đăng nhập bằng cách nhấp vào menu ngữ cảnh ở phía bên phải của thanh ứng dụng.
- Luồng đăng nhập sẽ được hiển thị trong hộp thoại.
- Nếu người dùng chưa từng đăng nhập trước đó, họ sẽ được nhắc tạo tài khoản bằng địa chỉ email và mật khẩu hợp lệ.
- Nếu người dùng đã đăng nhập trước đó, họ sẽ được nhắc nhập mật khẩu của mình.
- Khi người dùng đã đăng nhập, nhấp vào menu ngữ cảnh sẽ hiển thị tùy chọn Đăng xuất .
Việc thêm luồng đăng nhập yêu cầu ba bước.
Trước hết, hãy tạo tiện ích AppBarMenuButton
. Tiện ích này sẽ kiểm soát cửa sổ bật lên của menu ngữ cảnh tùy thuộc vào loginState
của người dùng. Thêm hàng nhập khẩu
lib/src/widgets.dart
import 'application_state.dart';
import 'package:provider/provider.dart';
import 'authentication.dart';
Nối đoạn mã sau vào 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),
),
),
];
}
}
Thứ hai, trong cùng lớp widgets.dart
, hãy tạo tiện ích 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,
),
),
]),
);
}
}
Thứ ba, tìm tiện ích appBar hiện có trong main.dart.
Thêm AppBarMenuButton
để hiển thị tùy chọn Đăng nhập hoặc Đăng xuất .
lib/main.dart
import 'src/widgets.dart';
appBar: AppBar(
title: const Text('Music Box'),
backgroundColor: Colors.deepPurple.shade400,
actions: const <Widget>[
AppBarMenuButton(),
],
),
Chạy lệnh flutter run
để khởi động lại ứng dụng với những thay đổi này. Bạn sẽ có thể nhìn thấy menu ngữ cảnh ở phía bên phải của thanh ứng dụng. Nhấp vào nó sẽ đưa bạn đến hộp thoại đăng nhập.
Sau khi đăng nhập bằng địa chỉ email và mật khẩu hợp lệ, bạn sẽ có thể thấy tùy chọn Đăng xuất trong menu ngữ cảnh.
Trong bảng điều khiển Firebase, trong Xác thực , bạn sẽ có thể thấy địa chỉ email được liệt kê là người dùng mới.
Chúc mừng! Người dùng hiện có thể đăng nhập vào ứng dụng!
5. Thêm kết nối cơ sở dữ liệu
Bây giờ bạn đã sẵn sàng chuyển sang đăng ký thiết bị bằng API hiện diện của Firebase.
Trên dòng lệnh, hãy chạy các lệnh sau để thêm các phần phụ thuộc cần thiết:
flutter pub add firebase_app_installations
flutter pub add firebase_database
Tạo cơ sở dữ liệu
Trong bảng điều khiển Firebase,
- Điều hướng đến phần Cơ sở dữ liệu thời gian thực của bảng điều khiển Firebase . Nhấp vào Tạo cơ sở dữ liệu .
- Nếu được nhắc chọn chế độ bắt đầu cho các quy tắc bảo mật của bạn, hãy chọn Chế độ kiểm tra ngay bây giờ**.** (Chế độ kiểm tra tạo Quy tắc bảo mật cho phép tất cả các yêu cầu được thông qua. Bạn sẽ thêm Quy tắc bảo mật sau. Điều quan trọng là không bao giờ đưa vào sản xuất với Quy tắc bảo mật của bạn vẫn ở Chế độ thử nghiệm.)
Cơ sở dữ liệu hiện đang trống. Xác định databaseURL
của bạn trong Cài đặt dự án , trong tab Chung . Cuộn xuống phần Ứng dụng web .
Thêm databaseURL
của bạn vào tệp firebase_options.dart
:
lib/firebase_options.dart
static const FirebaseOptions web = FirebaseOptions(
apiKey: yourApiKey,
...
databaseURL: 'https://<YOUR_DATABASE_URL>,
...
);
Đăng ký thiết bị bằng API hiện diện RTDB
Bạn muốn đăng ký thiết bị của người dùng khi chúng xuất hiện trực tuyến. Để thực hiện việc này, bạn sẽ tận dụng Cài đặt Firebase và API hiện diện Firebase RTDB để theo dõi danh sách các thiết bị trực tuyến từ một người dùng. Đoạn mã sau sẽ giúp hoàn thành mục tiêu này:
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';
}
Quay lại dòng lệnh, xây dựng và chạy ứng dụng trên thiết bị của bạn hoặc trong trình duyệt có flutter run.
Trong ứng dụng của bạn, hãy đăng nhập với tư cách người dùng. Hãy nhớ đăng nhập với cùng một người dùng trên các nền tảng khác nhau.
Trong bảng điều khiển Firebase , bạn sẽ thấy thiết bị của mình hiển thị dưới một ID người dùng trong cơ sở dữ liệu của bạn.
6. Đồng bộ trạng thái thiết bị
Chọn thiết bị dẫn
Để đồng bộ hóa trạng thái giữa các thiết bị, hãy chỉ định một thiết bị làm thiết bị dẫn đầu hoặc bộ điều khiển. Thiết bị dẫn đầu sẽ ra lệnh cho các trạng thái trên thiết bị theo dõi.
Tạo phương thức setLeadDevice
trong application_state.dart
và theo dõi thiết bị này bằng khóa active_device
trong 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;
});
}
}
Để thêm chức năng này vào menu ngữ cảnh của thanh ứng dụng, hãy tạo PopupMenuItem
có tên là Controller
bằng cách sửa đổi tiện ích SignedInMenuButton
. Menu này sẽ cho phép người dùng thiết lập thiết bị dẫn đầu.
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();
}
}
}
Ghi trạng thái của thiết bị dẫn vào cơ sở dữ liệu
Sau khi đặt thiết bị dẫn, bạn có thể đồng bộ hóa trạng thái của thiết bị dẫn với RTDB bằng mã sau. Nối đoạn mã sau vào cuối application_state.dart.
Thao tác này sẽ bắt đầu lưu trữ hai thuộc tính: trạng thái trình phát (phát hoặc tạm dừng) và vị trí thanh trượt.
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');
}
}
}
Và cuối cùng, bạn cần gọi setActiveDeviceState
bất cứ khi nào trạng thái trình phát của bộ điều khiển cập nhật. Thực hiện các thay đổi sau đối với tệp player_widget.dart
hiện có:
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;
}
Đọc trạng thái của thiết bị dẫn từ cơ sở dữ liệu
Có hai phần để đọc và sử dụng trạng thái của thiết bị dẫn. Trước tiên, bạn muốn thiết lập trình xử lý cơ sở dữ liệu về trạng thái trình phát chính trong application_state
. Trình nghe này sẽ thông báo cho các thiết bị theo dõi khi nào cần cập nhật màn hình thông qua lệnh gọi lại. Lưu ý rằng bạn đã xác định giao diện OnLeadDeviceChangeCallback
trong bước này. Nó chưa được triển khai; bạn sẽ triển khai giao diện này trong player_widget.dart
ở bước tiếp theo.
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();
});
}
}
Thứ hai, khởi động trình nghe cơ sở dữ liệu trong quá trình khởi tạo trình phát trong player_widget.dart
. Truyền hàm _updatePlayer
để có thể cập nhật trạng thái trình phát của người theo dõi bất cứ khi nào giá trị cơ sở dữ liệu thay đổi.
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');
}
}
}
}
Bây giờ bạn đã sẵn sàng để thử nghiệm ứng dụng:
- Trên dòng lệnh, chạy ứng dụng trên trình mô phỏng và/hoặc trong trình duyệt có:
flutter run -d <device-name>
- Mở ứng dụng trong trình duyệt, trên trình mô phỏng iOS hoặc trình mô phỏng Android. Chuyển đến menu ngữ cảnh, chọn một ứng dụng làm thiết bị dẫn đầu. Bạn sẽ có thể thấy trình phát của thiết bị theo dõi thay đổi khi thiết bị dẫn đầu cập nhật.
- Bây giờ hãy thay đổi thiết bị dẫn đầu, phát hoặc tạm dừng nhạc và quan sát các thiết bị theo dõi cập nhật tương ứng.
Nếu các thiết bị theo dõi cập nhật đúng cách, bạn đã thành công trong việc tạo bộ điều khiển thiết bị chéo. Chỉ còn một bước quan trọng nữa thôi.
7. Cập nhật quy tắc bảo mật
Trừ khi chúng tôi viết các quy tắc bảo mật tốt hơn, nếu không ai đó có thể viết trạng thái cho thiết bị mà họ không sở hữu! Vì vậy, trước khi bạn hoàn tất, hãy cập nhật Quy tắc bảo mật cơ sở dữ liệu thời gian thực để đảm bảo người dùng duy nhất có thể đọc hoặc ghi vào thiết bị là người dùng đã đăng nhập vào thiết bị đó. Trong Bảng điều khiển Firebase, hãy điều hướng đến Cơ sở dữ liệu thời gian thực và sau đó đến tab Quy tắc . Dán các quy tắc sau chỉ cho phép người dùng đã đăng nhập đọc và ghi trạng thái thiết bị của riêng họ:
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
},
}
}
8. Xin chúc mừng!
Xin chúc mừng, bạn đã xây dựng thành công bộ điều khiển từ xa cho nhiều thiết bị bằng Flutter!
Tín dụng
Cùng nhau tốt đẹp hơn, một bài hát của Firebase
- Âm nhạc của Ryan Vernon
- Lời bài hát và bìa album của Marissa Christy
- Giọng nói của JP Gomez
9. Tiền thưởng
Là một thách thức bổ sung, hãy cân nhắc sử dụng Flutter FutureBuilder
để thêm loại thiết bị khách hàng tiềm năng hiện tại vào giao diện người dùng một cách không đồng bộ. Nếu bạn cần hỗ trợ, nó sẽ được triển khai trong thư mục chứa trạng thái hoàn thành của lớp học lập trình.