Mobil Geliştiriciler İçin En Önemli 20 Flutter Mülakat Sorusu

Flutter mülakatlarına en sık sorulan 20 soruyla hazırlanın. Widget yapısı, state management, Dart, mimari ve en iyi uygulamalar detaylı şekilde açıklanmaktadır.

Mobil geliştiriciler için Flutter mülakat soruları

Flutter mülakatları; framework hakimiyetini, Dart dil bilgisini ve mobil mimari kalıplarını değerlendirmektedir. Bu rehber, temel kavramlardan ileri düzey konulara kadar en sık sorulan 20 soruyu detaylı cevaplar ve kod örnekleriyle kapsamaktadır.

Mulakat Ipucu

Mülakatçılar, "nasıl" yanında "neden" sorusunu da açıklayabilen adayları takdir etmektedir. Her kavram için kullanım senaryolarını ve teknik ödünleşimleri anlamak fark yaratmaktadır.

Temel Flutter ve Dart Sorulari

1. StatelessWidget ile StatefulWidget arasındaki fark nedir?

StatelessWidget, görünümü yalnızca başlangıç yapılandırmasına bağlı olan değişmez bir widget temsil etmektedir. Bir kez oluşturulduktan sonra asla değişmez. StatefulWidget ise zamanla değişebilen değiştirilebilir bir durumu korumakta ve widget yeniden oluşturmalarını tetiklemektedir.

stateless_example.dartdart
// StatelessWidget: static display, no change after construction
class WelcomeMessage extends StatelessWidget {
  // Final parameter - never changes
  final String username;

  const WelcomeMessage({super.key, required this.username});

  
  Widget build(BuildContext context) {
    // Build called once (unless parent rebuilds)
    return Text('Welcome, $username');
  }
}
stateful_example.dartdart
// StatefulWidget: mutable state, can rebuild itself
class LikeButton extends StatefulWidget {
  const LikeButton({super.key});

  
  State<LikeButton> createState() => _LikeButtonState();
}

class _LikeButtonState extends State<LikeButton> {
  // Mutable local state
  int _likeCount = 0;

  void _incrementLike() {
    // setState triggers rebuild with new state
    setState(() {
      _likeCount++;
    });
  }

  
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: _incrementLike,
      child: Text('Likes: $_likeCount'),
    );
  }
}

Temel kural: varsayılan olarak StatelessWidget kullanılmalı, StatefulWidget yalnızca widget-yerel durum gerektiğinde tercih edilmelidir.

2. Flutter'ın widget ağacı nasıl çalışır?

Flutter, arayüzü birbiriyle bağlantılı üç ağaç şeklinde düzenlemektedir: Widget Tree (değişmez bildirim), Element Tree (yaşam döngüsü ve bağlama) ve Render Tree (yerleşim ve çizim).

widget_tree_example.dartdart
// Declarative structure - widgets describe the UI
class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    // Each widget creates an Element in the Element Tree
    return MaterialApp(
      home: Scaffold(
        // Scaffold creates multiple RenderObjects
        body: Center(
          // Center modifies its child's layout
          child: Column(
            children: [
              // Each Text has its own RenderParagraph
              Text('First'),
              Text('Second'),
            ],
          ),
        ),
      ),
    );
  }
}

setState çağrıldığında Flutter, yalnızca değişen öğeleri yeniden oluşturmak için eski ve yeni widget ağaçlarını karşılaştırmaktadır. Bu farklılaştırma, anahtarlara ve widget türlerine dayanmaktadır.

3. Const constructor nedir ve neden kullanılır?

Const constructor yapıları, widget'ları çalışma zamanı yerine derleme zamanında oluşturmaktadır. Flutter bu örnekleri yeniden kullanabilmekte ve gereksiz yeniden oluşturmaları önleyerek performansı artırmaktadır.

const_example.dartdart
class OptimizedScreen extends StatelessWidget {
  const OptimizedScreen({super.key});

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        // ✅ const: same instance reused on each build
        const Icon(Icons.star, size: 48),
        const SizedBox(height: 16),
        const Text('Static title'),

