Flutter & Nasıl Yapılır?

Flutter & Nasıl Yapılır?
Flutter & Nasıl Yapılır?

Mobil uygulama geliştirme dünyasında modern, hızlı ve performanslı çözümler üretmek her geçen gün daha kritik bir hale gelmektedir. Google tarafından geliştirilen ve açık kaynak kodlu olarak sunulan Flutter, tek bir kod tabanı (codebase) kullanarak hem Android hem de iOS platformlarında yerel (native) performansında çalışan uygulamalar geliştirmenize olanak tanır. Bu rehberimizde, sıfırdan profesyonel seviyeye Flutter & Nasıl Yapılır? sorusunun cevabını arayacak, modern mimari prensiplerine uygun, güvenli ve ölçeklenebilir bir mobil uygulamanın adım adım nasıl inşa edildiğini göreceğiz.

Bu eğitim makalesi, mobil uygulama geliştirme sürecinde karşılaşılan en temel zorlukları aşmanız için tasarlanmıştır. Proje kurulumundan başlayarak, Dart 3.x dilinin sunduğu en güncel özellikleri, Riverpod durum yönetimi (state management) kütüphanesini, Dio ile güvenli API entegrasyonunu ve kullanıcı dostu arayüz tasarımlarını ele alacağız. Yazılım dünyasında kabul görmüş en iyi pratikleri (best practices) uygulayarak, temiz kod (clean code) yazmanın inceliklerini öğreneceksiniz.

Eğitim boyunca yazacağımız tüm kodlar, 2026 yılı standartlarına uygun, null-safety (boş değer güvenliği) uyumlu ve performans optimizasyonları yapılmış şekilde sunulacaktır. Hazırsanız, modern mobil uygulama geliştirme yolculuğumuza ilk adımı atalım.

Gereksinimler ve Ön Hazırlık

Başarılı bir Flutter geliştirme ortamı kurmak, projenizin gelecekteki kararlılığı için hayati önem taşır. Geliştirme sürecine başlamadan önce bilgisayarınızda kurulu olması gereken araçlar ve minimum sistem gereksinimleri şunlardır:

  • Flutter SDK: En az sürüm 3.24 veya üzeri (Dart 3.x desteği ile birlikte).
  • IDE (Geliştirme Ortamı): Visual Studio Code (önerilen) veya Android Studio. VS Code kullanıyorsanız "Flutter" ve "Dart" eklentilerinin kurulu olduğundan emin olun.
  • Android SDK & Emulator: Android uygulamalarını test etmek için güncel Android SDK ve en az bir adet sanal cihaz (AVD).
  • Xcode (Sadece macOS kullanıcıları için): iOS simülatörü ve iOS derlemeleri için Xcode 15 veya üzeri sürüm.
  • Git: Sürüm kontrol sistemi projenizin yönetiminde kritik rol oynar.

Gereksinimleri tamamladıktan sonra terminal veya komut satırını açarak aşağıdaki komutu çalıştırmalısınız. Bu komut, sisteminizdeki eksikleri analiz eder ve size yapmanız gerekenleri listeler:

flutter doctor

Eğer tüm adımlar yeşil onay işareti aldıysa, ilk Flutter projemizi oluşturmaya ve profesyonel klasör yapımızı kurmaya hazırız demektir.

Adım 1: Flutter Projesi Oluşturma ve Klasör Yapısı Nasıl Yapılır?

Yeni bir projeye başlarken doğru klasör yapısını (folder structure) seçmek, projenin büyüdükçe spagetti koda dönüşmesini engeller. Bu adımda, terminal üzerinden temiz bir proje oluşturacak ve endüstri standardı olan "Feature-First" (Özellik Öncelikli) mimari yapısını kuracağız.

İlk olarak terminali açın ve projenizi oluşturmak istediğiniz dizine giderek aşağıdaki komutu çalıştırın. Bu komut, projemizi Kotlin ve Swift dilleriyle uyumlu şekilde oluşturacaktır:

