Flutter ve Firebase ile Uygulama Geliştirme: İslam Sanatları Projesi

Öğrencilerimizin hem görsel hem de ruhsal dünyasına hitap edecek, aynı zamanda modern bir altyapı (Flutter & Firebase) ile çalışacak bu uygulama.

Uygulama tasarımında İslam sanatlarına (Hüsn-i Hat, Ebru, Tezhip, Mimari) uygun estetik renkler (zümrüt yeşili, altın sarısı ve krem) kullandık. Uygulama; Firestore'dan verileri gerçek zamanlı çekecek bir Ana Sayfa, içeriklerin detaylı okunduğu bir Detay Sayfası ve sizin (veya görevli öğrencilerin) sisteme yeni içerik girebilmesi için bir İçerik Ekleme Sayfası barındırıyor.

Hazırladığımız bu projeyi, okuldaki laboratuvar bilgisayarlarına veya Pardus ETAP yüklü akıllı tahtalara (Cinnamon masaüstü ortamına) doğrudan yerel bir Linux uygulaması olarak kurmak, öğrencilerin erişimini çok daha kolaylaştıracaktır.


Değerler eğitimi kapsamında geliştirdiğimiz "İslamda Sanat ve Estetik" projesini, okullarımızda yaygın olarak kullanılan yerli işletim sistemimiz Pardus üzerine kurarak yerel bir Linux masaüstü uygulaması haline getirebiliriz. Pardus'un (özellikle ETAP sürümlerinin) sunduğu Cinnamon masaüstü ortamında, Flutter uygulamaları son derece performanslı ve akıcı çalışmaktadır.

1. Gerekli Sistem Bağımlılıklarının Kurulması

Flutter'ın Linux üzerinde masaüstü uygulaması (executable) derleyebilmesi için C++ derleme araçlarına ve GTK kütüphanelerine ihtiyacı vardır. Pardus terminalini (Ctrl+Alt+T) açın ve aşağıdaki komutları sırasıyla çalıştırın:

Bash:
# Sistem paket listesini güncelleyelim
sudo apt update

# Flutter ve Linux Desktop derlemesi için gerekli temel araçları kuralım
sudo apt install -y curl git unzip xz-utils zip libglu1-mesa

# Linux masaüstü (GTK) bağımlılıklarını kuralım
sudo apt install -y clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev

2. Flutter SDK Kurulumu

Pardus'ta Flutter SDK'yı kurmanın en sağlıklı yolu, doğrudan resmi GitHub deposundan kararlı (stable) sürümü çekmektir. Laboratuvar bilgisayarlarında herkesin (veya yönetici hesabının) erişebileceği bir dizine kurulum yapalım:

Bash:
# Geliştirme araçları için bir klasör oluşturalım
mkdir -p ~/development
cd ~/development

# Flutter'ın kararlı sürümünü indirelim
git clone https://github.com/flutter/flutter.git -b stable

# Flutter komutlarını sistem yoluna (PATH) ekleyelim (Pardus varsayılan olarak Bash kullanır)
echo 'export PATH="$PATH:$HOME/development/flutter/bin"' >> ~/.bashrc

# Değişiklikleri mevcut terminale uygulayalım
source ~/.bashrc

Kurulumun doğruluğunu ve Linux masaüstü desteğinin aktif olup olmadığını kontrol edelim:

Bash:
# Linux masaüstü desteğini aktif edelim
flutter config --enable-linux-desktop

# Flutter doktoru ile eksik var mı kontrol edelim
flutter doctor

(Not: Android Studio uyarıları alabilirsiniz, ancak sadece Linux masaüstü uygulaması derleyeceğimiz için bu uyarıları şimdilik göz ardı edebilirsiniz.)

3. Firebase ve FlutterFire CLI Kurulumu

Projemiz arka planda Firestore kullandığı için Firebase araçlarını kurmamız gerekiyor:

Bash:
# Node.js tabanlı Firebase CLI'ı kuralım
curl -sL https://firebase.tools | bash

# Firebase hesabımıza giriş yapalım (Tarayıcı açılacaktır)
firebase login

