1. परिचय
इस कोडलैब में, मल्टीप्लेयर काउंटर ऐप्लिकेशन बनाया जाता है. इसमें, Flutter के फ़्रंटएंड और Firebase के बैकएंड, दोनों के लिए Dart का इस्तेमाल करने का तरीका बताया जाता है.
इसमें, अपने ऐप्लिकेशन और सर्वर के बीच डेटा मॉडल शेयर करने का तरीका भी बताया जाता है. इससे, लॉजिक को डुप्लीकेट करने की ज़रूरत नहीं पड़ती.
आपको क्या सीखने को मिलेगा
- शेयर किए गए कारोबारी लॉजिक को, Dart के स्टैंडअलोन पैकेज में एक्सट्रैक्ट करना.
- Dart में, Firebase के लिए Cloud Functions को नेटिव तरीके से लिखना और डिप्लॉय करना.
- सर्वरलेस कोल्ड स्टार्ट को कम करने के लिए, Dart के Ahead-of-Time (AOT) कंपाइलेशन का इस्तेमाल करना.
- Firebase Emulator Suite का इस्तेमाल करके, अपने स्टैक को स्थानीय तौर पर टेस्ट करना.
2. ज़रूरी शर्तें
- Flutter SDK (सबसे नया स्टेबल वर्शन).
- Firebase CLI (v15.15.0 या इसके बाद वाला वर्शन होना चाहिए).
- कोई कोड एडिटर, जैसे कि Antigravity, Visual Studio Code, IntelliJ या Android Studio. इसमें Dart और Flutter प्लग इन इंस्टॉल होने चाहिए.
- Flutter और Firebase के बारे में बुनियादी जानकारी.
3. बैकएंड के लिए Dart का इस्तेमाल क्यों करें?
कई क्लाउड ऐप्लिकेशन, फ़्रंटएंड यूज़र इंटरफ़ेस (यूआई) के लिए Dart और बैकएंड के लिए TypeScript, Python या Go जैसी किसी दूसरी भाषा का इस्तेमाल करते हैं. इसके लिए, डेटा मॉडल के दो अलग-अलग सेट बनाए रखने पड़ते हैं. डेटाबेस स्कीमा में बदलाव होने पर, आपको दोनों कोडबेस अपडेट करने होंगे.
ध्यान दें: बैकएंड पर Dart का इस्तेमाल करने से, आपको कोड को डुप्लीकेट किए बिना, क्लाइंट पर Flutter के रिस्पॉन्सिव यूज़र इंटरफ़ेस (यूआई) को सर्वर पर सुरक्षित तरीके से पुष्टि करने की सुविधा के साथ जोड़ा जा सकता है.
4. Flutter ऐप्लिकेशन बनाना
Flutter का स्टैंडर्ड ऐप्लिकेशन बनाएं:
flutter create my_counter
cd my_counter
# Run the app to see the default counter example
flutter run
Flutter के स्टैंडर्ड ऐप्लिकेशन में, lib/main.dart स्थानीय तौर पर काउंटर की स्थिति को मैनेज करता है:
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
यह तरीका स्थानीय स्थिति के लिए काम करता है. हालांकि, यह मल्टीप्लेयर ऐप्लिकेशन के लिए काम नहीं करता, क्योंकि इसमें सर्वर को सोर्स ऑफ़ ट्रुथ के तौर पर काम करना होता है. एक से ज़्यादा खिलाड़ियों के लिए, हम इस लॉजिक को अगले चरणों में बैकएंड पर ले जाएंगे.
5. शेयर किया गया पैकेज बनाना
फ़्रंटएंड और बैकएंड पर मॉडल को डुप्लीकेट करने से बचने के लिए, अपने प्रोजेक्ट रिपॉज़िटरी में Dart का शेयर किया गया पैकेज बनाएं. Flutter ऐप्लिकेशन और Firebase के फ़ंक्शन, दोनों इस पैकेज पर निर्भर करते हैं.
my_counter प्रोजेक्ट की रूट डायरेक्ट्री से, ये कमांड चलाएं:
mkdir -p packages
cd packages
dart create -t package shared
डिपेंडेंसी जोड़ें
packages/shared/pubspec.yaml में, JSON सीरियलाइज़ेशन टूल जोड़ें:
dependencies:
json_annotation: ^4.9.0
dev_dependencies:
build_runner: ^2.4.9
json_serializable: ^6.8.0
शेयर किए गए मॉडल तय करना
packages/shared/lib/src/models.dart बनाएं. इस फ़ाइल में, ऐप्लिकेशन और सर्वर, दोनों के लिए इस्तेमाल किए जाने वाले डेटा स्ट्रक्चर के बारे में बताया गया है.
import 'package:json_annotation/json_annotation.dart';
part 'models.g.dart';
@JsonSerializable()
class IncrementResponse {
final bool success;
final String? message;
final int? newCount;
const IncrementResponse({required this.success, this.message, this.newCount});
factory IncrementResponse.fromJson(Map<String, dynamic> json) =>
_$IncrementResponseFromJson(json);
Map<String, dynamic> toJson() => _$IncrementResponseToJson(this);
}
// Store the function name as a constant to ensure consistency between client and server.
const incrementCallable = 'increment';
packages/shared/lib/shared.dart में, अपने मॉडल एक्सपोर्ट करें:
library shared;
export 'src/models.dart';
packages/shared डायरेक्ट्री में, JSON सीरियलाइज़ेशन कोड जनरेट करने के लिए, बिल्ड रनर चलाएं:
dart run build_runner build
6. Firebase के लिए Cloud Functions सेट अप करना
Firebase के लिए Cloud Functions, सर्वरलेस फ़्रेमवर्क है. इसकी मदद से, आपको अपने सर्वर को मैनेज और स्केल किए बिना, बैकएंड कोड को अपने-आप चलाने की सुविधा मिलती है. Dart इसके लिए सबसे सही है, क्योंकि यह Ahead-of-Time (AOT) को बाइनरी में कंपाइल करता है. इसके लिए, Node.js या Java जैसे हैवी रनटाइम एनवायरमेंट की ज़रूरत नहीं होती. इससे, आपके फ़ंक्शन के कोल्ड स्टार्ट टाइम में काफ़ी कमी आती है.
अपने प्रोजेक्ट की रूट डायरेक्ट्री पर जाएं और Firebase के लिए Cloud Functions को शुरू करें:
cd ../..
firebase experiments:enable dartfunctions
firebase init functions
dart pub add google_cloud_firestore
- भाषा के लिए पूछे जाने पर, Dart चुनें.
शेयर किए गए पैकेज को लिंक करना
functions/pubspec.yaml में, शेयर किए गए पैकेज के लिए कोई रिलेटिव पाथ जोड़ें:
dependencies:
firebase_functions:
google_cloud_firestore:
shared:
path: ../packages/shared
7. फ़ंक्शन लिखना
बैकएंड लॉजिक लिखने के लिए, functions/bin/server.dart खोलें और इसके कॉन्टेंट को इस कोड से बदलें:
import 'dart:convert';
import 'package:firebase_functions/firebase_functions.dart';
import 'package:google_cloud_firestore/google_cloud_firestore.dart'
show FieldValue;
import 'package:shared/shared.dart';
void main(List<String> args) async {
await fireUp(args, (firebase) {
// Listen for calls to the http request and name defined in the shared package.
firebase.https.onRequest(name: incrementCallable, (request) async {
// In a production app, verify the user with request.auth?.uid here.
print('Incrementing counter on the server...');
// Get firestore database instance
final firestore = firebase.adminApp.firestore();
// Get a reference to the counter document
final counterDoc = firestore.collection('counters').doc('global');
// Get the current snapshot for the count data
final snapshot = await counterDoc.get();
// Increment response we will send back
IncrementResponse incrementResponse;
// Check for the current count and if the snapshot exists
if (snapshot.data() case {'count': int value} when snapshot.exists) {
if (request.method == 'GET') {
// Get the current result
incrementResponse = IncrementResponse(
success: true,
message: 'Read-only sync complete',
newCount: value,
);
} else if (request.method == 'POST') {
// Increment count by one
final step = request.url.queryParameters['step'] as int? ?? 1;
await counterDoc.update({'count': FieldValue.increment(step)});
incrementResponse = IncrementResponse(
success: true,
message: 'Atomic increment complete',
newCount: value + step,
);
} else {
throw FailedPreconditionError(
'only GET and POST requests are allowed',
);
}
} else {
// Create a new document with a count of 1
await counterDoc.set({'count': 1});
incrementResponse = const IncrementResponse(
success: true,
message: 'Cloud-sync complete',
newCount: 1,
);
}
// Return the response as JSON
return Response(
200,
body: jsonEncode(incrementResponse.toJson()),
headers: {'Content-Type': 'application/json'},
);
});
});
}
8. Firebase Emulator Suite की मदद से, स्थानीय तौर पर टेस्ट करना
डिप्लॉय किए बिना, फ़्रंटएंड और बैकएंड, दोनों को स्थानीय तौर पर चलाया जा सकता है.
अपने प्रोजेक्ट की रूट डायरेक्ट्री से, Firebase Emulator Suite शुरू करें:
# Enable functions and firestore for the emulators
firebase init emulators
# Start the emulators and optionally open up the Admin UI
firebase emulators:start
शेयर किए गए पैकेज को लिंक करना
pubspec.yaml में, शेयर किए गए पैकेज के लिए कोई रिलेटिव पाथ जोड़ें और http पैकेज जोड़ें:
dependencies:
http: ^1.6.0
shared:
path: ../packages/shared
अपने Flutter प्रोजेक्ट में, lib/main.dart खोलें और इसके कॉन्टेंट को इस कोड से बदलें. यह फ़्रंटएंड कोड, बैकएंड की तरह ही IncrementResponse क्लास का इस्तेमाल करता है.
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:shared/shared.dart';
/// Get from emulator output when running or when deploying:
/// ✔ functions[us-central1-increment]: http function initialized
/// (http://127.0.0.1:5001/demo-no-project/us-central1/increment).
const incrementUrl = 'FIREBASE_FUNCTIONS_URL_HERE';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) => MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.blue),
home: const CounterPage(),
);
}
class CounterPage extends StatefulWidget {
const CounterPage({super.key});
@override
State<CounterPage> createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _count = 0;
bool _loading = false;
@override
void initState() {
super.initState();
// Fetch the current count
_increment(readOnly: true).ignore();
}
Future<void> _increment({bool readOnly = false}) async {
setState(() => _loading = true);
try {
// Call the Dart function.
final uri = Uri.parse(incrementUrl);
final response = readOnly ? await http.get(uri) : await http.post(uri);
// Parse the response back into the shared Dart object.
final responseData = jsonDecode(response.body);
final incrementResponse = IncrementResponse.fromJson(responseData);
if (incrementResponse.success) {
setState(() => _count = incrementResponse.newCount ?? _count);
}
} catch (e) {
print("Error calling function: $e");
} finally {
setState(() => _loading = false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Multiplayer Counter')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('You have pushed the button this many times:'),
Text(
'$_count',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _loading ? null : _increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
);
}
}
Flutter ऐप्लिकेशन चलाएं. फ़्लोटिंग ऐक्शन बटन पर क्लिक करने पर, ऐप्लिकेशन स्थानीय Dart बैकएंड को कॉल करता है, नया काउंट वापस लेता है, और यूज़र इंटरफ़ेस (यूआई) को अपडेट करता है.
9. Firebase पर डिप्लॉय करना
Dart बैकएंड को डिप्लॉय करने के लिए, यह कमांड चलाएं:
firebase deploy --only functions
कमांड चलने के बाद, यूआरएल कॉपी करें और flutter ऐप्लिकेशन के सोर्स कोड में, FIREBASE_FUNCTIONS_URL_HERE को बदलें. यह कोड हमने पहले जोड़ा था.
10. समस्या का हल
firebase: command not found
पक्का करें कि Firebase CLI इंस्टॉल हो और आपका PATH अपडेट हो. इसे npm का इस्तेमाल करके इंस्टॉल किया जा सकता है: npm install -g firebase-tools.
init functions टेंप्लेट में Dart मौजूद नहीं है
firebase init functions चलाने पर, Dart को डिप्लॉय करने के लिए विकल्पों की सूची के तौर पर दिखाने और टेंप्लेट कोड बनाने के लिए, firebase experiments:enable dartfunctions चलाकर, एक्सपेरिमेंट फ़्लैग सेट करना होगा.
फ़ंक्शन एम्युलेटर कनेक्ट नहीं हो रहा है
पक्का करें कि आपने localhost और पोर्ट 5001 का इस्तेमाल किया हो. अगर Android Emulator पर टेस्ट किया जा रहा है, तो डिवाइस localhost को आपकी होस्ट मशीन पर रिज़ॉल्व नहीं करता. main.dart में, एम्युलेटर कॉन्फ़िगरेशन को 10.0.2.2 का इस्तेमाल करने के लिए अपडेट करें.
शेयर किया गया पैकेज नहीं मिला
functions/pubspec.yaml में, रिलेटिव पाथ की पुष्टि करें. अगर आपका फ़ोल्डर स्ट्रक्चर, कोडलैब से अलग है, तो path: ../packages/shared को सही डायरेक्ट्री पर पॉइंट करने के लिए अडजस्ट करें.
क्या मुझे json_serializable का इस्तेमाल करना होगा?
हालांकि, यह ज़रूरी नहीं है. फिर भी, json_serializable का इस्तेमाल करने से, fromJson और toJson तरीके मैन्युअल तरीके से लिखने पर होने वाली गड़बड़ियां नहीं होती हैं. इससे यह पक्का होता है कि आपका फ़्रंटएंड और बैकएंड, डेटा के एक ही फ़ॉर्मैट का इस्तेमाल करें.
11. बधाई हो
आपने Dart का फ़ुल-स्टैक ऐप्लिकेशन बना लिया है. डेटा मॉडल को शेयर किए गए पैकेज में बनाए रखने से, यह पक्का होता है कि आपके एपीआई रिस्पॉन्स और क्लाइंट यूज़र इंटरफ़ेस (यूआई) सिंक रहें. इसके लिए, पूरे स्टैक में एक ही प्रोग्रामिंग भाषा का इस्तेमाल किया जाता है.