Flutter & Mobil İle İleri Düzey Çoklu Cihaz Paylaşımlı Pano Nasıl Yapılır?

Flutter & Mobil İle İleri Düzey Çoklu Cihaz Paylaşımlı Pano Nasıl Yapılır?
Flutter & Mobil İle İleri Düzey Çoklu Cihaz Paylaşımlı Pano Nasıl Yapılır?

Ön Hazırlık ve Gerekli Kütüphanelerin Kurulumu

Geliştireceğimiz ileri düzey paylaşımlı pano uygulaması için yerel pano yönetimi, gerçek zamanlı veri senkronizasyonu, veri güvenliği ve durum yönetimi (state management) kütüphanelerine ihtiyacımız olacak. 2026 yılı standartlarında en kararlı ve performanslı çalışan paketleri tercih edeceğiz. Gerçek zamanlı senkronizasyon için Supabase veya Firebase altyapısını kullanabiliriz; bu rehberde esnekliği ve açık kaynaklı yapısı nedeniyle Supabase Realtime altyapısını tercih edeceğiz.

İlk adım olarak Flutter projenizin kök dizininde bulunan pubspec.yaml dosyasını açın ve aşağıdaki bağımlılıkları ekleyin. Bu kütüphaneler uygulamanın temel yapı taşlarını oluşturacaktır:

dependencies:
  flutter:
    sdk: flutter
  flutter_riverpod: ^3.0.0 # Durum yönetimi için
  supabase_flutter: ^2.8.0 # Gerçek zamanlı veritabanı senkronizasyonu için
  encrypt: ^5.0.3 # Uçtan uca şifreleme (AES-256) için
  crypto: ^3.0.3 # Şifreleme anahtarları üretmek için
  flutter_background_service: ^5.0.0 # Arka planda panoyu dinlemek için
  shared_preferences: ^2.3.0 # Yerel ayarlar ve cihaz kimliği için

Yukarıdaki kod bloğu, projemizin ihtiyaç duyduğu tüm modern kütüphaneleri tanımlar. Paketleri projenize dahil etmek için terminalinizde flutter pub get komutunu çalıştırın. Kurulum tamamlandıktan sonra platform spesifik izinleri yapılandırmamız gerekecektir. Özellikle arka plan servislerinin Android ve iOS üzerinde sorunsuz çalışabilmesi için işletim sistemi düzeyinde bazı izinlerin tanımlanması şarttır.

Yerel Pano (Clipboard) İzleme ve Veri Yakalama Adımları

Uygulamamızın ilk işlevi, kullanıcının cihazında gerçekleşen kopyalama işlemlerini tespit etmektir. Flutter'ın yerleşik Clipboard sınıfı panodan veri okumamızı ve yazmamızı sağlar ancak panodaki değişiklikleri otomatik olarak tetikleyen bir "listener" (dinleyici) mekanizmasına sahip değildir. Bu sorunu aşmak için uygulama ön plandayken periyodik olarak panoyu kontrol eden veya yerel işletim sistemi olaylarını dinleyen bir servis yazmamız gerekir.

Aşağıdaki kod örneğinde, yerel panodaki değişiklikleri saniyede bir kontrol eden ve yalnızca yeni bir metin kopyalandığında tetiklenen güvenli bir ClipboardService sınıfı oluşturuyoruz:

import 'dart:async';
import 'package:flutter/services.dart';

class ClipboardService {
  String _lastCopiedText = '';
  Timer? _clipboardTimer;
  final StreamController _clipStreamController = StreamController.broadcast();

  // Panodaki yeni değişiklikleri yayınlayan akış (Stream)
  Stream get clipStream => _clipStreamController.stream;

  // Pano dinleyicisini başlatır
  void startListening() {
    _clipboardTimer = Timer.periodic(const Duration(seconds: 1), (timer) async {
      final ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
      if (data != null && data.text != null) {
        final String currentText = data.text!;
        if (currentText != _lastCopiedText && currentText.isNotEmpty) {
          _lastCopiedText = currentText;
          _clipStreamController.add(currentText);
        }
      }
    });
  }

  // Panoya yeni veri yazar (Diğer cihazdan gelen veriyi yapıştırmak için)
  Future writeToClipboard(String text) async {
    if (text == _lastCopiedText) return;
    _lastCopiedText = text;
    await Clipboard.setData(ClipboardData(text: text));
  }

  // Kaynakları temizler
  void dispose() {
    _clipboardTimer?.cancel();
    _clipStreamController.close();
  }
}

Bu kod bloğu, sistem panosunu arka planda bloke etmeden hafif bir zamanlayıcı ile sorgular. _lastCopiedText değişkeni, aynı verinin tekrar tekrar işlenmesini ve sonsuz döngüye girilmesini engeller. writeToClipboard fonksiyonu ise buluttan gelen yeni veriyi kullanıcının yerel panosuna yazarak doğrudan "yapıştır" komutuna hazır hale getirir.

