Codelab Firebase Lintas Perangkat

Codelab Firebase Lintas Perangkat

Tentang codelab ini

subjectTerakhir diperbarui Okt 31, 2022
account_circleDitulis oleh Yun Miao

1. Pengantar

Terakhir Diperbarui: 14-03-2022

FlutterFire untuk komunikasi lintas perangkat

Seiring dengan banyaknya perangkat teknologi kesehatan pribadi, perangkat wearable, dan otomatisasi rumah yang terhubung ke internet, komunikasi lintas perangkat menjadi bagian yang semakin penting dalam membuat aplikasi seluler. Menyiapkan komunikasi lintas perangkat seperti mengontrol browser dari aplikasi ponsel, atau mengontrol apa yang diputar di TV dari ponsel, secara tradisional lebih kompleks daripada mem-build aplikasi seluler normal .

Realtime Database Firebase menyediakan Presence API yang memungkinkan pengguna melihat status online/offline perangkat mereka; Anda akan menggunakannya dengan Layanan Penginstalan Firebase untuk melacak dan menghubungkan semua perangkat tempat pengguna yang sama login. Anda akan menggunakan Flutter untuk membuat aplikasi dengan cepat untuk beberapa platform, lalu Anda akan mem-build prototipe lintas perangkat yang memutar musik di satu perangkat dan mengontrol musik di perangkat lain.

Hal yang akan Anda build

Dalam codelab ini, Anda akan membuat pengontrol jarak jauh pemutar musik sederhana. Aplikasi Anda akan:

  • Memiliki pemutar musik sederhana di Android, iOS, dan web, yang dibuat dengan Flutter.
  • Izinkan pengguna login.
  • Menghubungkan perangkat saat pengguna yang sama login di beberapa perangkat.
  • Mengizinkan pengguna mengontrol pemutaran musik di satu perangkat dari perangkat lain.

7f0279938e1d3ab5.gif

Hal yang akan Anda pelajari

  • Cara mem-build dan menjalankan aplikasi pemutar musik Flutter.
  • Cara mengizinkan pengguna login dengan Firebase Auth.
  • Cara menggunakan Firebase RTDB Presence API dan Layanan Penginstalan Firebase untuk menghubungkan perangkat.

Hal yang akan Anda perlukan

  • Lingkungan pengembangan Flutter. Ikuti petunjuk dalam panduan penginstalan Flutter untuk menyiapkannya.
  • Diperlukan versi Flutter minimum 2.10 atau yang lebih tinggi. Jika Anda memiliki versi yang lebih rendah, jalankan flutter upgrade.
  • Akun Firebase.

2. Mempersiapkan

Mendapatkan kode awal

Kita telah membuat aplikasi pemutar musik di Flutter. Kode awal terletak di repo Git. Untuk memulai, di command line, clone repo, pindah ke folder dengan status awal, dan instal dependensi:

git clone https://github.com/FirebaseExtended/cross-device-controller.git

cd cross-device-controller/starter_code

flutter pub get

Mem-build aplikasi

Anda dapat menggunakan IDE favorit untuk mem-build aplikasi, atau menggunakan command line.

Di direktori aplikasi, build aplikasi untuk web dengan perintah flutter run -d web-server.Anda akan dapat melihat perintah berikut.

lib/main.dart is being served at http://localhost:<port>

Buka http://localhost:<port> untuk melihat pemutar musik.

Jika sudah terbiasa dengan emulator Android atau simulator iOS, Anda dapat mem-build aplikasi untuk platform tersebut dan menginstalnya dengan perintah flutter run -d <device_name>.

Aplikasi web akan menampilkan pemutar musik mandiri dasar. Pastikan fitur pemutar berfungsi sebagaimana mestinya. Ini adalah aplikasi pemutar musik sederhana yang dirancang untuk codelab ini. Aplikasi ini hanya dapat memutar lagu Firebase, Better Together.

Menyiapkan emulator Android atau simulator iOS

Jika sudah memiliki perangkat Android atau perangkat iOS untuk pengembangan, Anda dapat melewati langkah ini.

Untuk membuat emulator Android, download Android Studio yang juga mendukung pengembangan Flutter, dan ikuti petunjuk di Membuat dan mengelola perangkat virtual.

Untuk membuat simulator iOS, Anda memerlukan lingkungan Mac. Download XCode, lalu ikuti petunjuk di Ringkasan Simulator > Gunakan Simulator > Buka dan tutup simulator.

