Firebase क्रॉस डिवाइस कोडलैब (कोड बनाना सीखना)

1. शुरुआती जानकारी

पिछले अपडेट की तारीख: 14-03-2022

क्रॉस डिवाइस कम्यूनिकेशन के लिए FlutterFire

आजकल, होम ऑटोमेशन, पहनने योग्य डिवाइस, और सेहत से जुड़ी टेक्नोलॉजी वाले डिवाइसों का इस्तेमाल तेज़ी से बढ़ रहा है. इसलिए, मोबाइल ऐप्लिकेशन बनाने के लिए, अलग-अलग डिवाइसों के बीच कम्यूनिकेशन की सुविधा उपलब्ध कराना ज़रूरी हो गया है. क्रॉस डिवाइस कम्यूनिकेशन सेट अप करना, जैसे कि फ़ोन ऐप्लिकेशन से ब्राउज़र को कंट्रोल करना या फ़ोन से टीवी पर चलने वाले कॉन्टेंट को कंट्रोल करना, आम तौर पर सामान्य मोबाइल ऐप्लिकेशन बनाने से ज़्यादा मुश्किल होता है .

Firebase का रीयलटाइम डेटाबेस, Presence API उपलब्ध कराता है. इससे उपयोगकर्ता अपने डिवाइस के ऑनलाइन/ऑफ़लाइन स्टेटस को देख सकते हैं. इसका इस्तेमाल Firebase Installations Service के साथ किया जाता है. इससे उन सभी डिवाइसों को ट्रैक और कनेक्ट किया जा सकता है जिन पर एक ही उपयोगकर्ता ने साइन इन किया है. इस कोर्स में, आपको अलग-अलग प्लैटफ़ॉर्म के लिए ऐप्लिकेशन बनाने के लिए, Flutter का इस्तेमाल करना सिखाया जाएगा. इसके बाद, आपको एक ऐसा प्रोटोटाइप बनाना होगा जो एक डिवाइस पर संगीत चलाता हो और दूसरे डिवाइस पर संगीत को कंट्रोल करता हो!

आपको क्या बनाने को मिलेगा

इस कोडलैब में, आपको एक आसान म्यूज़िक प्लेयर रिमोट कंट्रोलर बनाने का तरीका बताया जाएगा. आपका ऐप्लिकेशन:

  • Android, iOS, और वेब पर Flutter की मदद से बनाया गया एक आसान म्यूज़िक प्लेयर.
  • उपयोगकर्ताओं को साइन इन करने की अनुमति दें.
  • जब एक ही उपयोगकर्ता ने कई डिवाइसों पर साइन इन किया हो, तब डिवाइसों को कनेक्ट करें.
  • इस कुकी का इस्तेमाल, लोगों को एक डिवाइस से दूसरे डिवाइस पर संगीत चलाने की सुविधा देने के लिए किया जाता है.

7f0279938e1d3ab5.gif

आपको क्या सीखने को मिलेगा

  • Flutter की मदद से म्यूज़िक प्लेयर ऐप्लिकेशन बनाने और उसे चलाने का तरीका.
  • उपयोगकर्ताओं को Firebase Auth की मदद से साइन इन करने की अनुमति कैसे दें.
  • डिवाइसों को कनेक्ट करने के लिए, Firebase RTDB Presence API और Firebase Installation Service का इस्तेमाल कैसे करें.

आपको इन चीज़ों की ज़रूरत होगी

2. सेट अप करना

स्टार्टर कोड पाना

हमने Flutter में एक म्यूज़िक प्लेयर ऐप्लिकेशन बनाया है. स्टार्टर कोड, Git रिपो में मौजूद होता है. शुरू करने के लिए, कमांड लाइन पर repo को क्लोन करें. इसके बाद, शुरुआती स्थिति वाले फ़ोल्डर में जाएं और डिपेंडेंसी इंस्टॉल करें:

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

cd cross-device-controller/starter_code

flutter pub get

ऐप्लिकेशन बनाना

ऐप्लिकेशन बनाने के लिए, अपने पसंदीदा आईडीई का इस्तेमाल किया जा सकता है. इसके अलावा, कमांड लाइन का इस्तेमाल भी किया जा सकता है.

