Flutter Monorepo: Melos ile Çoklu Paket Yönetimi

11 dakika okuma9 Mart 2026
Flutter monorepoMelos FlutterFlutter multi-packageFlutter shared codeFlutter workspaceFlutter modular architectureMelos CI/CDFlutter code sharing

# Flutter'da Multi-Package Monorepo ve Paylasilmis Kod Yonetimi

Birden fazla Flutter uygulamasinin ayni is mantigi, UI bilesenleri ve API istemcisini paylastigi projeleri yonetmek, basit gorunen ama uc ay sonra kopyala-yapistir kodlarin icinde bogulan bir probleme donusur. Paylasilan paketlerle yapilandirilan monorepo, her seyi tek bir depoda tutarken paketler arasinda net sinirlar koruyarak bu sorunu cozer. Yonettigim cok uygulamali bir projede monorepo yapisina gecis, kod tekrarina %60'in uzerinde azalma ve yeni bir ozelligi tum uygulamalara yayma suresinde iki haftadan uc gune dusus sagladi.

Flutter Projeleri Icin Neden Monorepo?

Tek bir uygulamaniz oldugunda standart Flutter proje yapisi gayet yeterlidir. Ikinci bir uygulama eklediginiz an -- belki bir musteri uygulamasi ve bir yonetim paneli, ya da farkli marka varyantlarina sahip bir white-label urun -- isler karisir.

Kopyala-yapistir tuzagi: Paylasilmis kodu depolar arasinda kopyalayarak baslarsiniz. Ilk basta hizli hissedilir. Sonra bir depodaki hata duzeltmesi digerlerine ulasmaz. Modeller birbirinden uzaklasir. A uygulamasindaki API istemcisi sayfalandirmayi B uygulamasindan farkli sekilde ele alir. Uc ay sonra ayni kodun uc farkli versiyonunu bakimda tutuyorsunuzdur.

Paket yayinlama yukunu: Paylasilan kodu yayinlanmis Dart paketlerine cikarabilirsiniz. Ama simdi ozel bir paket kayit defteri, versiyon disiplini ve her kucuk degisiklik icin bir yayin sureci gerekir. Cogu ekip icin bu gereksiz bir surtusmedir.

Monorepo cozumu: Her sey tek bir depoda yasayor. Paylasilan kod yerel paketler halinde organize ediliyor. Tum uygulamalar bu paketlere path bagimliliklari uzerinden referans veriyor. Paylasilan API istemcisinde yapilan bir degisiklik aninda her uygulamada mevcut oluyor. Tek PR, tek inceleme, tek merge.