Uçtan Uca Şifreleme (E2EE) ile Güvenli Veri Transferi

Kullanıcıların panolarında kopyaladıkları veriler şifreler, kişisel mesajlar veya kredi kartı bilgileri gibi son derece hassas bilgiler içerebilir. Bu verilerin bulut sunucularına düz metin (plain text) olarak gönderilmesi kabul edilemez bir güvenlik açığıdır. Bu nedenle, verileri buluta göndermeden önce cihaz üzerinde şifrelemeli (Encryption) ve alıcı cihazda deşifre etmeliyiz (Decryption).

Bu adımda, AES-256-CBC algoritmasını kullanarak verileri güvenli hale getireceğiz. Şifreleme anahtarı (Secret Key) ve Başlatma Vektörü (IV - Initialization Vector), kullanıcının tüm cihazlarında ortak olarak belirleyeceği bir paroladan türetilecektir. İşte şifreleme işlemlerini yürütecek olan EncryptionHelper sınıfı:

import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:encrypt/encrypt.dart' as encrypt;

class EncryptionHelper {
  late final encrypt.Encrypter _encrypter;
  late final encrypt.IV _iv;

  // Kullanıcının belirlediği özel paroladan 32 byte'lık güvenli anahtar üretir
  EncryptionHelper(String userPassword) {
    // Parolayı SHA-256 ile hashleyerek 32 byte (256 bit) uzunluğunda anahtar elde ediyoruz
    final keyBytes = sha256.convert(utf8.encode(userPassword)).bytes;
    final key = encrypt.Key(Uint8List.fromList(keyBytes));
    
    // IV için sabit veya türetilmiş 16 byte'lık bir değer kullanıyoruz
    final ivBytes = md5.convert(utf8.encode(userPassword)).bytes;
    _iv = encrypt.IV(Uint8List.fromList(ivBytes));
    
    _encrypter = encrypt.Encrypter(encrypt.AES(key, mode: encrypt.AESMode.cbc));
  }

  // Veriyi şifreler (Encrypt)
  String encryptText(String plainText) {
    try {
      final encrypted = _encrypter.encrypt(plainText, iv: _iv);
      return encrypted.base64;
    } catch (e) {
      throw Exception('Şifreleme hatası: ${e.toString()}');
    }
  }

  // Şifrelenmiş veriyi çözer (Decrypt)
  String decryptText(String encryptedBase64) {
    try {
      final decrypted = _encrypter.decrypt64(encryptedBase64, iv: _iv);
      return decrypted;
    } catch (e) {
      throw Exception('Deşifre etme hatası: Anahtar yanlış veya veri bozulmuş.');
    }
  }
}

Bu şifreleme sınıfı, kullanıcının parolasını doğrudan saklamak yerine SHA-256 algoritmasıyla tek yönlü olarak hashler. Elde edilen 256-bitlik bu hash, AES şifrelemesi için anahtar olarak kullanılır. Böylece, sunucu tarafındaki hiç kimse (veritabanı yöneticileri dahil) kullanıcının panosundaki verileri asla okuyamaz. Veriler bulutta tamamen anlamsız karakter yığınları olarak depolanır.

Supabase ile Gerçek Zamanlı Senkronizasyon Altyapısı

Verilerimizi güvenli bir şekilde şifreledikten sonra, bunları diğer cihazlara anında iletmemiz gerekir. Supabase Realtime, PostgreSQL tabanlı veritabanı değişikliklerini WebSocket protokolü üzerinden istemcilere anlık olarak yayınlar. Bu işlem için öncelikle Supabase panelinizde clipboard_sync adında bir tablo oluşturmalısınız. Tablo yapısı şu şekilde olmalıdır:

Sütun Adı Veri Tipi Açıklama
id uuid (Primary Key) Her pano kaydı için benzersiz kimlik
device_id text Kopyalamayı yapan cihazın benzersiz kimliği
encrypted_data text AES-256 ile şifrelenmiş pano içeriği
created_at timestamp with time zone Kopyalama zamanı (Default: now())

Kritik Güvenlik Uyarısı: Supabase üzerinde Row Level Security (RLS) politikalarını aktif etmeyi unutmayın. Kullanıcıların yalnızca kendi oluşturdukları pano kayıtlarına erişebilmesi için auth.uid() kontrolü ekleyen RLS kuralları tanımlanmalıdır.

Şimdi, Supabase istemcisini kullanarak veritabanına yeni şifrelenmiş veri ekleyen ve veritabanındaki değişiklikleri canlı olarak dinleyen SyncService sınıfını yazalım:

import 'package:supabase_flutter/supabase_flutter.dart';

class SyncService {
  final SupabaseClient _client = Supabase.instance.client;

  // Şifrelenmiş pano verisini buluta gönderir
  Future uploadClipboard(String encryptedData, String deviceId) async {
    try {
      await _client.from('clipboard_sync').insert({
        'device_id': deviceId,
        'encrypted_data': encryptedData,
      });
    } catch (e) {
      print('Veri yükleme hatası: $e');
    }
  }