3. Menyiapkan Firebase

Buatproject Firebase

Buka browser ke http://console.firebase.google.com/.

  1. Login ke Firebase.
  2. Di Firebase console, klik Tambahkan Project (atau Buat project), lalu beri nama project Firebase Anda Firebase-Cross-Device-Codelab.
  3. Klik opsi pembuatan project. Setujui persyaratan Firebase jika diminta. Lewati penyiapan Google Analytics, karena Anda tidak akan menggunakan Analytics untuk aplikasi ini.

Anda tidak perlu mendownload file yang disebutkan atau mengubah file build.gradle. Anda akan mengonfigurasinya saat melakukan inisialisasi FlutterFire.

Menginstal Firebase SDK

Kembali ke command line, di direktori project, jalankan perintah berikut untuk menginstal Firebase:

flutter pub add firebase_core

Di file pubspec.yaml, edit versi untuk firebase_core menjadi minimal 1.13.1, atau jalankan flutter upgrade

Melakukan inisialisasi FlutterFire

  1. Jika belum menginstal Firebase CLI, Anda dapat menginstalnya dengan menjalankan curl -sL https://firebase.tools | bash.
  2. Login dengan menjalankan firebase login dan mengikuti petunjuknya.
  3. Instal FlutterFire CLI dengan menjalankan dart pub global activate flutterfire_cli.
  4. Konfigurasikan FlutterFire CLI dengan menjalankan flutterfire configure.
  5. Pada perintah, pilih project yang baru saja Anda buat untuk codelab ini, seperti Firebase-Cross-Device-Codelab.
  6. Pilih iOS, Android, dan Web saat Anda diminta untuk memilih dukungan konfigurasi.
  7. Saat diminta memasukkan ID paket Apple, ketik domain unik, atau masukkan com.example.appname, yang tidak masalah untuk tujuan codelab ini.

Setelah dikonfigurasi, file firebase_options.dart akan dibuat untuk Anda yang berisi semua opsi yang diperlukan untuk inisialisasi.

Di editor, tambahkan kode berikut ke file main.dart untuk melakukan inisialisasi Flutter dan 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());
}

Kompilasi aplikasi dengan perintah:

flutter run

Anda belum mengubah elemen UI apa pun, sehingga tampilan dan perilaku aplikasi belum berubah. Namun, sekarang Anda memiliki aplikasi Firebase, dan dapat mulai menggunakan produk Firebase, termasuk:

  • Firebase Authentication, yang memungkinkan pengguna login ke aplikasi Anda.
  • Firebase Realtime Database(RTDB); Anda akan menggunakan API kehadiran untuk melacak status online/offline perangkat
  • Aturan Keamanan Firebase akan memungkinkan Anda mengamankan database.
  • Layanan Penginstalan Firebase untuk mengidentifikasi perangkat yang telah digunakan oleh satu pengguna untuk login.

4. Menambahkan Firebase Auth

Mengaktifkan login dengan email untuk Firebase Authentication

Untuk mengizinkan pengguna login ke aplikasi web, Anda akan menggunakan metode login Email/Sandi:

  1. Di Firebase console, luaskan menu Build di panel kiri.
  2. Klik Authentication, lalu klik tombol Get Started, lalu tab Sign-in method.
  3. Klik Email/Password di daftar Sign-in providers, setel tombol akses Enable ke posisi aktif, lalu klik Save. 58e3e3e23c2f16a4.png

Mengonfigurasi Firebase Authentication di Flutter

Di command line, jalankan perintah berikut untuk menginstal paket flutter yang diperlukan:

flutter pub add firebase_auth

flutter pub add provider

Dengan konfigurasi ini, Anda kini dapat membuat alur login dan logout. Karena status autentikasi tidak boleh berubah dari layar ke layar, Anda akan membuat class application_state.dart untuk melacak perubahan status tingkat aplikasi, seperti login dan logout. Pelajari lebih lanjut hal ini di dokumentasi pengelolaan status Flutter.

Tempel kode berikut ke dalam file application_state.dart baru:

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();
 
}
}

Untuk memastikan ApplicationState akan diinisialisasi saat aplikasi dimulai, Anda akan menambahkan langkah inisialisasi ke 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(),
 
));
}

Sekali lagi, UI aplikasi seharusnya tetap sama, tetapi sekarang Anda dapat mengizinkan pengguna login dan menyimpan status aplikasi.

