↑↓ seç · Enter aç · Esc kapat

Mutlu Tekin
Mutlu Tekin
← Yazılar

Bölüm 9 — Konu 41: Permission Yönetimi (`permission_handler`)

3 dk okuma #flutter
Dizi · 41/61 Flutter Türkçe Tutorial
  1. Bölüm 1 — Konu 1: Flutter Nedir, Mimarisi (Widget → Element → RenderObject, Skia/Impeller)
  2. Bölüm 1 — Konu 2: Ortam Kurulumu (Flutter SDK, Android Studio/VS Code, Emulator/Simulator, DevTools'a İlk Bakış)
  3. Bölüm 1 — Konu 3: İlk Proje Yapısı (`pubspec.yaml`, `lib/`, Klasör Mimarisi, `flutter create` Anatomisi)
  4. Bölüm 1 — Konu 4: Hot Reload / Hot Restart Ne Yapıyor, Neden Önemli
  5. Bölüm 2 — Konu 5: "Her Şey Widget'tır" Felsefesi, Widget Ağacı
  6. Bölüm 2 — Konu 6: StatelessWidget vs StatefulWidget + `setState()` Derinlemesine (Rebuild Mekanizması)
  7. Bölüm 2 — Konu 7: Temel Layout — Container, Row, Column, Stack, Padding, Align, Center
  8. Bölüm 2 — Konu 8: Constraint Sistemi — "Constraints Go Down, Sizes Go Up, Parent Sets Position"
  9. Bölüm 2 — Konu 9: `Expanded`, `Flexible`, `Spacer`, Intrinsic Widget'lar
  10. Bölüm 3 — Konu 10: MaterialApp/CupertinoApp, Scaffold, AppBar
  11. Bölüm 3 — Konu 11: `ListView`, `GridView`, `SingleChildScrollView` (+ Builder Pattern)
  12. Bölüm 3 — Konu 12: Text, TextStyle, Icon, Image, Asset Yönetimi
  13. Bölüm 3 — Konu 13: Custom Widget Yazma Prensipleri (Composition Over Inheritance)
  14. Bölüm 3 — Konu 14: Navigator 1.0 — Push/Pop, Named Routes
  15. Bölüm 4 — Konu 15: Form, TextField/TextFormField, GlobalKey<FormState>, Validasyon
  16. Bölüm 4 — Konu 16: Theme Sistemi (ThemeData, ColorScheme)
  17. Bölüm 4 — Konu 17: Responsive & Adaptive Tasarım (MediaQuery, LayoutBuilder, OrientationBuilder, Breakpoint Stratejileri)
  18. Bölüm 5 — Konu 18: Future/Async-Await Flutter Bağlamında, FutureBuilder
  19. Bölüm 5 — Konu 19: `Stream`, `StreamBuilder`
  20. Bölüm 5 — Konu 20: HTTP İstekleri (`http` / `dio` Paketleri)
  21. Bölüm 5 — Konu 21: JSON Serialization (Manuel → `json_serializable`/`freezed`)
  22. Bölüm 5 — Konu 22: Local Storage — `shared_preferences` → `Hive` → `sqflite`/Drift
  23. Bölüm 6 — Konu 23: InheritedWidget ve InheritedModel — "Neden setState Yetmiyor" Sorusunun Cevabı
  24. Bölüm 6 — Konu 24: Provider Paketi
  25. Bölüm 6 — Konu 25: Riverpod (Modern Yaklaşım, Provider'ın Halefi)
  26. Bölüm 6 — Konu 26: BLoC/Cubit Pattern (`flutter_bloc`)
  27. Bölüm 6 — Konu 27: GetX (Tartışmalı Ama Yaygın)
  28. Bölüm 6 — Konu 28: Karşılaştırma — Hangi Projede Hangisi?
  29. Bölüm 7 — Konu 29: Navigator 2.0 (Router, RouteInformationParser, RouterDelegate)
  30. Bölüm 7 — Konu 30: `go_router` Paketi (Pratik ve Modern Çözüm)
  31. Bölüm 7 — Konu 31: Deep Linking
  32. Bölüm 7 — Konu 32: Repository Pattern, Katmanlı Mimari (Data/Domain/Presentation)
  33. Bölüm 7 — Konu 33: Dependency Injection (`get_it`, `injectable`)
  34. Bölüm 7 — Konu 34: Clean Architecture Uyarlaması, SOLID Prensipleri
  35. Bölüm 8 — Konu 35: Implicit Animasyonlar
  36. Bölüm 8 — Konu 36: Explicit Animasyonlar (`AnimationController`, `Tween`, `Curve`)
  37. Bölüm 8 — Konu 37: Hero Animasyonları
  38. Bölüm 8 — Konu 38: `CustomPainter` / `Canvas`
  39. Bölüm 8 — Konu 39: Rive / Lottie Entegrasyonu
  40. Bölüm 9 — Konu 40: Platform Channels
  41. Bölüm 9 — Konu 41: Permission Yönetimi (`permission_handler`)
  42. Bölüm 9 — Konu 42: Kamera, Konum, Sensörler
  43. Bölüm 9 — Konu 43: Push Notification (Firebase Cloud Messaging)
  44. Bölüm 9 — Konu 44: Android/iOS Build Sistemleri
  45. Bölüm 10 — Konu 45: Test Yazımı (Unit, Widget, Integration, Golden)
  46. Bölüm 10 — Konu 46: CI/CD
  47. Bölüm 10 — Konu 47: Rebuild Optimizasyonu (`const`, `key` Kullanımı)
  48. Bölüm 10 — Konu 48: DevTools Profiling
  49. Bölüm 10 — Konu 49: Lazy Loading, Pagination, Büyük Liste Performansı
  50. Bölüm 11 — Konu 50: Firebase Ekosistemi (Auth, Firestore, Storage, Functions)
  51. Bölüm 11 — Konu 51: Supabase Alternatifi
  52. Bölüm 11 — Konu 52: GraphQL (Opsiyonel)
  53. Bölüm 11 — Konu 53: Store Yayınlama Süreci (İmzalama, Listing, Versiyonlama)
  54. Bölüm 11 — Konu 54: App Size, Obfuscation, Flavor Yönetimi
  55. Bölüm 12 — Konu 55: Flutter Web / Desktop
  56. Bölüm 12 — Konu 56: Custom `RenderObject` Yazımı
  57. Bölüm 12 — Konu 57: Engine Mimarisi Derinlemesine (Impeller vs Skia)
  58. Bölüm 12 — Konu 58: Plugin Geliştirme, pub.dev'e Paket Yayınlama
  59. Bölüm 12 — Konu 59: Monorepo Mimarisi (Melos)
  60. Bölüm 12 — Konu 60: Erişilebilirlik (Accessibility) Derinlemesine
  61. Bölüm 12 — Konu 61: Yerelleştirme (Localization / i18n)
Dizinin sayfası →
İçindekiler 9 başlık
  1. Neden İzin Sistemi Var?
  2. Paketi Ekleme ve Platform Ayarları
  3. İzin İsteme — Temel Akış
  4. İzin İstemeden Önce Kontrol Etmek
  5. Birden Fazla İzni Aynı Anda İsteme
  6. UI ile Birleştirme — Gerçekçi Bir Akış
  7. İyi Pratikler — Ne Zaman İzin İstenmeli?
  8. 🎯 Bu Dersten Çıkarılması Gerekenler
  9. 📝 Ödevler

Konu 40'ta native kodla elle nasıl konuşulacağını gördük. Bu derste, o mekanizmayı elle kurmana gerek bırakmayan, hazır ve çok yaygın kullanılan bir paketle — kullanıcıdan izin istemeyi (konum, kamera, bildirim vb.) öğreneceğiz. Bu, Konu 40'ın sonunda bahsettiğimiz "çoğu native ihtiyaç hazır paketlerle karşılanır" ilkesinin ilk somut örneği.

Neden İzin Sistemi Var?

Hem Android hem iOS, kullanıcının mahremiyetini korumak için, bir uygulamanın hassas kaynaklara (konum, kamera, mikrofon, kişiler, bildirimler) erişmeden önce açıkça izin istemesini zorunlu kılar. Bu izinler çalışma zamanında (runtime) istenir — yani kullanıcı, uygulamayı kullanırken, tam da o özelliğe ihtiyaç duyulduğu anda bir izin diyaloğu görür (App Store/Play Store kurulumu sırasında değil).

Paketi Ekleme ve Platform Ayarları

bash
flutter pub add permission_handler

Paketi eklemek yeterli değildir — her platformun kendi manifest/plist dosyasında da hangi izinleri kullanacağını beyan etmen gerekir:

xml
<!-- android/app/src/main/AndroidManifest.xml -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.CAMERA"/>
xml
<!-- ios/Runner/Info.plist -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>Yakınındaki mağazaları gösterebilmek için konumuna ihtiyacımız var.</string>
<key>NSCameraUsageDescription</key>
<string>Profil fotoğrafı çekebilmen için kameraya ihtiyacımız var.</string>

Dikkat et — iOS'taki fark önemli: iOS'ta her izin için neden istediğini açıklayan bir metin (...UsageDescription) zorunludur — bu metin, kullanıcıya gösterilen izin diyaloğunda görünür. Bunu unutursan, uygulaman çalışma zamanında çökebilir (App Store, bu açıklama olmadan uygulamanı reddedebilir de).

İzin İsteme — Temel Akış

dart
import 'package:permission_handler/permission_handler.dart';

Future<void> konumIzniIste() async {
  final durum = await Permission.location.request();

  if (durum.isGranted) {
    print('İzin verildi, konum alınabilir');
  } else if (durum.isDenied) {
    print('Kullanıcı izni reddetti (tekrar sorulabilir)');
  } else if (durum.isPermanentlyDenied) {
    print('Kullanıcı kalıcı olarak reddetti — ayarlara yönlendirmemiz lazım');
    openAppSettings(); // paketin sunduğu hazır fonksiyon
  }
}

Permission.location — Dart Bölüm 7'de öğrendiğimiz enum benzeri sabit değerler kalıbı (Permission, paketin tanımladığı bir dizi sabit: .camera, .microphone, .contacts, .notification vb.).

.request() — Bölüm 5'te öğrendiğimiz async/await kalıbıyla, izin diyaloğunu gösterir ve kullanıcının kararını bekler — bu yüzden Future<PermissionStatus> döner.

PermissionStatus — bu da bir enum, ama sıradan granted/denied ikilisinden daha zengin:

Durum Anlamı
granted İzin verildi
denied Reddedildi (kullanıcıya tekrar sorulabilir)
permanentlyDenied Kalıcı reddedildi — artık uygulama içinden tekrar sorulamaz, kullanıcı sistem ayarlarından elle açmalı
restricted (Sadece iOS) Ebeveyn kontrolü gibi bir sistem kısıtlaması var
limited (Bazı izinler için, örn. iOS fotoğraflar) Kısmi erişim verildi

permanentlyDenied neden önemli? Bir kullanıcı bir izni iki kez reddederse (platforma göre değişir), sistem bunu "kalıcı red" sayar ve uygulamanın bir daha izin diyaloğu göstermesine izin vermez — tek çözüm, kullanıcıyı openAppSettings() ile sistem ayarlarına yönlendirip, izni elle açmasını istemektir.

İzin İstemeden Önce Kontrol Etmek

dart
Future<void> kameraErisimiKontrolEt() async {
  final durum = await Permission.camera.status; // sadece KONTROL et, sormaz

  if (durum.isGranted) {
    // doğrudan kamerayı aç
  } else {
    final yeniDurum = await Permission.camera.request(); // şimdi SOR
    if (yeniDurum.isGranted) {
      // kamerayı aç
    }
  }
}

.status (sadece okuma) ile .request() (isteme, diyalog gösterme) arasındaki fark önemli — Bölüm 6 Konu 24'te Provider'da öğrendiğimiz context.read (sadece erişim) ile context.watch (aktif katılım) ayrımına kavramsal olarak benzer: biri pasif kontrol, diğeri aktif bir eylem tetikliyor.

Birden Fazla İzni Aynı Anda İsteme

dart
Future<void> coklulzinIste() async {
  final durumlar = await [
    Permission.camera,
    Permission.microphone,
    Permission.location,
  ].request(); // List<Permission> üzerinde .request()!

  if (durumlar[Permission.camera]!.isGranted &&
      durumlar[Permission.microphone]!.isGranted) {
    print('Video kaydı için gerekli izinler tam');
  }
}

[Permission.camera, ...].request() — Dart Bölüm 4'te öğrendiğimiz List üzerinde, paketin extension method (Dart Bölüm 12'de öğrendiğimiz kavramı hatırla) olarak eklediği bir .request() metodu — birden fazla izni tek bir kullanıcı etkileşiminde ister ve sonucu bir Map<Permission, PermissionStatus> olarak döner.

UI ile Birleştirme — Gerçekçi Bir Akış

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

  @override
  State<KonumEkrani> createState() => _KonumEkraniState();
}

