Firebase Cihazlar Arası Codelab

1. Giriş

Son Güncelleme: 14.03.2022

Cihazlar arası iletişim için FlutterFire

Ev otomasyonu, giyilebilir ve kişisel sağlık teknolojisi cihazlarının çok sayıda online hale gelmesine şahit olduğumuz için cihazlar arası iletişim, mobil uygulama geliştirmenin giderek daha önemli bir parçası haline geliyor. Cihazlar arası iletişimin kurulumu, normal bir mobil uygulama oluşturmaktan geleneksel olarak daha karmaşıktır. Bu işlem, geleneksel olarak daha karmaşık bir süreçtir. Bu işlem, geleneksel olarak daha karmaşık bir süreçtir.

Firebase'in Realtime Database'i, kullanıcıların cihazlarının online/çevrimdışı durumunu görmelerine olanak tanıyan Presence API 'yi sunar. aynı kullanıcının oturum açtığı tüm cihazları izlemek ve bağlamak için bu uygulamayı Firebase Yüklemeler Hizmeti ile birlikte kullanırsınız. Farklı platformlara yönelik uygulamaları hızlıca oluşturmak için Flutter'ı kullanacaksınız. Ardından bir cihazda müzik çalan, diğer cihazda müziği kontrol eden cihazlar arası bir prototip oluşturacaksınız.

Oluşturacaklarınız

Bu codelab'de basit bir müzik çaların uzaktan kumandası geliştireceksiniz. Uygulamanız şunları yapabilecek:

  • Android, iOS ve web'de Flutter ile geliştirilmiş 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ğlayabilirsiniz.
  • Kullanıcıların bir cihazda başka bir cihazdaki müzik çalmayı kontrol etmesine izin verin.

7f0279938e1d3ab5.gif

Neler öğreneceksiniz?

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

Gerekenler

  • Flutter geliştirme ortamı. Uzantıyı ayarlamak için Flutter yükleme kılavuzundaki talimatları uygulayın.
  • En az Flutter 2.10 veya sonraki sürümleri gerekir. Daha düşük bir sürüme sahipseniz flutter upgrade. komutunu çalıştırın.
  • Firebase hesabı.

2. Kurulum

Başlangıç kodunu al

Flutter'da bir müzik çalar uygulaması oluşturduk. Başlangıç kodu bir Git deposunda bulunur. Başlamak için komut satırında depoyu klonlayı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şturma

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

Uygulama dizininizde şu komutu kullanarak uygulamayı web için oluşturun: flutter run -d web-server.Aşağıdaki istemi göreceksiniz.

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

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

Android emülatörü veya iOS simülatörü hakkında bilginiz varsa bu platformlar için uygulama geliştirip flutter run -d <device_name> komutuyla yükleyebilirsiniz.

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

Android emülatörü veya iOS simülatörü kurma

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

Android emülatörü oluşturmak için Flutter ile geliştirmeyi de destekleyen Android Studio'yu indirin ve Sanal cihazları oluşturma ve yönetme başlıklı makaledeki talimatları uygulayın.

iOS simülatörü oluşturmak için Mac ortamına ihtiyacınız vardır. XCode'u indirin ve Simulator Overview (Simülatöre Genel Bakış) bölümündeki talimatları uygulayın > Simülatörü Kullan > Bir simülasyon aracını açıp kapatın.

3. Firebase'i ayarlama

Firebase projesi oluşturma

Bir tarayıcı açarak http://console.firebase.google.com/ adresine gidin.

  1. Firebase'de oturum açın.
  2. Firebase konsolunda Proje Ekle'yi (veya Proje oluştur'u) tıklayın ve Firebase projenizi Firebase-Cross-Device-Codelab olarak adlandırın.
  3. Proje oluşturma seçeneklerini tıklayarak ilerleyin. İstenirse Firebase şartlarını kabul edin. Bu uygulama için Analytics'i kullanmayacağınızdan Google Analytics kurulumunu atlayın.

Belirtilen dosyaları indirmeniz veya build.gradle dosyalarını değiştirmeniz gerekmez. FlutterFire'ı başlattığınızda bunları yapılandıracaksınız.

Firebase SDK'sını yükleme

Firebase'i yüklemek için komut satırına dönüp 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 komutunu çalıştırın

FlutterFire'ı başlatma

  1. Firebase CLI'ı yüklemediyseniz curl -sL https://firebase.tools | bash komutunu çalıştırarak yükleyebilirsiniz.
  2. firebase login komutunu çalıştırıp talimatları uygulayarak giriş yapın.
  3. dart pub global activate flutterfire_cli komutunu çalıştırarak FlutterFire KSA'yı yükleyin.
  4. flutterfire configure komutunu çalıştırarak FlutterFire KSA'yı yapılandırın.
  5. Komut isteminde, bu codelab için oluşturduğunuz projeyi (ör. Firebase-Cihazlar-Codelab) 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 kod bu codelab'in amacına uygun değildir.