अपनी ऐप्लिकेशन डायरेक्ट्री में, वेब के लिए ऐप्लिकेशन बनाएं. इसके लिए, flutter run -d web-server.कमांड का इस्तेमाल करें. आपको यह प्रॉम्प्ट दिखेगा.

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

म्यूज़िक प्लेयर देखने के लिए, http://localhost:<port> पर जाएं.

अगर आपको Android एम्युलेटर या iOS सिम्युलेटर के बारे में जानकारी है, तो उन प्लैटफ़ॉर्म के लिए ऐप्लिकेशन बनाया जा सकता है. साथ ही, flutter run -d <device_name> कमांड का इस्तेमाल करके उसे इंस्टॉल किया जा सकता है.

वेब ऐप्लिकेशन में, बुनियादी स्टैंडअलोन म्यूज़िक प्लेयर दिखना चाहिए. पक्का करें कि प्लेयर की सुविधाएं उम्मीद के मुताबिक काम कर रही हों. यह एक सामान्य म्यूज़िक प्लेयर ऐप्लिकेशन है, जिसे इस कोडलैब के लिए डिज़ाइन किया गया है. यह सिर्फ़ Firebase का गाना, Better Together चला सकता है.

Android एम्युलेटर या iOS सिम्युलेटर सेट अप करना

अगर आपके पास डेवलपमेंट के लिए पहले से ही कोई Android या iOS डिवाइस है, तो इस चरण को छोड़ा जा सकता है.

Android एम्युलेटर बनाने के लिए, Android Studio डाउनलोड करें. यह Flutter डेवलपमेंट के साथ भी काम करता है. इसके बाद, वर्चुअल डिवाइस बनाना और उन्हें मैनेज करना में दिए गए निर्देशों का पालन करें.

iOS सिम्युलेटर बनाने के लिए, आपको Mac एनवायरमेंट की ज़रूरत होगी. XCode डाउनलोड करें. इसके बाद, सिम्युलेटर की खास जानकारी > सिम्युलेटर का इस्तेमाल करें > सिम्युलेटर खोलें और बंद करें में दिए गए निर्देशों का पालन करें.

3. Firebase सेट अप करना

Firebase प्रोजेक्ट बनाना

  1. अपने Google खाते का इस्तेमाल करके, Firebase कंसोल में साइन इन करें.
  2. नया प्रोजेक्ट बनाने के लिए, बटन पर क्लिक करें. इसके बाद, प्रोजेक्ट का नाम डालें. उदाहरण के लिए, Firebase-Cross-Device-Codelab.
  3. जारी रखें पर क्लिक करें.
  4. अगर आपसे कहा जाए, तो Firebase की शर्तें पढ़ें और स्वीकार करें. इसके बाद, जारी रखें पर क्लिक करें.
  5. (ज़रूरी नहीं) Firebase कंसोल में एआई की मदद पाने की सुविधा चालू करें. इसे "Firebase में Gemini" कहा जाता है.
  6. इस कोडलैब के लिए, आपको Google Analytics की ज़रूरत नहीं है. इसलिए, Google Analytics के विकल्प को टॉगल करके बंद करें.
  7. प्रोजेक्ट बनाएं पर क्लिक करें. इसके बाद, प्रोजेक्ट के प्रोविज़न होने का इंतज़ार करें. इसके बाद, जारी रखें पर क्लिक करें.

Firebase SDK टूल इंस्टॉल करना

कमांड लाइन पर वापस जाएं और प्रोजेक्ट डायरेक्ट्री में, Firebase को इंस्टॉल करने के लिए यह कमांड चलाएं:

flutter pub add firebase_core

pubspec.yaml फ़ाइल में, firebase_core के वर्शन को कम से कम 1.13.1 पर सेट करें या flutter upgrade चलाएं