class _KonumEkraniState extends State<KonumEkrani> {
  PermissionStatus? _durum;

  Future<void> _konumIzniIste() async {
    final durum = await Permission.location.request();
    setState(() => _durum = durum); // Bölüm 2 Konu 6 — rebuild tetikleme
  }

  @override
  Widget build(BuildContext context) {
    if (_durum == null) {
      return Center(
        child: ElevatedButton(
          onPressed: _konumIzniIste,
          child: const Text('Konum İznini Ver'),
        ),
      );
    }

    if (_durum!.isGranted) {
      return const Center(child: Text('Konum alınıyor...'));
    }

    if (_durum!.isPermanentlyDenied) {
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('Konum izni kapalı görünüyor.'),
            TextButton(
              onPressed: openAppSettings,
              child: const Text('Ayarları Aç'),
            ),
          ],
        ),
      );
    }

    return const Center(child: Text('Bu özellik için konum izni gerekli.'));
  }
}

Bu, gerçek uygulamalarda göreceğin standart deseni gösteriyor: her PermissionStatus durumu için farklı bir UI — kullanıcıyı asla "hiçbir şey olmuyor" hissiyle baş başa bırakmamak, özellikle permanentlyDenied durumunda açık bir çıkış yolu (ayarlara yönlendirme) sunmak önemlidir.

