1. Wprowadzenie
Ostatnia aktualizacja: 14.03.2022 r.
FlutterFire umożliwia komunikację między urządzeniami
Ponieważ obserwujemy, jak w internecie pojawia się coraz więcej automatyki domowej, urządzeń do noszenia i osobistych technologii zdrowotnych, komunikacja między urządzeniami staje się coraz ważniejszym elementem tworzenia aplikacji mobilnych. Konfigurowanie komunikacji między urządzeniami, np. sterowanie przeglądarką za pomocą aplikacji na telefonie czy sterowanie odtwarzaniem na telewizorze z telefonu, jest zazwyczaj bardziej skomplikowane niż tworzenie zwykłej aplikacji mobilnej .
Baza danych czasu rzeczywistego Firebase udostępnia interfejs Presence API , który pozwala użytkownikom sprawdzać stan urządzeń online i offline. będziesz używać go w połączeniu z usługą instalacji Firebase do śledzenia i łączenia wszystkich urządzeń, na których zalogował się ten sam użytkownik. Za pomocą Flutter szybko utworzysz aplikacje na wiele platform, a potem zbudujesz prototyp obejmujący wiele urządzeń, który odtwarza muzykę na jednym urządzeniu, a steruje muzyką na innym.
Co utworzysz
W ramach tego ćwiczenia w Codelabs dowiesz się, jak stworzyć prosty kontroler zdalny do odtwarzacza muzyki. Twoja aplikacja będzie:
- Prosty odtwarzacz muzyki na Androida, iOS i w przeglądarce, oparty na technologii Flutter.
- Zezwalaj użytkownikom na logowanie się.
- Połącz urządzenia, gdy ten sam użytkownik jest zalogowany na kilku urządzeniach.
- Aplikacje tego typu pozwalają użytkownikom sterować odtwarzaniem muzyki na jednym urządzeniu z innego.
Czego się nauczysz
- Jak utworzyć i uruchomić aplikację odtwarzacza muzyki Flutter.
- Jak umożliwić użytkownikom 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 Flutter. Aby ją skonfigurować, postępuj zgodnie z instrukcjami podanymi w przewodniku instalacji Flutter.
- Wymagana jest wersja Flutter co najmniej w wersji 2.10. Jeśli masz starszą wersję, uruchom
flutter upgrade.
- konto Firebase.
2. Przygotowanie
Pobierz kod startowy
Opracowaliśmy aplikację odtwarzacza muzyki w ramach technologii Flutter. Kod startowy znajduje się w repozytorium Git. Aby rozpocząć, w wierszu poleceń skopiuj 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
Utwórz aplikację
Możesz utworzyć aplikację przy użyciu swojego ulubionego IDE lub użyć wiersza poleceń.
W katalogu aplikacji skompiluj aplikację internetową za pomocą polecenia flutter run -d web-server.
powinno być wyświetlone następujący prompt.
lib/main.dart is being served at http://localhost:<port>
Wejdź na http://localhost:<port>
, by zobaczyć odtwarzacz muzyki.
Jeśli znasz emulator Androida lub symulator iOS, możesz utworzyć aplikację na te platformy i zainstalować ją za pomocą polecenia flutter run -d <device_name>
.
Aplikacja internetowa powinna pokazywać prosty, samodzielny odtwarzacz muzyki. Upewnij się, że funkcje odtwarzacza działają zgodnie z oczekiwaniami. To prosta aplikacja odtwarzacza muzyki przeznaczona do tego ćwiczenia z programowania. Może odtworzyć tylko utwór Firebase Better Together.
Konfigurowanie emulatora Androida lub symulatora iOS
Jeśli masz już urządzenie z Androidem lub iOS potrzebne do programowania, możesz pominąć ten krok.
Aby utworzyć emulator Androida, pobierz Android Studio, które obsługuje też programowanie Flutter, a potem wykonaj instrukcje podane w artykule Tworzenie urządzeń wirtualnych i zarządzanie nimi.
Do utworzenia symulatora iOS potrzebne jest środowisko Mac. Pobierz XCode i postępuj zgodnie z instrukcjami podanymi w artykule Omówienie symulatora. Użyj symulatora > Otwieranie i zamykanie symulatora
3. Konfiguruję Firebase
Utwórz projekt Firebase.
Otwórz stronę http://console.firebase.google.com/ w przeglądarce.
- Zaloguj się w Firebase.
- W konsoli Firebase kliknij Dodaj projekt (lub Utwórz projekt) i nadaj projektowi Firebase nazwę Firebase-Różne urządzenia z programowaniem.
- Klikaj opcje tworzenia projektów. Zaakceptuj warunki korzystania z Firebase, jeśli pojawi się taka prośba. Pomiń konfigurowanie Google Analytics, ponieważ nie będziesz korzystać z Analytics w tej aplikacji.
Nie musisz pobierać wspomnianych plików ani zmieniać plików build.gradle. Skonfigurujesz je podczas inicjowania FlutterFire.
Zainstaluj pakiet SDK Firebase
W wierszu poleceń, w katalogu projektu, uruchom następujące 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
Zainicjuj FlutterFire
- Jeśli nie masz zainstalowanego interfejsu wiersza poleceń Firebase, możesz go zainstalować, uruchamiając
curl -sL https://firebase.tools | bash
. - Zaloguj się, uruchamiając aplikację
firebase login
i postępując zgodnie z instrukcjami. - Zainstaluj interfejs wiersza poleceń FlutterFire, uruchamiając
dart pub global activate flutterfire_cli
. - Aby skonfigurować interfejs wiersza poleceń FlutterFire, uruchom polecenie
flutterfire configure
. - W wyświetlonym komunikacie wybierz projekt utworzony przed chwilą w ramach tego ćwiczenia z programowania. Przykład: Firebase-Różne urządzenia-Codelab.
- Gdy pojawi się prośba o wybranie obsługi konfiguracji, wybierz iOS, Android i Internet.
- Gdy pojawi się prośba o podanie identyfikatora pakietu Apple, wpisz unikalną domenę lub wpisz
com.example.appname
. Jest to dobre rozwiązanie w przypadku tego ćwiczenia.
Po skonfigurowaniu zostanie wygenerowany plik firebase_options.dart
zawierający wszystkie opcje wymagane do zainicjowania.
Aby zainicjować Flutter i Firebase, dodaj w edytorze ten kod do pliku 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());
}
Skompiluj aplikację za pomocą tego polecenia:
flutter run
Nie zmieniono jeszcze żadnych elementów interfejsu użytkownika, więc wygląd i działanie aplikacji się nie zmieniły. Jednak masz już 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); będziesz używać interfejsu obecności API do śledzenia stanu online/offline urządzenia
- Reguły zabezpieczeń Firebase pozwolą zabezpieczyć bazę danych.
- Usługa instalacji Firebase do identyfikowania urządzeń, na których zalogował się dany użytkownik.
4. Dodaj Uwierzytelnianie Firebase
Włączanie logowania przez e-mail w Uwierzytelnianiu Firebase
Aby umożliwić użytkownikom logowanie się w aplikacji internetowej, użyj metody logowania E-mail/hasło:
- W konsoli Firebase w panelu po lewej stronie rozwiń menu Tworzenie.
- Kliknij Authentication (Uwierzytelnianie), a następnie kliknij przycisk Get Started (Rozpocznij) i kartę Sign-inMethod (Metoda logowania).
- Na liście Dostawcy logowania kliknij E-mail/hasło, ustaw przełącznik Włącz w pozycji włączenia, a potem kliknij Zapisz.
Konfigurowanie uwierzytelniania Firebase we Flutter
W wierszu poleceń uruchom te polecenia, aby zainstalować niezbędne pakiety Flutter:
flutter pub add firebase_auth
flutter pub add provider
Dzięki tej konfiguracji możesz teraz utworzyć proces logowania i wylogowywania. Ponieważ stan uwierzytelniania nie powinien zmieniać się z poziomu ekranu na ekran, musisz utworzyć klasę application_state.dart
, aby śledzić zmiany stanu na poziomie aplikacji, takie jak logowanie i wylogowywanie się. Więcej informacji znajdziesz w dokumentacji dotyczącej zarządzania stanem Flutter.
Do nowego pliku application_state.dart
wklej ten kod:
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 zezwolić użytkownikom na logowanie się i zapisywanie stanów aplikacji.
Tworzenie procesu logowania
W tym kroku wykonasz proces logowania się i wylogowywania. Proces ten będzie wyglądał tak:
- Wylogowany użytkownik rozpocznie proces logowania, klikając menu kontekstowe po prawej stronie paska aplikacji.
- W oknie wyświetli się procedura logowania.
- Jeśli użytkownik nigdy nie logował się na konto, zostanie poproszony o utworzenie konta z użyciem prawidłowego adresu e-mail i hasła.
- Jeśli użytkownik logował się wcześniej, zostanie poproszony o podanie hasła.
- Gdy użytkownik się zaloguje, kliknięcie menu kontekstowego spowoduje wyświetlenie opcji Wyloguj się.
Dodanie procedury logowania wymaga wykonania 3 kroków.
Najpierw utwórz widżet AppBarMenuButton
. Ten widżet będzie sterował wyskakującym menu menu kontekstowego w zależności od loginState
użytkownika. Dodaj operacje importu
lib/src/widgets.dart
import 'application_state.dart';
import 'package:provider/provider.dart';
import 'authentication.dart';
Dołącz poniższy kod do 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),
),
),
];
}
}
Następnie 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 sekcji main.dart.
. Dodaj element AppBarMenuButton
, aby wyświetlić opcje 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(),
],
),
Aby ponownie uruchomić aplikację z tymi zmianami, uruchom polecenie flutter run
. Menu kontekstowe powinno być widoczne po prawej stronie paska aplikacji. Kliknięcie go przeniesie Cię do okna logowania.
Gdy zalogujesz się, podając prawidłowy adres e-mail i hasło, w menu kontekstowym powinna być widoczna opcja Wyloguj się.
W sekcji Uwierzytelnianie w konsoli Firebase powinien pojawić się adres e-mail nowego użytkownika.
Gratulacje! Użytkownicy mogą teraz logować się w aplikacji.
5. Dodaj połączenie z bazą danych
Teraz możesz przejść do rejestracji urządzeń 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
- W konsoli Firebase przejdź do sekcji Baza danych czasu rzeczywistego. Kliknij Utwórz bazę danych.
- Jeśli pojawi się prośba o wybranie trybu początkowego dla reguł zabezpieczeń, wybierz teraz Tryb testowy**. Tryb testowy tworzy reguły zabezpieczeń, które zezwalają na wszystkie żądania. Reguły zabezpieczeń dodasz później. Ważne jest, aby nigdy nie przejść do produkcji, gdy reguły zabezpieczeń są nadal w trybie testowym).
Baza danych jest na razie pusta. Znajdź 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ń przy użyciu interfejsu RTDB Presence API
Chcesz rejestrować urządzenia użytkownika, gdy są one widoczne online. Wykorzystaj Instalacje Firebase i interfejs Firebase RTDB Presence API do śledzenia listy urządzeń online poszczególnych użytkowników. W osiągnięciu tego celu pomoże Ci następujący 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';
}
Z poziomu wiersza poleceń skompiluj i uruchom aplikację na urządzeniu lub w przeglądarce za pomocą flutter run.
W aplikacji zaloguj się jako użytkownik. Pamiętaj, aby zalogować 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. Synchronizuj stan urządzenia
Wybierz urządzenie do kontaktu
Aby synchronizować stany między urządzeniami, wyznacz jedno urządzenie jako lidera lub kontroler. Urządzenie potencjalnego klienta będzie dyktować stany urządzeń obserwatorów.
Utwórz metodę setLeadDevice
w usłudze application_state.dart
i śledź to urządzenie za pomocą klucza active_device
w bazie danych czasu rzeczywistego:
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 element PopupMenuItem
o nazwie Controller
, modyfikując widżet SignedInMenuButton
. To menu pozwoli użytkownikom ustawić urządzenie potencjalnego klienta.
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ć stany urządzenia potencjalnego klienta z bazą danych czasu rzeczywistego za pomocą poniższego kodu. Dołącz ten kod na końcu application_state.dart.
Spowoduje to rozpoczęcie zapisywania 2 atrybutów: stan odtwarzacza (odtwarzanie lub wstrzymanie) oraz pozycja 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');
}
}
}
I na koniec musisz wywoływać setActiveDeviceState
za każdym razem, gdy zmieni się stan odtwarzacza kontrolera. 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
Informacje o stanie urządzenia głównego można odczytać i wykorzystać z 2 części. Najpierw musisz skonfigurować detektor bazy danych detektora stanu głównego odtwarzacza w application_state
. Ten detektor poinformuje urządzenia obserwujące, kiedy należy zaktualizować ekran, korzystając z oddzwonienia zwrotnego. Zwróć uwagę, że w tym kroku został zdefiniowany interfejs OnLeadDeviceChangeCallback
. Nie został jeszcze wdrożony. 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();
});
}
}
Następnie uruchom odbiornik bazy danych podczas inicjowania odtwarzacza w player_widget.dart
. Przekazuj funkcję _updatePlayer
, aby stan obserwatora odtwarzacza był aktualizowany za każdym razem, gdy zmienia 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');
}
}
}
}
Teraz możesz przetestować aplikację:
- W wierszu poleceń uruchom aplikację w emulatorach lub w przeglądarce z tym kodem:
flutter run -d <device-name>
- Możesz otwierać aplikacje w przeglądarce, w symulatorze iOS lub w emulatorze Androida. Przejdź do menu kontekstowego i wybierz jedną aplikację, która ma być urządzeniem lidera. Powinny być widoczne dane o urządzeniach obserwujących graczy zmienia się w miarę aktualizacji urządzenia lidera.
- Teraz zmień urządzenie lidera, włącz lub wstrzymaj muzykę i obserwuj, jak urządzenia obserwują odpowiednio się aktualizują.
Jeśli urządzenia obserwujące zaktualizują się prawidłowo, oznacza to, że kontroler na innym urządzeniu został utworzony. Został już tylko jeden kluczowy krok.
7. Aktualizowanie reguł zabezpieczeń
Jeśli nie opracujemy 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 zabezpieczeń Bazy danych czasu rzeczywistego, aby mieć pewność, że jedyni użytkownicy, którzy mogą odczytywać dane na urządzeniu i zapisywać na nim dane, są na nim zalogowanymi. W konsoli Firebase otwórz Bazę danych czasu rzeczywistego, a następnie kartę Reguły. Wklej te reguły, które pozwalają tylko zalogowanym użytkownikom na odczytywanie i zapisywanie stanów urządzenia:
{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
},
}
}
8. Gratulacje!
Gratulacje! Udało Ci się utworzyć pilota do wielu urządzeń za pomocą Flutter.
Środki
Better Together, utwór Firebase
- Muzyka Ryana Vernona
- Tekst i okładka albumu: Marissa Christy
- Głos JP Gomeza
9. Premia
Dodatkowym wyzwaniem może być użycie narzędzia Flutter FutureBuilder
, które pozwoli asynchronicznie dodać obecny typ urządzenia głównego do interfejsu. Jeśli potrzebujesz pomocy, zostanie ona zaimplementowana w folderze zawierającym stan zakończenia ćwiczenia w Codelabs.