FlutterFire को शुरू करना

  1. अगर आपने Firebase CLI इंस्टॉल नहीं किया है, तो curl -sL https://firebase.tools | bash चलाकर इसे इंस्टॉल किया जा सकता है.
  2. firebase login चलाकर और दिए गए निर्देशों का पालन करके लॉग इन करें.
  3. dart pub global activate flutterfire_cli चलाकर, FlutterFire CLI इंस्टॉल करें.
  4. flutterfire configure चलाकर, FlutterFire CLI को कॉन्फ़िगर करें.
  5. प्रॉम्प्ट में, वह प्रोजेक्ट चुनें जिसे आपने इस कोडलैब के लिए अभी बनाया है. जैसे, Firebase-Cross-Device-Codelab.
  6. कॉन्फ़िगरेशन से जुड़ी सहायता चुनने के लिए कहा जाए, तो iOS, Android, और वेब चुनें.
  7. जब आपसे Apple बंडल आईडी मांगा जाए, तब कोई यूनीक डोमेन टाइप करें या com.example.appname डालें. यह कोडलैब के लिए ठीक है.

कॉन्फ़िगर करने के बाद, आपके लिए एक firebase_options.dart फ़ाइल जनरेट की जाएगी. इसमें, शुरू करने के लिए ज़रूरी सभी विकल्प शामिल होंगे.

Flutter और Firebase को शुरू करने के लिए, अपने एडिटर में main.dart फ़ाइल में यह कोड जोड़ें:

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

इस कमांड की मदद से ऐप्लिकेशन को कंपाइल करें:

flutter run

आपने अब तक यूज़र इंटरफ़ेस (यूआई) के किसी भी एलिमेंट में बदलाव नहीं किया है. इसलिए, ऐप्लिकेशन के लुक और व्यवहार में कोई बदलाव नहीं हुआ है. हालांकि, अब आपके पास Firebase ऐप्लिकेशन है. इसलिए, Firebase के प्रॉडक्ट इस्तेमाल किए जा सकते हैं. जैसे:

  • Firebase Authentication, जिसकी मदद से आपके ऐप्लिकेशन के उपयोगकर्ता साइन इन कर सकते हैं.
  • Firebase रीयलटाइम डेटाबेस(आरटीडीबी); डिवाइस के ऑनलाइन/ऑफ़लाइन स्टेटस को ट्रैक करने के लिए, प्रेज़ेंस एपीआई का इस्तेमाल किया जाएगा
  • Firebase के सुरक्षा नियमों की मदद से, डेटाबेस को सुरक्षित किया जा सकता है.
  • Firebase Installations Service का इस्तेमाल करके, उन डिवाइसों की पहचान की जाती है जिन पर किसी एक उपयोगकर्ता ने साइन इन किया है.

4. Firebase Auth जोड़ना

Firebase Authentication के लिए, ईमेल से साइन-इन करने की सुविधा चालू करना

उपयोगकर्ताओं को वेब ऐप्लिकेशन में साइन इन करने की अनुमति देने के लिए, आपको ईमेल/पासवर्ड साइन-इन करने का तरीका इस्तेमाल करना होगा:

  1. Firebase कंसोल में, बाईं ओर मौजूद पैनल में Build मेन्यू को बड़ा करें.
  2. पुष्टि करें पर क्लिक करें. इसके बाद, शुरू करें बटन पर क्लिक करें. इसके बाद, साइन-इन करने का तरीका टैब पर क्लिक करें.
  3. साइन-इन की सुविधा देने वाली कंपनियां सूची में, ईमेल/पासवर्ड पर क्लिक करें. इसके बाद, चालू करें स्विच को चालू करें और फिर सेव करें पर क्लिक करें. 58e3e3e23c2f16a4.png

Flutter में Firebase Authentication को कॉन्फ़िगर करना

ज़रूरी Flutter पैकेज इंस्टॉल करने के लिए, कमांड लाइन में ये कमांड चलाएं:

flutter pub add firebase_auth

flutter pub add provider

इस कॉन्फ़िगरेशन की मदद से, अब साइन-इन और साइन-आउट करने की प्रोसेस बनाई जा सकती है. स्क्रीन के हिसाब से, पुष्टि की स्थिति नहीं बदलनी चाहिए. इसलिए, आपको ऐप्लिकेशन लेवल पर स्थिति में होने वाले बदलावों को ट्रैक करने के लिए, application_state.dart क्लास बनानी होगी. जैसे, लॉग इन और लॉग आउट करना. इसके बारे में ज़्यादा जानने के लिए, Flutter स्टेट मैनेजमेंट दस्तावेज़ पढ़ें.