flutter create --org com.egitim --platforms android,ios flutter_nasil_yapilir

Proje başarıyla oluşturulduktan sonra, projenin bağımlılıklarını yöneteceğimiz pubspec.yaml dosyasını açın. Modern bir uygulama için gerekli olan durum yönetimi (Riverpod) ve HTTP istemcisi (Dio) paketlerini ekleyelim. Aşağıdaki örnek kod bloğunda pubspec.yaml dosyasının bağımlılıklar kısmını görebilirsiniz:

dependencies:
  flutter:
    sdk: flutter
  flutter_riverpod: ^2.5.0
  dio: ^5.4.0
  cupertino_icons: ^1.0.8

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^3.0.0

Paketleri projeye dahil etmek için terminalde flutter pub get komutunu çalıştırın. Şimdi lib klasörünün altında şu klasör yapısını oluşturun:

  • lib/core/: Uygulama genelinde kullanılacak servisler, temalar ve ağ istemcileri.
  • lib/features/: Uygulamanın her bir özelliği (örneğin: posts, profile, auth).
  • lib/features/posts/models/: Veri modelleri.
  • lib/features/posts/services/: API isteklerini atan servis sınıfları.
  • lib/features/posts/providers/: Riverpod durum yöneticileri.
  • lib/features/posts/views/: Kullanıcı arayüzü (UI) ekranları.

Adım 2: Dart 3 ve Modern Sınıf Yapıları ile Model Oluşturma Nasıl Yapılır?

API'den gelen JSON verilerini güvenli bir şekilde Dart nesnelerine dönüştürmek, uygulamanın çökmesini önleyen en önemli adımlardan biridir. Dart 3 ile gelen güçlü tip güvenliği (type safety) ve desen eşleştirme (pattern matching) özelliklerini kullanarak sağlam bir veri modeli oluşturacağız.

Bu adımda, sahte bir API servisinden (JSONPlaceholder) çekeceğimiz gönderi (Post) verilerini temsil edecek sınıfı yazacağız. Null-safety kurallarına tam uyum sağlayarak, API'den gelebilecek olası eksik veya hatalı verileri (null pointer exceptions) absorbe edecek bir yapı kuruyoruz.

Aşağıdaki örnek kod, lib/features/posts/models/post_model.dart dosyasında yer almalıdır. Sınıfın kurucu fonksiyonunda (constructor) tüm alanların zorunlu (required) veya nullable (boş olabilir) olduğunu net bir şekilde belirtiyoruz:

class PostModel {
  final int id;
  final int userId;
  final String title;
  final String body;

  const PostModel({
    required this.id,
    required this.userId,
    required this.title,
    required this.body,
  });

  // JSON verisini Dart nesnesine dönüştüren factory metodu
  factory PostModel.fromJson(Map json) {
    return PostModel(
      id: json['id'] as int? ?? 0,
      userId: json['userId'] as int? ?? 0,
      title: json['title'] as String? ?? 'Başlıksız',
      body: json['body'] as String? ?? 'İçerik yok',
    );
  }

  // Dart nesnesini tekrar JSON formatına dönüştüren metot
  Map toJson() {
    return {
      'id': id,
      'userId': userId,
      'title': title,
      'body': body,
    };
  }
}

Yukarıdaki kod bloğunda, ?? (null coalescing) operatörünü kullanarak API'den gelebilecek olası null değerlere karşı varsayılan (default) değerler atadık. Bu işlem, uygulamanızın çalışma zamanında (runtime) beklenmedik şekilde kapanmasını engeller.

Adım 3: Dio ile Güvenli API Servisi Nasıl Yapılır?

Flutter projelerinde standart HTTP kütüphanesi yerine Dio kullanmak; interceptor (ara katman yazılımları), küresel konfigürasyonlar, istek iptal etme ve dosya yükleme gibi gelişmiş özellikleri kolayca yönetmemizi sağlar. Bu adımda, singleton tasarım deseni (singleton design pattern) kullanarak güvenli bir API servis sınıfı yazacağız.

