Firebase Cihazlar Arası Codelab

1. Giriş

Son Güncelleme: 2022-03-14

Cihazlar arası iletişim için FlutterFire

Çok sayıda ev otomasyonu, giyilebilir ve kişisel sağlık teknolojisi cihazının çevrimiçi hale geldiğine tanık olduğumuz için, cihazlar arası iletişim, mobil uygulama geliştirmenin giderek daha önemli bir parçası haline geliyor. Bir tarayıcıyı telefon uygulamasından kontrol etmek veya TV'nizde oynatılanları telefonunuzdan kontrol etmek gibi cihazlar arası iletişimi kurmak, geleneksel olarak normal bir mobil uygulama oluşturmaktan daha karmaşıktır.

Firebase'in Gerçek Zamanlı Veritabanı, kullanıcıların cihazlarının çevrimiçi/çevrimdışı durumunu görmesine olanak tanıyan Presence API'sini sağlar; Aynı kullanıcının oturum açtığı tüm cihazları takip etmek ve bağlamak için bunu Firebase Kurulum Hizmeti ile birlikte kullanacaksınız. Flutter'ı, birden çok platform için hızlı bir şekilde uygulamalar oluşturmak için kullanacaksınız ve ardından, oynatılan cihazlar arası bir prototip oluşturacaksınız. bir cihazda müzik ve diğerinde müziği kontrol eder!

Ne inşa edeceksin

Bu codelab'de basit bir müzik çalar uzaktan kumandası oluşturacaksınız. Uygulamanız:

  • Flutter ile oluşturulmuş, Android, iOS ve web üzerinde basit bir müzik çalara sahip olun.
  • Kullanıcıların oturum açmasına izin verin.
  • Aynı kullanıcı birden fazla cihazda oturum açtığında cihazları bağlayın.
  • Kullanıcıların bir cihazdaki müzik çalma işlemini başka bir cihazdan kontrol etmesine izin verin.

7f0279938e1d3ab5.gif

Ne öğreneceksin

  • Flutter müzik çalar uygulaması nasıl oluşturulur ve çalıştırılır.
  • Kullanıcıların Firebase Auth ile oturum açmasına nasıl izin verilir?
  • Cihazları bağlamak için Firebase RTDB Presence API ve Firebase Kurulum Hizmeti nasıl kullanılır?

İhtiyacınız olan şey

  • Flutter geliştirme ortamı. Kurulumu yapmak için Flutter kurulum kılavuzundaki talimatları izleyin.
  • Minimum Flutter 2.10 veya daha yüksek bir sürüm gereklidir. Daha düşük bir sürümünüz varsa flutter upgrade.
  • Bir Firebase hesabı.

2. Kurulum

Başlangıç ​​kodunu alın

Flutter'da bir müzik çalar uygulaması oluşturduk. Başlangıç ​​kodu Git deposunda bulunur. Başlamak için komut satırında repoyu kopyalayın, başlangıç ​​durumunun bulunduğu klasöre taşıyın ve bağımlılıkları yükleyin:

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

cd cross-device-controller/starter_code

flutter pub get

Uygulamayı oluşturun

Uygulamayı oluşturmak için favori IDE'nizle çalışabilir veya komut satırını kullanabilirsiniz.

Uygulama dizininizde, flutter run -d web-server. Aşağıdaki istemi görebilmeniz gerekir.

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

Müzik çaları görmek için http://localhost:<port> ziyaret edin.

Android emülatörüne veya iOS simülatörüne aşina iseniz, bu platformlar için uygulamayı oluşturabilir ve flutter run -d <device_name> komutuyla yükleyebilirsiniz.

Web uygulaması temel bir bağımsız müzik çalar göstermelidir. Oynatıcı özelliklerinin amaçlandığı gibi çalıştığından emin olun. Bu codelab için tasarlanmış basit bir müzik çalar uygulamasıdır. Yalnızca bir Firebase şarkısı olan Better Together'ı çalabilir.

Bir Android emülatörü veya iOS simülatörü ayarlayın

Geliştirme için zaten bir Android cihazınız veya iOS cihazınız varsa bu adımı atlayabilirsiniz.

Bir Android emülatörü oluşturmak için Flutter geliştirmeyi de destekleyen Android Studio'yu indirin ve Sanal cihazlar oluşturma ve yönetme bölümündeki talimatları izleyin.

Bir iOS simülatörü oluşturmak için bir Mac ortamına ihtiyacınız olacak. XCode'u indirin ve Simülatöre Genel Bakış > Simülatörü Kullan > Simülatörü açma ve kapatma bölümündeki talimatları izleyin.

3. Firebase'i kurma

Firebase projesi oluşturma