नई application_state.dart फ़ाइल में यह चिपकाएं:

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

यह पक्का करने के लिए कि ऐप्लिकेशन शुरू होने पर ApplicationState शुरू हो जाए, आपको 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(),
  ));
}

ऐप्लिकेशन का यूज़र इंटरफ़ेस (यूआई) पहले जैसा ही होना चाहिए. हालांकि, अब उपयोगकर्ताओं को साइन इन करने और ऐप्लिकेशन की स्थितियों को सेव करने की अनुमति दी जा सकती है.

साइन इन करने का फ़्लो बनाना

इस चरण में, आपको साइन इन और साइन आउट करने की प्रोसेस पर काम करना होगा. यूज़र फ़्लो ऐसा दिखेगा:

  1. जिस उपयोगकर्ता ने लॉग आउट किया हुआ है वह ऐप्लिकेशन बार की दाईं ओर मौजूद कॉन्टेक्स्ट मेन्यू 71fcc1030a336423.png पर क्लिक करके, साइन-इन करने की प्रोसेस शुरू करेगा.
  2. साइन-इन करने का फ़्लो, एक डायलॉग बॉक्स में दिखेगा.
  3. अगर उपयोगकर्ता ने पहले कभी साइन इन नहीं किया है, तो उसे मान्य ईमेल पते और पासवर्ड का इस्तेमाल करके खाता बनाने के लिए कहा जाएगा.
  4. अगर उपयोगकर्ता ने पहले साइन इन किया है, तो उसे अपना पासवर्ड डालने के लिए कहा जाएगा.
  5. उपयोगकर्ता के साइन इन करने के बाद, कॉन्टेक्स्ट मेन्यू पर क्लिक करने से साइन आउट करें विकल्प दिखेगा.

c295f6fa2e1d40f3.png

साइन-इन फ़्लो जोड़ने के लिए, तीन चरण पूरे करने होते हैं.

सबसे पहले, एक AppBarMenuButton विजेट बनाएं. यह विजेट, उपयोगकर्ता के loginState के आधार पर कॉन्टेक्स्ट मेन्यू पॉप-अप को कंट्रोल करेगा. इंपोर्ट जोड़ना

lib/src/widgets.dart

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

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

इसके बाद, उसी widgets.dart क्लास में 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,
          ),
        ),
      ]),
    );
  }
}

तीसरा, main.dart. में जाकर मौजूदा ऐप्लिकेशन बार विजेट ढूंढें. AppBarMenuButton को जोड़कर, साइन इन करें या साइन आउट करें विकल्प दिखाएं.

lib/main.dart

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

इन बदलावों के साथ ऐप्लिकेशन को रीस्टार्ट करने के लिए, flutter run कमांड चलाएं. आपको ऐप्लिकेशन बार की दाईं ओर, संदर्भ मेन्यू 71fcc1030a336423.png दिखेगा. इस पर क्लिक करने से, आपको साइन इन करने के लिए एक डायलॉग बॉक्स दिखेगा.

मान्य ईमेल पते और पासवर्ड से साइन इन करने के बाद, आपको कॉन्टेक्स्ट मेन्यू में साइन आउट करें का विकल्प दिखेगा.

Firebase कंसोल में, Authentication में जाकर देखें. आपको नए उपयोगकर्ता के तौर पर, ईमेल पता दिखना चाहिए.

888506c86a28a72c.png

बधाई हो! अब उपयोगकर्ता, ऐप्लिकेशन में साइन इन कर सकते हैं!

5. डेटाबेस कनेक्शन जोड़ना

अब Firebase Presence API का इस्तेमाल करके, डिवाइस रजिस्टर किया जा सकता है.

ज़रूरी डिपेंडेंसी जोड़ने के लिए, कमांड लाइन में ये कमांड चलाएं:

flutter pub add firebase_app_installations

flutter pub add firebase_database

डेटाबेस बनाना