        // ❌ Non-const: new instance on each build
        Icon(Icons.star, color: Theme.of(context).primaryColor),
      ],
    );
  }
}

// Custom widget with const constructor
class StaticCard extends StatelessWidget {
  final String title;

  // All fields must be final for const
  const StaticCard({super.key, required this.title});

  
  Widget build(BuildContext context) {
    return Card(child: Text(title));
  }
}

Flutter analizörü, prefer_const_constructors lint kuralıyla kaçırılan fırsatları işaretlemektedir.

4. Flutter'daki farklı Key türlerini açıklayın

Key yapıları, yeniden düzenlemeler sırasında widget durumunu korumaktadır. Key olmadan Flutter ağaçtaki konuma güvenmekte ve bu durum listeleri yeniden sıralarken hatalara neden olabilmektedir.

keys_example.dartdart
class TodoList extends StatelessWidget {
  final List<Todo> todos;

  const TodoList({super.key, required this.todos});

  
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: todos.length,
      itemBuilder: (context, index) {
        final todo = todos[index];
        // ✅ ValueKey: preserves state if order changes
        return TodoTile(
          key: ValueKey(todo.id),
          todo: todo,
        );
      },
    );
  }
}

// Different key types for different contexts
class KeyExamples extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Column(
      children: [
        // ValueKey: based on a unique value
        Container(key: ValueKey('unique-id')),

        // ObjectKey: based on object identity
        Container(key: ObjectKey(myObject)),

        // UniqueKey: new key on each build
        Container(key: UniqueKey()),

        // GlobalKey: access state from outside
        Form(key: _formKey),
      ],
    );
  }
}

GlobalKey yapıları ayrıca herhangi bir yerden widget durumuna erişim sağlamaktadır, ancak dikkatli kullanılmalıdır.

Dart Sorulari

5. Dart'ta final ile const arasındaki fark nedir?

final, yalnızca bir kez atanabilen ve çalışma zamanında değerlendirilen bir değişken tanımlamaktadır. const ise derleme zamanı sabiti oluşturmakta, değişmez ve yürütme öncesinde değerlendirilmektedir.

final_const_example.dartdart
class DateExample {
  // final: value assigned at runtime
  final DateTime createdAt = DateTime.now();

  // const: value known at compile time
  static const int maxItems = 100;
  static const String appName = 'MyApp';

  // ❌ Error: DateTime.now() not const (runtime value)
  // static const DateTime timestamp = DateTime.now();
}

void demonstrateDifference() {
  // final: each call can have different value
  final timestamp1 = DateTime.now();
  final timestamp2 = DateTime.now();
  print(timestamp1 == timestamp2); // false (different)

  // const: same instance reused
  const list1 = [1, 2, 3];
  const list2 = [1, 2, 3];
  print(identical(list1, list2)); // true (same instance)
}

Flutter'da statik widget'lar için const, hesaplanmış değerler için final tercih edilmelidir.

6. Future ve async/await nasıl çalışır?

Future, daha sonra mevcut olacak bir değeri temsil etmektedir. async/await ise iç içe callback'ler olmadan bu asenkron işlemleri yönetmek için okunabilir bir söz dizimi sunmaktadır.

async_example.dartdart
class UserRepository {
  final ApiClient _client;

  UserRepository(this._client);

  // Future: promise of a future value
  Future<User> fetchUser(String id) async {
    try {
      // await suspends execution until resolution
      final response = await _client.get('/users/$id');
      return User.fromJson(response);
    } catch (e) {
      // Errors propagate normally with async/await
      throw UserNotFoundException(id);
    }
  }

  // Parallel execution with Future.wait
  Future<List<User>> fetchUsers(List<String> ids) async {
    // All requests start simultaneously
    final futures = ids.map((id) => fetchUser(id));
    // Wait for all to complete
    return Future.wait(futures);
  }