  // Gerçek zamanlı değişiklikleri dinleyen akış (Stream)
  Stream subscribeToClipboard() {
    return _client
        .from('clipboard_sync')
        .stream(primaryKey: ['id'])
        .order('created_at', ascending: false)
        .limit(1)
        .map((event) {
          if (event.isNotEmpty) {
            return event.first;
          }
          return {};
        });
  }
}

Bu servis, Supabase'in stream özelliğini kullanarak veritabanına eklenen son satırı anında yakalar. limit(1) parametresi sayesinde ağ trafiği optimize edilir ve yalnızca en güncel pano verisi cihaza indirilir. Cihazlar kendi gönderdikleri verileri filtrelemek için gelen verideki device_id değerini kontrol edecektir.

Arka Plan Servisleri ve Platform Sınırlamaları

Mobil işletim sistemleri (iOS ve Android), pil ömrünü ve kullanıcı güvenliğini korumak amacıyla arka planda çalışan uygulamaların pano (clipboard) erişimini ciddi şekilde sınırlandırır. Özellikle Android 10+ ve iOS 14+ sürümlerinden itibaren, uygulama arka plandayken doğrudan sistem panosuna erişmek kısıtlanmıştır.

Bu kısıtlamaları aşmak ve uygulamanın arka planda da çalışabilmesini sağlamak için iki temel strateji uygulanır:

  • Android Foreground Service: Kullanıcıya bildirim çubuğunda sürekli bir bildirim göstererek uygulamanın işletim sistemi tarafından kapatılmasını önler.
  • iOS Background Fetch & Push Notifications: iOS tarafında arka planda pano senkronizasyonu için sessiz push bildirimleri (Silent Push Notifications) tetiklenerek uygulamanın kısa süreliğine uyanması ve panoyu güncellemesi sağlanır.

Aşağıdaki kod bloğunda, Android için arka plan servisini başlatan ve uygulamanın arka planda dahi senkronizasyon kanalını açık tutmasını sağlayan yapıyı kuruyoruz:

import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_background_service/flutter_background_service.dart';

Future initializeBackgroundService() async {
  final service = FlutterBackgroundService();

  await service.configure(
    androidConfiguration: AndroidConfiguration(
      onStart: onStart,
      autoStart: true,
      isForegroundMode: true,
      notificationChannelId: 'clipboard_sync_channel',
      initialNotificationTitle: 'Paylaşımlı Pano Aktif',
      initialNotificationContent: 'Cihazlar arası senkronizasyon arka planda çalışıyor...',
    ),
    iosConfiguration: IosConfiguration(
      autoStart: true,
      onForeground: onStart,
      onBackground: onIosBackground,
    ),
  );
}

@pragma('vm:entry-point')
void onStart(ServiceInstance service) async {
  DartPluginRegistrant.ensureInitialized();
  
  // Arka plan servisi başladığında yapılacak işlemler
  service.on('stopService').listen((event) {
    service.stopSelf();
  });

  // Burada periyodik olarak pano kontrolleri veya WebSocket dinlemeleri yapılabilir
}

@pragma('vm:entry-point')
bool onIosBackground(ServiceInstance service) {
  WidgetsFlutterBinding.ensureInitialized();
  return true;
}

Bu servis yapılandırması, uygulamanız kapalı olsa dahi işletim sisteminin senkronizasyon motorunu aktif tutmasını sağlar. @pragma('vm:entry-point') anotasyonu, Flutter'ın ağaç sallama (tree-shaking) mekanizmasının bu fonksiyonları derleme aşamasında silmesini engeller; çünkü bu fonksiyonlar yerel (native) işletim sistemi tarafından doğrudan çağrılır.

Çoklu Cihaz Pano Uygulamasının Arayüzü ve Durum Yönetimi

Şimdiye kadar geliştirdiğimiz tüm servisleri (Clipboard, Encryption, Sync) Riverpod durum yönetimi ile birleştirerek şık ve işlevsel bir kullanıcı arayüzü (UI) inşa edeceğiz. Uygulamamızda kullanıcının şifreleme parolasını girebileceği bir alan, anlık senkronizasyon durumunu gösteren bir gösterge ve son kopyalanan metinlerin listesi yer alacaktır.

Aşağıdaki kod örneğinde, tüm iş mantığını (business logic) yöneten Riverpod StateNotifier yapısını ve ana ekran arayüzünü oluşturuyoruz:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';

// Servis sağlayıcıları (Providers)
final clipboardServiceProvider = Provider((ref) =>                
Bu yazıya tepkinizi paylaşın:
Emre Yılmaz

Bilgi odaklı nasıl yapılır içerikleriyle karmaşık süreçleri herkes için anlaşılır kılıyorum. Okuyucularımın zamanını verimli kullanmalarını sağlayan stratejiler geliştiriyorum.

Yorumlar (0)

Yorum Yaz