Firebase कंसोल में,

  1. Firebase कंसोल के Realtime Database सेक्शन पर जाएं. डेटाबेस बनाएं पर क्लिक करें.
  2. अगर सुरक्षा के नियमों के लिए शुरुआती मोड चुनने का अनुरोध किया जाता है, तो फ़िलहाल टेस्ट मोड चुनें**.** (टेस्ट मोड में, सुरक्षा के ऐसे नियम बनाए जाते हैं जो सभी अनुरोधों को अनुमति देते हैं. सुरक्षा के नियमों को बाद में जोड़ा जाएगा. यह ज़रूरी है कि सुरक्षा नियमों को टेस्ट मोड में रखते हुए, कभी भी प्रोडक्शन पर न जाएं.)

फ़िलहाल, डेटाबेस खाली है. सामान्य टैब में जाकर, प्रोजेक्ट सेटिंग में अपना databaseURL ढूंढें. नीचे की ओर स्क्रोल करके, वेब ऐप्लिकेशन सेक्शन पर जाएं.

1b6076f60a36263b.png

firebase_options.dart फ़ाइल में अपना databaseURL जोड़ें:

lib/firebase_options.dart

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

RTDB Presence API का इस्तेमाल करके डिवाइसों को रजिस्टर करना

आपको किसी उपयोगकर्ता के डिवाइसों को तब रजिस्टर करना है, जब वे ऑनलाइन दिखें. इसके लिए, आपको Firebase Installations और Firebase RTDB Presence API का इस्तेमाल करना होगा. इससे, एक ही उपयोगकर्ता के ऑनलाइन डिवाइसों की सूची को ट्रैक किया जा सकेगा. इस लक्ष्य को पूरा करने के लिए, यहां दिया गया कोड इस्तेमाल करें:

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

कमांड लाइन पर वापस जाएं और अपने डिवाइस पर या ब्राउज़र में flutter run. की मदद से ऐप्लिकेशन बनाएं और उसे चलाएं

अपने ऐप्लिकेशन में, उपयोगकर्ता के तौर पर साइन इन करें. अलग-अलग प्लैटफ़ॉर्म पर एक ही उपयोगकर्ता के तौर पर साइन इन करना न भूलें.

Firebase कंसोल में, आपको अपने डिवाइस, डेटाबेस में एक उपयोगकर्ता आईडी के नीचे दिखने चाहिए.

5bef49cea3564248.png

6. डिवाइस के सिंक होने की स्थिति

कोई लीड डिवाइस चुनें

डिवाइसों के बीच स्टेटस सिंक करने के लिए, किसी एक डिवाइस को लीडर या कंट्रोलर के तौर पर सेट करें. लीड डिवाइस, फ़ॉलोअर डिवाइसों पर स्थितियां तय करेगा.

application_state.dart में setLeadDevice तरीका बनाएं. साथ ही, RTDB में मौजूद active_device कुंजी की मदद से इस डिवाइस को ट्रैक करें:

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

ऐप्लिकेशन बार के संदर्भ मेन्यू में यह सुविधा जोड़ने के लिए, SignedInMenuButton विजेट में बदलाव करके Controller नाम का PopupMenuItem बनाएं. इस मेन्यू की मदद से, उपयोगकर्ता लीड डिवाइस सेट कर पाएंगे.

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

लीड डिवाइस की स्थिति को डेटाबेस में लिखता है

लीड डिवाइस सेट करने के बाद, यहां दिए गए कोड का इस्तेमाल करके, लीड डिवाइस की स्थितियों को RTDB के साथ सिंक किया जा सकता है. application_state.dart.इस कोड को application_state.dart. के आखिर में जोड़ें. इससे दो एट्रिब्यूट सेव होने लगेंगे: प्लेयर की स्थिति (चालू या बंद) और स्लाइडर की पोज़िशन.

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

आखिर में, जब भी कंट्रोलर के प्लेयर की स्थिति अपडेट हो, तब आपको setActiveDeviceState को कॉल करना होगा. मौजूदा player_widget.dart फ़ाइल में ये बदलाव करें:

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

डेटाबेस से लीड डिवाइस की स्थिति को पढ़ना