  // Sequential processing
  Future<void> processSequentially(List<String> ids) async {
    for (final id in ids) {
      // Each request waits for the previous one
      await fetchUser(id);
    }
  }
}

Paralellik için Future.wait, sıralı işleme için async döngüler kullanılmaktadır.

FutureBuilder vs Riverpod

FutureBuilder basit senaryolarda işe yaramaktadır, ancak Riverpod (AsyncValue) karmaşık uygulamalarda daha iyi önbellek yönetimi, hata işleme ve yenileme yetenekleri sunmaktadır.

7. Stream yapılarını ve kullanım alanlarını açıklayın

Stream yapıları, asenkron değer dizilerini temsil etmektedir ve gerçek zamanlı olaylar için idealdir: WebSocket bağlantıları, sensör verileri veya kullanıcı etkileşimleri.

stream_example.dartdart
class MessageService {
  // StreamController manages creation and broadcasting
  final _messageController = StreamController<Message>.broadcast();

  // Expose only the Stream (not the Sink)
  Stream<Message> get messages => _messageController.stream;

  void addMessage(Message message) {
    _messageController.sink.add(message);
  }

  void dispose() {
    _messageController.close();
  }
}

// Usage with StreamBuilder
class MessageList extends StatelessWidget {
  final MessageService service;

  const MessageList({super.key, required this.service});

  
  Widget build(BuildContext context) {
    return StreamBuilder<Message>(
      stream: service.messages,
      builder: (context, snapshot) {
        // Handle all possible states
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const CircularProgressIndicator();
        }
        if (snapshot.hasError) {
          return Text('Error: ${snapshot.error}');
        }
        if (!snapshot.hasData) {
          return const Text('No messages');
        }
        return MessageCard(message: snapshot.data!);
      },
    );
  }
}

.broadcast() Stream yapıları birden fazla dinleyiciye izin vermektedir; tek abonelikli Stream'lerden farklı olarak çoklu tüketimi desteklemektedir.

Flutter mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

Mimari ve State Management Sorulari

8. State management çözümlerini karşılaştırın: Provider, Riverpod, Bloc

Her çözüm farklı ihtiyaçlara hitap etmektedir. Provider basitlik ve yerel entegrasyon sunmaktadır. Riverpod tür güvenliği ve test edilebilirlik getirmektedir. Bloc ise katı olay güdümlü mimari dayatmaktadır.

riverpod_example.dartdart
// Riverpod: declarative and type-safe approach
final userProvider = FutureProvider.autoDispose<User>((ref) async {
  final repository = ref.watch(userRepositoryProvider);
  return repository.fetchCurrentUser();
});

class UserProfile extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    // AsyncValue handles loading/error/data
    final userAsync = ref.watch(userProvider);

    return userAsync.when(
      loading: () => const CircularProgressIndicator(),
      error: (error, stack) => Text('Error: $error'),
      data: (user) => Text(user.displayName),
    );
  }
}
bloc_example.dartdart
// Bloc: explicit events/states separation
abstract class AuthEvent {}
class LoginRequested extends AuthEvent {
  final String email;
  final String password;
  LoginRequested(this.email, this.password);
}

abstract class AuthState {}
class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthSuccess extends AuthState {
  final User user;
  AuthSuccess(this.user);
}
class AuthFailure extends AuthState {
  final String error;
  AuthFailure(this.error);
}

class AuthBloc extends Bloc<AuthEvent, AuthState> {
  AuthBloc() : super(AuthInitial()) {
    on<LoginRequested>(_onLoginRequested);
  }

  Future<void> _onLoginRequested(
    LoginRequested event,
    Emitter<AuthState> emit,
  ) async {
    emit(AuthLoading());
    try {
      final user = await _repository.login(event.email, event.password);
      emit(AuthSuccess(user));
    } catch (e) {
      emit(AuthFailure(e.toString()));
    }
  }
}

Modern API'si ve kolay test edilebilirliği sayesinde yeni projeler için Riverpod önerilmektedir.

