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 membangun aplikasi seluler. Menyiapkan komunikasi lintas perangkat seperti mengontrol browser dari aplikasi ponsel, atau mengontrol konten yang diputar di TV dari ponsel, biasanya lebih rumit daripada membuat aplikasi seluler biasa .

Realtime Database Firebase menyediakan Presence API yang memungkinkan pengguna melihat status online/offline perangkat mereka; Anda akan menggunakannya dengan Firebase Installations Service untuk melacak dan menghubungkan semua perangkat tempat pengguna yang sama telah login. Anda akan menggunakan Flutter untuk membuat aplikasi dengan cepat untuk beberapa platform, lalu Anda akan membuat 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 Firebase Installation Service untuk menghubungkan perangkat.

Hal yang akan Anda perlukan

  • Lingkungan pengembangan Flutter. Ikuti petunjuk di 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 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 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 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 didesain untuk codelab ini. Fitur ini hanya dapat memutar lagu Firebase, Lebih Baik Bersama.

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, lalu ikuti petunjuk di Membuat dan mengelola perangkat virtual.

Untuk membuat simulator iOS, Anda memerlukan lingkungan Mac. Download XCode, lalu ikuti petunjuk di Ringkasan Simulator > Menggunakan Simulator > Membuka dan menutup simulator.

3. Menyiapkan Firebase

Membuat project Firebase

  1. Login ke Firebase console menggunakan Akun Google Anda.
  2. Klik tombol untuk membuat project baru, lalu masukkan nama project (misalnya, Firebase-Cross-Device-Codelab).
  3. Klik Lanjutkan.
  4. Jika diminta, tinjau dan setujui persyaratan Firebase, lalu klik Continue.
  5. (Opsional) Aktifkan bantuan AI di Firebase console (disebut "Gemini di Firebase").
  6. Untuk codelab ini, Anda tidak memerlukan Google Analytics, jadi nonaktifkan opsi Google Analytics.
  7. Klik Buat project, tunggu hingga project Anda disediakan, lalu klik Lanjutkan.

Instal 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 setidaknya 1.13.1, atau jalankan flutter upgrade

Menginisialisasi 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. Konfigurasi FlutterFire CLI dengan menjalankan flutterfire configure.
  5. Di 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 untuk 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 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 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 presence API untuk melacak status online/offline perangkat
  • Aturan Keamanan Firebase akan memungkinkan Anda mengamankan database.
  • Firebase Installations Service untuk mengidentifikasi perangkat yang telah digunakan untuk login oleh satu pengguna.

4. Menambahkan Firebase Auth

Mengaktifkan login email untuk Firebase Authentication

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

  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.

Tempelkan 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 tampilan alurnya:

  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 pernah login sebelumnya, mereka akan diminta untuk memasukkan sandi mereka.
  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. 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 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 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 untuk memilih mode awal untuk aturan keamanan Anda, pilih Test Mode untuk saat ini**.** (Mode Pengujian membuat Aturan Keamanan yang mengizinkan semua permintaan. Anda akan menambahkan Aturan Keamanan nanti. Penting untuk tidak pernah menggunakan aplikasi produksi dengan Aturan Keamanan Anda masih dalam Mode Pengujian.)

Database saat ini kosong. Temukan databaseURL Anda di Project settings, di tab General. Scroll 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>,
    ...
  );

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 yang 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 command line, build dan jalankan aplikasi di perangkat Anda 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 Anda muncul di bawah satu ID pengguna di database Anda.

5bef49cea3564248.png

6. Status sinkronisasi perangkat

Pilih perangkat utama

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 bernama Controller dengan mengubah widget SignedInMenuButton. Menu ini akan memungkinkan pengguna menyetel 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();
    }
  }
}

Menulis status perangkat utama ke database

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

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 utama dari database

Ada dua bagian untuk membaca dan menggunakan status perangkat utama. Pertama, Anda ingin menyiapkan pemroses database status pemutar utama di application_state. Pemroses ini akan memberi tahu perangkat pengikut kapan harus memperbarui layar melalui callback. Perhatikan bahwa Anda telah menentukan antarmuka OnLeadDeviceChangeCallback pada 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 sehingga status pemutar 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 utama. Anda akan dapat melihat perubahan pemutar perangkat pengikut saat perangkat pemimpin diupdate.
  3. Sekarang, ubah perangkat utama, putar atau jeda musik, dan amati perangkat pengikut yang diperbarui dengan tepat.

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

7. Memperbarui Aturan Keamanan

Jika kita tidak menulis aturan keamanan yang lebih baik, seseorang dapat menulis status ke perangkat yang bukan miliknya. Jadi, sebelum Anda menyelesaikan, perbarui Aturan Keamanan Realtime Database untuk memastikan bahwa hanya pengguna yang login ke perangkat tersebut yang dapat membaca atau menulis ke perangkat. Di Firebase Console, buka Realtime Database, lalu buka tab Rules. Tempel 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 membuat pengontrol jarak jauh lintas perangkat menggunakan Flutter.

Kredit

Better Together, sebuah 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 FutureBuilder Flutter untuk menambahkan jenis perangkat utama saat ini ke UI secara asinkron. Jika Anda memerlukan bantuan, fitur ini diimplementasikan di folder yang berisi status akhir codelab.

Dokumen referensi dan langkah selanjutnya