Zamanla birikimli olarak artan faydalar:

  • **Atomik degisiklikler**: Paylasilan bir modeli guncelleyip, onu kullanan tum uygulamalari tek bir commit'te duzeltebilirsiniz.
  • **Tutarli araclar**: Tek bir lint yapilandirmasi, tek bir CI pipeline'i, tek bir kod kalitesi kural seti.
  • **Kolay ise alistirma**: Yeni gelistiriciler tek bir depo klonlar ve ihtiyaclari olan her seye sahip olur.
  • **Refactoring guveni**: Paylasilan bir paketteki sinifi yeniden adlandirdiginizda her kirilmayi aninda gorursunuz.
  • Melos: Monorepo Orkestratoru

    Melos, Dart ve Flutter monorepolari yonetmek icin fiili standart aractir. Bagimlilik cozumleme, betik orkestrasyon ve paketler arasi secici komut calistirma islerini halleder. Onu tum paketlerinizin uyum icinde calmasini saglayan bir orkestra sefi olarak dusunebilirsiniz.

    Kurulum

    yaml
    # Kok dizinindeki pubspec.yaml'a ekleyin
    dev_dependencies:
      melos: ^6.1.0
    bash
    dart pub get
    dart run melos bootstrap

    Melos Yapilandirmasi

    Deponuzun kokunde bulunan `melos.yaml` dosyasi calisma alaninizi tanimladiginiz yerdir. Produksiyona hazir bir yapilandirma:

    yaml
    name: flutter_calisma_alani
    repository: https:"code-comment">//github.com/kurum/flutter-calisma-alani
    
    packages:
      - apps/*
      - packages/*
    
    command:
      bootstrap:
        usePubspecOverrides: true
    
    scripts:
      analyze:
        run: dart analyze --fatal-infos
        exec:
          concurrency: 5
        description: Tum paketlerde statik analiz calistir.
    
      test:
        run: flutter test --coverage
        exec:
          concurrency: 3
          failFast: true
        packageFilters:
          dirExists: test
        description: Test dizini olan tum paketlerde test calistir.
    
      format:
        run: dart format --set-exit-if-changed .
        description: Calisma alaninda formatlama kontrolu yap.
    
      build:runner:
        run: dart run build_runner build --delete-conflicting-outputs
        exec:
          concurrency: 1
          failFast: true
        packageFilters:
          dependsOn: build_runner
        description: build_runner'a bagimliligi olan paketlerde build_runner calistir.
    
      clean:
        run: flutter clean
        exec:
          concurrency: 5
        description: Tum paketleri temizle.
    
      pub:upgrade:
        run: flutter pub upgrade
        exec:
          concurrency: 3
        description: Tum paketlerde bagimliliklari guncelle.

    `packageFilters` secenegi guclu bir yetenektir. build_runner'i her pakette calistirmak yerine (cogu kullanmaz bile), Melos yalnizca gercekten ona bagimli olan paketleri hedef alir. Bu, CI suresinden onemli olcude tasarruf saglar.

    Gercek Bir Monorepo Icin Klasor Yapisi

    Produksiyonda kullandigim klasor yapisi asagidadir. Birden fazla projede evrilmis olup netlik ve pratiklik arasinda denge kurar:

    flutter_calisma_alani/
      melos.yaml
      pubspec.yaml
      apps/
        musteri_app/
          lib/
          test/
          pubspec.yaml
          android/
          ios/
        yonetim_app/
          lib/
          test/
          pubspec.yaml
          android/
          ios/
        kurye_app/
          lib/
          test/
          pubspec.yaml
          android/
          ios/
      packages/
        core/
          lib/
            src/
              constants/
              extensions/
              errors/
              utils/
            core.dart
          test/
          pubspec.yaml
        models/
          lib/
            src/
              user/
              order/
              product/
            models.dart
          test/
          pubspec.yaml
        api_client/
          lib/
            src/
              interceptors/
              endpoints/
              client.dart
            api_client.dart
          test/
          pubspec.yaml
        ui_kit/
          lib/
            src/
              theme/
              widgets/
              tokens/
            ui_kit.dart
          test/
          pubspec.yaml
        analytics/
          lib/
            src/
              providers/
              events/
            analytics.dart
          test/
          pubspec.yaml
      tools/
        ci/
        scripts/

    `packages/` altindaki her dizin, kendi `pubspec.yaml`, testleri ve barrel dosyasina sahip bagimsiz bir Dart veya Flutter paketidir. `apps/` dizini gercek Flutter uygulamalarini icerir. `tools/` dizini CI betiklerini ve yardimci araclari barindirir.

    Paylasilan Paketler Detayli Inceleme

    Core Paketi

    Core paketi, diger tum paketlerin bagimli oldugu temel yardimci araclari icerir. Kararli, iyi test edilmis olmali ve seyrek degismelidir.

    dart
    class=class="code-string">"code-comment">// packages/core/lib/src/errors/app_exception.dart
    abstract class AppException implements Exception {
      final String message;
      final String? code;
      final StackTrace? stackTrace;
    
      const AppException({
        required this.message,
        this.code,
        this.stackTrace,
      });
    
      @override
      String toString() => class="code-string">'AppException($code): $message';
    }
    
    class NetworkException extends AppException {
      final int? statusCode;
    
      const NetworkException({
        required super.message,
        this.statusCode,
        super.code,
        super.stackTrace,
      });
    }
    
    class CacheException extends AppException {
      const CacheException({
        required super.message,
        super.code,
        super.stackTrace,
      });
    }
    dart
    class=class="code-string">"code-comment">// packages/core/lib/src/extensions/date_extensions.dart
    extension DateTimeX on DateTime {
      String get formatted => class="code-string">'$day/$month/$year';
    
      bool get isToday {
        final now = DateTime.now();
        return year == now.year && month == now.month && day == now.day;
      }
    
      bool get isPast => isBefore(DateTime.now());
    }

    Models Paketi

    Paylasilan modeller, bir monorepodaki en etkili pakettir. Tum uygulamalar ayni `User`, `Order` veya `Product` modelini kullandiginda, model kaymalarindan kaynaklanan tum bir hata sinifini ortadan kaldirirsiniz.

    dart
    class=class="code-string">"code-comment">// packages/models/lib/src/user/user.dart
    import class="code-string">'package:freezed_annotation/freezed_annotation.dart';
    
    part class="code-string">'user.freezed.dart';
    part class="code-string">'user.g.dart';
    
    @freezed
    class User with _$User {
      const factory User({
        required String id,
        required String email,
        required String displayName,
        required UserRole role,
        required DateTime createdAt,
        String? avatarUrl,
        @Default(false) bool isVerified,
      }) = _User;
    
      factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
    }
    
    enum UserRole {
      @JsonValue(class="code-string">'customer')
      customer,
      @JsonValue(class="code-string">'admin')
      admin,
      @JsonValue(class="code-string">'driver')
      driver,
    }

    API Client Paketi

    Paylasilan bir API istemcisi, tum uygulamalarda tutarli kimlik dogrulama, hata yonetimi ve istek/yanit islemesini saglar.

    dart
    class=class="code-string">"code-comment">// packages/api_client/lib/src/client.dart
    import class="code-string">'package:dio/dio.dart';
    import class="code-string">'package:core/core.dart';
    import class="code-string">'package:models/models.dart';
    
    class ApiClient {
      final Dio _dio;
    
      ApiClient({required String baseUrl, required TokenProvider tokenProvider})
          : _dio = Dio(BaseOptions(baseUrl: baseUrl)) {
        _dio.interceptors.addAll([
          AuthInterceptor(tokenProvider: tokenProvider),
          LoggingInterceptor(),
          RetryInterceptor(maxRetries: class="code-number">3),
        ]);
      }
    
      Future<User> getCurrentUser() async {
        try {
          final response = await _dio.get(class="code-string">'/users/me');
          return User.fromJson(response.data);
        } on DioException catch (e) {
          throw NetworkException(
            message: e.message ?? class="code-string">'Kullanici bilgisi alinamadi',
            statusCode: e.response?.statusCode,
          );
        }
      }
    
      Future<List<Order>> getOrders({int page = class="code-number">1, int limit = class="code-number">20}) async {
        try {
          final response = await _dio.get(
            class="code-string">'/orders',
            queryParameters: {class="code-string">'page': page, class="code-string">'limit': limit},
          );
          return (response.data as List)
              .map((json) => Order.fromJson(json))
              .toList();
        } on DioException catch (e) {
          throw NetworkException(
            message: e.message ?? class="code-string">'Siparisler alinamadi',
            statusCode: e.response?.statusCode,
          );
        }
      }
    }

    UI Kit Paketi

    UI kit, tum uygulamalarda tutarli bir gorsel dil saglar. Paylasilan tema, tasarim tokenlari ve yeniden kullanilabilir widget'lari icerir.

    dart
    class=class="code-string">"code-comment">// packages/ui_kit/lib/src/theme/app_theme.dart
    import class="code-string">'package:flutter/material.dart';
    
    class AppTheme {
      static ThemeData light({Color? primaryColor}) {
        final primary = primaryColor ?? const Color(0xFF2563EB);
        return ThemeData(
          useMaterial3: true,
          colorSchemeSeed: primary,
          brightness: Brightness.light,
          textTheme: _textTheme,
          inputDecorationTheme: _inputTheme,
          elevatedButtonTheme: _buttonTheme(primary),
        );
      }
    
      static const _textTheme = TextTheme(
        headlineLarge: TextStyle(fontWeight: FontWeight.w700, fontSize: class="code-number">28),
        titleMedium: TextStyle(fontWeight: FontWeight.w600, fontSize: class="code-number">16),
        bodyMedium: TextStyle(fontWeight: FontWeight.w400, fontSize: class="code-number">14),
      );
    }

    Paketler Arasi Bagimlilik Yonetimi

    Monorepoda bagimliliklari dogru kurmak kritik oneme sahiptir. Paylasilan tum paketleri kullanan bir uygulamanin `pubspec.yaml` dosyasi:

    yaml
    # apps/musteri_app/pubspec.yaml
    name: musteri_app
    description: Musteriye yonelik mobil uygulama.
    
    dependencies:
      flutter:
        sdk: flutter
      core:
        path: ../../packages/core
      models:
        path: ../../packages/models
      api_client:
        path: ../../packages/api_client
      ui_kit:
        path: ../../packages/ui_kit
      analytics:
        path: ../../packages/analytics
    
      # Uygulamaya ozel bagimliliklar
      flutter_bloc: ^8.1.0
      go_router: ^14.0.0

    Paylasilan paketlerin birbirine nasil bagimli oldugu:

    yaml
    # packages/api_client/pubspec.yaml
    name: api_client
    description: Kimlik dogrulama ve hata yonetimli paylasilan HTTP istemcisi.
    
    dependencies:
      dio: ^5.4.0
      core:
        path: ../core
      models:
        path: ../models
    
    dev_dependencies:
      test: ^1.25.0
      mockito: ^5.4.0
      build_runner: ^2.4.0

    Bagimlilik Kurali

    Paketler yonlu dongusuz bir graf (DAG) olusturmalidir. A paketi B paketine bagimliysa, B asla A'ya bagimli olmamalidir. Pratikte bu su anlama gelir: `core` hicbir seye bagimli degildir, `models` `core`'a, `api_client` `core` ve `models`'a, uygulamalar ise her seye bagimlidir.

    Dongusel bagimliliklar Dart'ta derleme zamani hatasidir, bu yuzden derleyici bunu zorlar. Ama daha ince bir nokta olarak, derin bagimlilik zincirlerinden de kacinmalisiniz. `core`'u degistirmek her paketin yeniden derlenmesini gerektiriyorsa, CI sureleriniz zarar gorur. `core`'u kucuk ve kararli tutun.

    Monorepolar Icin CI/CD

    Monoreponun en buyuk operasyonel avantaji secici CI'dir. Yalnizca bir dosya degistiginde her paketi yeniden derleyip test etmemelisiniz.

    Etkilenen Paketleri Tespit Etmek

    Melos hangi paketlerin bir degisiklikten etkilendigini belirleyebilir:

    bash
    # Son release etiketinden bu yana degisen paketleri listele
    melos list --since=origin/main --diff
    
    # Yalnizca etkilenen paketlerde testleri calistir
    melos run test --since=origin/main

    GitHub Actions Is Akisi

    yaml
    # .github/workflows/ci.yml
    name: CI
    
    on:
      pull_request:
        branches: [main]
    
    jobs:
      analyze-and-test:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
            with:
              fetch-depth: 0
    
          - uses: subosito/flutter-action@v2
            with:
              flutter-version: '3.24.0'
              cache: true
    
          - name: Melos Kur
            run: dart pub global activate melos
    
          - name: Bootstrap
            run: melos bootstrap
    
          - name: Etkilenen paketleri analiz et
            run: melos run analyze --since=origin/main
    
          - name: Etkilenen paketleri test et
            run: melos run test --since=origin/main
    
          - name: Formatlama kontrolu
            run: melos run format
    
      build-musteri-app:
        needs: analyze-and-test
        if: |
          contains(github.event.pull_request.labels.*.name, 'build:musteri') ||
          contains(github.event.pull_request.title, '[musteri]')
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v4
          - uses: subosito/flutter-action@v2
            with:
              flutter-version: '3.24.0'
          - run: melos bootstrap
          - run: cd apps/musteri_app && flutter build apk --release

    `--since=origin/main` bayragi kilit unsurdur. Melos mevcut dal ile main'i karsilastirir ve yalnizca degisen paketlerde komut calistirir. 15 paketli bir monorepoda bu, CI suresini 25 dakikadan 5 dakikanin altina dusurur.

    Monorepoda Test Stratejisi

    Her paketin kendi testleri olmalidir. Bu istege bagli degildir -- monorepo gelistirmeyi surdurulebilir kilan temeldir.

    Paket Seviyesi Birim Testleri

    Paylasilan paketler kapsamli birim testlerine ihtiyac duyar, cunku birden fazla uygulama onlara bagimlidir. `models` paketindeki bir hata her uygulamayi etkiler.

    dart
    class=class="code-string">"code-comment">// packages/models/test/user_test.dart
    import class="code-string">'package:models/models.dart';
    import class="code-string">'package:test/test.dart';
    
    void main() {
      group(class="code-string">'User', () {
        test(class="code-string">'fromJson dogru sekilde User olusturur', () {
          final json = {
            class="code-string">'id': class="code-string">'usr_123',
            class="code-string">'email': class="code-string">'test@example.com',
            class="code-string">'display_name': class="code-string">'Test Kullanici',
            class="code-string">'role': class="code-string">'customer',
            class="code-string">'created_at': class="code-string">'class="code-number">2025-class="code-number">01-15T10:class="code-number">30:00Z',
            class="code-string">'is_verified': true,
          };
    
          final user = User.fromJson(json);
    
          expect(user.id, class="code-string">'usr_123');
          expect(user.email, class="code-string">'test@example.com');
          expect(user.role, UserRole.customer);
          expect(user.isVerified, isTrue);
        });
    
        test(class="code-string">'toJson dogru map uretir', () {
          final user = User(
            id: class="code-string">'usr_123',
            email: class="code-string">'test@example.com',
            displayName: class="code-string">'Test Kullanici',
            role: UserRole.admin,
            createdAt: DateTime(class="code-number">2025, class="code-number">1, class="code-number">15),
          );
    
          final json = user.toJson();
    
          expect(json[class="code-string">'role'], class="code-string">'admin');
          expect(json[class="code-string">'is_verified'], false);
        });
      });
    }

    Uygulama Seviyesinde Entegrasyon Testleri

    Uygulamalarin, paylasilan paketlerin birlikte nasil calistigini deneyimleyen entegrasyon testleri olmalidir.

    dart
    class=class="code-string">"code-comment">// apps/musteri_app/test/integration/order_flow_test.dart
    import class="code-string">'package:musteri_app/features/orders/order_bloc.dart';
    import class="code-string">'package:api_client/api_client.dart';
    import class="code-string">'package:models/models.dart';
    import class="code-string">'package:flutter_test/flutter_test.dart';
    import class="code-string">'package:mockito/mockito.dart';
    
    class MockApiClient extends Mock implements ApiClient {}
    
    void main() {
      late OrderBloc bloc;
      late MockApiClient mockApi;
    
      setUp(() {
        mockApi = MockApiClient();
        bloc = OrderBloc(apiClient: mockApi);
      });
    
      test(class="code-string">'API istemcisinden siparisleri yukler', () async {
        when(mockApi.getOrders()).thenAnswer(
          (_) async => [
            Order(id: class="code-string">'class="code-number">1', status: OrderStatus.pending, total: class="code-number">29.99),
          ],
        );
    
        bloc.add(LoadOrders());
    
        await expectLater(
          bloc.stream,
          emitsInOrder([
            isA<OrdersLoading>(),
            isA<OrdersLoaded>(),
          ]),
        );
      });
    }

    Paylasilan Paketleri Izole Test Etmek

    Kritik bir kural: Paylasilan paket testleri asla uygulama koduna bagimli olmamalidir. `api_client` testiniz `musteri_app`'tan bir sey import ediyorsa, olusmayi bekleyen bir dongusel bagimlilikla karsi karsiyasinizdir. Paketin kendi icinde mock ve fake'ler kullanin.

    Monorepo mu Multi-Repo mu: Ne Zaman Hangisini Secmeli

    Bu bir inanc tartismasi degildir. Her yaklasim farkli ekip yapilarina ve proje gercekliklerine uygundur.

    Monorepoyu secin:

  • Onemli is mantigi paylasan 2-5 uygulamaniz varsa.
  • Kucuk ila orta olcekli bir ekip (2-15 gelistirici) uygulamalar arasinda calisiyorsa.
  • Paylasilan kod sik degisiyor ve aninda mevcut olmasi gerekiyorsa.
  • Tek bir CI/CD pipeline'i ve birlesmis kod inceleme sureci istiyorsaniz.
  • Uygulamalar benzer kadansta versiyonlaniyor ve yayinlaniyorsa.
  • Multi-repoyu secin:

  • Uygulamalar farkli yayin dongulerinde tamamen bagimsiz ekiplere aitse.
  • Paylasilan kod kararli ve seyrek degisiyorsa (versiyonlu paket olarak yayinlayin).
  • Kurumunuzda 50'den fazla gelistirici var ve monorepo darbogazina neden olacaksa.
  • Uygulamalar farkli teknoloji yiginlari kullaniyor (biri Flutter, biri React Native).
  • Duzenleyici gereksinimler denetim izleri icin ayri depolar gerektiriyorsa.
  • Hibrit yaklasim: Bazi ekipler monorepo ile baslar ve zaman icinde kararli paketleri yayinlanmis paketlere terfi ettirir. Alti aydir degismemis `core` paketi versiyonlu bir bagimlilik haline gelebilir. Her API guncellemesiyle degisen `models` paketi monorepoda kalir.

    Sik Yapilan Monorepo Hatalari

    1. Tanri Paketi

    Her seyi tek bir `shared` paketine koymak amaci bozar. `shared` icinde modeller, API istemcileri, yardimci araclar, tema verileri ve analitik varsa, her degisiklik her seyin yeniden derlenmesini tetikler. Kolayliga gore degil, alana gore bolumlendirin.

    2. Uygulamalar Arasi Import

    A uygulamasi asla B uygulamasindan kod import etmemelidir. Iki uygulamanin da ayni widget'a ihtiyaci varsa, onu `ui_kit` paketine tasiyiniz. Uygulamalar arasi import'lar gorunmez baglasma olusturur ve uygulamalari bagimsiz olarak derlemeyi imkansiz kilar.

    3. Tutarsiz Bagimlilik Versiyonlari

    `musteri_app` `dio: ^5.3.0` kullanirken `yonetim_app` `dio: ^5.4.0` kullaniyorsa, ince calisma zamani farkliliklari davet ediyorsunuz demektir. Versiyon kisitlamalarini merkezilestirin veya tutarliligi zorlamak icin Melos bagimlilik override'larini kullanin.

    4. Bootstrap Adimini Atlamak

    Yeni ekip uyeleri depoyu klonlayip uygulama dizininde `flutter pub get` calistiracaktir. Path bagimliliklari cozulmediigi icin bu basarisiz olur. `melos bootstrap`'un calistirilacak ilk komut oldugunu belgeleyin ve bir kurulum betigi ile zorlayin.

    5. Paketleri Uygulamalar Uzerinden Test Etmek

    `api_client` paketinizi musteri uygulamasini calistirip tiklayarak test etmek bir test stratejisi degildir. Her paket bagimsiz calisabilen kendi birim testlerine sahip olmalidir. CI pipeline'i bunu zorlamalidir.

    6. Aracsiz Monorepo

    Melos (veya benzer bir arac) olmadan bir monorepo, sadece birden fazla pubspec dosyasi olan bir klasordur. Tum avantajlari -- secici test, betik orkestrasyon, bagimlilik yonetimi -- kaybedersiniz. Arac yatirimini ilk gunden yapin.

    Adim Adim Baslangic

    Ikna oldunuz ve ilk Flutter monoreponuzu kurmak istiyorsaniz, minimal yol haritasi:

    bash
    # Calisma alani olustur
    mkdir calisma_alanim && cd calisma_alanim
    dart pub init
    # dev_dependencies'e melos ekleyin, ardindan:
    dart pub get
    
    # Yapiyi olustur
    mkdir -p apps/uygulamam packages/core packages/models
    
    # Paketleri baslat
    cd packages/core && dart pub init && cd ../..
    cd packages/models && dart pub init && cd ../..
    
    # Kok dizinde melos.yaml olustur (yukaridaki yapilandirmaya bakin)
    
    # Her seyi bootstrap et
    dart run melos bootstrap

    Buradan itibaren paylasilan kodu kademeli olarak paketlere tasiyin. Her seyi bir anda cikarmayin. Modellerle baslayin -- cikarilmasi en kolay olan ve en aninda deger saglayan onlardir.

    Deneyimlerime gore, iyi yapilandirilmis bir monorepo cok uygulamali bir Flutter projesinin belkemigi haline gelir. Baslangic kurulumu bir iki gun surer. Sonraki aylarda kazanilan zaman haftalarla olculur. Anahtar, disiplinli paket sinirlari ve CI'ya en basindan yatirim yapmaktir.

    Cok uygulamali bir Flutter mimarisi planliyorsaniz, ekibinize en uygun yapiyi birlikte belirleyelim.

    İlgili Makaleler

    Flutter Projeniz mi Var?

    iOS, Android ve web için yüksek performanslı Flutter uygulamaları geliştiriyorum.

    İletişime Geç