1. Giriş
Bu codelab'de çok oyunculu bir sayaç uygulaması oluşturacaksınız. Flutter ön ucu ve Firebase arka ucu için Dart'ı nasıl kullanacağınızı öğreneceksiniz.
Ayrıca, veri modellerini uygulamanız ve sunucunuz arasında nasıl paylaşacağınızı da öğrenerek mantığı kopyalama ihtiyacını ortadan kaldırabilirsiniz.
Neler öğreneceksiniz?
- Paylaşılan iş mantığını bağımsız bir Dart paketine ayırın.
- Cloud Functions for Firebase'i Dart'ta yerel olarak yazıp dağıtın.
- Sunucusuz baştan başlatma işlemlerini azaltmak için Dart'ın önceden derleme (AOT) özelliğinden yararlanın.
- Firebase Emulator Suite'i kullanarak yığınınızı yerel olarak test edin.
2. Ön koşullar
- Flutter SDK (en son kararlı sürüm).
- Firebase CLI (v15.15.0 veya sonraki bir sürüm gerekir).
- Dart ve Flutter eklentilerinin yüklü olduğu Antigravity, Visual Studio Code, IntelliJ veya Android Studio gibi bir kod düzenleyici.
- Flutter ve Firebase hakkında temel düzeyde bilgi sahibi olmanız gerekir.
3. Arka uçta neden Dart kullanılmalıdır?
Birçok bulut uygulaması, ön uç kullanıcı arayüzü için Dart'ı, arka uç için ise TypeScript, Python veya Go gibi başka bir dili kullanır. Bu işlem için iki ayrı veri modeli kümesinin korunması gerekir. Bir veritabanı şeması değiştiğinde her iki kod tabanını da güncellemeniz gerekir.
Not: Arka uçta Dart kullanmak, kodu kopyalamadan istemcide Flutter'ın duyarlı kullanıcı deneyimini sunucu tarafında güvenli doğrulama ile birleştirmenize olanak tanır.
4. Flutter uygulamasını oluşturma
Standart bir Flutter uygulaması oluşturun:
flutter create my_counter
cd my_counter
# Run the app to see the default counter example
flutter run
Standart bir Flutter uygulamasında lib/main.dart sayaç durumunu yerel olarak işler:
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
Bu yaklaşım yerel durum için işe yarar ancak sunucunun doğruluk kaynağı olarak hareket etmesi gereken çok oyunculu bir uygulamada ölçeklenmez. Birden fazla oyuncuyu desteklemek için bu mantığı aşağıdaki adımlarda arka uca taşıyacağız.
5. Paylaşılan paketi oluşturma
Ön uçta ve arka uçta modellerin yinelenmesini önlemek için proje deponuzda paylaşılan bir Dart paketi oluşturun. Hem Flutter uygulaması hem de Firebase için işlevler bu pakete bağlıdır.
my_counter projesinin kökünden aşağıdaki komutları çalıştırın:
mkdir -p packages
cd packages
dart create -t package shared
Bağımlılık ekleme
packages/shared/pubspec.yaml bölümünde JSON serileştirme araçlarını ekleyin:
dependencies:
json_annotation: ^4.9.0
dev_dependencies:
build_runner: ^2.4.9
json_serializable: ^6.8.0
Paylaşılan modellerinizi tanımlama
Oluşturma packages/shared/lib/src/models.dart Bu dosya, hem uygulama hem de sunucu tarafından kullanılan veri yapısını tanımlar.
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'da modellerinizi dışa aktarın:
library shared;
export 'src/models.dart';
packages/shared dizininde, JSON serileştirme kodunu oluşturmak için derleme çalıştırıcıyı çalıştırın:
dart run build_runner build
6. Cloud Functions for Firebase'i ayarlama
Cloud Functions for Firebase, kendi sunucularınızı yönetip ölçeklendirmeniz gerekmeden arka uç kodunu otomatik olarak çalıştırmanızı sağlayan sunucusuz bir çerçevedir. Dart, Ahead-of-Time (AOT) derlemesiyle ikili programa derlendiği ve Node.js veya Java gibi ağır bir çalışma zamanı ortamı gerektirmediği için bu iş için idealdir. Bu sayede, işlevlerinizin sıfırdan başlatma süreleri önemli ölçüde azalır.
Projenizin kök dizinine gidin ve Cloud Functions for Firebase'i başlatın:
cd ../..
firebase experiments:enable dartfunctions
firebase init functions
dart pub add google_cloud_firestore
- Dil istendiğinde Dart'ı seçin.
Paylaşılan paketi bağlama
functions/pubspec.yaml içinde, paylaşılan pakete göreli bir yol ekleyin:
dependencies:
firebase_functions:
google_cloud_firestore:
shared:
path: ../packages/shared
7. İşlevi yazma
Arka uç mantığını yazmak için functions/bin/server.dart bağlantısını açın ve içeriği aşağıdaki kodla değiştirin:
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 ile yerel olarak test etme
Hem ön ucu hem de arka ucu dağıtmadan yerel olarak çalıştırabilirsiniz.
Projenizin kökünden Firebase Emulator Suite'i başlatın:
# Enable functions and firestore for the emulators
firebase init emulators
# Start the emulators and optionally open up the Admin UI
firebase emulators:start
Paylaşılan paketi bağlama
pubspec.yaml içinde, paylaşılan pakete göreli bir yol ekleyin ve http paketini ekleyin:
dependencies:
http: ^1.6.0
shared:
path: ../packages/shared
Flutter projenizde lib/main.dart dosyasını açın ve içeriğini aşağıdaki kodla değiştirin. Bu ön uç kodu, arka uçla aynı IncrementResponse sınıfını kullanır.
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 uygulamasını çalıştırın. Kaydırma işlemi düğmesini tıkladığınızda uygulama, yerel Dart arka ucunu çağırır, yeni sayıyı alır ve kullanıcı arayüzünü günceller.
9. Firebase'e dağıtma
Dart arka uçunuzu dağıtmak için aşağıdaki komutu çalıştırın:
firebase deploy --only functions
Komut çalıştıktan sonra URL'yi kopyalayın ve daha önce eklediğimiz Flutter uygulaması kaynak kodundaki FIREBASE_FUNCTIONS_URL_HERE yerine yapıştırın.
10. Sorun giderme
firebase: command not found
Firebase CLI'nin yüklendiğinden ve PATH'nizin güncellendiğinden emin olun. npm kullanarak yükleyebilirsiniz: npm install -g firebase-tools.
Başlatma işlevleri şablonlarında Dart eksik
Dart'ın, firebase init functions çalıştırılırken dağıtılacak seçenekler listesi olarak gösterilmesi ve şablon kodunun oluşturulması için firebase experiments:enable dartfunctions çalıştırılarak bir deneme işareti ayarlanması gerekir.
İşlevler emülatörü bağlanmıyor
localhost ve bağlantı noktası 5001 kullandığınızı doğrulayın. Android emülatöründe test yapıyorsanız cihaz, localhost öğesini ana makinenize çözmez. main.dart içindeki emülatör yapılandırmasını 10.0.2.2 kullanacak şekilde güncelleyin.
Paylaşılan paket bulunamadı
functions/pubspec.yaml içindeki göreli yolu doğrulayın. Klasör yapınız codelab'den farklıysa path: ../packages/shared değerini doğru dizini gösterecek şekilde ayarlayın.
json_serializable kullanmam gerekiyor mu?
Kesinlikle zorunlu olmasa da json_serializable kullanmak, fromJson ve toJson yöntemlerinin manuel olarak yazılmasından kaynaklanan hataları önler. Bu, ön uç ve arka ucunuzun tam olarak aynı veri biçimini beklediğinden emin olmanızı sağlar.
11. Tebrikler
Full-stack Dart uygulamasını başarıyla oluşturdunuz. Veri modellerinizi paylaşılan bir pakette tutarak API yanıtlarınızın ve istemci kullanıcı arayüzünüzün senkronize kalmasını sağlarsınız. Ayrıca, tüm yığın boyunca tek bir programlama dili kullanırsınız.