9. Flutter'da Clean Architecture nedir?

Clean Architecture, kodu bağımsız katmanlara ayırmaktadır: Domain (iş mantığı), Data (veri kaynakları) ve Presentation (kullanıcı arayüzü). Bu ayrım, test edilebilirliği ve bakımı kolaylaştırmaktadır.

domain/entities/user.dartdart
// Entity: pure business object, no framework dependency
class User {
  final String id;
  final String email;
  final String name;

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

// domain/repositories/user_repository.dart
// Interface: abstract contract, implementation in Data
abstract class UserRepository {
  Future<User> getUser(String id);
  Future<void> updateUser(User user);
}

// domain/usecases/get_user_usecase.dart
// Use case: isolated business logic
class GetUserUseCase {
  final UserRepository _repository;

  GetUserUseCase(this._repository);

  Future<User> call(String userId) {
    return _repository.getUser(userId);
  }
}
data/repositories/user_repository_impl.dartdart
// Concrete implementation with data sources
class UserRepositoryImpl implements UserRepository {
  final UserRemoteDataSource _remoteDataSource;
  final UserLocalDataSource _localDataSource;

  UserRepositoryImpl(this._remoteDataSource, this._localDataSource);

  
  Future<User> getUser(String id) async {
    try {
      // Try local cache first
      final cachedUser = await _localDataSource.getUser(id);
      if (cachedUser != null) return cachedUser;
    } catch (_) {}

    // Fallback to API
    final user = await _remoteDataSource.fetchUser(id);
    await _localDataSource.cacheUser(user);
    return user;
  }
}

Domain katmanı hiçbir şeye bağımlı değildir; Data katmanı Domain'e, Presentation katmanı ise her ikisine bağımlıdır.

10. Dependency injection nasıl uygulanır?

Dependency injection, bağımlılıkları dışarıdan sağlayarak bileşenleri birbirinden ayırmaktadır. Riverpod, provider yapısıyla bu konuda üstün performans göstermektedir.

di_example.dartdart
// Provider definitions (dependencies)
final apiClientProvider = Provider<ApiClient>((ref) {
  return ApiClient(baseUrl: Environment.apiUrl);
});

final userRepositoryProvider = Provider<UserRepository>((ref) {
  // Automatically injects ApiClient
  final client = ref.watch(apiClientProvider);
  return UserRepositoryImpl(client);
});

final getUserUseCaseProvider = Provider<GetUserUseCase>((ref) {
  final repository = ref.watch(userRepositoryProvider);
  return GetUserUseCase(repository);
});

// Usage in a widget
class UserScreen extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final getUserUseCase = ref.watch(getUserUseCaseProvider);
    // Use the use case...
  }
}

// Tests: easy dependency override
void main() {
  testWidgets('displays user', (tester) async {
    await tester.pumpWidget(
      ProviderScope(
        overrides: [
          // Replace with mock for tests
          userRepositoryProvider.overrideWithValue(MockUserRepository()),
        ],
        child: const MyApp(),
      ),
    );
  });
}

En büyük avantaj: testlerde herhangi bir bağımlılık mock ile değiştirilebilmektedir.

Performans Sorulari

11. Yeniden oluşturma performansı nasıl optimize edilir?

Yeniden oluşturmaları en aza indirmek performansı artırmaktadır. Temel teknikler: const widget'lar, granüler parçalama ve Riverpod seçicileri.

rebuild_optimization.dartdart
// ❌ Bad: everything rebuilds on each change
class BadExample extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    final user = ref.watch(userProvider);
    return Column(
      children: [
        Text(user.name),
        Text(user.email),
        const ExpensiveWidget(), // Rebuilds unnecessarily
      ],
    );
  }
}

// ✅ Good: selector to rebuild only if name changes
class GoodExample extends ConsumerWidget {
  
  Widget build(BuildContext context, WidgetRef ref) {
    // select: rebuilds only if name changes
    final name = ref.watch(userProvider.select((u) => u.name));
    return Text(name);
  }
}