# FlutterFire aracını aktif edelim
dart pub global activate flutterfire_cli

# Dart araçlarını PATH'e ekleyelim
echo 'export PATH="$PATH":"$HOME/.pub-cache/bin"' >> ~/.bashrc
source ~/.bashrc

Harika bir değerler eğitimi projesi. Öğrencilerinizin hem görsel hem de ruhsal dünyasına hitap edecek, aynı zamanda modern bir altyapı (Flutter & Firebase) ile çalışacak bu uygulama için ihtiyacınız olan tüm kodları adım adım hazırladım.

Uygulama tasarımında İslam sanatlarına (Hüsn-i Hat, Ebru, Tezhip, Mimari) uygun estetik renkler (zümrüt yeşili, altın sarısı ve krem) kullandık. Uygulama; Firestore'dan verileri gerçek zamanlı çekecek bir Ana Sayfa, içeriklerin detaylı okunduğu bir Detay Sayfası ve sizin (veya görevli öğrencilerin) sisteme yeni içerik girebilmesi için bir İçerik Ekleme Sayfası barındırıyor.

Proje Oluşturma ve Paketleri Ekleme

Öncelikle terminalden yeni projemizi oluşturup içine girelim:

Bash:

flutter create islam_sanati_app
cd islam_sanati_app

Firebase CLI ayarlarını az önce çözdüğümüz şekilde yapalım (Terminalde bu komutları çalıştırıp projenizi seçin):

Bash:
flutterfire configure

Gerekli paketleri projeye dahil edelim:

Bash:
flutter pub add firebase_core cloud_firestore

Klasör Yapısı

Kodların daha düzenli olması ve öğrencilere modern yazılım mimarisini göstermek adına lib klasörü içinde şöyle bir yapı kuracağız:

  • lib/main.dart (Ana giriş ve tema)

  • lib/models/art_model.dart (Veri modelimiz)

  • lib/screens/home_screen.dart (Liste ekranı)

  • lib/screens/detail_screen.dart (Okuma ekranı)

  • lib/screens/add_content_screen.dart (Veri ekleme ekranı)


Kodların Tamamı

Aşağıdaki kodları ilgili dosyaları oluşturarak yapıştırabilirsiniz.

lib/models/art_model.dart

Veritabanından gelecek verileri nesneye dönüştüreceğimiz model sınıfımız.

Dart:
import 'package:cloud_firestore/cloud_firestore.dart';

class ArtModel {
  final String id;
  final String title;
  final String description;
  final String category;
  final String imageUrl;

  ArtModel({
    required this.id,
    required this.title,
    required this.description,
    required this.category,
    required this.imageUrl,
  });

  // Firestore'dan gelen veriyi modele çevirme
  factory ArtModel.fromFirestore(DocumentSnapshot doc) {
    Map data = doc.data() as Map<String, dynamic>;
    return ArtModel(
      id: doc.id,
      title: data['title'] ?? '',
      description: data['description'] ?? '',
      category: data['category'] ?? '',
      imageUrl: data['imageUrl'] ?? '',
    );
  }

  // Modeli Firestore'a yazılacak formata çevirme
  Map<String, dynamic> toMap() {
    return {
      'title': title,
      'description': description,
      'category': category,
      'imageUrl': imageUrl,
      'timestamp': FieldValue.serverTimestamp(),
    };
  }
}

lib/main.dart

Uygulamanın başlangıç noktası ve teması.

Dart:
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'firebase_options.dart';
import 'screens/home_screen.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(const IslamSanatiApp());
}

class IslamSanatiApp extends StatelessWidget {
  const IslamSanatiApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'İslamda Sanat ve Estetik',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        // Estetik renk paleti (Zümrüt Yeşili ve Altın)
        primaryColor: const Color(0xFF0F4C3A), 
        scaffoldBackgroundColor: const Color(0xFFF9F6F0),
        appBarTheme: const AppBarTheme(
          backgroundColor: Color(0xFF0F4C3A),
          foregroundColor: Colors.white,
          centerTitle: true,
          elevation: 0,
        ),
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFF0F4C3A),
          secondary: const Color(0xFFD4AF37), // Altın Sarısı
        ),
      ),
      home: const HomeScreen(),
    );
  }
}

