Codelab Firebase Lintas Perangkat

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 perangkat mereka saat online/offline; Anda akan menggunakannya dengan Layanan Penginstalan Firebase untuk melacak dan menghubungkan semua perangkat yang digunakan pengguna yang sama untuk 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 mem-build 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.
  • Hubungkan perangkat jika pengguna yang sama login di beberapa perangkat.
  • Memungkinkan pengguna mengontrol pemutaran musik di satu perangkat dari perangkat lain.

7f0279938e1d3ab5.gif

Hal yang akan Anda pelajari

  • Cara membangun 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 baru. 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 repositori 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

Membangun aplikasi

Anda dapat menggunakan IDE favorit Anda untuk membangun 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 terbiasa 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 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, dan ikuti petunjuk di Ringkasan Simulator > Gunakan Simulator > Membuka dan menutup simulator.

3. Menyiapkan Firebase

Buat project Firebase

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

  1. Login ke Firebase.
  2. Di Firebase console, klik Add Project (atau Create a project), dan 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 Anda 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 Anda, tambahkan kode berikut ke file main.dart Anda 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, kini 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 perangkat online/offline
  • Aturan Keamanan Firebase akan memungkinkan Anda mengamankan database.
  • Layanan Penginstalan Firebase untuk mengidentifikasi perangkat yang digunakan untuk login oleh satu pengguna.

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 Mulai, lalu tab Metode login.
  3. Klik Email/Sandi di daftar Penyedia login, setel tombol Aktifkan ke posisi aktif, lalu klik Simpan. 58e3e3e23c2f16a4.png

Mengonfigurasi Firebase Authentication di Flutter

Pada 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 hal ini lebih lanjut 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 sudah 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.

Pada 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 memilih mode awal untuk aturan keamanan Anda, pilih Test Mode untuk saat ini**.** (Mode Pengujian membuat Aturan Keamanan yang mengizinkan semua permintaan lewat. Anda akan menambahkan Aturan Keamanan nanti. Penting untuk tidak pernah melakukan produksi dengan Aturan Keamanan yang masih dalam Mode Pengujian.)

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

1b6076f60a36263b.pngS

Tambahkan databaseURL Anda 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 muncul secara 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 login sebagai pengguna yang sama di platform yang berbeda.

Di Firebase console, Anda akan melihat perangkat muncul dengan 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 menyetel 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.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 harus 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 prospek. 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. Hal ini belum diimplementasikan; 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 untuk 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, pada simulator iOS, atau emulator Android. Buka menu konteks, pilih satu aplikasi untuk menjadi perangkat pemimpin. Anda akan dapat melihat perangkat pengikut pemain berubah saat perangkat pemimpin diperbarui.
  3. Sekarang ganti perangkat pemimpin, putar atau jeda musik, dan amati pembaruan perangkat pengikut yang sesuai.

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

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 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 membangun remote control 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