लीड डिवाइस की स्थिति को पढ़ने और इस्तेमाल करने के दो हिस्से होते हैं. सबसे पहले, आपको application_state में लीड प्लेयर की स्थिति का डेटाबेस लिसनर सेट अप करना होगा. यह लिसनर, फ़ॉलो करने वाले डिवाइसों को यह बताएगा कि स्क्रीन को कब अपडेट करना है. इसके लिए, वह कॉलबैक का इस्तेमाल करेगा. ध्यान दें कि आपने इस चरण में एक इंटरफ़ेस OnLeadDeviceChangeCallback तय किया है. इसे अभी लागू नहीं किया गया है. अगले चरण में, player_widget.dart में इस इंटरफ़ेस को लागू किया जाएगा.

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

दूसरा, player_widget.dart में प्लेयर के शुरू होने के दौरान डेटाबेस लिसनर शुरू करें. _updatePlayer फ़ंक्शन पास करें, ताकि डेटाबेस की वैल्यू बदलने पर, फ़ॉलो करने वाले प्लेयर की स्थिति को अपडेट किया जा सके.

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

अब ऐप्लिकेशन की जांच की जा सकती है:

  1. कमांड लाइन पर, एम्युलेटर और/या ब्राउज़र में ऐप्लिकेशन को इस कमांड के साथ चलाएं: flutter run -d <device-name>
  2. ऐप्लिकेशन को ब्राउज़र, iOS सिम्युलेटर या Android एम्युलेटर में खोलें. कॉन्टेक्स्ट मेन्यू में जाएं. इसके बाद, लीडर डिवाइस के तौर पर इस्तेमाल करने के लिए कोई ऐप्लिकेशन चुनें. लीडर डिवाइस के अपडेट होने पर, आपको फ़ॉलोअर डिवाइसों के प्लेयर बदलते हुए दिखने चाहिए.
  3. अब लीडर डिवाइस बदलें, संगीत चलाएं या रोकें. इसके बाद, देखें कि फ़ॉलोअर डिवाइसों पर संगीत उसी के मुताबिक अपडेट हो रहा है या नहीं.

अगर फ़ॉलो करने वाले डिवाइसों पर अपडेट ठीक से हो जाता है, तो इसका मतलब है कि आपने क्रॉस डिवाइस कंट्रोलर बना लिया है. बस एक ज़रूरी चरण बचा है.

7. सुरक्षा के नियम अपडेट करना

जब तक हम बेहतर सुरक्षा नियम नहीं लिखते, तब तक कोई व्यक्ति किसी ऐसे डिवाइस पर स्टेट लिख सकता है जिसका मालिकाना हक उसके पास नहीं है! इसलिए, प्रोसेस पूरी करने से पहले, रीयलटाइम डेटाबेस के सुरक्षा नियमों को अपडेट करें. इससे यह पक्का किया जा सकेगा कि डिवाइस में साइन इन करने वाला उपयोगकर्ता ही डिवाइस पर डेटा पढ़ या लिख सकता है. Firebase कंसोल में, रीयलटाइम डेटाबेस पर जाएं. इसके बाद, नियम टैब पर जाएं. यहां दिए गए नियमों को चिपकाएं. इससे सिर्फ़ साइन इन किया हुआ उपयोगकर्ता ही अपने डिवाइस की स्थितियां पढ़ और लिख पाएगा:

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

8. बधाई हो!

bcd986f7106d892b.gif

बधाई हो, आपने Flutter का इस्तेमाल करके, अलग-अलग डिवाइसों के लिए रिमोट कंट्रोलर बना लिया है!

क्रेडिट

बेटर टुगेदर, एक फ़ायरबेस सॉन्ग

  • रायन वर्नोन के संगीत वाली फ़िल्में
  • मरिसा क्रिस्टी के लिखे बोल और एल्बम कवर
  • जेपी गोमेज़ की आवाज़ में

9. बोनस

एक और चुनौती के तौर पर, Flutter FutureBuilder का इस्तेमाल करके, मौजूदा लीड डिवाइस टाइप को यूज़र इंटरफ़ेस (यूआई) में एसिंक्रोनस तरीके से जोड़ें. अगर आपको मदद चाहिए, तो इसे उस फ़ोल्डर में लागू किया जाता है जिसमें कोडलैब का पूरा किया गया वर्शन मौजूद होता है.

रेफ़रंस दस्तावेज़ और अगले चरण