1. Wprowadzenie
Ostatnia aktualizacja: 2022-03-14
FlutterFire do komunikacji między urządzeniami
Wraz z pojawianiem się w internecie dużej liczby urządzeń do automatyzacji domu, urządzeń do noszenia i technologii związanych ze zdrowiem osobistym komunikacja między urządzeniami staje się coraz ważniejszym elementem tworzenia aplikacji mobilnych. Konfigurowanie komunikacji między urządzeniami, np. sterowanie przeglądarką z aplikacji na telefonie lub sterowanie odtwarzaniem na telewizorze z telefonu, jest zwykle bardziej skomplikowane niż tworzenie zwykłej aplikacji mobilnej.
Baza danych czasu rzeczywistego Firebase udostępnia Presence API , które pozwala użytkownikom sprawdzać stan online/offline urządzenia. Używaj go z usługą Instalacje Firebase, aby śledzić i łączyć wszystkie urządzenia, na których zalogował się ten sam użytkownik. Za pomocą Fluttera szybko utworzysz aplikacje na różne platformy, a następnie zbudujesz prototyp na wiele urządzeń, który odtwarza muzykę na jednym urządzeniu i steruje nią na innym.
Co utworzysz
W tym ćwiczeniu z programowania utworzysz prosty pilot do odtwarzacza muzyki. Twoja aplikacja będzie:
- Prosty odtwarzacz muzyki na Androida, iOS i w przeglądarce utworzony za pomocą Fluttera.
- Zezwalaj użytkownikom na logowanie się.
- Łączenie urządzeń, gdy ten sam użytkownik jest zalogowany na kilku urządzeniach.
- Umożliwia użytkownikom sterowanie odtwarzaniem muzyki na jednym urządzeniu z poziomu innego urządzenia.
Czego się nauczysz
- Jak utworzyć i uruchomić aplikację odtwarzacza muzyki w Flutterze.
- Jak zezwolić użytkownikom na logowanie się za pomocą Uwierzytelniania Firebase.
- Jak używać interfejsu Firebase RTDB Presence API i usługi instalacji Firebase do łączenia urządzeń.
Czego potrzebujesz
- środowisko programistyczne Fluttera, Aby go skonfigurować, postępuj zgodnie z instrukcjami w przewodniku instalacji Fluttera.
- Wymagana jest minimalna wersja Fluttera 2.10 lub nowsza. Jeśli masz starszą wersję, uruchom polecenie
flutter upgrade.
- Konto Firebase.
2. Przygotowanie
Pobieranie kodu startowego
Utworzyliśmy aplikację odtwarzacza muzyki w Flutterze. Kod początkowy znajduje się w repozytorium Git. Aby rozpocząć, w wierszu poleceń sklonuj repozytorium, przejdź do folderu ze stanem początkowym i zainstaluj zależności:
git clone https://github.com/FirebaseExtended/cross-device-controller.git
cd cross-device-controller/starter_code
flutter pub get
Tworzenie aplikacji
Aplikację możesz utworzyć w ulubionym środowisku IDE lub przy użyciu wiersza poleceń.
W katalogu aplikacji skompiluj aplikację na potrzeby internetu za pomocą polecenia flutter run -d web-server.
Powinien pojawić się ten prompt.
lib/main.dart is being served at http://localhost:<port>
Otwórz http://localhost:<port>
, aby wyświetlić odtwarzacz muzyki.
Jeśli znasz emulator Androida lub symulator iOS, możesz skompilować aplikację na te platformy i zainstalować ją za pomocą polecenia flutter run -d <device_name>
.
Aplikacja internetowa powinna wyświetlać podstawowy samodzielny odtwarzacz muzyki. Sprawdź, czy funkcje odtwarzacza działają zgodnie z oczekiwaniami. To prosta aplikacja do odtwarzania muzyki zaprojektowana na potrzeby tych ćwiczeń z programowania. Może odtworzyć tylko utwór Better Together z Firebase.
Konfigurowanie emulatora Androida lub symulatora iOS
Jeśli masz już urządzenie z Androidem lub iOS do celów programistycznych, możesz pominąć ten krok.
Aby utworzyć emulator Androida, pobierz Android Studio, które obsługuje też tworzenie aplikacji we Flutterze, i postępuj zgodnie z instrukcjami w artykule Tworzenie urządzeń wirtualnych i zarządzanie nimi.
Aby utworzyć symulator iOS, musisz mieć środowisko Mac. Pobierz XCode i postępuj zgodnie z instrukcjami w sekcji Omówienie symulatora > Korzystanie z symulatora > Otwieranie i zamykanie symulatora.
3. Konfigurowanie Firebase
Tworzenie projektu Firebase
- Zaloguj się w konsoli Firebase, korzystając ze swojego konta Google.
- Kliknij przycisk, aby utworzyć nowy projekt, a potem wpisz jego nazwę (np.
Firebase-Cross-Device-Codelab
).
- Kliknij Dalej.
- Po wyświetleniu monitu przeczytaj i zaakceptuj warunki usługi Firebase, a potem kliknij Dalej.
- (Opcjonalnie) Włącz w konsoli Firebase pomoc AI (nazywaną „Gemini w Firebase”).
- W tym samouczku nie potrzebujesz Google Analytics, więc wyłącz opcję Google Analytics.
- Kliknij Utwórz projekt, poczekaj, aż projekt zostanie udostępniony, a następnie kliknij Dalej.
Instalowanie pakietu SDK Firebase
W wierszu poleceń w katalogu projektu uruchom to polecenie, aby zainstalować Firebase:
flutter pub add firebase_core
W pliku pubspec.yaml
zmień wersję firebase_core
na co najmniej 1.13.1 lub uruchom flutter upgrade
Inicjowanie FlutterFire
- Jeśli nie masz zainstalowanego wiersza poleceń Firebase, możesz go zainstalować, uruchamiając polecenie
curl -sL https://firebase.tools | bash
. - Zaloguj się, uruchamiając polecenie
firebase login
i postępując zgodnie z wyświetlanymi instrukcjami. - Zainstaluj interfejs wiersza poleceń FlutterFire, uruchamiając polecenie
dart pub global activate flutterfire_cli
. - Skonfiguruj interfejs wiersza poleceń FlutterFire, uruchamiając polecenie
flutterfire configure
. - W odpowiedzi na wyświetlony monit wybierz projekt, który został utworzony na potrzeby tego laboratorium, np. Firebase-Cross-Device-Codelab.
- Gdy pojawi się prośba o wybranie obsługi konfiguracji, kliknij iOS, Android i Sieć.
- Gdy pojawi się prośba o podanie identyfikatora pakietu Apple, wpisz unikalną domenę lub wpisz
com.example.appname
. W tym samouczku jest to dopuszczalne.
Po skonfigurowaniu zostanie wygenerowany plik firebase_options.dart
zawierający wszystkie opcje wymagane do inicjowania.
W edytorze dodaj do pliku main.dart ten kod, aby zainicjować Fluttera i 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());
}
Skompiluj aplikację za pomocą tego polecenia:
flutter run
Nie zmieniono jeszcze żadnych elementów interfejsu, więc wygląd i działanie aplikacji nie uległy zmianie. Masz już jednak aplikację Firebase i możesz zacząć korzystać z usług Firebase, takich jak:
- Uwierzytelnianie Firebase, które umożliwia użytkownikom logowanie się w aplikacji.
- Baza danych czasu rzeczywistego Firebase (RTDB): do śledzenia stanu urządzenia (online/offline) będziesz używać interfejsu Presence API.
- Reguły zabezpieczeń Firebase pozwolą Ci zabezpieczyć bazę danych.
- Usługa instalacji Firebase do identyfikowania urządzeń, na których zalogował się jeden użytkownik.
4. Dodawanie Uwierzytelniania Firebase
Włączanie logowania przez e-mail w usłudze Uwierzytelnianie Firebase
Aby umożliwić użytkownikom logowanie się w aplikacji internetowej, użyj metody logowania E-mail/hasło:
- W konsoli Firebase rozwiń menu Build (Kompilacja) w panelu po lewej stronie.
- Kliknij Uwierzytelnianie, a następnie przycisk Rozpocznij i kartę Metoda logowania.
- Na liście Dostawcy logowania kliknij E-mail/hasło, ustaw przełącznik Włącz w pozycji włączonej, a następnie kliknij Zapisz.
Konfigurowanie uwierzytelniania Firebase w Flutterze
W wierszu poleceń uruchom te polecenia, aby zainstalować niezbędne pakiety Fluttera:
flutter pub add firebase_auth
flutter pub add provider
Dzięki tej konfiguracji możesz teraz utworzyć proces logowania i wylogowywania. Stan uwierzytelniania nie powinien się zmieniać na poszczególnych ekranach, dlatego utworzysz klasę application_state.dart
, aby śledzić zmiany stanu na poziomie aplikacji, takie jak logowanie i wylogowywanie. Więcej informacji znajdziesz w dokumentacji zarządzania stanem w Flutterze.
Wklej ten kod do nowego pliku 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();
}
}
Aby mieć pewność, że ApplicationState
zostanie zainicjowany po uruchomieniu aplikacji, dodaj krok inicjowania do 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(),
));
}
Interfejs aplikacji powinien pozostać taki sam, ale teraz możesz umożliwić użytkownikom logowanie się i zapisywanie stanów aplikacji.
Tworzenie przepływu logowania
W tym kroku zajmiesz się procesem logowania i wylogowywania. Proces będzie wyglądać tak:
- Wylogowany użytkownik rozpocznie proces logowania, klikając menu kontekstowe
po prawej stronie paska aplikacji.
- Proces logowania zostanie wyświetlony w oknie.
- Jeśli użytkownik nigdy wcześniej się nie logował, pojawi się prośba o utworzenie konta przy użyciu prawidłowego adresu e-mail i hasła.
- Jeśli użytkownik logował się już wcześniej, zostanie poproszony o wpisanie hasła.
- Gdy użytkownik się zaloguje, kliknięcie menu kontekstowego spowoduje wyświetlenie opcji Wyloguj.
Dodanie procesu logowania wymaga wykonania 3 kroków.
Najpierw utwórz widżet AppBarMenuButton
. Ten widżet będzie kontrolować wyskakujące menu kontekstowe w zależności od loginState
użytkownika. Dodaj importy
lib/src/widgets.dart
import 'application_state.dart';
import 'package:provider/provider.dart';
import 'authentication.dart';
Dodaj do pliku widgets.dart.
ten kod:
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),
),
),
];
}
}
Po drugie, w tej samej klasie widgets.dart
utwórz widżet 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,
),
),
]),
);
}
}
Po trzecie, znajdź istniejący widżet appBar w main.dart.
Dodaj AppBarMenuButton
, aby wyświetlić opcję Zaloguj się lub Wyloguj się.
lib/main.dart
import 'src/widgets.dart';
appBar: AppBar(
title: const Text('Music Box'),
backgroundColor: Colors.deepPurple.shade400,
actions: const <Widget>[
AppBarMenuButton(),
],
),
Uruchom polecenie flutter run
, aby ponownie uruchomić aplikację z tymi zmianami. Menu kontekstowe powinno być widoczne po prawej stronie paska aplikacji. Kliknięcie tego przycisku spowoduje otwarcie okna logowania.
Po zalogowaniu się za pomocą prawidłowego adresu e-mail i hasła w menu kontekstowym powinna być widoczna opcja Wyloguj się.
W konsoli Firebase w sekcji Uwierzytelnianie powinien być widoczny adres e-mail jako nowy użytkownik.
Gratulacje! Użytkownicy mogą teraz zalogować się w aplikacji.
5. Dodawanie połączenia z bazą danych
Teraz możesz przejść do rejestracji urządzenia za pomocą interfejsu Firebase Presence API.
W wierszu poleceń uruchom te polecenia, aby dodać niezbędne zależności:
flutter pub add firebase_app_installations
flutter pub add firebase_database
Utwórz bazę danych
W konsoli Firebase:
- Otwórz sekcję Baza danych czasu rzeczywistego w konsoli Firebase. Kliknij Utwórz bazę danych.
- Jeśli pojawi się prośba o wybranie trybu początkowego dla reguł bezpieczeństwa, wybierz na razie Tryb testowy**.** (Tryb testowy tworzy reguły bezpieczeństwa, które zezwalają na wszystkie żądania. Reguły zabezpieczeń dodasz później. Ważne jest, aby nigdy nie przechodzić do środowiska produkcyjnego, gdy reguły bezpieczeństwa są nadal w trybie testowym).
Baza danych jest obecnie pusta. Znajdź swój databaseURL
w Ustawieniach projektu na karcie Ogólne. Przewiń w dół do sekcji Aplikacje internetowe.
Dodaj databaseURL
do pliku firebase_options.dart
.:
lib/firebase_options.dart
static const FirebaseOptions web = FirebaseOptions(
apiKey: yourApiKey,
...
databaseURL: 'https://<YOUR_DATABASE_URL>,
...
);
Rejestrowanie urządzeń za pomocą interfejsu RTDB Presence API
Chcesz zarejestrować urządzenia użytkownika, gdy pojawią się w trybie online. Aby to zrobić, skorzystaj z instalacji Firebase i interfejsu Firebase RTDB Presence API, aby śledzić listę urządzeń online jednego użytkownika. Pomoże Ci w tym ten kod:
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';
}
Wróć do wiersza poleceń i skompiluj oraz uruchom aplikację na urządzeniu lub w przeglądarce za pomocą polecenia flutter run.
Zaloguj się w aplikacji jako użytkownik. Pamiętaj, aby logować się jako ten sam użytkownik na różnych platformach.
W konsoli Firebase urządzenia powinny być widoczne w bazie danych pod jednym identyfikatorem użytkownika.
6. Synchronizowanie stanu urządzenia
Wybierz urządzenie główne
Aby synchronizować stany między urządzeniami, wyznacz jedno z nich jako główne lub kontroler. Urządzenie główne będzie określać stany urządzeń podrzędnych.
Utwórz metodę setLeadDevice
w application_state.dart
i śledź to urządzenie za pomocą klucza active_device
w 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;
});
}
}
Aby dodać tę funkcję do menu kontekstowego paska aplikacji, utwórz PopupMenuItem
o nazwie Controller
, modyfikując widżet SignedInMenuButton
. W tym menu użytkownicy będą mogli ustawić urządzenie główne.
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();
}
}
}
Zapisywanie stanu urządzenia głównego w bazie danych
Po ustawieniu urządzenia głównego możesz zsynchronizować jego stany z bazą danych czasu rzeczywistego za pomocą tego kodu. Dołącz poniższy kod na końcu application_state.dart.
Rozpocznie to przechowywanie 2 atrybutów: stanu odtwarzacza (odtwarzanie lub wstrzymanie) i pozycji suwaka.
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');
}
}
}
Na koniec musisz wywołać funkcję setActiveDeviceState
za każdym razem, gdy stan odtwarzacza kontrolera zostanie zaktualizowany. Wprowadź te zmiany w istniejącym pliku 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;
}
Odczytywanie stanu urządzenia głównego z bazy danych
Aby odczytać stan urządzenia głównego i go użyć, musisz wykonać 2 czynności. Najpierw skonfiguruj w application_state
odbiornik bazy danych stanu głównego odtwarzacza. Ten odbiornik poinformuje urządzenia śledzące, kiedy zaktualizować ekran, za pomocą wywołania zwrotnego. Zwróć uwagę, że w tym kroku zdefiniowano interfejs OnLeadDeviceChangeCallback
. Nie jest jeszcze zaimplementowany. Zaimplementujesz ten interfejs w player_widget.dart
w następnym kroku.
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();
});
}
}
Po drugie, uruchom nasłuchiwanie bazy danych podczas inicjowania odtwarzacza w player_widget.dart
. Przekaż funkcję _updatePlayer
, aby stan gracza śledzącego mógł być aktualizowany za każdym razem, gdy zmieni się wartość w bazie danych.
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');
}
}
}
}
Możesz teraz przetestować aplikację:
- W wierszu poleceń uruchom aplikację na emulatorach lub w przeglądarce za pomocą tego polecenia:
flutter run -d <device-name>
- Otwórz aplikacje w przeglądarce, na symulatorze iOS lub emulatorze Androida. Otwórz menu kontekstowe i wybierz jedną aplikację jako urządzenie główne. Powinno być widać, jak odtwarzacze na urządzeniach podrzędnych zmieniają się wraz z aktualizacją urządzenia głównego.
- Teraz zmień urządzenie główne, odtwórz lub wstrzymaj muzykę i obserwuj, jak urządzenia podrzędne odpowiednio się aktualizują.
Jeśli urządzenia podrzędne zostaną prawidłowo zaktualizowane, oznacza to, że udało Ci się utworzyć kontroler na wielu urządzeniach. Pozostał tylko jeden kluczowy krok.
7. Aktualizowanie reguł zabezpieczeń
Jeśli nie napiszemy lepszych reguł bezpieczeństwa, ktoś może zapisać stan na urządzeniu, które nie jest jego własnością. Zanim skończysz, zaktualizuj reguły bezpieczeństwa bazy danych czasu rzeczywistego, aby mieć pewność, że tylko użytkownik zalogowany na urządzeniu może odczytywać i zapisywać dane na tym urządzeniu. W konsoli Firebase otwórz Bazę danych czasu rzeczywistego, a następnie kliknij kartę Reguły. Wklej te reguły, aby zezwolić tylko zalogowanym użytkownikom na odczytywanie i zapisywanie stanów własnych urządzeń:
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
},
}
}
8. Gratulacje!
Gratulacje! Udało Ci się utworzyć zdalny kontroler na różne urządzenia za pomocą Fluttera.
Środki
Better Together, a Firebase Song
- Muzyka: Ryan Vernon
- Teksty i okładka albumu: Marissa Christy
- Głos: JP Gomez
9. Premia
Dodatkowym wyzwaniem może być użycie FlutteraFutureBuilder
do asynchronicznego dodawania do interfejsu typu bieżącego urządzenia wiodącego. Jeśli potrzebujesz pomocy, znajdziesz ją w folderze zawierającym ukończoną wersję ćwiczenia.