Yapılandırıldığında sizin için başlatma için gereken tüm seçenekleri içeren bir firebase_options.dart dosyası oluşturulur.

Flutter ve Firebase'i başlatmak için düzenleyicinizde 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ğıdaki Firebase ürünlerini kullanmaya başlayabilirsiniz:

  • Firebase Kimlik Doğrulaması, kullanıcılarınızın uygulamanızda oturum açmasına olanak tanır.
  • Firebase Realtime Database(RTDB); Cihazın çevrimiçi/çevrimdışı durumunu izlemek için varlık API'sini kullanmanız gerekir.
  • Firebase Güvenlik Kuralları, veritabanının güvenliğini sağlamanıza olanak tanır.
  • Firebase Kurulum Hizmeti, tek bir kullanıcının oturum açtığı cihazları tanımlar.

4. Firebase Auth Ekle

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/Şifre oturum açma yöntemini kullanırsınız:

  1. Firebase konsolunda sol paneldeki Derleme menüsünü genişletin.
  2. Kimlik Doğrulama'yı tıklayın, ardından Başlayın düğmesini ve ardından Oturum açma yöntemi sekmesini tıklayın.
  3. Oturum açma sağlayıcıları listesinde E-posta/Şifre'yi tıklayın, Etkinleştir anahtarını açık konuma getirin ve Kaydet'i tıklayın. 58e3e3e23c2f16a4.png

Firebase Authentication'ı Flutter'da 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 giriş ve çıkış 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 bilgiye Flutter durumu yönetimi dokümanlarından ulaşabilirsiniz.

Şunu 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 ürününün başlatıldığından emin olmak için main.dart yazılımına bir başlatma adımı eklersiniz:

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

Yine, uygulamanın kullanıcı arayüzü aynı kalmalıydı 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 oturumu kapatma akışı üzerinde çalışacaksınız. Akış şöyle görünecektir:

  1. Çıkış yapmış bir kullanıcı, uygulama çubuğunun sağ tarafındaki içerik menüsünü 71fcc1030a336423.pngtıklayarak oturum açma akışını başlatır.
  2. Oturum açma akışı, iletişim kutusunda görüntülenir.
  3. Kullanıcı daha önce hiç oturum açmadıysa geçerli bir e-posta adresi ve şifre kullanarak hesap oluşturması istenir.
  4. Kullanıcı daha önce oturum açmışsa şifresini girmesi istenir.
  5. Kullanıcı oturum açtıktan sonra içerik menüsünü tıkladığında Oturumu kapat seçeneği gösterilir.

c295f6fa2e1d40f3.png

Oturum açma akışı eklemek için üç adım gerekir.

Her şeyden önce bir AppBarMenuButton widget'ı oluşturun. Bu widget, kullanıcının loginState ürününe bağlı olarak içerik menüsü pop-up'ını kontrol eder. İçe aktarma işlemlerini ekleme

lib/src/widgets.dart

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

Aşağıdaki kodu widgets.dart. adlı reklam grubuna ekleyin

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üsü, main.dart. ürününde mevcut appBar widget'ını bulun. Oturum aç veya Oturumu kapat seçeneğinin gösterilmesini sağlamak için AppBarMenuButton simgesini 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. Uygulama çubuğunun sağ tarafında içerik menüsünü 71fcc1030a336423.png görebiliyorsunuz. Bu düğmeyi tıkladığınızda oturum açma iletişim kutusuna yönlendirilirsiniz.

Geçerli bir e-posta adresi ve şifre ile oturum açtığınızda, içerik menüsünde Oturumu kapat seçeneğini görebilirsiniz.

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

888506c86a28a72c.png

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

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şturun

Firebase konsolunda

  1. Firebase konsolunun Realtime Database 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 isteklere izin veren Güvenlik Kuralları oluşturur. Güvenlik Kuralları'nı daha sonra ekleyeceksiniz. Güvenlik Kurallarınız hâlâ Test Modu'ndayken hiçbir zaman üretime geçmemek önemlidir.)

Veritabanı şu anda boş. databaseURL öğenizi Genel sekmesinin altındaki Proje ayarları bölümünde bulun. Aşağı kaydırarak Web uygulamaları bölümüne gidin.

1b6076f60a36263b.png

firebase_options.dart dosyasına databaseURL ekleyin:

lib/firebase_options.dart

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

RTDB Presence API'yi kullanarak cihazları kaydetme

