Tek Codebase, Çift Platform
Google tarafından geliştirilen Flutter, Dart programlama dili ile yazılmış widget tabanlı bir UI framework'üdür. Skia/Impeller render motoru sayesinde 60-120 fps native performans sunarken iOS ve Android için aynı kod tabanı kullanılmasına olanak tanır. Cofreex olarak Flutter'ı kurumsal projelerde BLoC veya Riverpod state management, clean architecture katmanları ve kapsamlı test stratejisiyle kullanıyoruz.
- Native performans — Skia/Impeller render motoru ile 120fps
- Tek codebase — iOS, Android, Web ve Desktop
- Hot reload ile hızlı iterasyon ve geliştirme
- Dart'ın null safety ve tip sistemi ile güvenli kod
- BLoC / Riverpod ile ölçeklenebilir state yönetimi
- Clean Architecture ile bakımı kolay katmanlı yapı
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import '../domain/repositories/order_repository.dart';
import '../domain/entities/order.dart';
part 'order_bloc.freezed.dart';
// ── Events ────────────────────────────────────────────────────
@freezed
sealed class OrderEvent with _$OrderEvent {
const factory OrderEvent.fetch({
@Default(1) int page,
String? status,
}) = FetchOrders;
const factory OrderEvent.refresh() = RefreshOrders;
const factory OrderEvent.cancel(String orderId) = CancelOrder;
const factory OrderEvent.updateFilter(OrderFilter filter) = UpdateFilter;
}
// ── States ────────────────────────────────────────────────────
@freezed
sealed class OrderState with _$OrderState {
const factory OrderState.initial() = OrderInitial;
const factory OrderState.loading() = OrderLoading;
const factory OrderState.loaded(
List<Order> orders, {
@Default(false) bool hasMore,
@Default(1) int currentPage,
}) = OrderLoaded;
const factory OrderState.refreshing(List<Order> current) = OrderRefreshing;
const factory OrderState.error(String message, {int? code}) = OrderError;
}
// ── BLoC ──────────────────────────────────────────────────────
class OrderBloc extends Bloc<OrderEvent, OrderState> {
final OrderRepository _repository;
OrderBloc({required OrderRepository repository})
: _repository = repository,
super(const OrderState.initial()) {
on<FetchOrders>(_onFetch,
transformer: sequential()); // Eş zamanlı event'ları sıraya al
on<RefreshOrders>(_onRefresh);
on<CancelOrder>(_onCancel,
transformer: droppable()); // Çift-tıklamayı engelle
on<UpdateFilter>(_onUpdateFilter);
}
Future<void> _onFetch(
FetchOrders event,
Emitter<OrderState> emit,
) async {
emit(const OrderState.loading());
final result = await _repository.getOrders(
page: event.page,
status: event.status,
);
result.fold(
(failure) => emit(OrderState.error(
failure.message,
code: failure.statusCode,
)),
(paginatedOrders) => emit(OrderState.loaded(
paginatedOrders.items,
hasMore: paginatedOrders.hasMore,
currentPage: event.page,
)),
);
}
Future<void> _onRefresh(
RefreshOrders event,
Emitter<OrderState> emit,
) async {
final currentOrders = state is OrderLoaded
? (state as OrderLoaded).orders
: <Order>[];
emit(OrderState.refreshing(currentOrders));
final result = await _repository.getOrders(page: 1);
result.fold(
(failure) => emit(OrderState.error(failure.message)),
(data) => emit(OrderState.loaded(data.items, hasMore: data.hasMore)),
);
}
Future<void> _onCancel(
CancelOrder event,
Emitter<OrderState> emit,
) async {
final result = await _repository.cancelOrder(event.orderId);
result.fold(
(failure) => emit(OrderState.error(failure.message)),
(_) => add(const OrderEvent.refresh()),
);
}
void _onUpdateFilter(
UpdateFilter event,
Emitter<OrderState> emit,
) {
add(OrderEvent.fetch(status: event.filter.status));
}
}
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../data/datasources/remote/order_remote_datasource.dart';
import '../data/repositories/order_repository_impl.dart';
import '../domain/entities/order.dart';
part 'order_providers.g.dart';
// ── Repository Provider ───────────────────────────────────────
@riverpod
OrderRepository orderRepository(OrderRepositoryRef ref) {
final dio = ref.watch(dioProvider);
final dataSource = OrderRemoteDataSource(dio);
final cache = ref.watch(localCacheProvider);
return OrderRepositoryImpl(remote: dataSource, cache: cache);
}
// ── Orders List — AsyncNotifier ───────────────────────────────
@riverpod
class Orders extends _$Orders {
int _page = 1;
@override
Future<List<Order>> build() async {
_page = 1;
return _fetchPage(1);
}
Future<List<Order>> _fetchPage(int page) async {
final repo = ref.read(orderRepositoryProvider);
final result = await repo.getOrders(page: page);
return result.fold(
(failure) => throw failure,
(data) => data.items,
);
}
/// Sayfayı yenile (pull-to-refresh)
Future<void> refresh() async {
ref.invalidateSelf();
await future;
}
/// Sonraki sayfayı yükle (infinite scroll)
Future<void> loadNextPage() async {
final current = await future;
_page++;
final next = await _fetchPage(_page);
state = AsyncData([...current, ...next]);
}
/// Sipariş iptal et
Future<void> cancelOrder(String id) async {
final repo = ref.read(orderRepositoryProvider);
final result = await repo.cancelOrder(id);
result.fold(
(f) => throw f,
(_) => refresh(),
);
}
}
// ── Tekil Sipariş — family provider ──────────────────────────
@riverpod
Future<Order> orderDetail(OrderDetailRef ref, String orderId) async {
final repo = ref.watch(orderRepositoryProvider);
final result = await repo.getOrderById(orderId);
return result.fold(
(failure) => throw failure,
(order) => order,
);
}
// ── Filtreli Siparişler ───────────────────────────────────────
@riverpod
AsyncValue<List<Order>> filteredOrders(
FilteredOrdersRef ref,
OrderFilter filter,
) {
final allOrders = ref.watch(ordersProvider);
return allOrders.whenData(
(orders) => orders.where((o) {
if (filter.status != null && o.status != filter.status) return false;
if (filter.minAmount != null && o.total < filter.minAmount!) return false;
if (filter.maxAmount != null && o.total > filter.maxAmount!) return false;
return true;
}).toList(),
);
}
// ── UI'da Kullanım ────────────────────────────────────────────
// class OrdersPage extends ConsumerWidget {
// @override
// Widget build(BuildContext context, WidgetRef ref) {
// final ordersAsync = ref.watch(ordersProvider);
//
// return ordersAsync.when(
// data: (orders) => OrderList(orders: orders),
// loading: () => const CenteredLoader(),
// error: (e, _) => ErrorView(message: e.toString()),
// );
// }
// }
import 'package:dio/dio.dart';
import 'package:dio_smart_retry/dio_smart_retry.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import '../core/constants/api_constants.dart';
import '../core/errors/api_exception.dart';
class ApiService {
late final Dio _dio;
final FlutterSecureStorage _storage;
ApiService({FlutterSecureStorage? storage})
: _storage = storage ?? const FlutterSecureStorage() {
_dio = Dio(BaseOptions(
baseUrl: ApiConstants.baseUrl,
connectTimeout: const Duration(seconds: 10),
receiveTimeout: const Duration(seconds: 30),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-App-Version': ApiConstants.appVersion,
},
));
_dio.interceptors.addAll([
_AuthInterceptor(_storage, _dio),
_LoggingInterceptor(),
RetryInterceptor(
dio: _dio,
logPrint: print,
retries: 3,
retryDelays: const [
Duration(seconds: 1),
Duration(seconds: 2),
Duration(seconds: 3),
],
retryEvaluator: (error, attempt) async =>
error.type != DioExceptionType.badResponse,
),
]);
}
Future<T> get<T>(
String path, {
Map<String, dynamic>? queryParams,
required T Function(dynamic json) fromJson,
}) async {
try {
final response = await _dio.get(
path,
queryParameters: queryParams,
);
return fromJson(response.data);
} on DioException catch (e) {
throw _mapException(e);
}
}
Future<T> post<T>(
String path, {
required Map<String, dynamic> body,
required T Function(dynamic json) fromJson,
}) async {
try {
final response = await _dio.post(path, data: body);
return fromJson(response.data);
} on DioException catch (e) {
throw _mapException(e);
}
}
ApiException _mapException(DioException e) {
return switch (e.type) {
DioExceptionType.connectionTimeout ||
DioExceptionType.sendTimeout ||
DioExceptionType.receiveTimeout => const ApiException.timeout(),
DioExceptionType.badResponse => ApiException.serverError(
statusCode: e.response?.statusCode ?? 0,
message: e.response?.data?['message'] ?? 'Sunucu hatası',
),
DioExceptionType.connectionError => const ApiException.noInternet(),
_ => const ApiException.unknown(),
};
}
}
// ── Auth Interceptor ──────────────────────────────────────────
class _AuthInterceptor extends Interceptor {
final FlutterSecureStorage _storage;
final Dio _dio;
const _AuthInterceptor(this._storage, this._dio);
@override
Future<void> onRequest(
RequestOptions options,
RequestInterceptorHandler handler,
) async {
final token = await _storage.read(key: 'access_token');
if (token != null) {
options.headers['Authorization'] = 'Bearer $token';
}
return handler.next(options);
}
@override
Future<void> onError(
DioException err,
ErrorInterceptorHandler handler,
) async {
if (err.response?.statusCode == 401) {
// Token yenile
final refreshed = await _refreshToken();
if (refreshed) {
// İsteği tekrar gönder
final opts = err.requestOptions;
final token = await _storage.read(key: 'access_token');
opts.headers['Authorization'] = 'Bearer $token';
final response = await _dio.fetch(opts);
return handler.resolve(response);
}
}
return handler.next(err);
}
Future<bool> _refreshToken() async {
try {
final refresh = await _storage.read(key: 'refresh_token');
if (refresh == null) return false;
final resp = await _dio.post('/auth/refresh', data: {
'refresh_token': refresh,
});
await _storage.write(
key: 'access_token',
value: resp.data['access_token'],
);
return true;
} catch (_) {
await _storage.deleteAll();
return false;
}
}
}
import 'package:go_router/go_router.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../features/auth/presentation/pages/login_page.dart';
import '../features/auth/presentation/pages/splash_page.dart';
import '../features/home/presentation/pages/home_page.dart';
import '../features/orders/presentation/pages/orders_page.dart';
import '../features/orders/presentation/pages/order_detail_page.dart';
import '../features/profile/presentation/pages/profile_page.dart';
import '../features/shell/presentation/widgets/main_shell.dart';
// ── Route Paths ───────────────────────────────────────────────
class AppRoutes {
static const splash = '/';
static const login = '/login';
static const home = '/home';
static const orders = '/orders';
static const orderDetail = '/orders/:orderId';
static const profile = '/profile';
}
// ── Router Provider ───────────────────────────────────────────
final appRouterProvider = Provider<GoRouter>((ref) {
final authState = ref.watch(authStateProvider);
return GoRouter(
initialLocation: AppRoutes.splash,
debugLogDiagnostics: true,
refreshListenable: GoRouterRefreshStream(authState.stream),
// ── Auth Guard ─────────────────────────────────────────
redirect: (context, state) {
final isLoggedIn = authState.valueOrNull?.isAuthenticated ?? false;
final isSplash = state.matchedLocation == AppRoutes.splash;
final isLoginPage = state.matchedLocation == AppRoutes.login;
if (isSplash) return null; // Splash kendi yönlendirmesini yapar
if (!isLoggedIn && !isLoginPage) return AppRoutes.login;
if (isLoggedIn && isLoginPage) return AppRoutes.home;
return null;
},
routes: [
// Splash
GoRoute(
path: AppRoutes.splash,
pageBuilder: (ctx, state) => NoTransitionPage(
child: const SplashPage(),
),
),
// Login
GoRoute(
path: AppRoutes.login,
pageBuilder: (ctx, state) => CustomTransitionPage(
child: const LoginPage(),
transitionsBuilder: (ctx, anim, sec, child) =>
FadeTransition(opacity: anim, child: child),
),
),
// ── Shell Route (Bottom Nav) ──────────────────────────
ShellRoute(
builder: (ctx, state, child) => MainShell(child: child),
routes: [
GoRoute(
path: AppRoutes.home,
builder: (ctx, state) => const HomePage(),
),
GoRoute(
path: AppRoutes.orders,
builder: (ctx, state) => const OrdersPage(),
routes: [
// Nested: Sipariş Detay
GoRoute(
path: ':orderId',
builder: (ctx, state) {
final orderId = state.pathParameters['orderId']!;
final extra = state.extra as OrderExtra?;
return OrderDetailPage(
orderId: orderId,
preloadOrder: extra?.order,
);
},
),
],
),
GoRoute(
path: AppRoutes.profile,
builder: (ctx, state) => const ProfilePage(),
),
],
),
],
// ── Hata Sayfası ──────────────────────────────────────
errorBuilder: (ctx, state) => ErrorPage(
error: state.error?.toString() ?? 'Sayfa bulunamadı',
),
);
});
// ── Kullanım: Tip-güvenli Navigasyon ─────────────────────────
// context.push(
// '/orders/42',
// extra: OrderExtra(order: cachedOrder),
// );
//
// context.go(AppRoutes.home); // Tab değiştir
// context.pushNamed('orderDetail', // İsimli rota
// pathParameters: {'orderId': id},
// );
Kullandığımız Flutter Paketleri
Kurumsal projelerde tercih ettiğimiz BLoC state management kütüphanesi. Event-driven mimari ve sealed class'lar ile tip-güvenli durum yönetimi sağlar.
Provider'ın kompile-time güvenli ve test edilebilir halefi. AsyncNotifier ile asenkron state, family provider ile parametrik veri erişimi sağlar.
İnterceptor zinciri, retry mekanizması, multipart upload ve cancel token desteğiyle kurumsal kalite HTTP istemci. Auth token yönetimi interceptor katmanında yapılır.
Google destekli declarative routing çözümü. ShellRoute ile bottom navigation, deep link ve auth guard desteği ile URL tabanlı navigasyon sağlar.
Service locator pattern ile bağımlılık enjeksiyonu. Injectable anotasyonlarıyla otomatik kayıt, singleton ve factory scope yönetimi sunar.
Hive hafif key-value store; Isar ise indexli sorgular için Flutter'a özel yüksek performanslı NoSQL veritabanı. Offline-first mimaride önbellek katmanı olarak kullanılır.
iOS Keychain ve Android Keystore altyapısını kullanarak token, şifre ve hassas verilerin şifrelenmiş şekilde saklanmasını sağlar.
Dart'ta immutable data class'lar, union type'lar ve copyWith desteği için code generation aracı. BLoC event/state ve entity tanımlarında kullanılır.
Flutter ile Hangi Projeleri Yapıyoruz?
E-ticaret Uygulaması
Ürün kataloğu, sepet yönetimi, ödeme entegrasyonu (iyzico, Stripe), sipariş takibi ve push notification ile tam özellikli alışveriş deneyimi.
Kurumsal Mobil App
Çalışan yönetimi, izin / puantaj takibi, belge onay akışları ve ERP/CRM entegrasyonları ile kurumsal iç süreçleri mobil'e taşıma.
Takip & Lojistik
Gerçek zamanlı harita takibi (google_maps_flutter), rota optimizasyonu, kargo durum bildirimleri ve sürücü uygulama yönetimi.
Sosyal Platform
Anlık mesajlaşma (WebSocket), medya paylaşımı, hikaye / feed akışı, bildirim altyapısı ve içerik moderasyon araçları.
Finans & Ödeme
Bütçe takibi, fatura yönetimi, biometric kimlik doğrulama, PCI DSS uyumlu ödeme akışları ve gerçek zamanlı varlık dashboard'u.
IoT Kontrol Arayüzü
Bluetooth/BLE cihaz bağlantısı, MQTT protokolü, sensör verisi grafikleştirme ve uzaktan cihaz kontrol paneli uygulamaları.
Clean Architecture ile Ölçeklenebilir Yapı
Kurumsal Flutter projelerinde Clean Architecture prensiplerini uyguluyoruz. Presentation, Domain ve Data katmanlarına bölünmüş yapı sayesinde iş mantığı UI'dan bağımsızdır; birim testleri mock repository'ler ile kolayca yazılır. Feature-first klasör yapısı ile her özellik kendi state, domain ve data alt katmanlarını barındırır.
lib/
├── core/
│ ├── constants/ # API URL, uygulama sabitleri
│ ├── di/ # GetIt servis kaydı (injection.dart)
│ ├── errors/ # Failure sınıfları, ApiException
│ ├── network/ # ApiService, Dio konfigürasyonu
│ └── utils/ # Extensions, validators, formatters
│
├── features/
│ ├── auth/
│ │ ├── data/
│ │ │ ├── datasources/ # AuthRemoteDataSource
│ │ │ ├── models/ # UserModel (json_serializable)
│ │ │ └── repositories/ # AuthRepositoryImpl
│ │ ├── domain/
│ │ │ ├── entities/ # User entity
│ │ │ ├── repositories/ # AuthRepository (abstract)
│ │ │ └── usecases/ # LoginUseCase, LogoutUseCase
│ │ └── presentation/
│ │ ├── bloc/ # AuthBloc, AuthEvent, AuthState
│ │ ├── pages/ # LoginPage, RegisterPage
│ │ └── widgets/ # LoginForm, SocialLoginButtons
│ │
│ └── orders/ # Aynı yapı: data / domain / presentation
│
├── shared/
│ ├── widgets/ # AppButton, AppTextField, LoadingOverlay
│ └── theme/ # AppTheme, AppColors, AppTextStyles
│
└── app/
├── app.dart # MaterialApp.router tanımı
└── router/ # GoRouter konfigürasyonu
Flutter ile Mobil Projenizi Hayata Geçirelim
iOS ve Android için tek codebase, native performans. Ücretsiz teknik danışmanlık için bize ulaşın.