İyi Pratikler — Ne Zaman İzin İstenmeli?

Kötü pratik: Uygulama açılır açılmaz, kullanıcı henüz hiçbir şey yapmadan, tüm izinleri (konum + kamera + bildirim + kişiler) art arda istemek. Bu, kullanıcıyı ürkütür ve genelde hepsinin reddedilmesiyle sonuçlanır.

İyi pratik — "bağlamsal izin isteme" (contextual permission): İzni, kullanıcı tam olarak o özelliği kullanmaya çalıştığı anda iste (örn. "Fotoğraf Çek" butonuna bastığında kamera izni). Bu hem daha anlaşılır (kullanıcı "neden bu izni istiyor" sorusuna kendi kendine cevap bulur) hem de kabul oranını belirgin şekilde artırır.


🎯 Bu Dersten Çıkarılması Gerekenler

  • İzinler çalışma zamanında istenir; Android'de AndroidManifest.xml'de, iOS'ta Info.plist'te (kullanım açıklamasıyla birlikte) beyan edilmesi zorunludur.
  • Permission.x.status, sadece kontrol eder; Permission.x.request(), diyaloğu gösterip kullanıcıdan karar ister.
  • PermissionStatus, sadece granted/denied değil, permanentlyDenied (kalıcı red — openAppSettings() gerekir), restricted, limited gibi daha zengin durumlar içerir.
  • Birden fazla izin, [Permission.a, Permission.b].request() ile tek seferde istenebilir, sonuç bir Map<Permission, PermissionStatus> döner.
  • İyi pratik: izinleri, uygulama açılışında değil, kullanıcı tam o özelliğe ihtiyaç duyduğu anda (bağlamsal olarak) istemek.

📝 Ödevler

  • [ ] permission_handler ekle, kamera izni isteyen ve sonucu ekranda (granted/denied/permanentlyDenied) gösteren bir buton yap.
  • [ ] permanentlyDenied durumunu simüle et (izni birkaç kez reddet), openAppSettings() ile sistem ayarlarına yönlendiren bir buton ekle.
  • [ ] Aynı ekranda hem kamera hem mikrofon iznini tek bir kullanıcı etkileşiminde iste, ikisinin de granted olup olmadığını kontrol et.
  • [ ] Kendi cümlelerinle, "neden uygulamanın açılışında tüm izinleri birden istemenin kötü bir pratik olduğunu" açıkla.

Sıradaki konu: Bölüm 9 — Konu 42: Kamera, Konum, Sensörler