lib/screens/home_screen.dart

Öğrencilerin içerikleri göreceği ana liste ekranı. Firestore'dan verileri gerçek zamanlı çeker.

Dart:
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import '../models/art_model.dart';
import 'detail_screen.dart';
import 'add_content_screen.dart';

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('İslam Sanatları', style: TextStyle(fontFamily: 'serif')),
        actions: [
          IconButton(
            icon: const Icon(Icons.add_circle_outline),
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute(builder: (context) => const AddContentScreen()),
              );
            },
          )
        ],
      ),
      body: StreamBuilder<QuerySnapshot>(
        stream: FirebaseFirestore.instance
            .collection('arts')
            .orderBy('timestamp', descending: true)
            .snapshots(),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator());
          }
          if (!snapshot.hasData || snapshot.data!.docs.isEmpty) {
            return const Center(
                child: Text('Henüz içerik eklenmemiş.', 
                style: TextStyle(fontSize: 18, color: Colors.grey)));
          }

          return ListView.builder(
            padding: const EdgeInsets.all(12),
            itemCount: snapshot.data!.docs.length,
            itemBuilder: (context, index) {
              ArtModel art = ArtModel.fromFirestore(snapshot.data!.docs[index]);
              
              return Card(
                elevation: 4,
                margin: const EdgeInsets.only(bottom: 16),
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(15),
                ),
                child: InkWell(
                  onTap: () {
                    Navigator.push(
                      context,
                      MaterialPageRoute(builder: (context) => DetailScreen(art: art)),
                    );
                  },
                  child: Row(
                    children: [
                      // Görsel Alanı
                      ClipRRect(
                        borderRadius: const BorderRadius.only(
                          topLeft: Radius.circular(15),
                          bottomLeft: Radius.circular(15),
                        ),
                        child: art.imageUrl.isNotEmpty
                            ? Image.network(
                                art.imageUrl,
                                width: 120,
                                height: 120,
                                fit: BoxFit.cover,
                                errorBuilder: (context, error, stackTrace) => 
                                    _buildPlaceholder(),
                              )
                            : _buildPlaceholder(),
                      ),
                      // Metin Alanı
                      Expanded(
                        child: Padding(
                          padding: const EdgeInsets.all(12.0),
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Text(
                                art.category,
                                style: TextStyle(
                                  color: Theme.of(context).colorScheme.secondary,
                                  fontWeight: FontWeight.bold,
                                  fontSize: 12,
                                ),
                              ),
                              const SizedBox(height: 4),
                              Text(
                                art.title,
                                style: const TextStyle(
                                  fontSize: 18,
                                  fontWeight: FontWeight.bold,
                                  color: Color(0xFF2C3E50),
                                ),
                                maxLines: 2,
                                overflow: TextOverflow.ellipsis,
                              ),
                            ],
                          ),
                        ),
                      )
                    ],
                  ),
                ),
              );
            },
          );
        },
      ),
    );
  }

  Widget _buildPlaceholder() {
    return Container(
      width: 120,
      height: 120,
      color: Colors.grey[300],
      child: const Icon(Icons.image_not_supported, color: Colors.grey),
    );
  }
}

lib/screens/detail_screen.dart

Seçilen sanat dalının veya eserin detaylı olarak okunacağı, estetik görünümü ön planda tutan sayfa.

Dart:
import 'package:flutter/material.dart';
import '../models/art_model.dart';

class DetailScreen extends StatelessWidget {
  final ArtModel art;