http://console.firebase.google.com/ adresine bir tarayıcı açın.

  1. Firebase'de oturum açın.
  2. Firebase konsolunda Proje Ekle'ye (veya Proje oluştur'a ) tıklayın ve Firebase projenize Firebase-Cross-Device-Codelab adını verin.
  3. Proje oluşturma seçeneklerine tıklayın. İstenirse Firebase şartlarını kabul edin. Bu uygulama için Analytics kullanmayacağınız için Google Analytics kurulumunu atlayın.

Bahsedilen dosyaları indirmenize veya build.gradle dosyalarını değiştirmenize gerek yoktur. FlutterFire'ı başlattığınızda bunları yapılandıracaksınız.

Firebase SDK'yı yükleyin

Firebase'i yüklemek için komut satırına döndüğünüzde proje dizininde aşağıdaki komutu çalıştırın:

flutter pub add firebase_core

pubspec.yaml dosyasında firebase_core sürümünü en az 1.13.1 olacak şekilde düzenleyin veya flutter upgrade çalıştırın

FlutterFire'ı başlat

  1. Firebase CLI yüklü değilse curl -sL https://firebase.tools | bash .
  2. firebase login çalıştırarak ve talimatları izleyerek giriş yapın.
  3. dart pub global activate flutterfire_cli çalıştırarak FlutterFire CLI'yi yükleyin.
  4. FlutterFire CLI'yi flutterfire configure çalıştırarak yapılandırın.
  5. İstendiğinde, bu codelab için yeni oluşturduğunuz, Firebase-Cross-Device-Codelab gibi bir projeyi seçin.
  6. Yapılandırma desteğini seçmeniz istendiğinde iOS , Android ve Web'i seçin.
  7. Apple paket kimliği istendiğinde benzersiz bir alan adı yazın veya com.example.appname değerini girin; bu, bu codelab'in amacı açısından uygundur.

Yapılandırıldıktan sonra, başlatma için gerekli tüm seçenekleri içeren bir firebase_options.dart dosyası sizin için oluşturulacaktır.

Flutter ve Firebase'i başlatmak için editörünüzde main.dart dosyanıza aşağıdaki kodu ekleyin:

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

Uygulamayı şu komutla derleyin:

flutter run

Henüz hiçbir kullanıcı arayüzü öğesini değiştirmediğiniz için uygulamanın görünümü ve davranışı değişmedi. Ancak artık bir Firebase uygulamanız var ve aşağıdakiler de dahil olmak üzere Firebase ürünlerini kullanmaya başlayabilirsiniz:

  • Kullanıcılarınızın uygulamanızda oturum açmasına olanak tanıyan Firebase Kimlik Doğrulaması .
  • Firebase Gerçek Zamanlı Veritabanı(RTDB) ; cihazın çevrimiçi/çevrimdışı durumunu izlemek için iletişim durumu API'sini kullanacaksınız
  • Firebase Güvenlik Kuralları veritabanının güvenliğini sağlamanıza olanak tanır.
  • Tek bir kullanıcının oturum açtığı cihazları tanımlamak için Firebase Kurulum Hizmeti .

4. Firebase Kimlik Doğrulaması Ekleyin

Firebase Authentication için e-postayla oturum açmayı etkinleştir

Kullanıcıların web uygulamasında oturum açmasına izin vermek için E-posta/Parola oturum açma yöntemini kullanacaksınız:

  1. Firebase konsolunda sol paneldeki Oluştur menüsünü genişletin.
  2. Kimlik Doğrulama öğesine tıklayın ve ardından Başlayın düğmesine ve ardından Oturum açma yöntemi sekmesine tıklayın.
  3. Oturum açma sağlayıcıları listesinde E-posta/Parola'ya tıklayın, Etkinleştir anahtarını açık konuma getirin ve ardından Kaydet'e tıklayın. 58e3e3e23c2f16a4.png

Flutter'da Firebase Kimlik Doğrulamasını Yapılandırma

Gerekli flutter paketlerini yüklemek için komut satırında aşağıdaki komutları çalıştırın:

flutter pub add firebase_auth

flutter pub add provider

Bu yapılandırmayla artık oturum açma ve oturum kapatma akışını oluşturabilirsiniz. Kimlik doğrulama durumunun ekrandan ekrana değişmemesi gerektiğinden, oturum açma ve oturum kapatma gibi uygulama düzeyindeki durum değişikliklerini takip etmek için bir application_state.dart sınıfı oluşturacaksınız. Bu konuda daha fazla bilgiyi Flutter durum yönetimi belgelerinde bulabilirsiniz.

Aşağıdakini yeni application_state.dart dosyasına yapıştırın:

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

Uygulama başlatıldığında ApplicationState başlatılacağından emin olmak için main.dart bir başlatma adımı ekleyeceksiniz:

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

