Ders İçeriği

Çoğu mobil uygulama, internet üzerinden veri alıp gönderme ihtiyacı duyar. Bu, API'lerle iletişim kurmak, web servislerinden veri çekmek veya sunucuya veri göndermek anlamına gelir. Flutter, bu tür ağ işlemlerini gerçekleştirmek için çeşitli araçlar sunar.

1. HTTP Paketini Kullanarak API İstekleri (GET, POST)

Flutter'da ağ istekleri yapmak için en yaygın kullanılan paketlerden biri http paketidir. Bu paket, HTTP istekleri (GET, POST, PUT, DELETE vb.) yapmanızı ve yanıtları işlemenizi sağlar.
Öncelikle http paketini pubspec.yaml dosyanıza eklemeniz gerekir:
dependencies:
  flutter:
    sdk: flutter
  http: ^1.2.1 # En güncel sürümü kullanın

Sonra flutter pub get komutunu çalıştırın.
GET İsteği Örneği:
Bir API'den veri çekmek için GET isteği kullanılır. Örneğin, bir JSON placeholder API'sinden kullanıcı listesi çekelim.
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert'; // JSON işlemleri için

class User {
  final int id;
  final String name;
  final String email;

  User({required this.id, required this.name, required this.email});

  factory User.fromJson(Map<String, dynamic> json) {
    return User(
      id: json['id'],
      name: json['name'],
      email: json['email'],
    );
  }
}

class FetchDataScreen extends StatefulWidget {
  const FetchDataScreen({Key? key}) : super(key: key);

  @override
  State<FetchDataScreen> createState() => _FetchDataScreenState();
}

class _FetchDataScreenState extends State<FetchDataScreen> {
  late Future<List<User>> futureUsers;

  @override
  void initState() {
    super.initState();
    futureUsers = fetchUsers();
  }

  Future<List<User>> fetchUsers() async {
    final response = await http.get(Uri.parse('https://jsonplaceholder.typicode.com/users'));

    if (response.statusCode == 200) {
      // Eğer sunucu 200 OK yanıtı döndürürse, JSON'ı ayrıştır.
      List jsonResponse = json.decode(response.body);
      return jsonResponse.map((user) => User.fromJson(user)).toList();
    } else {
      // Eğer sunucu bir hata yanıtı döndürürse (örneğin 404 veya 500),
      // bir istisna fırlat.
      throw Exception('Kullanıcılar yüklenemedi');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('API Verisi Çekme')),
      body: Center(
        child: FutureBuilder<List<User>>(
          future: futureUsers,
          builder: (context, snapshot) {
            if (snapshot.hasData) {
              return ListView.builder(
                itemCount: snapshot.data!.length,
                itemBuilder: (context, index) {
                  return Card(
                    margin: const EdgeInsets.all(8.0),
                    child: ListTile(
                      title: Text(snapshot.data![index].name),
                      subtitle: Text(snapshot.data![index].email),
                    ),
                  );
                },
              );
            } else if (snapshot.hasError) {
              return Text('${snapshot.error}');
            }
            // Varsayılan olarak, yüklenirken bir yükleme göstergesi göster.
            return const CircularProgressIndicator();
          },
        ),
      ),
    );
  }
}

void main() {
  runApp(MaterialApp(
    home: const FetchDataScreen(),
  ));
}
POST İsteği Örneği:
Sunucuya veri göndermek için POST isteği kullanılır. Örneğin, yeni bir kullanıcı oluşturalım.
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

Future<http.Response> createUser(String name, String job) {
  return http.post(
    Uri.parse('https://reqres.in/api/users'),
    headers: <String, String>{
      'Content-Type': 'application/json; charset=UTF-8',
    },
    body: jsonEncode(<String, String>{
      'name': name,
      'job': job,
    }),
  );
}

class CreateUserScreen extends StatefulWidget {
  const CreateUserScreen({Key? key}) : super(key: key);

  @override
  State<CreateUserScreen> createState() => _CreateUserScreenState();
}

class _CreateUserScreenState extends State<CreateUserScreen> {
  final TextEditingController _nameController = TextEditingController();
  final TextEditingController _jobController = TextEditingController();
  String? _responseMessage;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Kullanıcı Oluştur')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: <Widget>[
            TextField(
              controller: _nameController,
              decoration: const InputDecoration(labelText: 'Ad'),
            ),
            TextField(
              controller: _jobController,
              decoration: const InputDecoration(labelText: 'Meslek'),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () async {
                final response = await createUser(_nameController.text, _jobController.text);
                if (response.statusCode == 201) {
                  setState(() {
                    _responseMessage = 'Kullanıcı başarıyla oluşturuldu: ${response.body}';
                  });
                } else {
                  setState(() {
                    _responseMessage = 'Hata: ${response.statusCode} - ${response.body}';
                  });
                }
              },
              child: const Text('Kullanıcı Oluştur'),
            ),
            const SizedBox(height: 20),
            if (_responseMessage != null) Text(_responseMessage!),
          ],
        ),
      ),
    );
  }
}

void main() {
  runApp(MaterialApp(
    home: const CreateUserScreen(),
  ));
}

2. JSON Serileştirme ve Deserileştirme