Kullanıcıların cihazlarını internette göründüklerinde kaydettirmek istiyorsunuz. Bunu yapmak için, tek bir kullanıcıya ait online cihazların listesini izlemek amacıyla Firebase INSTALLs ve Firebase RTDB Presence API'den yararlanırsınız. Aşağıdaki kod bu hedefe ulaşmaya 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 dönerek uygulamayı cihazınızda veya tarayıcıda flutter run. ile derleyip çalıştırın

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ızdaki tek bir kullanıcı kimliği altında listelendiğini göreceksiniz.

5bef49cea3564248.png

6. Cihaz durumunu senkronize et

Hizmet talebi cihazı seçin

Cihazlar arasında durum senkronizasyonu yapmak için bir cihazı lider veya denetleyici olarak atayın. Potansiyel müşteri cihazı, takip eden cihazlarda durumları belirtir.

application_state.dart ürününde bir setLeadDevice yöntemi oluşturun ve bu cihazı RTDB'de active_device anahtarıyla takip edin:

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şlevi, uygulama çubuğu içerik menüsüne eklemek için SignedInMenuButton widget'ını değiştirerek Controller adlı bir PopupMenuItem oluşturun. Bu menü, kullanıcıların potansiyel müşteri cihazını 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();
    }
  }
}

Potansiyel müşteri cihazının durumunu veritabanına yazma

Hizmet talebi cihazını ayarladıktan sonra aşağıdaki kodu kullanarak hizmet talebi cihazının durumlarını RTDB ile senkronize edebilirsiniz. Aşağıdaki kodu application_state.dart. öğesinin sonuna ekleyin. Bu işlem, iki özelliği kaydetmeye başlar: oynatıcı durumu (oynatma veya duraklatma) ve kaydırma çubuğu 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');
      }
    }
  }

Son olarak da kumandanın oyuncu durumu güncellendiğinde setActiveDeviceState çağrısı yapmanız gerekir. Mevcut player_widget.dart dosyasında şu 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;
  }

Potansiyel müşteri cihazının durumunu veritabanından okuma

Hizmet talebi cihazının durumunu okumak ve kullanmak için iki bölüm vardır. İlk olarak application_state bölgesindeki potansiyel müşteri oyuncu durumu için bir veritabanı dinleyicisi oluşturmanız gerekir. Bu dinleyici, geri arama yoluyla izleyenlerin ekranı ne zaman güncelleyeceğini belirtir. Bu adımda bir OnLeadDeviceChangeCallback arayüzü tanımladığınıza dikkat edin. Henüz uygulanmadı; bu arayüzü bir sonraki adımda player_widget.dart içinde 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, player_widget.dart uygulamasındaki oynatıcı başlatma sırasında veritabanı dinleyiciyi 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 şu komutu kullanarak uygulamayı emülatörlerde ve/veya bir tarayıcıda çalıştırın: flutter run -d <device-name>
  2. Uygulamaları tarayıcıda, iOS simülatöründe veya Android emülatöründe açın. İçerik menüsüne gidin ve bir uygulamayı öncü cihaz olarak seçin. Takipçi cihazlarını görebilirsiniz. Lider cihaz güncellendiğinde oyuncular değişir.
  3. Şimdi lider cihazı değiştirin, müzik çalın veya duraklatın ve takipçi cihazlarının buna göre güncellendiğini gözlemleyin.

Takipçi cihazları düzgün bir şekilde güncelleniyorsa cihazlar arası kumanda oluşturmayı başarmışsınız demektir. Tek bir önemli adım kaldı.

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

Daha iyi güvenlik kuralları yazmadığımız sürece, başka biri sahibi olmadığı bir cihaza durum yazabilir. Bu nedenle, işlemi tamamlamadan önce Realtime Database Güvenlik Kuralları'nı güncelleyerek bir cihazda okuma veya yazma işlemi yapabilen tek kullanıcının söz konusu cihazda oturum açan kullanıcı olduğundan emin olun. Firebase konsolunda Realtime Database'e, ardından Kurallar sekmesine gidin. Yalnızca oturum açmış kullanıcının kendi cihaz durumlarını okuyup 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 cihazlar arası uzaktan kumandayı başarıyla geliştirdiniz.

Kredi

Better Together, Bir Firebase Şarkısı

  • Müzikleri Ryan Vernon'a ait
  • Şarkı sözleri ve Marissa Christy'den albüm kapağı
  • Seslendiren: JP Gomez

9. Bonus

Ek bir zorluk olarak, mevcut potansiyel müşteri cihaz türünü kullanıcı arayüzüne eşzamansız olarak eklemek için Flutter FutureBuilder'ı kullanabilirsiniz. Yardıma ihtiyacınız varsa bu destek, codelab'in bitmiş durumunu içeren klasöre uygulanır.

Referans belgeler ve sonraki adımlar