// ✅ Good: splitting into smaller widgets
class OptimizedScreen extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Header(), // Static, never rebuilds
        const UserNameWidget(), // Rebuilds if name changes
        const UserEmailWidget(), // Rebuilds if email changes
        const Footer(), // Static
      ],
    );
  }
}

Aşırı yeniden oluşturmaları tespit etmek için DevTools Performance kullanılmalıdır.

12. Uzun listeler nasıl optimize edilir?

Uzun listeler, ListView.builder ile tembel yükleme gerektirmektedir. 20'den fazla öğe için doğrudan children içeren ListView kullanımından kaçınılmalıdır.

list_optimization.dartdart
class OptimizedList extends StatelessWidget {
  final List<Item> items;

  const OptimizedList({super.key, required this.items});

  
  Widget build(BuildContext context) {
    // ✅ ListView.builder: builds on demand
    return ListView.builder(
      // Fixed height improves scrolling
      itemExtent: 72,
      itemCount: items.length,
      itemBuilder: (context, index) {
        return ItemTile(
          // Key to preserve state during scroll
          key: ValueKey(items[index].id),
          item: items[index],
        );
      },
    );
  }
}

// List widget with infinite loading
class InfiniteList extends ConsumerStatefulWidget {
  
  ConsumerState<InfiniteList> createState() => _InfiniteListState();
}

class _InfiniteListState extends ConsumerState<InfiniteList> {
  final _scrollController = ScrollController();

  
  void initState() {
    super.initState();
    _scrollController.addListener(_onScroll);
  }

  void _onScroll() {
    // Load more data near the end
    if (_scrollController.position.pixels >=
        _scrollController.position.maxScrollExtent - 200) {
      ref.read(itemsProvider.notifier).loadMore();
    }
  }

  
  Widget build(BuildContext context) {
    final items = ref.watch(itemsProvider);
    return ListView.builder(
      controller: _scrollController,
      itemCount: items.length,
      itemBuilder: (context, index) => ItemTile(item: items[index]),
    );
  }
}

Cok uzun listeler (1000+ oge) icin ListView.separated veya scrollable_positioned_list gibi ozel paketler degerlendirilmelidir.

Gorsel Onbellekleme

Gorsel iceren listelerde yeniden yuklemeleri onlemek icin cached_network_image kullanilmalidir. Onbelleklenmemis gorseller kaydirma sirasinda takilmalara neden olmaktadir.

13. Impeller render motoru nasıl çalışır?

Impeller, Flutter 3.16+ sürümünden itibaren Skia'nın yerini varsayılan render motoru olarak almaktadır. Shader'ları önceden derleyerek ilk gösterimdeki "jank" sorununu ortadan kaldırmaktadır.

impeller_benefits.dartdart
// Complex animations benefit from Impeller
class SmoothAnimation extends StatefulWidget {
  
  State<SmoothAnimation> createState() => _SmoothAnimationState();
}

class _SmoothAnimationState extends State<SmoothAnimation>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    )..repeat();
  }

  
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        // Impeller: no runtime shader compilation
        return Transform.rotate(
          angle: _controller.value * 2 * 3.14159,
          child: child,
        );
      },
      child: const FlutterLogo(size: 100),
    );
  }
}

Impeller, iOS'ta varsayılan olarak etkindir. Android'de uyumluluk flutter run --enable-impeller komutuyla kontrol edilmelidir.

14. Deep linking ile navigasyon nasıl yönetilir?

Deep linking, uygulamayı URL aracılığıyla belirli bir ekranda açmaya olanak tanımaktadır. GoRouter bu işlevi yerel olarak desteklemektedir.