  const DetailScreen({super.key, required this.art});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: CustomScrollView(
        slivers: [
          SliverAppBar(
            expandedHeight: 250.0,
            pinned: true,
            flexibleSpace: FlexibleSpaceBar(
              title: Text(
                art.title,
                style: const TextStyle(
                  color: Colors.white,
                  shadows: [Shadow(color: Colors.black, blurRadius: 10)],
                ),
              ),
              background: art.imageUrl.isNotEmpty
                  ? Image.network(art.imageUrl, fit: BoxFit.cover)
                  : Container(color: Theme.of(context).primaryColor),
            ),
          ),
          SliverToBoxAdapter(
            child: Padding(
              padding: const EdgeInsets.all(20.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Chip(
                    label: Text(art.category),
                    backgroundColor: Theme.of(context).colorScheme.secondary.withOpacity(0.2),
                    labelStyle: TextStyle(
                      color: Theme.of(context).colorScheme.secondary,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(height: 20),
                  Text(
                    art.description,
                    style: const TextStyle(
                      fontSize: 16,
                      height: 1.6, // Okunabilirliği artırmak için satır aralığı
                      color: Color(0xFF34495E),
                    ),
                  ),
                ],
              ),
            ),
          )
        ],
      ),
    );
  }
}

lib/screens/add_content_screen.dart

Uygulamayı içerikle doldurmak için kullanacağınız form sayfası. CRUD işlemlerinin "Create" (Oluşturma) adımı.

Dart:
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

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

  @override
  State<AddContentScreen> createState() => _AddContentScreenState();
}

class _AddContentScreenState extends State<AddContentScreen> {
  final _formKey = GlobalKey<FormState>();
  String _title = '';
  String _category = 'Hüsn-i Hat';
  String _description = '';
  String _imageUrl = '';
  bool _isLoading = false;

  final List<String> _categories = ['Hüsn-i Hat', 'Ebru', 'Tezhip', 'Mimari', 'Minyatür', 'Çini'];

  Future<void> _saveContent() async {
    if (_formKey.currentState!.validate()) {
      _formKey.currentState!.save();
      setState(() => _isLoading = true);

      try {
        await FirebaseFirestore.instance.collection('arts').add({
          'title': _title,
          'category': _category,
          'description': _description,
          'imageUrl': _imageUrl,
          'timestamp': FieldValue.serverTimestamp(),
        });
        
        if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('İçerik başarıyla eklendi!')),
          );
          Navigator.pop(context); // İşlem bitince önceki sayfaya dön
        }
      } catch (e) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Hata oluştu: $e')),
        );
      } finally {
        if (mounted) {
          setState(() => _isLoading = false);
        }
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Yeni İçerik Ekle')),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16.0),
        child: Form(
          key: _formKey,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              DropdownButtonFormField<String>(
                value: _category,
                decoration: const InputDecoration(labelText: 'Kategori', border: OutlineInputBorder()),
                items: _categories.map((cat) {
                  return DropdownMenuItem(value: cat, child: Text(cat));
                }).toList(),
                onChanged: (val) => setState(() => _category = val!),
              ),
              const SizedBox(height: 16),
              TextFormField(
                decoration: const InputDecoration(labelText: 'Başlık', border: OutlineInputBorder()),
                validator: (val) => val!.isEmpty ? 'Başlık boş bırakılamaz' : null,
                onSaved: (val) => _title = val!,
              ),
              const SizedBox(height: 16),
              TextFormField(
                decoration: const InputDecoration(labelText: 'Görsel URL (İsteğe Bağlı)', border: OutlineInputBorder()),
                onSaved: (val) => _imageUrl = val ?? '',
              ),
              const SizedBox(height: 16),
              TextFormField(
                maxLines: 8,
                decoration: const InputDecoration(labelText: 'İçerik / Açıklama', border: OutlineInputBorder()),
                validator: (val) => val!.isEmpty ? 'İçerik boş bırakılamaz' : null,
                onSaved: (val) => _description = val!,
              ),
              const SizedBox(height: 24),
              ElevatedButton(
                style: ElevatedButton.styleFrom(
                  backgroundColor: Theme.of(context).colorScheme.secondary,
                  padding: const EdgeInsets.symmetric(vertical: 16),
                ),
                onPressed: _isLoading ? null : _saveContent,
                child: _isLoading 
                    ? const CircularProgressIndicator(color: Colors.white)
                    : const Text('Kaydet', style: TextStyle(fontSize: 18, color: Colors.white)),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

4. Firestore Veritabanı Kuralları

Kodları çalıştırıp test etmeden önce, Firebase Konsolunda "Firestore Database" sekmesine gidip veritabanını oluşturun. Öğrencileriniz verileri görebilsin, siz de veri ekleyebilin diye geçici olarak test modunda başlatabilirsiniz. "Rules" (Kurallar) sekmesindeki kuralları geliştirme aşamasında şu şekilde güncelleyebilirsiniz:

JavaScript:
rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      // Geçici olarak herkese okuma/yazma izni verir. (Projeyi yayına alırken burayı güncelleyeceğiz)
      allow read, write: if true; 
    }
  }
}

