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

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

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

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

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

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

आपको क्या बनाना होगा

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

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

7f0279938e1d3ab5.gif

आपको इनके बारे में जानकारी मिलेगी

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

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

  • Flutter डेवलपमेंट एनवायरमेंट. इसे सेट अप करने के लिए, Flutter का इंस्टॉलेशन गाइड में दिए गए निर्देशों का पालन करें.
  • Flutter का कम से कम 2.10 या इसके बाद वाला वर्शन होना ज़रूरी है. अगर आपके पास इसका पुराना वर्शन है, तो flutter upgrade. चलाएं
  • Firebase खाता.

2. सेट अप किया जा रहा है

स्टार्टर कोड पाएं

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

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

cd cross-device-controller/starter_code

flutter pub get

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

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

अपनी ऐप्लिकेशन डायरेक्ट्री में, 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 का गाना चला सकता है.

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

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

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

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

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

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

http://console.firebase.google.com/ पर कोई ब्राउज़र खोलें.

  1. Firebase में साइन इन करें.
  2. Firebase कंसोल में, प्रोजेक्ट जोड़ें (या प्रोजेक्ट बनाएं) पर क्लिक करें और अपने Firebase प्रोजेक्ट को Firebase-क्रॉस-डिवाइस-कोडलैब नाम दें.
  3. प्रोजेक्ट बनाने के विकल्पों पर क्लिक करें. अनुरोध किए जाने पर, Firebase की शर्तें स्वीकार करें. Google Analytics को सेट अप न करें, क्योंकि इस ऐप्लिकेशन के लिए Analytics का इस्तेमाल नहीं किया जाएगा.

आपको बताई गई फ़ाइलें डाउनलोड करने या create.gradle फ़ाइलों को बदलने की ज़रूरत नहीं है. FlutterFire को शुरू करने के बाद कॉन्फ़िगर करें.

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

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

flutter pub add firebase_core

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

FlutterFire को चालू करें

  1. अगर आपने Firebase सीएलआई इंस्टॉल नहीं किया है, तो इसे इंस्टॉल करने के लिए curl -sL https://firebase.tools | bash का इस्तेमाल करें.
  2. firebase login चलाकर और निर्देशों का पालन करके लॉग इन करें.
  3. dart pub global activate flutterfire_cli चलाकर, FlutterFire सीएलआई इंस्टॉल करें.
  4. flutterfire configure चलाकर, FlutterFire सीएलआई को कॉन्फ़िगर करें.
  5. प्रॉम्प्ट पर, वह प्रोजेक्ट चुनें जिसे आपने इस कोडलैब के लिए अभी-अभी बनाया है. जैसे, Firebase-क्रॉस-डिवाइस-कोडलैब.
  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 से पुष्टि करने की सुविधा. इससे उपयोगकर्ता आपके ऐप्लिकेशन में साइन इन कर पाएंगे.
  • Firebase रीयल टाइम डेटाबेस(RTDB); आपको डिवाइस की ऑनलाइन/ऑफ़लाइन स्थिति ट्रैक करने के लिए, मौजूदगी एपीआई का इस्तेमाल करना होगा
  • Firebase के सुरक्षा नियम की मदद से, डेटाबेस को सुरक्षित रखा जा सकता है.
  • Firebase इंस्टॉलेशन सेवा की मदद से, उन डिवाइसों की पहचान की जाती है जिनमें किसी एक उपयोगकर्ता ने साइन इन किया है.

4. Firebase पुष्टि करें

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

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

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

Flutter में Firebase से पुष्टि करने की सुविधा कॉन्फ़िगर करना

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

flutter pub add firebase_auth

flutter pub add provider

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

इसे नई 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. में मौजूद appBar विजेट खोजें. साइन इन या साइन आउट करें विकल्प दिखाने के लिए 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 कंसोल में, पुष्टि करें में जाकर, आपको नए उपयोगकर्ता के तौर पर जोड़ा गया ईमेल पता दिखेगा.

888506c86a28a72c.png

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

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

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

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

flutter pub add firebase_app_installations

flutter pub add firebase_database

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

Firebase कंसोल में,

  1. Firebase कंसोल के रीयलटाइम डेटाबेस सेक्शन पर जाएं. डेटाबेस बनाएं पर क्लिक करें.
  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 मौजूद एपीआई का इस्तेमाल करके डिवाइसों को रजिस्टर करना

आप किसी उपयोगकर्ता के डिवाइस को ऑनलाइन दिखने पर रजिस्टर करना चाहते हों. ऐसा करने के लिए, आपको Firebase इंस्टॉलेशन और Firebase RTDB exists 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. के आखिर में यह कोड जोड़ें. इससे दो एट्रिब्यूट सेव होने लगेंगे: प्लेयर की स्थिति (चलाएं या रोकें) और स्लाइडर की पोज़िशन.

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 का इस्तेमाल करके क्रॉस डिवाइस रिमोट कंट्रोलर बना लिया है!

क्रेडिट

बेटर टुगेदर, एक Firebase गाना

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

9. बोनस

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

पहचान फ़ाइलें और अगले चरण