deep_linking.dartdart
final router = GoRouter(
  routes: [
    GoRoute(
      path: '/products/:productId',
      builder: (context, state) {
        // Extract URL parameter
        final productId = state.pathParameters['productId']!;
        return ProductScreen(productId: productId);
      },
    ),
    GoRoute(
      path: '/search',
      builder: (context, state) {
        // Query parameters (?query=flutter)
        final query = state.uri.queryParameters['query'] ?? '';
        return SearchScreen(initialQuery: query);
      },
    ),
  ],
);

// Programmatic navigation
void navigateToProduct(BuildContext context, String id) {
  // go: replaces navigation stack
  context.go('/products/$id');

  // push: adds to stack (allows back)
  context.push('/products/$id');

  // pushNamed with extra for complex data
  context.pushNamed(
    'productDetail',
    pathParameters: {'productId': id},
    extra: ProductData(id: id),
  );
}

Sistem düzeyinde deep linking'i etkinleştirmek için yerel dosyalar (AndroidManifest.xml, Info.plist) yapılandırılmalıdır.

15. Formlar nasıl etkili şekilde doğrulanır?

Doğrulama; Form, TextFormField ve özel doğrulayıcıları birleştirmektedir. formz gibi Zod benzeri paketler yapıya düzen katmaktadır.

form_validation.dartdart
class RegistrationForm extends StatefulWidget {
  
  State<RegistrationForm> createState() => _RegistrationFormState();
}

class _RegistrationFormState extends State<RegistrationForm> {
  final _formKey = GlobalKey<FormState>();
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();
  final _confirmController = TextEditingController();

  
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      // Real-time validation
      autovalidateMode: AutovalidateMode.onUserInteraction,
      child: Column(
        children: [
          TextFormField(
            controller: _emailController,
            decoration: const InputDecoration(labelText: 'Email'),
            keyboardType: TextInputType.emailAddress,
            validator: _validateEmail,
          ),
          TextFormField(
            controller: _passwordController,
            decoration: const InputDecoration(labelText: 'Password'),
            obscureText: true,
            validator: _validatePassword,
          ),
          TextFormField(
            controller: _confirmController,
            decoration: const InputDecoration(labelText: 'Confirm'),
            obscureText: true,
            validator: _validateConfirmPassword,
          ),
          ElevatedButton(
            onPressed: _submit,
            child: const Text('Create account'),
          ),
        ],
      ),
    );
  }

  String? _validateEmail(String? value) {
    if (value == null || value.isEmpty) {
      return 'Email required';
    }
    final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
    if (!emailRegex.hasMatch(value)) {
      return 'Invalid email format';
    }
    return null;
  }

  String? _validatePassword(String? value) {
    if (value == null || value.length < 8) {
      return 'Minimum 8 characters';
    }
    if (!value.contains(RegExp(r'[A-Z]'))) {
      return 'At least one uppercase letter required';
    }
    if (!value.contains(RegExp(r'[0-9]'))) {
      return 'At least one number required';
    }
    return null;
  }

  String? _validateConfirmPassword(String? value) {
    if (value != _passwordController.text) {
      return 'Passwords do not match';
    }
    return null;
  }

  void _submit() {
    if (_formKey.currentState!.validate()) {
      // Form is valid, proceed
    }
  }
}

AutovalidateMode.onUserInteraction modu en iyi kullanıcı deneyimini sunmaktadır: hatalar yalnızca etkileşimden sonra gösterilmektedir.

Test Sorulari

16. Flutter widget'ları nasıl test edilir?

Widget testleri, cihaz bağımlılığı olmadan kullanıcı arayüzünü doğrulamaktadır. flutter_test paketi gerekli araçları sağlamaktadır.

