🚀 Flutter ve PocketBase ile Modern Bildirim Sistemi (FCM V1 API)

Bu rehberde; Coolify üzerinde barındırdığımız PocketBase backend’i ile Flutter uygulamamız arasında, Google’ın en yeni güvenlik protokolü olan FCM HTTP v1 API kullanarak nasıl sorunsuz bir bildirim sistemi kuracağımızı anlatacağım.

Geleneksel “Legacy API” artık kapandığı için, OAuth 2.0 ve Refresh Token yöntemini kullanarak sunucusuz (Serverless) bir mimari kuracağız.

🏗️ Mimari Yapı

  1. Flutter: Kullanıcı Token’ını alır, PocketBase’e kaydeder.

  2. Google Cloud: Bize kalıcı bir “Refresh Token” sağlar.

  3. PocketBase (Hooks): JavaScript ile Google’dan anlık erişim izni alır ve bildirimi gönderir.

  4. Database: Gönderilen bildirimleri loglar.


BÖLÜM 1: Google Cloud & Firebase Ayarları (En Kritik Kısım)

Google artık statik “Server Key” kabul etmiyor. Bunun yerine dinamik bir yetkilendirme yapmamız gerekiyor.

1.1. Google Cloud Konsol Ayarları

  1. Google Cloud Console‘a gidin ve Firebase projenizi seçin.

  2. APIs & Services > Credentials menüsüne gidin.

  3. + CREATE CREDENTIALS -> OAuth client ID seçin.

    • Application Type: Web application

    • Name: PocketBase Backend

    • Authorized redirect URIs: https://developers.google.com/oauthplayground (Burası çok önemli!)

  4. Create diyip çıkan Client ID ve Client Secret‘ı not edin.

1.2. OAuth Consent Screen (Rıza Ekranı)

  1. OAuth consent screen menüsüne gidin.

  2. User Type: External seçin.

  3. Test Users: Kendi gmail adresinizi ekleyin (Yoksa “Access Denied” hatası alırsınız).

1.3. Sonsuz Yetki (Refresh Token) Alma

  1. Google OAuth 2.0 Playground‘a gidin.

  2. Sağ üstteki ⚙️ (Ayarlar) butonuna basın.

    • Use your own OAuth credentials‘ı işaretleyin.

    • Az önce aldığınız Client ID ve Client Secret‘ı girin.

  3. Sol menüden (Step 1) Cloud Messaging API v1‘i bulun (https://www.googleapis.com/auth/firebase.messaging) ve yetki verin.

  4. Step 2’de Exchange authorization code for tokens butonuna basın.

  5. Size verilen refresh_token değerini kopyalayın. Bu bizim altın anahtarımız!


BÖLÜM 2: PocketBase Backend Kurulumu

Coolify veya sunucunuzdaki PocketBase dosyalarına erişim sağlayın.

2.1. Veritabanı Şeması

PocketBase Admin Paneli’nde şu ayarlamaları yapın:

  1. Users Koleksiyonu: fcm_token adında bir Text alanı ekleyin.

  2. Yeni Koleksiyon Oluşturun: Adı duyurular olsun.

    • baslik (Text)

    • icerik (Text)

    • Bu tablo gönderilen bildirimlerin loglarını tutacak.

2.2. Sihirli Hook Dosyası (pb_hooks/broadcast.pb.js)

PocketBase’in pb_hooks klasörüne broadcast.pb.js adında bir dosya oluşturun ve aşağıdaki kodu yapıştırın. Bu kod, PocketBase v0.23+ sürümleriyle tam uyumludur.

(Kodun başındaki değişkenleri kendi ID’lerinizle değiştirmeyi unutmayın!)

JavaScript

routerAdd("POST", "/api/duyuru-yap", (c) => {
    // --- 1. AYARLAR ---
    const CLIENT_ID = "BURAYA_CLIENT_ID_GELECEK";
    const CLIENT_SECRET = "BURAYA_CLIENT_SECRET_GELECEK";
    const REFRESH_TOKEN = "BURAYA_REFRESH_TOKEN_GELECEK";
    const PROJECT_ID = "justtalk-XXXX"; // Firebase Proje ID'niz

    try {
        // --- 2. VERİYİ GARANTİLİ OKUMA (v0.23+ Uyumlu) ---
        let title = "Varsayılan Başlık";
        let body = "Varsayılan İçerik";

        try {
            // Ham veriyi okuyup JSON parse ediyoruz (En sağlam yöntem)
            const rawBody = readerToString(c.request.body);
            if (rawBody) {
                const json = JSON.parse(rawBody);
                if (json.title) title = json.title;
                if (json.body) body = json.body;
            }
        } catch (readError) {
            console.log("Veri okuma hatası (Varsayılan kullanılıyor): " + readError);
        }

        console.log("📨 Gönderiliyor: " + title + " - " + body);

        // --- 3. GOOGLE TOKEN AL (Dinamik Yetkilendirme) ---
        const tokenResponse = $http.send({
            url: "https://oauth2.googleapis.com/token",
            method: "POST",
            headers: { "Content-Type": "application/x-www-form-urlencoded" },
            body: "client_id=" + CLIENT_ID + 
                  "&client_secret=" + CLIENT_SECRET + 
                  "&refresh_token=" + REFRESH_TOKEN + 
                  "&grant_type=refresh_token"
        });

        if (tokenResponse.statusCode !== 200) {
            return c.json(500, { error: "Google Token alınamadı", details: tokenResponse.json });
        }
        const accessToken = tokenResponse.json.access_token;

        // --- 4. BİLDİRİMİ GÖNDER (FCM V1 API) ---
        const fcmResponse = $http.send({
            url: "https://fcm.googleapis.com/v1/projects/" + PROJECT_ID + "/messages:send",
            method: "POST",
            headers: {
                "Authorization": "Bearer " + accessToken,
                "Content-Type": "application/json"
            },
            body: JSON.stringify({
                "message": {
                    "topic": "tum_kullanicilar", // Tüm abonelere gönder
                    "notification": {
                        "title": title,
                        "body": body
                    }
                }
            })
        });

        // --- 5. LOGLAMA (DB Kayıt) ---
        if (fcmResponse.statusCode === 200) {
            try {
                const collection = $app.findCollectionByNameOrId("duyurular");
                const record = new Record(collection);
                record.set("baslik", title);
                record.set("icerik", body);
                $app.save(record); // Admin yetkisiyle kaydet
            } catch (dbError) {
                console.log("❌ DB Log Hatası: " + dbError);
            }
        }

        return c.json(fcmResponse.statusCode, fcmResponse.json);

    } catch (err) {
        return c.json(500, { error: "Sunucu içi hata", details: err.toString() });
    }
});

Bu işlemden sonra PocketBase sunucusunu yeniden başlatın (Restart).


BÖLÜM 3: Flutter Entegrasyonu

3.1. Gerekli Paketler

pubspec.yaml dosyanıza ekleyin:

YAML

dependencies:
  firebase_core: latest
  firebase_messaging: latest
  http: latest

3.2. Ön Planda Bildirim Gösterme (main.dart)

Kullanıcı uygulama açıkken bildirim alırsa ekranda görünmesi için:

Dart

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(); // Firebase başlatma
  
  // iOS ve Android için ön plan ayarı
  await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions(
    alert: true, 
    badge: true, 
    sound: true,
  );
  
  // Genel kanala abone ol (Broadcast için şart)
  await FirebaseMessaging.instance.subscribeToTopic('tum_kullanicilar');

  runApp(const MyApp());
}