Bu temel yapı, hem eğitim materyali sunmak hem de sınıf içi projelerde öğrencilere arka plan servislerinin nasıl senkronize çalıştığını göstermek için harika bir başlangıç noktasıdır. Uygulamayı çalıştırdıktan sonra "Ekle" butonuna basarak örnek bir "Ebru Sanatı" veya "Süleymaniye Camii" başlığı girip test edebilirsiniz.


Kaynak Kodlar : https://github.com/nuritiras/islam_sanati_app

4. Projenin Derlenmesi ve Çalıştırılması

Önceki adımlarda hazırladığımız "İslam Sanatı" projesini Pardus üzerinde derlemeye hazırız. Proje klasörünüzün içine terminalden girin:

Bash:
cd ~/islam_sanati_app

# Gerekli Flutter paketlerini indirelim
flutter pub get

# Firebase yapılandırmasını Linux'u da kapsayacak şekilde güncelleyelim
# (Gelen ekranda projenizi seçip platformlardan 'linux'u işaretlemeyi unutmayın)
flutterfire configure

# Uygulamamızı Pardus (Linux) üzerinde çalıştıralım!
flutter run -d linux

Uygulamanız başarıyla açıldığında, Firebase Firestore'dan verilerin gerçek zamanlı olarak Pardus masaüstünüze geldiğini göreceksiniz.

5. Pardus Cinnamon İçin Uygulama Kısayolu Oluşturma (Bonus)

Derlediğimiz uygulamayı her seferinde terminalden başlatmak yerine, Cinnamon masaüstü ortamının uygulama menüsüne eklemek çok daha profesyonel bir çözümdür.

Öncelikle uygulamanın nihai (Release) sürümünü derleyelim:

Bash:
flutter build linux

Bu komut, uygulamayı build/linux/x64/release/bundle/ dizininde çalıştırılabilir bir dosya olarak oluşturur.

Şimdi Cinnamon uygulama menüsü için bir .desktop dosyası oluşturalım:

Bash:
nano ~/.local/share/applications/islam-sanati.desktop

Açılan nano editörüne şu metni yapıştırın (dosya yolundaki KULLANICI_ADINIZ kısmını kendi oturum adınıza göre değiştirmeyi unutmayın):

Ini, TOML
[Desktop Entry]
Version=1.0
Type=Application
Name=İslamda Sanat ve Estetik
Comment=Değerler Eğitimi Uygulaması
Exec=/home/KULLANICI_ADINIZ/islam_sanati_app/build/linux/x64/release/bundle/islam_sanati_app
Icon=utilities-terminal
Terminal=false
Categories=Education;

Ctrl+O, Enter ve Ctrl+X tuş kombinasyonlarıyla dosyayı kaydedip çıkın. Artık uygulamanız Pardus'un başlat menüsünde Eğitim kategorisi altında "İslamda Sanat ve Estetik" adıyla görünecek ve tek tıklamayla çalışacaktır!


Bu adımları takip ederek projeyi okuldaki tüm Pardus bilgisayarlara kolayca entegre edebilirsiniz. 

Yorumlar

Bu blogdaki popüler yayınlar

Dart Programlama Dil Uygulama Sınavı Çalışma Soruları

Dart Uygulama Sınavı: Pardus ETAP 23 Kurulum Otomasyonu

Pardus ETAP 23 İçin Flutter ile Dijital "Öğrenci Seçici" Uygulaması