widget_test.dartdart
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  testWidgets('displays welcome message', (tester) async {
    // Arrange: build the widget
    await tester.pumpWidget(
      const ProviderScope(
        child: MaterialApp(
          home: WelcomeScreen(username: 'Alice'),
        ),
      ),
    );

    // Assert: verify content
    expect(find.text('Welcome, Alice'), findsOneWidget);
  });

  testWidgets('button increments counter', (tester) async {
    await tester.pumpWidget(
      const MaterialApp(home: CounterScreen()),
    );

    // Initial state
    expect(find.text('0'), findsOneWidget);

    // Act: tap the button
    await tester.tap(find.byType(ElevatedButton));
    await tester.pump(); // Rebuild after setState

    // Assert: counter incremented
    expect(find.text('1'), findsOneWidget);
  });

  testWidgets('form validates email', (tester) async {
    await tester.pumpWidget(
      const MaterialApp(home: LoginForm()),
    );

    // Enter invalid email
    await tester.enterText(
      find.byKey(const Key('email-field')),
      'invalid-email',
    );
    await tester.tap(find.byType(ElevatedButton));
    await tester.pump();

    // Verify error message
    expect(find.text('Invalid email format'), findsOneWidget);
  });
}

pump() tek bir kare ileri alırken, pumpAndSettle() tüm animasyonların tamamlanmasını beklemektedir.

17. Riverpod provider'ları nasıl test edilir?

Riverpod, ProviderContainer ve override yapıları aracılığıyla test sürecini kolaylaştırmaktadır.

provider_test.dartdart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';

// Repository mock
class MockUserRepository extends Mock implements UserRepository {}

void main() {
  late MockUserRepository mockRepository;
  late ProviderContainer container;

  setUp(() {
    mockRepository = MockUserRepository();
    container = ProviderContainer(
      overrides: [
        userRepositoryProvider.overrideWithValue(mockRepository),
      ],
    );
  });

  tearDown(() {
    container.dispose();
  });

  test('loads user from repository', () async {
    // Arrange
    final expectedUser = User(id: '1', name: 'Test');
    when(() => mockRepository.getUser('1'))
        .thenAnswer((_) async => expectedUser);

    // Act
    final user = await container.read(userProvider('1').future);

    // Assert
    expect(user, expectedUser);
    verify(() => mockRepository.getUser('1')).called(1);
  });

  test('handles repository error', () async {
    when(() => mockRepository.getUser(any()))
        .thenThrow(Exception('Network error'));

    expect(
      () => container.read(userProvider('1').future),
      throwsException,
    );
  });
}

Provider testleri UI render gerektirmediği için hızlı çalışmaktadır.

Dagitim Sorulari

18. Farklı ortamlar (dev, staging, prod) nasıl yönetilir?

Ortamlar, .env dosyaları veya --dart-define ile derleme zamanı sabitleri aracılığıyla yapılandırılmaktadır.

environment.dartdart
enum Environment { dev, staging, prod }

class AppConfig {
  final Environment environment;
  final String apiUrl;
  final bool enableAnalytics;

  const AppConfig._({
    required this.environment,
    required this.apiUrl,
    required this.enableAnalytics,
  });

  // Predefined configurations
  static const dev = AppConfig._(
    environment: Environment.dev,
    apiUrl: 'https://api-dev.example.com',
    enableAnalytics: false,
  );

  static const staging = AppConfig._(
    environment: Environment.staging,
    apiUrl: 'https://api-staging.example.com',
    enableAnalytics: true,
  );

  static const prod = AppConfig._(
    environment: Environment.prod,
    apiUrl: 'https://api.example.com',
    enableAnalytics: true,
  );

  // Read from --dart-define
  static AppConfig fromEnvironment() {
    const env = String.fromEnvironment('ENV', defaultValue: 'dev');
    switch (env) {
      case 'prod':
        return prod;
      case 'staging':
        return staging;
      default:
        return dev;
    }
  }
}
bash
# terminal
# Launch with specific environment
flutter run --dart-define=ENV=staging

# Production build
flutter build apk --dart-define=ENV=prod --release

19. Birden fazla uygulama varyantı için flavoring nasıl uygulanır?

Flavoring, farklı yapılandırmalara (ikon, isim, API) sahip birden fazla varyant (client1, client2) oluşturmaktadır.

main_client1.dartdart
import 'package:flutter/material.dart';
import 'config/flavor_config.dart';
import 'app.dart';