3.3. Admin Bildirim Paneli (UI)

Bu sayfayı uygulamanıza ekleyerek, terminale gerek kalmadan herkese bildirim atabilirsiniz.

Dart

// admin_notification_page.dart
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

class AdminNotificationPage extends StatefulWidget {
  const AdminNotificationPage({super.key});

  @override
  State<AdminNotificationPage> createState() => _AdminNotificationPageState();
}

class _AdminNotificationPageState extends State<AdminNotificationPage> {
  final _titleController = TextEditingController();
  final _bodyController = TextEditingController();
  bool _isLoading = false;

  Future<void> _bildirimGonder() async {
    setState(() => _isLoading = true);
    try {
      final response = await http.post(
        Uri.parse('https://SIZIN-POCKETBASE-URL.com/api/duyuru-yap'),
        headers: {'Content-Type': 'application/json'},
        body: jsonEncode({
          'title': _titleController.text,
          'body': _bodyController.text,
        }),
      );

      if (response.statusCode == 200) {
        ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('✅ Gönderildi!')));
      } else {
        throw Exception(response.body);
      }
    } catch (e) {
      ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Hata: $e')));
    } finally {
      setState(() => _isLoading = false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Admin Panel')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(controller: _titleController, decoration: const InputDecoration(labelText: 'Başlık')),
            const SizedBox(height: 10),
            TextField(controller: _bodyController, decoration: const InputDecoration(labelText: 'Mesaj')),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: _isLoading ? null : _bildirimGonder,
              child: _isLoading ? const CircularProgressIndicator() : const Text('HERKESE GÖNDER 🚀'),
            ),
          ],
        ),
      ),
    );
  }
}

🎉 Sonuç

Artık elimizde şunlar var:

  1. Güvenli: Google V1 API ve OAuth ile çalışan modern bir yapı.

  2. Sunucusuz: Ekstra Node.js veya Python sunucusu kiralamadan, her şeyi PocketBase içinde (Goja JS motoruyla) çözdük.

  3. Loglanabilir: Atılan her bildirim veritabanına kaydediliyor.

  4. Kullanıcı Dostu: Uygulama içinden bildirim atılabiliyor ve uygulama açıkken de bildirim düşüyor.

İyi kodlamalar! 🚀👨‍💻

Yorum bırakın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir