Codelab Lintas Perangkat Firebase

1. Perkenalan

Terakhir Diperbarui: 14-03-2022

FlutterFire untuk komunikasi lintas perangkat

Saat kita menyaksikan sejumlah besar perangkat otomatisasi rumah, perangkat wearable, dan teknologi kesehatan pribadi mulai online, komunikasi lintas perangkat menjadi bagian yang semakin penting dalam membangun aplikasi seluler. Menyiapkan komunikasi lintas perangkat seperti mengontrol browser dari aplikasi ponsel, atau mengontrol apa yang diputar di TV dari ponsel, biasanya lebih rumit dibandingkan membuat aplikasi seluler biasa.

Realtime Database Firebase menyediakan API Kehadiran yang memungkinkan pengguna melihat status online/offline perangkat mereka; Anda akan menggunakannya dengan Layanan Instalasi Firebase untuk melacak dan menghubungkan semua perangkat tempat pengguna yang sama telah masuk. Anda akan menggunakan Flutter untuk membuat aplikasi dengan cepat untuk berbagai platform, lalu Anda akan membuat prototipe lintas perangkat yang dapat diputar musik di satu perangkat dan mengontrol musik di perangkat lain!

Apa yang akan Anda bangun

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

  • Miliki pemutar musik sederhana di Android, iOS, dan web, yang dibuat dengan Flutter.
  • Izinkan pengguna untuk masuk.
  • Hubungkan perangkat saat pengguna yang sama masuk di beberapa perangkat.
  • Izinkan pengguna mengontrol pemutaran musik di satu perangkat dari perangkat lain.

7f0279938e1d3ab5.gif

Apa yang akan Anda pelajari

  • Cara membuat dan menjalankan aplikasi pemutar musik Flutter.
  • Cara mengizinkan pengguna masuk dengan Firebase Auth.
  • Cara menggunakan Firebase RTDB Presence API dan Layanan Instalasi Firebase untuk menghubungkan perangkat.

Apa yang Anda perlukan

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

2. Persiapan

Dapatkan kode awal

Kami telah membuat aplikasi pemutar musik di Flutter. Kode awal terletak di repo Git. Untuk memulai, pada baris perintah, kloning repo, pindahkan 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

Bangun aplikasinya

Anda dapat menggunakan IDE favorit Anda untuk membangun aplikasi, atau menggunakan baris perintah.

Di direktori aplikasi Anda, buat aplikasi untuk web dengan perintah flutter run -d web-server. Anda seharusnya dapat melihat prompt berikut.

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

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

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

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

Siapkan emulator Android atau simulator iOS

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

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

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

3. Menyiapkan Firebase

Buat proyek Firebase

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

  1. Masuk ke Firebase .
  2. Di Firebase console, klik Tambahkan Proyek (atau Buat proyek ), dan beri nama proyek Firebase Anda Firebase-Cross-Device-Codelab .
  3. Klik opsi pembuatan proyek. Terima persyaratan Firebase jika diminta. Lewati penyiapan Google Analytics, karena Anda tidak akan menggunakan Analytics untuk aplikasi ini.

Anda tidak perlu mengunduh file yang disebutkan atau mengubah file build.gradle. Anda akan mengonfigurasinya saat menginisialisasi FlutterFire.

Instal Firebase SDK

Kembali ke baris perintah, di direktori proyek, jalankan perintah berikut untuk menginstal Firebase:

flutter pub add firebase_core

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

Inisialisasi FlutterFire

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

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