API'lerden alınan veriler genellikle JSON (JavaScript Object Notation) formatındadır. Flutter'da bu JSON verilerini Dart nesnelerine dönüştürmek (deserileştirme) ve Dart nesnelerini JSON'a dönüştürmek (serileştirme) önemlidir.
Yukarıdaki GET isteği örneğinde User.fromJson factory constructor'ını kullanarak JSON verisini User nesnesine nasıl dönüştürdüğümüzü gördük. Bu, manuel serileştirme/deserileştirme yöntemidir.
Otomatik Serileştirme (json_serializable paketi ile):
Daha büyük ve karmaşık projelerde, JSON serileştirme/deserileştirme işlemini otomatikleştirmek için json_serializable ve build_runner gibi paketler kullanılır. Bu paketler, Dart sınıflarınızdan JSON dönüşüm kodunu otomatik olarak üretir.
Öncelikle gerekli paketleri pubspec.yaml dosyanıza ekleyin:
dependencies:
  json_annotation: ^4.8.1 # En güncel sürümü kullanın

dev_dependencies:
  build_runner: ^2.4.6 # En güncel sürümü kullanın
  json_serializable: ^6.7.1 # En güncel sürümü kullanın

Sonra flutter pub get komutunu çalıştırın.
Model Sınıfı Oluşturma:
import 'package:json_annotation/json_annotation.dart';

part 'user.g.dart'; // Otomatik oluşturulacak dosya

@JsonSerializable()
class User {
  final int id;
  final String name;
  final String email;

  User({required this.id, required this.name, required this.email});

  factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
  Map<String, dynamic> toJson() => _$UserToJson(this);
}
Bu dosyayı user.dart olarak kaydedin. Ardından terminalde aşağıdaki komutu çalıştırın:

flutter pub run build_runner build

Bu komut, user.g.dart adında bir dosya oluşturacak ve JSON serileştirme/deserileştirme mantığını içerecektir. Artık User.fromJson ve toJson metodlarını kullanarak JSON dönüşümlerini kolayca yapabilirsiniz.

3. Veri Modelleri Oluşturma

API'lerden gelen verileri düzenli bir şekilde temsil etmek için veri modelleri (model classes) oluşturmak önemlidir. Bu modeller, JSON verilerini Dart nesnelerine dönüştürmeyi ve uygulamanız içinde bu verilere tip güvenli bir şekilde erişmeyi sağlar.
Yukarıdaki User sınıfı, bir veri modeline iyi bir örnektir. Her bir alanın tipi belirtilmiştir (int, String) ve fromJson factory constructor'ı ile JSON'dan nesne oluşturma mantığı kapsüllenmiştir. Bu yaklaşım, uygulamanızın daha düzenli, okunabilir ve hata ayıklanabilir olmasını sağlar.
Veri Modeli Oluştururken Dikkat Edilmesi Gerekenler:
final anahtar kelimesi: Modeldeki tüm özellikler final olarak tanımlanmalıdır. Bu, nesne oluşturulduktan sonra değerlerinin değiştirilemeyeceği anlamına gelir ve uygulamanızın durumunu daha tahmin edilebilir hale getirir.
const constructor: Eğer model sınıfınızdaki tüm özellikler final ise, const constructor tanımlayabilirsiniz. Bu, aynı özelliklere sahip birden fazla nesne oluşturulduğunda bellek kullanımını optimize eder.
copyWith metodu: Bir model nesnesinin bazı özelliklerini değiştirerek yeni bir nesne oluşturmak için copyWith metodu eklemek faydalı olabilir. Bu, özellikle durum yönetimi çözümleriyle (örneğin BLoC) çalışırken sıkça kullanılır.
Equatable paketi: Eğer iki model nesnesinin içerik olarak eşit olup olmadığını karşılaştırmanız gerekiyorsa, equatable paketini kullanarak == operatörünü ve hashCode metodunu override edebilirsiniz. Bu, özellikle Bloc veya Provider gibi durum yönetimi çözümlerinde durum değişikliklerini algılamak için önemlidir.
import 'package:equatable/equatable.dart';

class Product extends Equatable {
  final String id;
  final String name;
  final double price;
  final String imageUrl;

  const Product({
    required this.id,
    required this.name,
    required this.price,
    required this.imageUrl,
  });

  // copyWith metodu
  Product copyWith({
    String? id,
    String? name,
    double? price,
    String? imageUrl,
  }) {
    return Product(
      id: id ?? this.id,
      name: name ?? this.name,
      price: price ?? this.price,
      imageUrl: imageUrl ?? this.imageUrl,
    );
  }

  // JSON'dan Product nesnesine dönüştürme
  factory Product.fromJson(Map<String, dynamic> json) {
    return Product(
      id: json['id'] as String,
      name: json['name'] as String,
      price: (json['price'] as num).toDouble(),
      imageUrl: json['imageUrl'] as String,
    );
  }

  // Product nesnesini JSON'a dönüştürme
  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'name': name,
      'price': price,
      'imageUrl': imageUrl,
    };
  }

  @override
  List<Object> get props => [id, name, price, imageUrl];
}
Bu Product modeli, Equatable kullanarak karşılaştırılabilirliği, copyWith ile kolayca kopyalanabilirliği ve fromJson/toJson metodları ile JSON dönüşümünü sağlamaktadır. Bu tür iyi tanımlanmış veri modelleri, uygulamanızın veri katmanını sağlam ve yönetilebilir hale getirir.