Uygulama kullanıcı arayüzünün aynı kalması gerekirdi ancak artık kullanıcıların oturum açmasına ve uygulama durumlarını kaydetmesine izin verebilirsiniz.

Oturum açma akışı oluşturma

Bu adımda oturum açma ve oturum kapatma akışı üzerinde çalışacaksınız. Akış şu şekilde görünecek:

  1. Oturumu kapatmış bir kullanıcı, içerik menüsüne tıklayarak oturum açma akışını başlatacaktır. 71fcc1030a336423.png Uygulama çubuğunun sağ tarafında.
  2. Oturum açma akışı bir iletişim kutusunda görüntülenecektir.
  3. Kullanıcı daha önce hiç oturum açmadıysa geçerli bir e-posta adresi ve parola kullanarak bir hesap oluşturması istenecektir.
  4. Kullanıcı daha önce oturum açtıysa şifresini girmesi istenecektir.
  5. Kullanıcı oturum açtıktan sonra içerik menüsüne tıklandığında Oturumu Kapat seçeneği gösterilir.

c295f6fa2e1d40f3.png

Oturum açma akışının eklenmesi üç adım gerektirir.

Öncelikle bir AppBarMenuButton widget'ı oluşturun. Bu widget, kullanıcının loginState bağlı olarak içerik menüsü açılır penceresini kontrol edecektir. İçe aktarmaları ekleyin

lib/src/widgets.dart

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

Aşağıdaki kodu 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),
        ),
      ),
    ];
  }
}

İkinci olarak, aynı widgets.dart sınıfında SignInDialog widget'ını oluşturun.

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,
          ),
        ),
      ]),
    );
  }
}

Üçüncü olarak, main.dart. Oturum aç veya Oturumu kapat seçeneğini görüntülemek için AppBarMenuButton ekleyin.

lib/main.dart

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

Uygulamayı bu değişikliklerle yeniden başlatmak için flutter run komutunu çalıştırın. İçerik menüsünü görebilmelisiniz 71fcc1030a336423.png Uygulama çubuğunun sağ tarafında. Üzerine tıkladığınızda oturum açma iletişim kutusuna yönlendirilirsiniz.

Geçerli bir e-posta adresi ve şifreyle oturum açtıktan sonra içerik menüsünde Oturumu kapat seçeneğini görebilmeniz gerekir.

Firebase konsolunda Kimlik Doğrulama altında, e-posta adresinin yeni kullanıcı olarak listelendiğini görebilmeniz gerekir.

888506c86a28a72c.png

Tebrikler! Kullanıcılar artık uygulamada oturum açabilir!

5. Veritabanı bağlantısı ekleyin

Artık Firebase Presence API'yi kullanarak cihaz kaydına geçmeye hazırsınız.

Gerekli bağımlılıkları eklemek için komut satırında aşağıdaki komutları çalıştırın:

flutter pub add firebase_app_installations

flutter pub add firebase_database

Veritabanı oluştur

Firebase konsolunda,

  1. Firebase konsolunun Gerçek Zamanlı Veritabanı bölümüne gidin. Veritabanı Oluştur'u tıklayın.
  2. Güvenlik kurallarınız için bir başlangıç ​​modu seçmeniz istenirse şimdilik Test Modu'nu seçin**.** (Test Modu, tüm isteklerin geçmesine izin veren Güvenlik Kuralları oluşturur. Güvenlik Kurallarını daha sonra ekleyeceksiniz. Hiçbir zaman üretime geçmemeniz önemlidir. Güvenlik Kurallarınız hâlâ Test Modundadır.)

Veritabanı şimdilik boş. databaseURL URL'nizi Genel sekmesi altındaki Proje ayarlarında bulun. Web uygulamaları bölümüne ilerleyin.

1b6076f60a36263b.png

databaseURL firebase_options.dart dosyasına ekleyin :

lib/firebase_options.dart

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

RTDB Presence API'yi kullanarak cihazları kaydedin

Bir kullanıcının cihazlarını çevrimiçi göründüklerinde kaydetmek istiyorsunuz. Bunu yapmak için, tek bir kullanıcının çevrimiçi cihazlarının listesini takip etmek amacıyla Firebase Kurulumlarından ve Firebase RTDB Presence API'sinden yararlanacaksınız. Aşağıdaki kod bu hedefe ulaşmanıza yardımcı olacaktır:

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

Komut satırına geri döndüğünüzde, uygulamayı cihazınızda veya flutter run.

Uygulamanızda kullanıcı olarak oturum açın. Farklı platformlarda aynı kullanıcı olarak oturum açmayı unutmayın.

Firebase konsolunda , cihazlarınızın veritabanınızda tek bir kullanıcı kimliği altında göründüğünü görmelisiniz.