Di editor Anda, tambahkan kode berikut ke file main.dart Anda untuk menginisialisasi 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 tidak berubah. Namun kini Anda telah memiliki aplikasi Firebase, dan dapat mulai menggunakan produk Firebase, antara lain:

  • Firebase Authentication , yang memungkinkan pengguna masuk ke aplikasi Anda.
  • Basis Data Waktu Nyata Firebase (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.

4. Tambahkan Firebase Auth

Aktifkan login email untuk Firebase Authentication

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

  1. Di Firebase console, perluas menu Build di panel kiri.
  2. Klik Autentikasi , lalu klik tombol Memulai , lalu tab Metode masuk .
  3. Klik Email/Kata Sandi di daftar Penyedia masuk , atur tombol Aktifkan ke posisi aktif, lalu klik Simpan . 58e3e3e23c2f16a4.png

Konfigurasikan Firebase Authentication di Flutter

Pada baris perintah, 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 masuk dan keluar. Karena status autentikasi tidak boleh berubah dari layar ke layar, Anda akan membuat kelas application_state.dart untuk melacak perubahan status tingkat aplikasi, seperti masuk dan keluar. Pelajari lebih lanjut tentang hal ini di dokumentasi pengelolaan status Flutter .

Rekatkan yang berikut ini ke file application_state.dart yang 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, namun sekarang Anda dapat mengizinkan pengguna masuk dan menyimpan status aplikasi.

Buat alur masuk

Pada langkah ini, Anda akan mengerjakan alur masuk dan keluar. Berikut tampilan alurnya:

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

c295f6fa2e1d40f3.png

Menambahkan alur masuk memerlukan tiga langkah.

Pertama-tama, buat widget AppBarMenuButton . Widget ini akan mengontrol popup menu konteks tergantung pada loginState pengguna. Tambahkan 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 kelas 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 Masuk atau Keluar .

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 seharusnya dapat melihat menu konteks 71fcc1030a336423.png di sisi kanan bilah aplikasi. Mengkliknya akan membawa Anda ke dialog masuk.

Setelah Anda masuk dengan alamat email dan kata sandi yang valid, Anda akan melihat opsi Keluar di menu konteks.

Di Firebase console, pada Authentication , Anda seharusnya dapat melihat alamat email yang terdaftar sebagai pengguna baru.

888506c86a28a72c.png

Selamat! Pengguna sekarang dapat masuk ke aplikasi!

5. Tambahkan koneksi database

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

Pada baris perintah, jalankan perintah berikut untuk menambahkan dependensi yang diperlukan:

flutter pub add firebase_app_installations

flutter pub add firebase_database

Buat basis data

Di konsol Firebase,

  1. Navigasikan ke bagian Realtime Database di Firebase console . Klik Buat Basis Data .
  2. Jika diminta untuk memilih mode awal untuk aturan keamanan Anda, pilih Mode Uji untuk saat ini**.** (Mode Uji membuat Aturan Keamanan yang mengizinkan semua permintaan lewat. Anda akan menambahkan Aturan Keamanan nanti. Penting untuk tidak pernah masuk ke produksi dengan Aturan Keamanan Anda masih dalam Mode Uji.)

Basis datanya kosong untuk saat ini. Temukan databaseURL Anda di Pengaturan proyek , di bawah tab Umum . Gulir ke bawah ke bagian Aplikasi web .

1b6076f60a36263b.png

Tambahkan databaseURL Anda ke file firebase_options.dart :

lib/firebase_options.dart

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

Daftarkan perangkat menggunakan RTDB Presence API

Anda ingin mendaftarkan perangkat pengguna saat perangkat tersebut muncul online. Untuk melakukannya, Anda akan memanfaatkan Instalasi Firebase dan Firebase RTDB Presence API untuk melacak daftar perangkat online dari satu pengguna. Kode berikut akan membantu mencapai tujuan 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 baris perintah, buat dan jalankan aplikasi di perangkat Anda atau di browser dengan flutter run.

Di aplikasi Anda, masuk sebagai pengguna. Ingatlah untuk masuk sebagai pengguna yang sama pada platform yang berbeda.

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

5bef49cea3564248.png

6. Sinkronkan status perangkat

Pilih perangkat utama

Untuk menyinkronkan status antar perangkat, tetapkan satu perangkat sebagai pemimpin, atau pengontrol. Perangkat utama akan menentukan status pada 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 fungsionalitas ini ke menu konteks bilah aplikasi, buat PopupMenuItem bernama Controller dengan memodifikasi widget SignedInMenuButton . Menu ini memungkinkan pengguna untuk mengatur perangkat utama.

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

Tulis status perangkat utama ke database

Setelah Anda menetapkan perangkat utama, Anda dapat menyinkronkan status perangkat utama ke RTDB dengan kode berikut. Tambahkan kode berikut ke akhir application_state.dart. 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');
      }
    }
  }

Dan terakhir, Anda perlu memanggil setActiveDeviceState setiap kali status pemutar 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;
  }

Baca status perangkat utama dari database

Ada dua bagian untuk membaca dan menggunakan status perangkat utama. Pertama, Anda ingin menyiapkan pendengar database status pemain utama di application_state . Pendengar ini akan memberi tahu perangkat pengikut kapan harus memperbarui layar melalui panggilan balik. Perhatikan Anda telah mendefinisikan antarmuka OnLeadDeviceChangeCallback pada langkah ini. Hal ini belum dilaksanakan; Anda akan mengimplementasikan 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 pendengar basis data selama inisialisasi pemutar di player_widget.dart . Lewati fungsi _updatePlayer sehingga 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. Pada baris perintah, 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 pemain di perangkat pengikut berubah seiring pembaruan perangkat pemimpin.
  3. Sekarang ubah perangkat pemimpin, putar atau jeda musik, dan amati pembaruan perangkat pengikut.

Jika perangkat pengikut diperbarui dengan benar, Anda telah berhasil membuat pengontrol lintas perangkat. Tinggal satu langkah penting lagi.

7. Perbarui Aturan Keamanan

Kecuali kita menulis aturan keamanan yang lebih baik, seseorang dapat menulis status ke perangkat yang bukan miliknya! Jadi sebelum Anda menyelesaikannya, perbarui Aturan Keamanan Realtime Database untuk memastikan satu-satunya pengguna yang dapat membaca atau menulis ke perangkat adalah pengguna yang masuk ke perangkat tersebut. Di Firebase Console, navigasikan ke Realtime Database, lalu ke tab Rules . Tempelkan aturan berikut yang hanya mengizinkan pengguna yang masuk 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 membuat remote kontrol 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. Bonusnya

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

Referensi dokumen dan langkah selanjutnya