১. ভূমিকা
এই কোডল্যাবে, আপনি একটি মাল্টিপ্লেয়ার কাউন্টার অ্যাপ তৈরি করবেন। Flutter ফ্রন্ট-এন্ড ও Firebase ব্যাক-এন্ড, দুটির জন্যই কীভাবে Dart ব্যবহার করতে হয় তা আপনি জানবেন।
এছাড়াও, আপনার অ্যাপ ও সার্ভারের মধ্যে কীভাবে ডেটা মডেল শেয়ার করতে হয় তা জানবেন, এর ফলে লজিক ডুপ্লিকেট করার প্রয়োজন হবে না।
আপনি Firebase স্যাম্পেল রিপোজিটরিের জন্য Cloud Functions-এ এই কোডল্যাবের সম্পূর্ণ সোর্স কোড দেখতে পাবেন।
আপনি কী শিখবেন
- স্ট্যান্ডঅ্যালোন Dart প্যাকেজে শেয়ার করা বিজনেস লজিক এক্সট্র্যাক্ট করুন।
- Dart-এ Firebase-এর জন্য Cloud Functions নেটিভ হিসেবে লেখা ও ডিপ্লয় করা।
- সার্ভারলেস কোল্ড স্টার্ট কমানোর জন্য Dart-এর Ahead-of-Time (AOT) কম্পাইলেশন ব্যবহার করুন।
- Firebase Emulator Suite ব্যবহার করে আপনার স্ট্যাক লোকালি পরীক্ষা করুন।
২. পূর্বশর্ত
- Flutter SDK (লেটেস্ট স্থিতিশীল ভার্সন)।
- Firebase CLI (v15.15.0 বা তার পরবর্তী যেকোনও ভার্সন থাকতে হবে)।
- Dart ও Flutter প্লাগ-ইন ইনস্টল করা আছে এমন Antigravity, Visual Studio Code, IntelliJ বা Android Studio-এর মতো কোড এডিটর।
- Flutter ও Firebase সম্পর্কে প্রাথমিক জ্ঞান।
৩. ব্যাকএন্ডের জন্য কেন Dart ব্যবহার করা হয়?
অনেক ক্লাউড অ্যাপ্লিকেশন ফ্রন্ট-এন্ড UI-এর জন্য Dart এবং ব্যাক-এন্ডের জন্য TypeScript, Python বা Go-এর মতো অন্য কোনও ভাষা ব্যবহার করে। এর জন্য ডেটা মডেলের দুটি আলাদা সেট বজায় রাখতে হয়। ডেটাবেস স্কিমা পরিবর্তন হলে, আপনাকে অবশ্যই দুটি কোডবেস আপডেট করতে হবে।
মনে রাখবেন: ব্যাকএন্ডে Dart ব্যবহার করলে, কোড ডুপ্লিকেট না করেই, ক্লায়েন্টে Flutter-এর রেসপন্সিভ ব্যবহারকারীর অভিজ্ঞতাকে সার্ভারে নিরাপদ যাচাইকরণের সাথে একত্রিত করতে পারবেন।
৪. 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++;
});
}
এই পদ্ধতি লোকাল স্টেটের ক্ষেত্রে কাজ করে, কিন্তু মাল্টিপ্লেয়ার অ্যাপ্লিকেশনের ক্ষেত্রে কাজ করে না, যেখানে সার্ভারকে অবশ্যই সোর্স অফ ট্রুথ হিসেবে কাজ করতে হবে। একাধিক প্লেয়ারকে সহায়তা করতে, আমরা নিম্নলিখিত ধাপগুলি অনুসরণ করে এই লজিককে ব্যাকএন্ডে সরিয়ে দেব।
৫. শেয়ার করা প্যাকেজ তৈরি করুন
ফ্রন্ট-এন্ড ও ব্যাক-এন্ডে মডেলের ডুপ্লিকেট এড়াতে, আপনার প্রোজেক্ট রিপোজিটরির মধ্যে একটি শেয়ার করা Dart প্যাকেজ তৈরি করুন। Flutter অ্যাপ ও Firebase-এর ফাংশন, দু'টিই এই প্যাকেজের উপর নির্ভরশীল।
my_counter প্রজেক্টের রুট থেকে, নিম্নলিখিত কমান্ডগুলি রান করুন:
mkdir -p packages
cd packages
dart create -t package shared
ডিপেন্ডেন্সি যোগ করা
packages/shared/pubspec.yaml-এ JSON সিরিয়ালাইজেশন টুল যোগ করুন:
dependencies:
json_annotation: ^4.11.0
dev_dependencies:
build_runner: ^2.13.1
json_serializable: ^6.13.1
আপনার শেয়ার করা মডেলের সংজ্ঞা দিন
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
৬. 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_admin_sdk: ^0.5.6
firebase_functions: ^0.8.0
google_cloud_firestore: ^0.5.5
shared:
path: ../packages/shared
৭. ফাংশনটি লেখো
ব্যাকএন্ড লজিক লিখতে, 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() {
runFunctions((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 =
int.tryParse(request.url.queryParameters['step'] ?? '') ?? 1;
await counterDoc.update({'count': FieldValue.increment(step)});
incrementResponse = IncrementResponse(
success: true,
message: 'Atomic increment complete',
newCount: value + step,
);
} else {
throw HttpResponseException.badRequest(
message: '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'},
);
});
});
}
৮. 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 ব্যাকএন্ড কল করে, নতুন সংখ্যাটি রিট্রিভ করে এবং UI আপডেট করে।
৯. Firebase-এ ডিপ্লয় করা
এই কোডল্যাবে, Firebase Local Emulator Suite ব্যবহার করে আপনি Firebase প্রোজেক্ট বা বিলিং অ্যাকাউন্ট ছাড়াই ফাংশন ব্যবহার করে দেখতে পেরেছেন। আপনি যদি বাস্তব পরিবেশে (যেমন প্রোডাকশন এনভায়রনমেন্ট) আপনার ফাংশন ব্যবহার করতে চান, তাহলে আপনাকে Firebase প্রোজেক্ট ও বিলিং সেট-আপ করতে হবে।
Firebase প্রোজেক্ট তৈরি করা
- আপনার Google অ্যাকাউন্ট ব্যবহার করে Firebase কনসোলে সাইন-ইন করুন।
- নতুন প্রোজেক্ট তৈরি করতে বোতামে ক্লিক করুন এবং তারপর প্রোজেক্টের নাম লিখুন।
- চালিয়ে যান বিকল্পে ক্লিক করুন।
- প্রম্পট করা হলে, Firebase-এর শর্তাবলী পর্যালোচনা করে সম্মতি জানান এবং তারপর চালিয়ে যান বিকল্পে ক্লিক করুন।
- (ঐচ্ছিক) Firebase কনসোলে AI সহায়তা চালু করুন (এটি "Firebase-এ Gemini" নামে পরিচিত)।
- এই কোডল্যাবের জন্য আপনার Google Analytics-এর প্রয়োজন নেই, তাই Google Analytics বিকল্প বন্ধ করুন।
- প্রোজেক্ট তৈরি করুন বিকল্পে ক্লিক করুন, আপনার প্রোজেক্টের প্রভিশনিংয়ের জন্য অপেক্ষা করুন এবং তারপর চালিয়ে যান বিকল্পে ক্লিক করুন।
আপনার Firebase দামের প্ল্যান আপগ্রেড করা
এই কোডল্যাবে Firebase পরিষেবা ব্যবহার করতে, আপনার Firebase প্রোজেক্টকে পে-অ্যাজ-ইউ-গো (Blaze) প্রাইসিং প্ল্যানে থাকতে হবে, যার অর্থ হল এটি Cloud বিলিং অ্যাকাউন্টের সাথে লিঙ্ক করা আছে।
- Cloud Billing অ্যাকাউন্টের জন্য ক্রেডিট কার্ডের মতো পেমেন্ট পদ্ধতি প্রয়োজন।
- বিশেষ প্রোমোশনের সময় বা কোনও ইভেন্টের অংশ হিসেবে এই কোডল্যাব করলে, Google Cloud ক্রেডিট উপলভ্য থাকতে পারে।
- আপনি Firebase ও Google Cloud-এ নতুন হলে, $৩০০ ক্রেডিট ও ফ্রি ট্রায়াল Cloud Billing অ্যাকাউন্টের জন্য উপযুক্ত কিনা তা চেক করে দেখুন।
আপনার প্রোজেক্ট Blaze প্ল্যানে আপগ্রেড করতে, এইসব ধাপ অনুসরণ করুন:
- Firebase কনসোলে, আপনার প্ল্যান আপগ্রেড করুন বিকল্প বেছে নিন।
- Blaze প্ল্যান বেছে নিন। আপনার প্রোজেক্টের সাথে Cloud বিলিং অ্যাকাউন্ট লিঙ্ক করতে অন-স্ক্রিন নির্দেশাবলী অনুসরণ করুন।
- এই কোডল্যাবের জন্য Google Cloud ক্রেডিট ব্যবহার করলে, বিলিং অ্যাকাউন্টের নাম সম্ভবত
Google Cloud Platform Trial Billing AccountবাMy Billing Accountহবে। - এই আপগ্রেডের অংশ হিসেবে আপনাকে Cloud Billing অ্যাকাউন্ট তৈরি করতে হলে, আপগ্রেড সম্পূর্ণ করার জন্য আপনাকে Firebase কনসোলে আপগ্রেড ফ্লোতে ফিরে যেতে হতে পারে।
- এই কোডল্যাবের জন্য Google Cloud ক্রেডিট ব্যবহার করলে, বিলিং অ্যাকাউন্টের নাম সম্ভবত
আপনার Firebase প্রোজেক্টে ডিপ্লয় করা
আপনার Dart ব্যাকএন্ড ডিপ্লয় করতে, Firebase CLI ব্যবহার করে নিম্নলিখিত কমান্ড রান করুন:
firebase use <PROJECT_ID>
firebase deploy --only functions
কমান্ড রান করার পরে, URL কপি করুন এবং আমরা আগে যোগ করা flutter অ্যাপ সোর্স কোডে FIREBASE_FUNCTIONS_URL_HERE-এর জায়গায় এটি বসান।
১০. সমস্যার সমাধান
firebase: command not found
Firebase CLI ইনস্টল করা আছে এবং আপনার PATH আপডেট করা আছে কিনা তা নিশ্চিত করুন। আপনি npm ব্যবহার করে এটি ইনস্টল করতে পারবেন: npm install -g firebase-tools.
init ফাংশন টেমপ্লেটে Dart নেই
firebase init functions চালানোর সময় ডেপ্লয় করার বিকল্পের তালিকা হিসেবে Dart-কে দেখাতে এবং টেমপ্লেট কোড তৈরি করতে, firebase experiments:enable dartfunctions চালিয়ে একটি এক্সপেরিমেন্ট ফ্ল্যাগ সেট করতে হবে।
ফাংশন এমুলেটর কানেক্ট হচ্ছে না
আপনি যে localhost ও পোর্ট 5001 ব্যবহার করছেন তা যাচাই করুন। আপনি Android এমুলেটরে পরীক্ষা করলে, ডিভাইসটি আপনার হোস্ট মেশিনে localhost সমাধান করে না। 10.0.2.2 ব্যবহার করতে main.dart-এ এমুলেটর কনফিগারেশন আপডেট করুন।
শেয়ার করা প্যাকেজটি খুঁজে পাওয়া যায়নি
functions/pubspec.yaml-এ রিলেটিভ পাথ যাচাই করুন। আপনার ফোল্ডার স্ট্রাকচার কোডল্যাব থেকে আলাদা হলে, সঠিক ডিরেক্টরি নির্দেশ করতে path: ../packages/shared অ্যাডজাস্ট করুন।
আমার কি json_serializable ব্যবহার করতে হবে?
json_serializable ব্যবহার করা বাধ্যতামূলক না হলেও, এটি fromJson ও toJson পদ্ধতি ম্যানুয়ালি লেখার ফলে হওয়া সমস্যা প্রতিরোধ করে। এটি নিশ্চিত করে যে আপনার ফ্রন্ট-এন্ড ও ব্যাক-এন্ডে একই ডেটা ফর্ম্যাট থাকবে।
১১. অভিনন্দন
আপনি একটি সম্পূর্ণ-স্ট্যাক Dart অ্যাপ্লিকেশন তৈরি করেছেন। শেয়ার করা প্যাকেজে আপনার ডেটা মডেল বজায় রাখার মাধ্যমে, আপনি নিশ্চিত করেন যে আপনার API উত্তর ও ক্লায়েন্ট UI সিঙ্ক্রোনাইজ করা আছে এবং আপনার সম্পূর্ণ স্ট্যাক জুড়ে একটি প্রোগ্রামিং ভাষা ব্যবহার করা হচ্ছে।
এই প্রজেক্টের সম্পূর্ণ কোড দেখতে, firebase/functions-samples রেপোজিটরিতে কোডল্যাবের স্যাম্পেল দেখুন।