API isteklerinde güvenlik ve hata yönetimi en kritik konudur. Sunucudan dönebilecek 400, 401, 403, 404 veya 500 gibi hata kodlarını yakalayıp kullanıcıya anlamlı hata mesajları göstermeliyiz. Ayrıca bağlantı zaman aşımı (timeout) sürelerini belirlemek, uygulamanın sonsuza kadar yanıt beklemesini önler.

Aşağıdaki örnek kod, lib/core/services/api_service.dart dosyasında yer almalıdır. Bu kod, Dio nesnesini yapılandırır ve hata yönetimini merkezi bir hale getirir:

import 'package:dio/dio.dart';

class ApiService {
  late final Dio _dio;

  ApiService() {
    _dio = Dio(
      BaseOptions(
        baseUrl: 'https://jsonplaceholder.typicode.com',
        connectTimeout: const Duration(seconds: 10),
        receiveTimeout: const Duration(seconds: 10),
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json',
        },
      ),
    );

    // Güvenlik ve loglama için interceptor ekliyoruz
    _dio.interceptors.add(InterceptorsWrapper(
      onRequest: (options, handler) {
        // Buraya JWT token ekleme gibi işlemler yapılabilir
        return handler.next(options);
      },
      onError: (DioException e, handler) {
        // Hata durumlarını burada loglayabilirsiniz
        return handler.next(e);
      },
    ));
  }

  // GET isteği atan genel fonksiyon
  Future getRequest(String path) async {
    try {
      final response = await _dio.get(path);
      return response;
    } on DioException catch (e) {
      throw _handleDioError(e);
    }
  }

  // Hata mesajlarını Türkçeleştiren yardımcı fonksiyon
  String _handleDioError(DioException error) {
    switch (error.type) {
      case DioExceptionType.connectionTimeout:
        return 'Sunucuya bağlanırken zaman aşımı oluştu.';
      case DioExceptionType.receiveTimeout:
        return 'Veri alınırken zaman aşımı oluştu.';
      case DioExceptionType.badResponse:
        final statusCode = error.response?.statusCode;
        return 'Sunucu hatası: Sürüm Kodu $statusCode';
      case DioExceptionType.cancel:
        return 'İstek iptal edildi.';
      default:
        return 'Beklenmeyen bir internet bağlantı hatası oluştu.';
    }
  }
}

Bu servis yapısı sayesinde, uygulamanın herhangi bir yerinde internet bağlantısı koptuğunda veya sunucu çöktüğünde, kullanıcıya ham hata kodları yerine doğrudan Türkçe ve anlaşılır hata mesajları gösterebileceğiz.

Adım 4: Riverpod ile Durum Yönetimi (State Management) Nasıl Yapılır?

Durum yönetimi (State Management), bir mobil uygulamanın beynidir. Kullanıcı arayüzü ile iş mantığını (business logic) birbirinden ayırarak kodun test edilebilirliğini ve sürdürülebilirliğini artırır. Flutter ekosisteminde şu an en çok tercih edilen ve güvenli olan kütüphane Riverpod'dur.

Bu adımda, API'den veri çekme işleminin durumlarını (Yükleniyor, Başarılı, Hatalı) yönetecek bir StateNotifier oluşturacağız. Riverpod'un FutureProvider veya StateNotifierProvider yapıları, asenkron verileri yönetmek için biçilmiş kaftandır.

Aşağıdaki örnek kod, lib/features/posts/providers/post_provider.dart dosyasında yer almalıdır. Bu kod, API servisinden verileri çeker ve UI katmanına durumu bildirir:

import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/post_model.dart';
import '../../../core/services/api_service.dart';

// API servisinin tek bir örneğini (instance) sağlayan provider
final apiServiceProvider = Provider((ref) {
  return ApiService();
});