void main() {
  FlavorConfig(
    flavor: Flavor.client1,
    name: 'App Client 1',
    apiUrl: 'https://api.client1.com',
    primaryColor: Colors.blue,
  );
  runApp(const App());
}

// config/flavor_config.dart
enum Flavor { client1, client2, internal }

class FlavorConfig {
  final Flavor flavor;
  final String name;
  final String apiUrl;
  final Color primaryColor;

  // Singleton for global access
  static FlavorConfig? _instance;
  static FlavorConfig get instance => _instance!;

  FlavorConfig({
    required this.flavor,
    required this.name,
    required this.apiUrl,
    required this.primaryColor,
  }) {
    _instance = this;
  }

  bool get isProduction => flavor != Flavor.internal;
}

Her flavor icin yerel Android (build.gradle) ve iOS (xcconfig) dosyalari yapilandirilmalidir.

20. Flutter güvenlik en iyi uygulamaları nelerdir?

Güvenlik; hassas veri depolama, girdi doğrulama ve tersine mühendisliğe karşı korumayı kapsamaktadır.

security_example.dartdart
import 'package:flutter_secure_storage/flutter_secure_storage.dart';

class SecureStorage {
  // Encrypted storage for sensitive data
  final _storage = const FlutterSecureStorage(
    aOptions: AndroidOptions(encryptedSharedPreferences: true),
    iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock),
  );

  Future<void> saveToken(String token) async {
    await _storage.write(key: 'auth_token', value: token);
  }

  Future<String?> getToken() async {
    return _storage.read(key: 'auth_token');
  }

  Future<void> deleteToken() async {
    await _storage.delete(key: 'auth_token');
  }
}

// Input validation
class InputValidator {
  // Injection prevention
  static String sanitize(String input) {
    return input
        .replaceAll(RegExp(r'[<>"\']'), '')
        .trim();
  }

  // Length validation
  static bool isValidLength(String input, int min, int max) {
    return input.length >= min && input.length <= max;
  }
}

// SSL pinning protection
class SecureApiClient {
  Dio createSecureClient() {
    final dio = Dio();
    // Add certificate pinning
    (dio.httpClientAdapter as IOHttpClientAdapter).createHttpClient = () {
      final client = HttpClient();
      client.badCertificateCallback = (cert, host, port) {
        // Verify certificate fingerprint
        return _isValidCertificate(cert);
      };
      return client;
    };
    return dio;
  }
}

Gizli bilgiler asla koda yazılmamalıdır. Token'lar için flutter_secure_storage, üretim derlemeleri için --obfuscate kullanılmalıdır.

Sonuc

Bu 20 soru, Flutter mülakatlarının temel alanlarını kapsamaktadır: framework temelleri, Dart hakimiyeti, mimari kalıplar ve üretim ortamı en iyi uygulamaları. Başarı, yalnızca söz dizimini bilmekte değil, altta yatan mekanizmaları derinlemesine anlamakta yatmaktadır.

Hazirlik Kontrol Listesi

  • StatelessWidget/StatefulWidget farkını ve kullanım senaryolarını kavrayın
  • Widget ağacını ve yeniden oluşturma optimizasyonunu anlayın
  • async/await, Future ve Stream yapılarını alıştırmalarla pratik edin
  • State management için Riverpod ile bir proje geliştirin
  • GoRouter ve deep linking konularını öğrenin
  • Birim testleri ve widget testleri yazın
  • Çoklu ortam yapılandırmasını kavrayın

Pratik yapmaya başla!

Mülakat simülatörleri ve teknik testlerle bilgini test et.

Kişisel projelerde düzenli pratik yapmak, bu bilgileri pekiştirmenin en etkili yoludur. Burada ele alınan her soru, Flutter framework'ünün inceliklerinde ustalaşmak için gerçek kodla daha derin bir keşfi hak etmektedir.

Etiketler

#flutter
#dart
#interview
#mobile development
#technical interview

Paylaş

İlgili makaleler