Membuat alur login

Pada langkah ini, Anda akan mengerjakan alur login dan logout. Berikut adalah tampilan alur:

  1. Pengguna yang logout akan memulai alur login dengan mengklik menu konteks 71fcc1030a336423.png di sisi kanan panel aplikasi.
  2. Alur login akan ditampilkan dalam dialog.
  3. Jika pengguna belum pernah login sebelumnya, mereka akan diminta untuk membuat akun menggunakan alamat email dan sandi yang valid.
  4. Jika pengguna telah login sebelumnya, mereka akan diminta untuk memasukkan sandi.
  5. Setelah pengguna login, mengklik menu konteks akan menampilkan opsi Logout.

c295f6fa2e1d40f3.png

Menambahkan alur login memerlukan tiga langkah.

Pertama-tama, buat widget AppBarMenuButton. Widget ini akan mengontrol pop-up menu konteks bergantung pada loginState pengguna. Menambahkan impor

lib/src/widgets.dart

import 'application_state.dart';
import 'package:provider/provider.dart';
import 'authentication.dart';

Tambahkan kode berikut ke 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),
        ),
      ),
    ];
  }
}

Kedua, di class widgets.dart yang sama, buat widget 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,
          ),
        ),
      ]),
    );
  }
}

Ketiga, temukan widget appBar yang ada di main.dart. Tambahkan AppBarMenuButton untuk menampilkan opsi Login atau Logout.

lib/main.dart

import 'src/widgets.dart';
appBar: AppBar(
 
title: const Text('Music Box'),
 
backgroundColor: Colors.deepPurple.shade400,
 
actions: const <Widget>[
   
AppBarMenuButton(),
 
],
),

Jalankan perintah flutter run untuk memulai ulang aplikasi dengan perubahan ini. Anda akan dapat melihat menu konteks 71fcc1030a336423.png di sisi kanan panel aplikasi. Mengkliknya akan mengarahkan Anda ke dialog login.

Setelah login dengan alamat email dan sandi yang valid, Anda akan dapat melihat opsi Logout di menu konteks.

Di Firebase console, di bagian Authentication, Anda akan dapat melihat alamat email yang tercantum sebagai pengguna baru.

888506c86a28a72c.png

Selamat! Pengguna kini dapat login ke aplikasi.

5. Menambahkan koneksi database

Sekarang Anda siap untuk melanjutkan ke pendaftaran perangkat menggunakan Firebase Presence API.

Di command line, jalankan perintah berikut untuk menambahkan dependensi yang diperlukan:

flutter pub add firebase_app_installations

flutter pub add firebase_database

Buat database

Di Firebase console,

  1. Buka bagian Realtime Database di Firebase console. Klik Create Database.
  2. Jika diminta untuk memilih mode awal untuk aturan keamanan Anda, pilih Mode Pengujian untuk saat ini**.** (Mode Pengujian membuat Aturan Keamanan yang mengizinkan semua permintaan. Anda akan menambahkan Aturan Keamanan nanti. Penting untuk tidak pernah melakukan produksi dengan Aturan Keamanan yang masih dalam Mode Pengujian.)

Database kosong untuk saat ini. Temukan databaseURL Anda di Project settings, di bagian tab General. Scroll ke bawah ke bagian Aplikasi web.

1b6076f60a36263b.png

Tambahkan databaseURL ke file firebase_options.dart:

lib/firebase_options.dart

 static const FirebaseOptions web = FirebaseOptions(
    apiKey: yourApiKey,
    ...
    databaseURL: 'https://<YOUR_DATABASE_URL>,
    ...
  );

Mendaftarkan perangkat menggunakan RTDB Presence API

Anda ingin mendaftarkan perangkat pengguna saat perangkat tersebut online. Untuk melakukannya, Anda akan memanfaatkan Firebase Installations dan Firebase RTDB Presence API untuk melacak daftar perangkat online dari satu pengguna. Kode berikut akan membantu mencapai sasaran ini:

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';
 
}

Kembali ke command line, build dan jalankan aplikasi di perangkat atau di browser dengan flutter run.

Di aplikasi Anda, login sebagai pengguna. Jangan lupa untuk login sebagai pengguna yang sama di platform yang berbeda.

Di Firebase console, Anda akan melihat perangkat muncul di bawah satu ID pengguna di database.

5bef49cea3564248.png

