Le 20 Domande più Frequenti nei Colloqui Flutter per Sviluppatori Mobile
Preparazione ai colloqui Flutter con le 20 domande più frequenti. Widget, gestione dello stato, Dart, architettura e best practice spiegate nel dettaglio con esempi di codice.

I colloqui Flutter valutano la padronanza del framework, del linguaggio Dart e dei pattern architetturali per applicazioni mobile. Questa guida copre le 20 domande più frequenti, dai fondamenti ai concetti avanzati, con risposte dettagliate ed esempi di codice.
Gli intervistatori apprezzano i candidati che spiegano il "perché" oltre al "come". Per ogni concetto, comprendere i casi d'uso e i compromessi tecnici fa la differenza.
Domande Fondamentali su Flutter e Dart
1. Qual è la differenza tra StatelessWidget e StatefulWidget?
StatelessWidget rappresenta un widget immutabile il cui aspetto dipende esclusivamente dalla configurazione iniziale. Una volta costruito, non cambia mai. StatefulWidget mantiene uno stato mutabile che può evolversi nel tempo, innescando la ricostruzione del widget.
// 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');
}
}// 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'),
);
}
}La regola: utilizzare StatelessWidget per impostazione predefinita, StatefulWidget solo per lo stato locale del widget.
2. Come funziona l'albero dei widget di Flutter?
Flutter organizza l'interfaccia in tre alberi interconnessi: il Widget Tree (dichiarazione immutabile), l'Element Tree (ciclo di vita e binding) e il Render Tree (layout e painting).
// 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'),
],
),
),
),
);
}
}Quando viene chiamato setState, Flutter confronta il vecchio e il nuovo albero dei widget per ricostruire solo gli elementi modificati. Questa differenziazione si basa sulle chiavi e sui tipi di widget.
3. Che cos'è un costruttore const e perché utilizzarlo?
I costruttori const creano widget al momento della compilazione anziché a runtime. Flutter può riutilizzare queste istanze, migliorando le prestazioni evitando ricostruzioni non necessarie.
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));
}
}L'analizzatore Flutter segnala le opportunità mancate con il lint prefer_const_constructors.
4. Spiegare i diversi tipi di Key in Flutter
Le Key preservano lo stato dei widget durante le riorganizzazioni. Senza chiavi, Flutter si basa sulla posizione nell'albero, il che può causare bug durante il riordinamento delle liste.
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),
],
);
}
}Le GlobalKey consentono anche di accedere allo stato di un widget da qualsiasi punto, ma vanno usate con parsimonia.
Domande su Dart
5. Qual è la differenza tra final e const in Dart?
final definisce una variabile assegnabile una sola volta, valutata a runtime. const crea una costante al momento della compilazione, immutabile e valutata prima dell'esecuzione.
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)
}In Flutter, preferire const per i widget statici e final per i valori calcolati.
6. Come funzionano i Future e async/await?
Future rappresenta un valore che sarà disponibile in seguito. async/await fornisce una sintassi leggibile per gestire queste operazioni asincrone senza callback annidati.
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);
}
}
}Future.wait per il parallelismo, cicli async per l'elaborazione sequenziale.
FutureBuilder funziona per i casi semplici, ma Riverpod (AsyncValue) offre una migliore gestione della cache, degli errori e delle capacità di aggiornamento per applicazioni complesse.
7. Spiegare gli Stream e il loro utilizzo
Gli Stream rappresentano sequenze asincrone di valori, ideali per eventi in tempo reale: WebSocket, dati dei sensori o interazioni utente.
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!);
},
);
}
}Gli Stream .broadcast() consentono ascoltatori multipli, a differenza degli Stream a sottoscrizione singola.
Pronto a superare i tuoi colloqui su Flutter?
Pratica con i nostri simulatori interattivi, flashcards e test tecnici.
Domande su Architettura e Gestione dello Stato
8. Confronto tra le soluzioni di gestione dello stato: Provider, Riverpod, Bloc
Ogni soluzione risponde a esigenze diverse. Provider offre semplicità e integrazione nativa. Riverpod garantisce type-safety e testabilità. Bloc impone un'architettura rigorosa basata sugli eventi.
// 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: 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()));
}
}
}Riverpod è consigliato per i nuovi progetti grazie alla sua API moderna e alla facilità di testing.
9. Che cos'è la Clean Architecture in Flutter?
La Clean Architecture separa il codice in livelli indipendenti: Domain (logica di business), Data (fonti dati) e Presentation (UI). Questa separazione facilita il testing e la manutenzione.
// 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);
}
}// 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;
}
}Il livello Domain non dipende da nulla, Data dipende da Domain, Presentation dipende da entrambi.
10. Come implementare la dependency injection?
La dependency injection disaccoppia i componenti fornendo le loro dipendenze dall'esterno. Riverpod eccelle in questo con i suoi provider.
// 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(),
),
);
});
}Il vantaggio principale: i test possono sostituire qualsiasi dipendenza con un mock.
Domande sulle Prestazioni
11. Come ottimizzare le prestazioni di rebuild?
Minimizzare i rebuild migliora le prestazioni. Tecniche chiave: widget const, suddivisione granulare e selettori Riverpod.
// ❌ 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
],
);
}
}Utilizzare DevTools Performance per identificare i rebuild eccessivi.
12. Come ottimizzare le liste lunghe?
Le liste lunghe richiedono il lazy loading con ListView.builder. Evitare ListView con figli diretti per più di 20 elementi.
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]),
);
}
}Per liste molto lunghe (oltre 1000 elementi), considerare ListView.separated o pacchetti specializzati come scrollable_positioned_list.
Per le liste con immagini, utilizzare cached_network_image per evitare ricaricamenti. Le immagini non memorizzate nella cache causano scatti durante lo scorrimento.
13. Spiegare il funzionamento del motore di rendering Impeller
Impeller sostituisce Skia come motore di rendering predefinito (Flutter 3.16+). Precompila gli shader per eliminare il "jank" alla prima visualizzazione.
// 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 è abilitato per impostazione predefinita su iOS. Su Android, verificare la compatibilità con flutter run --enable-impeller.
Domande su Navigazione e Form
14. Come gestire la navigazione con deep linking?
Il deep linking consente di aprire l'app su una schermata specifica tramite URL. GoRouter gestisce questa funzionalità in modo nativo.
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),
);
}Configurare i file nativi (AndroidManifest.xml, Info.plist) per abilitare il deep linking di sistema.
15. Come validare i form in modo efficace?
La validazione combina Form, TextFormField e validatori personalizzati. Pacchetti simili a Zod come formz aggiungono struttura.
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
}
}
}La modalità AutovalidateMode.onUserInteraction offre la migliore UX: gli errori vengono mostrati solo dopo l'interazione.
Domande sui Test
16. Come testare i widget Flutter?
I test dei widget verificano la UI senza dipendere da un dispositivo. Il pacchetto flutter_test fornisce le utility necessarie.
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() avanza di un frame, pumpAndSettle() attende il completamento di tutte le animazioni.
17. Come testare i provider Riverpod?
Riverpod facilita il testing attraverso ProviderContainer e gli override.
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,
);
});
}I test dei provider sono veloci perché non richiedono il rendering della UI.
Domande sul Deployment
18. Come gestire i diversi ambienti (dev, staging, prod)?
Gli ambienti vengono configurati tramite file .env o costanti al momento della compilazione con --dart-define.
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;
}
}
}# terminal
# Launch with specific environment
flutter run --dart-define=ENV=staging
# Production build
flutter build apk --dart-define=ENV=prod --release19. Come implementare il flavoring per più varianti dell'app?
Il flavoring crea varianti multiple (client1, client2) con configurazioni distinte (icona, nome, API).
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;
}Configurare i file nativi Android (build.gradle) e iOS (xcconfig) per ogni flavor.
20. Quali sono le best practice di sicurezza in Flutter?
La sicurezza copre l'archiviazione dei dati sensibili, la validazione degli input e la protezione contro il reverse engineering.
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;
}
}Non archiviare mai segreti nel codice. Utilizzare flutter_secure_storage per i token e --obfuscate per la produzione.
Conclusione
Queste 20 domande coprono gli aspetti essenziali dei colloqui Flutter: fondamenti del framework, padronanza di Dart, pattern architetturali e best practice di produzione. Il successo risiede nella comprensione profonda dei meccanismi sottostanti, non nella semplice conoscenza della sintassi.
Checklist di Preparazione
- La differenza tra StatelessWidget e StatefulWidget e i rispettivi casi d'uso
- Comprendere l'albero dei widget e l'ottimizzazione dei rebuild
- Praticare async/await, Future e Stream con esercizi
- Implementare un progetto con Riverpod per la gestione dello stato
- Conoscere GoRouter e il deep linking
- Scrivere test unitari e test dei widget
- Comprendere la configurazione multi-ambiente
Inizia a praticare!
Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.
La pratica regolare su progetti personali rimane il modo migliore per consolidare queste conoscenze. Ogni domanda trattata in questa guida merita un approfondimento con codice reale per padroneggiare le sfumature del framework Flutter.
Tag
Condividi
Articoli correlati

Flutter: Creare la prima applicazione multipiattaforma
Guida completa per creare un'applicazione mobile multipiattaforma con Flutter e Dart. Widget, gestione dello stato, navigazione e best practice per principianti.

State Management in Flutter nel 2026: Riverpod vs Bloc vs GetX - Guida Completa
Confronto pratico delle soluzioni di state management per Flutter nel 2026. Riverpod 3.0, Bloc 9.0 e GetX analizzati con esempi di codice reali, benchmark di performance e strategie di migrazione.

Domande per colloqui Laravel e PHP: le Top 25 nel 2026
Le 25 domande piu frequenti nei colloqui Laravel e PHP. Eloquent ORM, middleware, Artisan, code, test e architettura con risposte dettagliate ed esempi di codice.