5bef49cea3564248.png

6. Cihaz durumunu senkronize edin

Bir öncü cihaz seçin

Durumları cihazlar arasında senkronize etmek için bir cihazı lider veya denetleyici olarak atayın. Lider cihaz, takipçi cihazlardaki durumları belirleyecektir.

application_state.dart dosyasında bir setLeadDevice yöntemi oluşturun ve bu cihazı RTDB'de active_device anahtarıyla izleyin:

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

Bu işlevselliği uygulama çubuğu içerik menüsüne eklemek için SignedInMenuButton widget'ını değiştirerek Controller adında bir PopupMenuItem oluşturun. Bu menü kullanıcıların öncü cihazı ayarlamasına olanak tanır.

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

Lider cihazın durumunu veritabanına yazın

Bir öncü cihazı ayarladıktan sonra, aşağıdaki kodla öncü cihazın durumlarını RTDB ile senkronize edebilirsiniz. Aşağıdaki kodu application_state.dart. Bu, iki özelliği kaydetmeye başlayacaktır: oynatıcı durumu (oynatma veya duraklatma) ve kaydırıcı konumu.

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

Ve son olarak, kontrol cihazının oynatıcı durumu güncellendiğinde setActiveDeviceState çağırmanız gerekir. Mevcut player_widget.dart dosyasında aşağıdaki değişiklikleri yapın:

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

Lider cihazın durumunu veritabanından okuyun

Lead cihazının durumunu okumak ve kullanmak için iki bölüm vardır. İlk olarak, application_state içinde baş oynatıcı durumunun bir veritabanı dinleyicisini ayarlamak istiyorsunuz. Bu dinleyici, takipçi cihazlara ekranın ne zaman güncelleneceğini bir geri arama yoluyla söyleyecektir. Bu adımda OnLeadDeviceChangeCallback arayüzünü tanımladığınıza dikkat edin. Henüz uygulanmadı; bir sonraki adımda bu arayüzü player_widget.dart uygulayacaksınız.

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

İkinci olarak, oynatıcının başlatılması sırasında player_widget.dart dosyasında veritabanı dinleyicisini başlatın. Veritabanı değeri değiştiğinde takipçi oynatıcı durumunun güncellenebilmesi için _updatePlayer işlevini iletin.

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

Artık uygulamayı test etmeye hazırsınız:

  1. Komut satırında uygulamayı emülatörlerde ve/veya bir tarayıcıda şunu çalıştırın: flutter run -d <device-name>
  2. Uygulamaları bir tarayıcıda, bir iOS simülatöründe veya bir Android emülatöründe açın. Bağlam menüsüne gidin, lider cihaz olacak bir uygulamayı seçin. Lider cihaz güncellendikçe takipçi cihazların oynatıcılarının değiştiğini görebilmeniz gerekir.
  3. Şimdi lider cihazı değiştirin, müziği çalın veya duraklatın ve takipçi cihazların buna göre güncellendiğini gözlemleyin.

Takipçi cihazları düzgün bir şekilde güncellenirse, cihazlar arası denetleyici yapmayı başardınız. Geriye sadece çok önemli bir adım kaldı.

7. Güvenlik Kurallarını Güncelleyin

Daha iyi güvenlik kuralları yazmadığımız sürece birisi, sahibi olmadığı bir cihaza durum yazabilir! Bu nedenle, bitirmeden önce, bir cihazda okuma veya yazma yapabilen kullanıcıların yalnızca o cihazda oturum açan kullanıcı olduğundan emin olmak için Gerçek Zamanlı Veritabanı Güvenliği Kurallarını güncelleyin. Firebase Konsolunda Gerçek Zamanlı Veritabanına ve ardından Kurallar sekmesine gidin. Yalnızca oturum açmış kullanıcının kendi cihaz durumlarını okumasına ve yazmasına izin veren aşağıdaki kuralları yapıştırın:

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

8. Tebrikler!

bcd986f7106d892b.gif

Tebrikler, Flutter'ı kullanarak başarılı bir şekilde cihazlar arası uzaktan kumanda oluşturdunuz!

Kredi

Birlikte Daha İyi, bir Firebase Şarkısı

  • Müzik Ryan Vernon'a ait
  • Şarkı sözleri ve albüm kapağı - Marissa Christy
  • JP Gomez'in sesi

9.Bonus

Ek bir zorluk olarak, mevcut lider cihaz türünü kullanıcı arayüzüne eşzamansız olarak eklemek için Flutter FutureBuilder kullanmayı düşünün. Yardıma ihtiyacınız varsa codelab'in tamamlanmış durumunu içeren klasöre uygulanır.

Referans belgeleri ve sonraki adımlar