6. Menyinkronkan status perangkat

Memilih perangkat prospek

Untuk menyinkronkan status antar-perangkat, tetapkan satu perangkat sebagai pemimpin, atau pengontrol. Perangkat utama akan menentukan status di perangkat pengikut.

Buat metode setLeadDevice di application_state.dart, dan lacak perangkat ini dengan kunci active_device di 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;
      });
    }
  }

Untuk menambahkan fungsi ini ke menu konteks panel aplikasi, buat PopupMenuItem yang disebut Controller dengan mengubah widget SignedInMenuButton. Menu ini akan memungkinkan pengguna menetapkan perangkat prospek.

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();
    }
  }
}

Menulis status perangkat prospek ke database

Setelah menetapkan perangkat prospek, Anda dapat menyinkronkan status perangkat prospek ke RTDB dengan kode berikut. Tambahkan kode berikut ke akhir application_state.dart.Tindakan ini akan mulai menyimpan dua atribut: status pemutar (putar atau jeda) dan posisi penggeser.

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');
      }
    }
  }

Terakhir, Anda perlu memanggil setActiveDeviceState setiap kali status pemain pengontrol diperbarui. Buat perubahan berikut pada file player_widget.dart yang ada:

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;
 
}

Membaca status perangkat prospek dari database

Ada dua bagian untuk membaca dan menggunakan status perangkat utama. Pertama, Anda ingin menyiapkan pemroses database status pemain utama di application_state. Pemroses ini akan memberi tahu perangkat pengikut kapan harus memperbarui layar melalui callback. Perhatikan bahwa Anda telah menentukan antarmuka OnLeadDeviceChangeCallback di langkah ini. Antarmuka ini belum diterapkan; Anda akan menerapkan antarmuka ini di player_widget.dart pada langkah berikutnya.

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();
      });
    }
  }

Kedua, mulai pemroses database selama inisialisasi pemutar di player_widget.dart. Teruskan fungsi _updatePlayer agar status pemain pengikut dapat diperbarui setiap kali nilai database berubah.

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');
        }
      }
    }
  }

Sekarang Anda siap menguji aplikasi:

  1. Di command line, jalankan aplikasi di emulator dan/atau di browser dengan: flutter run -d <device-name>
  2. Buka aplikasi di browser, di simulator iOS, atau emulator Android. Buka menu konteks, pilih satu aplikasi untuk menjadi perangkat pemimpin. Anda akan dapat melihat perubahan pemain perangkat pengikut saat perangkat pemimpin diperbarui.
  3. Sekarang, ubah perangkat pemimpin, putar atau jeda musik, dan amati perangkat pengikut yang diperbarui.

Jika perangkat pengikut diupdate dengan benar, Anda telah berhasil membuat pengontrol lintas perangkat. Hanya ada satu langkah penting lagi yang perlu dilakukan.

7. Memperbarui Aturan Keamanan

Kecuali jika kita menulis aturan keamanan yang lebih baik, seseorang dapat menulis status ke perangkat yang bukan miliknya. Jadi, sebelum Anda selesai, perbarui Aturan Keamanan Realtime Database untuk memastikan bahwa satu-satunya pengguna yang dapat membaca atau menulis ke perangkat adalah pengguna yang login ke perangkat tersebut. Di Firebase Console, buka Realtime Database, lalu tab Aturan. Tempelkan aturan berikut yang hanya mengizinkan pengguna yang login untuk membaca dan menulis status perangkat mereka sendiri:

{
 
"rules": {
   
"users": {
           
"$uid": {
               
".read": "$uid === auth.uid",
               
".write": "$uid === auth.uid"
           
}
   
},
 
}
}

8. Selamat!

bcd986f7106d892b.gif

Selamat, Anda telah berhasil mem-build pengontrol jarak jauh lintas perangkat menggunakan Flutter.

Kredit

Better Together, Lagu Firebase

  • Musik oleh Ryan Vernon
  • Lirik dan sampul album oleh Marissa Christy
  • Suara oleh JP Gomez

9. Bonus

Sebagai tantangan tambahan, pertimbangkan untuk menggunakan Flutter FutureBuilder untuk menambahkan jenis perangkat prospek saat ini ke UI secara asinkron. Jika Anda memerlukan bantuan, bantuan tersebut akan diterapkan di folder yang berisi status selesai codelab.

Dokumen referensi dan langkah berikutnya