// Gönderileri asenkron olarak çeken ve durumu yöneten FutureProvider
final postsProvider = FutureProvider.autoDispose((ref) async {
  final apiService = ref.watch(apiServiceProvider);
  
  try {
    final response = await apiService.getRequest('/posts');
    final List data = response.data as List;
    
    // JSON listesini PostModel nesne listesine dönüştürüyoruz
    return data.map((json) => PostModel.fromJson(json as Map)).toList();
  } catch (e) {
    // Hata durumunu fırlatıyoruz, UI bunu yakalayacak
    throw Exception(e.toString());
  }
});

autoDispose özelliği, kullanıcı bu ekrandan ayrıldığında bellekteki (RAM) verilerin otomatik olarak temizlenmesini sağlar. Bu, özellikle düşük donanımlı mobil cihazlarda bellek sızıntılarını (memory leaks) önlemek için kritik bir optimizasyondur.

Adım 5: Kullanıcı Arayüzü (UI) ve Veri Listeleme Nasıl Yapılır?

Verilerimizi modelledik, API servisimizi yazdık ve durum yönetimimizi kurduk. Şimdi sıra, kullanıcıya bu verileri şık ve performanslı bir liste tasarımıyla sunmaya geldi. Flutter'ın ConsumerWidget sınıfını kullanarak Riverpod durumlarını dinleyeceğiz.

Veri yüklenirken dönen bir yükleme ikonu (CircularProgressIndicator), hata oluştuğunda bir hata mesajı ve tekrar deneme butonu, başarılı olduğunda ise kaydırılabilir bir liste (ListView.builder) göstereceğiz. Bu üçlü durum yönetimi, kullanıcı deneyimi (UX) açısından standarttır.

Aşağıdaki örnek kod, lib/features/posts/views/posts_view.dart dosyasında yer almalıdır. UI bileşenlerimizi olabildiğince modüler tutarak performansı artırıyoruz:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../providers/post_provider.dart';

class PostsView extends ConsumerWidget {
  const PostsView({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // Provider'ı dinliyoruz
    final postsAsync = ref.watch(postsProvider);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter & API Entegrasyonu'),
        centerTitle: true,
        backgroundColor: Colors.blueAccent,
      ),
      body: postsAsync.when(
        // Veri başarıyla yüklendiğinde çalışacak kısım
        data: (posts) {
          if (posts.isEmpty) {
            return const Center(child: Text('Gösterilecek veri bulunamadı.'));
          }
          return RefreshIndicator(
            onRefresh: () => ref.refresh(postsProvider.future),
            child: ListView.builder(
              itemCount: posts.length,
              padding: const EdgeInsets.all(12),
              itemBuilder: (context, index) {
                final post = posts[index];
                return Card(
                  elevation: 4,
                  margin: const EdgeInsets.symmetric(vertical: 8),
                  child: ListTile(
                    leading: CircleAvatar(
                      backgroundColor: Colors.blueAccent,
                      child: Text(post.id.toString(), style: const TextStyle(color: Colors.white)),
                    ),
                    title: Text(
                      post.title,
                      style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
                    ),
                    subtitle: Padding(
                      padding: const EdgeInsets.only(top: 8.0),
                      style: Text(post.body, maxLines: 2, overflow: TextOverflow.ellipsis),
                    ),
                  ),
                );
              },
            ),
          );
        },
        // Hata durumunda çalışacak kısım
        error: (err, stack) => Center(
          child: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Icon(Icons.error_outline, color: Colors.red, size: 60),
                const SizedBox(height: 16),
                Text(
                  err.toString().replaceAll('Exception:', ''),
                  textAlign: TextAlign.center,
                  style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500),
                ),
                const SizedBox(height: 1                
Bu yazıya tepkinizi paylaşın:
Selin Yılmaz

Zaman yönetimi ve üretkenlik ipuçları üzerine uzmanlaşmış bir içerik yazarıyım. Günlük yaşamı kolaylaştıran etkili yöntemleri okuyucularla paylaşarak hayat kalitesini artırmayı hedefliyorum.

Yorumlar (0)

Yorum Yaz