16. Flutter
Estimated time to read: 20 minutes
TL;DR
Flutter lets you build apps for iOS, Android, Web, and Desktop from a single codebase in Dart. For C++ developers, the syntax feels familiar — the execution model does not.
Three concepts carry the entire framework:
| Concept | What it means |
|---|---|
| Everything is a widget | UI is a composable tree of objects, not a class hierarchy you modify |
| UI = f(state) | You never touch UI elements directly — you change state, Flutter rebuilds |
| Async is the default | All I/O is non-blocking; the event loop runs everything on one thread |
Understand these three and you can read, debug, and extend any Flutter app — with or without AI assistance.
For step-by-step implementation (counter app, setState, layouts, navigation with full code examples) → 👉 Flutter Intro Material
This chapter does not repeat that content. It focuses on why Flutter works the way it does, and where to look things up in the official docs.
Fachgespräch 1 — Required Topics
The following Flutter and Dart concepts are required for Fachgespräch 1:
- Dart basics: OOP in Dart (classes, mixins, implicit interfaces), null safety, async/await,
finalvsconst - Widget tree: StatelessWidget vs StatefulWidget, widget lifecycle, BuildContext
- State: ephemeral state vs app state, what triggers a rebuild, why
build()must stay pure
AI Usage Policy — Read Before Writing Code
16.1 Why Flutter?
You know C++. For mobile development without Flutter, you would write:
- Android: Kotlin + Android SDK
- iOS: Swift + Apple SDK
That is two languages, two ecosystems, two UI implementations — for the same product. A related alternative, Kotlin Multiplatform, shares business logic but still requires you to write UI code separately for each platform (SwiftUI on iOS, Jetpack Compose on Android). Flutter goes further: one language, one UI codebase, all platforms.
The critical architectural decision: Flutter does not use native UI widgets. It draws everything itself using its own rendering engine (Skia/Impeller), like a game engine. The result is pixel-perfect consistency across platforms — but it also means your app looks the same on iOS and Android unless you explicitly handle platform-specific styling.
→ Full architecture explanation: Flutter Architectural Overview
A third approach worth knowing is React Native. React Native uses JavaScript and communicates with actual native platform widgets — so the resulting UI looks and feels platform-native, unlike Flutter's custom-rendered UI. The current architecture uses JSI (JavaScript Interface), a synchronous C++ API, with a shared C++ renderer core across all platforms. Your logic runs in JavaScript, your UI is rendered by the native platform. For teams already in the JavaScript/React ecosystem, this is often the pragmatic choice.
→ React Native Architecture Overview → Cross Platform Implementation
16.2 From C++ to Dart: What Changes Conceptually
Dart syntax is familiar. The execution and type model is not.
Start with the Dart Cheatsheet and the beginner material at SheLikesCoding before diving into Flutter. Then use the Dart Language Tour as your reference throughout the project.
16.2.1 No manual memory management
In C++ you manage heap allocations explicitly. Dart uses garbage collection. No pointers, no delete, no manual deallocation. You create objects; the runtime cleans them up. Variable assignments share references, not copies — just like in Java.
16.2.2 Everything is an object
Unlike C++, even primitives (int, double, bool) are objects with methods. There is no float type in Dart — only double. You can call methods on literals directly. This also means there is no new keyword: you instantiate classes like you call functions.
16.2.3 Null safety is enforced by the type system
In C++, any pointer can be null — discovered at runtime when the program crashes. Dart makes nullability explicit at compile time. A String can never be null. A String? can. The compiler forces you to handle the difference before the code runs.
Key null-aware operators: ?. (safe access), ?? (default value), ! (force unwrap — use carefully).
16.2.4 final and const
constis a compile-time constant — the value must be known when the code compilesfinalis assigned once at runtime — set when the object is created, never changed after
In Flutter, writing const before a widget tells the framework this widget never needs to be rebuilt. The compiler optimizes it away entirely. This is not just style — it matters at 60 frames per second.
16.2.5 Privacy
There is no private or protected keyword in Dart. Any identifier starting with _ is private to its file (library). Everything else is public. This is a different granularity than C++ class-level private — worth knowing when reading Flutter source code.
16.2.6 Collections
Dart simplifies C++ containers to two primary types: List<T> (like std::vector) and Map<K,V> (like std::map). There are Sets, but you will use them rarely. Collections support functional-style operations like where, map, and any via lambda expressions.
16.2.7 Lambdas
Dart has compact anonymous function syntax with =>. This is everywhere in Flutter — in button callbacks, list filtering, widget builder methods. Think of it as a mathematical mapping: input → output.
16.2.8 Mixins & Interfaces
Dart has single inheritance — one superclass only. But mixins add reusable behavior to multiple classes without creating a deep hierarchy. Use mixins for capabilities ("can load data", "can log"), inheritance for identity ("is an animal").
Every class in Dart automatically defines an interface. You can implements any class to enforce its contract, without inheriting its implementation. There is no separate interface keyword.
→ Mixins | Implicit interfaces
16.2.9 Async: Syntax
C++ typically blocks on results or uses explicit threading. Dart runs on a single-thread event loop — one thread handles both UI rendering and business logic. Blocking this thread freezes the UI immediately.
Instead of blocking, Dart uses Future<T>, async, and await. Every operation involving the network, a database, the file system, or Firebase is asynchronous.
The most common async mistake
If you call an async function without await, execution continues immediately. Your build() method runs — before the data arrives. The UI shows empty or default values, and you get no error message. Always ask: is this operation async? Does my UI depend on its result?
→ Asynchronous programming: futures, async, await
16.3 Everything Is a Widget
In C++ GUI programming you might think in terms of class hierarchies, inheritance, and objects with mutable fields. Flutter thinks in composition.
Your entire UI is a widget tree: widgets nested inside widgets, each describing one piece of the interface. Layout, padding, color, text, buttons, entire screens — all widgets. There is no separate concept of "containers" versus "controls."
This tree is not static. Flutter rebuilds parts of it whenever state changes.
→ Introduction to Widgets → Widget Catalog
16.3.1 Widget Types
| Type | When to use |
|---|---|
StatelessWidget |
UI is determined entirely by constructor arguments — never changes during lifetime |
StatefulWidget |
UI depends on data that can change during the widget's lifetime |
Rule of thumb: Start with StatelessWidget. Convert to StatefulWidget only when you need changing state. Making everything stateful by default leads to architectural problems.
Flutter provides two design systems: Material Design (Google/Android style) and Cupertino (Apple/iOS style). Both run cross-platform. Most apps pick one consistently. I suggest to use Material Design.
For full implementation examples including the counter app, setState, and navigation → Flutter Intro Material
16.4 UI = f(state)
In a C++ GUI you might hold a reference to a label and call label.setText("new value") when something changes. Flutter works differently.
You never directly modify a UI element. Instead, you change the underlying state and Flutter re-runs the build() method to produce a new widget tree. The framework diffs the old and new trees and updates only what changed.
This is the declarative UI paradigm. You describe what the UI should look like given the current state. Flutter handles the transition.
Practical consequence: if something on screen looks wrong, the problem is always in your state, not in your widget code. Debug state first.
→ State management introduction
16.5 Event Loop & Async
Flutter apps are event-driven. The event loop runs continuously: it waits for user interaction, processes callbacks, and handles async results (network, storage, timers). If you block the event loop, the UI freezes.
This is similar to how a GUI application in C++ uses a message loop — but in Dart, the single-thread model is enforced by the language. There are no background threads for UI work.
→ Asynchronous programming in Dart
16.5.1 The build() method rule
The build() method is Flutter's most-called function. It can run 60–120 times per second.
WidgetsFlutterBinding
When you need to perform async operations before the app's main widget tree is built (e.g., initializing Firebase), call WidgetsFlutterBinding.ensureInitialized() at the very start of main().
→ FutureBuilder — the standard pattern for async data in widgets
16.6 Layout
As described in Everything Is a Widget, even layout is just another widget and organize your widgets in rows and cols.
16.6.1 Constraint Model
Flutter layout is not CSS. It follows a specific protocol:
Parent passes constraints down. Child chooses its size within those constraints. Parent positions the child.
Every layout problem traces back to this model. When a widget overflows or behaves unexpectedly, the constraint chain broke somewhere.

The error often occurs when a Column or Row has a child widget that isn’t constrained in its size. It might be sufficient to wrap the control with Expanded.
Source of 80% of beginner layout frustration
Widgets have no size until their parent gives them constraints. Nested unbounded containers cause "RenderFlex children have non-zero flex but incoming height constraints are unbounded" errors. Read the constraint documentation fully — it resolves most layout confusion.
Minimal widget set for most layouts: Column, Row, Expanded, Padding, Container, SizedBox. Stack for overlapping elements. The new spacing attribute on Row and Column reduces boilerplate significantly.
→ Understanding constraints — read this fully
→ Layout documentation
→ Widget Catalog
16.6.2 Accessibility
Practical Accessibility in Flutter (and Code You’ll Actually Use) gives an good overview, what to do. Remember to check the accessibility, see Automated Accessibility Testing.
16.6.3 Adaptive
In Flutter there is no easy way to create an adaptive design, i.e. a design that is good for large screens and small screens, for touch and mouse. Basically, you need to program for each device individually and use if-statements to apply one or the other. The following attributes are relevant for your UI
- desktop vs. web vs. mobile
- use adaptive widgets for iOS and material, e.g. Radio.adaptive, for more see Adaptive UI Widgets
- width/height
- screen density or device pixel ratio
- text size, the user might increase the font size for better reading
- left to right or right to left text due to different localization
16.7 Local State vs. App State
Not all state is the same. Before building any screen, annotate your design with:
- What data does this screen need?
- What can change?
- Where does that data live — inside this widget, or shared across the app?
This prevents architectural chaos later and reduces rework when you implement navigation.
Local (ephemeral) state lives inside a single widget: a toggle, an input field, whether a dropdown is open. setState() is the right tool.
App state is shared across screens: the logged-in user, a shopping cart, global settings. Passing this through multiple widget layers with setState() leads to "prop drilling" — passing data through widget after widget just to reach the one that needs it.
The Flutter SDK includes InheritedWidget as a low-level mechanism to share data down the widget tree without prop drilling. In practice, you will use higher-level solutions built on top of it.
16.7.1 Suggested approach: ChangeNotifier + Provider
The official Flutter architecture guide recommends using ChangeNotifier (included in the Flutter SDK) with the Provider package for dependency injection. This is simpler than alternatives like Riverpod and directly supported by the Flutter team.
ChangeNotifier— a class that notifies listeners when its state changes (observer pattern)Provider— makes yourChangeNotifieravailable to widgets in the tree without passing it manually- Widgets listen to changes and rebuild automatically when
notifyListeners()is called
→ Simple app state management
→ ChangeNotifier
→ Different state management options
Don't over-architect early
Start with setState(). Move to ChangeNotifier + Provider when you find yourself passing data through more than two or three widget layers, or when multiple screens need the same data. Introduce this transition at the second or third screen — not before the first one works.
Based on Simple app state management, use this three-step approach:

16.7.1.1 Step 1: Create Your Model with ChangeNotifier (Observable/Publisher)
ChangeNotifier enables objects to notify listeners when the models state changes. Call notifyListeners() after state modifications.
class CartModel extends ChangeNotifier {
/// Internal, private state of the cart.
final List<Item> _items = [];
/// An unmodifiable view of the items in the cart.
UnmodifiableListView<Item> get items => UnmodifiableListView(_items);
/// The current total price of all items (assuming all items cost $42).
int get totalPrice => _items.length * 42;
/// Adds [item] to cart. This and [removeAll] are the only ways to modify the
/// cart from the outside.
void add(Item item) {
_items.add(item);
// This call tells the widgets that are listening to this model to rebuild.
notifyListeners(); // Triggers widget rebuilds of listeners
}
/// Removes all items from the cart.
void removeAll() {
_items.clear();
// This call tells the widgets that are listening to this model to rebuild.
notifyListeners();
}
}
Counter Example
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners();
}
}
16.7.1.2 Step 2: Register Models in main()
Use MultiProvider to make models available throughout your app:
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => CartModel()),
ChangeNotifierProvider(create: (context) => CounterModel()),
],
child: const MyApp(),
),
);
}
16.7.1.3 Step 3: Listen to Changes in Widgets
Option A: Consumer Widget Wraps only widgets that need to rebuild when the model changes:
return Consumer<CartModel>(
builder: (context, cart, child) {
return Text('Total price: ${cart.totalPrice}');
},
);
Option B: context.watch() Listens to model changes directly:
// code source: https://github.com/flutter/samples/blob/main/provider_shopper/lib/screens/cart.dart
@override
Widget build(BuildContext context) {
var cart = context.watch<CartModel>();
return ListView.builder(
itemCount: cart.items.length,
itemBuilder:
(context, index) => ListTile(
leading: const Icon(Icons.done),
trailing: IconButton(
icon: const Icon(Icons.remove_circle_outline),
onPressed: () {
cart.remove(cart.items[index]);
To call model methods without listening to changes, use context.read<CounterModel>().increment().
Handling Loading States For asynchronous operations, add loading indicators:
Widget build(BuildContext context) {
final cart = context.watch<CartModel>();
return cart.isLoading
? Center(child: CircularProgressIndicator())
: ListView.builder(
itemCount: cart.items.length,
itemBuilder: (context, index) {
final item = cart.items[index];
...
No StatefulWidget Needed
With Provider, state updates happen in the model via notifyListeners(). No need for setState() or StatefulWidget.
Provider Organization
Create separate providers for unrelated data. Group related data in the same provider for efficiency.
Alternative: You can also use ListenableBuilder for listening to model changes.
16.8 Professional Project Setup
Plan before you code
Read 8 Steps to Follow When Building Your Next Flutter App before writing any code. Identify screens, data flows, and shared state upfront.
16.8.1 IDE & Installation
We recommend Visual Studio Code — it is widely used in the Flutter community and has excellent Flutter/Dart tooling. Android Studio is a solid alternative, particularly if you prefer an integrated Android emulator setup. Cursor is increasingly used by Path B students for AI-assisted development.
→ Install VS Code → Install Flutter → VS Code shortcuts, extensions, and settings for Flutter development — enable "fix on save" and the Flutter extensions from the start → Useful extensions — see the Packages & VS Code Extensions section at the end of this chapter
16.9 Project Setup
Use your IDE to create a new project.
16.9.1 Folder structure
The default project puts everything in main.dart. That does not scale. A minimal professional structure separates concerns:
lib/
├── data/ ← data sources, repositories, API calls
├── domain/ ← business logic, models
├── ui/ ← screens, widgets
├── routing/ ← navigation configuration
└── utils/ ← helpers, extensions
→ Official Compass App — Flutter team's reference for production-style structure. Study it, do not copy it blindly.
→ App architecture guide
16.9.2 Define your theme early
Hardcoded colors and font sizes scattered across widgets become a refactoring nightmare. Use ThemeData from the beginning, generated via the Material Theme Builder. All widgets then reference Theme.of(context) rather than specific values.
→ Use themes to share colors and font styles
→ flex_color_scheme — a more powerful theming alternative
16.9.3 Navigation
Two patterns cover most apps:
- Root navigation — a
NavigationBarthat switches between major sections - Stack navigation —
Navigator.push()/Navigator.pop()for drilling into detail screens
For apps with complex routing, nested navigators, or preserved route states: use go_router, the officially recommended routing package.
→ Navigation and routing
→ NavigationBar
16.9.4 Recommended additions
- Add an About screen that shows the licenses your app uses → LicensePage
- Add a test screen to your primary navigation during development — add single widgets or groups here and use hot reload to experiment
- Add a launcher icon early → flutter_launcher_icons
- Add internationalization if you need multiple languages → Internationalization guide
16.10 Debugging
Most beginners debug with print(). That works for simple values, but Flutter gives you significantly better tools — use them from day one.
16.10.1 Logging instead of print
Replace print() with package:logging. It gives you log levels (info, warning, severe), structured output, and the ability to filter or disable logs per environment. Scattered print statements in production code are a code smell.
16.10.2 Breakpoints and the debugger
Set breakpoints directly in VS Code by clicking in the gutter next to a line number. When execution pauses, you can inspect variable values, step through code line by line, and evaluate expressions — without touching your source code.
→ Run with breakpoints in VS Code
→ Debugging in VS Code
16.10.3 Flutter DevTools
DevTools is Flutter's dedicated debugging and profiling suite. It runs in your browser alongside your app and gives you real-time insight into what is happening.
The most useful panels for beginners:
- Flutter Inspector — shows the live widget tree, lets you select widgets on screen and inspect their properties. Essential for understanding layout and finding unexpected widget nesting.
-
Widget Preview — render individual widgets in isolation without running the full app.
Intro to Dart and Flutter DevTools
- Run DevTools from VS Code
- Flutter Widget Previewer
- Flutter Inspector
16.10.4 Property Editor
The Property Editor lets you tweak widget properties (padding, colors, alignment) interactively while the app is running — without editing code and hot reloading. Useful for rapidly iterating on layout details.
→ Property Editor documentation
16.10.5 Common debugging scenarios
| Problem | Where to look |
|---|---|
| Widget overflows or wrong size | Flutter Inspector → constraint chain |
| UI shows empty data | Check async — is await missing? |
| setState doesn't update UI | Is state actually changing? Add a breakpoint in setState |
| Widget not found in tree | Widget Preview to isolate the component |
| App slow or janky | DevTools → Performance panel |
→ Debugging Flutter apps
→ Common Flutter errors
16.11 Data Persistence
Apps need to store data. Three tiers:
| Need | Solution |
|---|---|
| Small key-value settings | shared_preferences |
| Structured local data | SQLite, isar, or sembast |
| Remote data | REST API via http or dio |
All of these are async. Every read and write returns a Future. There are no synchronous alternatives.
For real-time applications (chat, live updates), consider Firebase or alternatives like Supabase or AppWrite.
→ Persistence cookbook
→ Networking
16.12 Architecture: Repository Pattern
Path B only
This section is primarily relevant for students on Path B (AI-assisted development), who are required to implement a proper layered architecture.
In a layered architecture, neither the UI nor the domain layer should manage the logic of where or how data is fetched (local database vs. remote API). This responsibility belongs to the data layer, implemented via the Repository Pattern.
A repository class exposes data to the rest of the app, abstracts the data source, and resolves conflicts between multiple sources (e.g., local cache fallback when offline).
→ Data layer — Android Architecture Guide (the concepts apply directly to Flutter)
→ Flutter architecture case study: Repository
→ Offline-first pattern
For data classes with JSON serialization, freezed significantly reduces boilerplate. Run dart run build_runner watch while developing to keep generated files updated.
16.13 Additional Material: Packages & VS Code Extensions
The following list was compiled by Verena Zaiser and complemented by students at the h_da.
16.13.1 Recommended pub.dev packages
When evaluating any package, check: last updated (still maintained?), number of contributors (will it survive?), popularity (many examples and Stack Overflow answers?).
UI
- fl_chart — charts
- Shadcn UI — UI component library
- cached_network_image — image loading with caching
Animations
Development tools
- freezed — data classes with JSON serialization (Path B recommended)
- openapi_generator — generate API client from OpenAPI spec
- faker — generating fake data for development
- sentry_flutter — crash reporting
- flutter_launcher_icons — app icon generation
Testing
Other useful tools
- mason + brickhub — project and feature templates
- Flutter Version Manager (fvm) — manage multiple Flutter versions
16.13.2 VS Code Extensions
- Awesome Flutter Snippets
- Build Runner
- Dart Data Class Generator
- Dart Barrel File Generator
- Flutter Color
- Pubspec Assist
- Version Lens
- Coverage Gutters
- Dart Code Metrics
16.13.3 Android Studio Extensions
16.13.4 Further Resources
16.14 Security (IT-Security Specialization — Add-On)
Target audience
This section is an add-on for students with the IT-Security specialization. Security aspects are not required for Fachgespräch 1 or 2 for other students, but the HTTPS and API key rules apply to everyone.
Flutter, while providing a robust framework, requires developers to consciously implement security best practices.
Security is not an afterthought
Integrate security considerations from the very beginning of your app's design and development lifecycle, rather than trying to patch them in later.
16.14.1 Data Encryption
data encryption
Encrypt sensitive the data using crypto or pointycastle.
For secure local storage of sensitive data (even if encrypted), consider using platform-specific secure storage solutions like flutter_secure_storage or encrypted databases like sqflite_sqlcipher.
Consider Package Maintenance
While sqflite_sqlcipher and flutter_secure_storage are popular, note their primary single-developer maintenance. For core encryption, crypto or pointycastle are robust choices.
16.14.2 Authentication & Passwords
Never store raw user passwords. Instead, always store cryptographically hashed and salted passwords using robust algorithms. Hashing is a one-way process, ensuring the original password cannot be retrieved. Salting adds randomness, protecting against rainbow table attacks.
Password hashing is usually performed on the backend server. Your Flutter app should send the plain password securely via HTTPS to the backend, which then performs the hashing and stores the hash. If you need to store a password locally for remote server access, encrypt it - e.g. use crypto - don't hash it, so it can be securely transmitted. However, never store plain passwords anywhere.
secure authentication
Read and follow the article Authentication General Guidelines.
authentication with biometrics
Make authentication simple. Use local_auth to implement authentication with biometrics such as fingerprint or facial recognition.
16.14.2.1 OAuth
OAuth 2.0 is the industry-standard protocol for authorization. For authentication, it's often extended by OpenID Connect (OIDC), which provides an identity layer on top of OAuth 2.0, issuing an ID Token for user verification. What is OAuth 2.0 gives a good general introduction.
In your Flutter app, use the lib oauth2 or oauth2_client to facilitate user login via OAuth2/OIDC and utilize obtained access tokens for API calls. For mobile apps, PKCE (Proof Key for Code Exchange) is essential for security.
Note, your Flutter app must be registered with the OAuth2/OIDC provider (e.g., Google), and after successful authorization, a deep link is needed for your app to receive the authorization code, which is then securely exchanged with the Token Endpoint for the actual tokens (including the ID Token) to proceed (e.g., send to your REST API). See the sequence diagram below.
sequenceDiagram
participant U as User 👤
participant A as Flutter App 📱
participant G_Auth as Google Auth Server 🔑
participant G_Token as Token Endpoint of Google Auth Server 🏷️
participant G_JWKS as Google JWKS Endpoint 📜
participant R_API as Your REST API 🔌
participant DB as Your Cloud DB 🗄️
U->>A: 1. Initiates "Login with Google"
A->>G_Auth: 2. Redirect to Google Login (client_id, redirect_uri, data for PKCE)
G_Auth-->>U: 3. Google Login Screen
U->>G_Auth: 4. Authenticates & Grants Consent
G_Auth-->>A: 5. Authorization Code (via deep link)
A->>G_Token: 6. Request Tokens (code, code_verifier for PKCE)
G_Token-->>A: 7. Token Response (id_token ♦️, access_token, refresh_token)
A->>R_API: 8. Initial Login Request (♦️)
R_API->>G_JWKS: 9. Fetch Google Public Keys (cached after first call)
G_JWKS-->>R_API: 10. Public Keys
R_API->>R_API: 11. Validate ♦️ using public keys
R_API->>DB: 12. Verify/Create User in Your DB (based on data of ♦️)
DB-->>R_API: 13. User Info/Status
R_API-->>A: 14. Return Your API's Session Token 🔹
A->>R_API: 15. Subsequent API Calls (using 🔹)
R_API->>DB: 16. Access Data in Cloud DB
DB-->>R_API: 17. Return DB Data
R_API-->>A: 18. Return JSON Data
A-->>U: 19. Display to User
The following videos give a good explanation
Exploring OAuth 2.0: Must-Know Flows Explained
OAuth PKCE | OAuth Proof Key for Code Exchange explained
16.14.3 Roles
role based access
Define roles and access rules at table or row levels to manage data permissions. Distinguish between read, edit, and delete access to ensure proper data security and authorization. Do the same for your navigation.
backend security check
Never trust the client app alone for access control—always enforce role checks on the backend.
16.14.4 API Keys
API keys grant access to backend services and external APIs. Exposing them in your client-side Flutter code can lead to unauthorized access and abuse.
no hard codes keys
Do not embed API keys directly in your source code. When your app is compiled, these keys become easily extractable through reverse engineering.
There is an excellent article discussing different options how to handle API keys in your flutter app How to Store API Keys in Flutter: --dart-define vs .env files.
16.14.5 HTTPS
Always use HTTPS/TLS: Ensure all communication between your Flutter app and backend servers, or any external APIs, is encrypted using HTTPS (TLS). This prevents eavesdropping and man-in-the-middle attacks.
Flutter's http package and Dio automatically handle HTTPS when you use https:// URLs.
16.14.5.1 Cross-Origin Resource Sharing (CORS)
CORS is a browser-level security mechanism that restricts web pages from making requests to a different domain than the one from which they originated.
When your Flutter application is deployed as a web app and attempts to fetch data from a backend server on a different domain, port, or protocol, the browser will typically block these requests unless the server explicitly allows them via CORS headers. This results in common errors like "No 'Access-Control-Allow-Origin' header is present on the requested resource.
The most secure and recommended approach is to correctly configure your backend server.
Specific Origins Over Wildcards
Avoid Access-Control-Allow-Origin: * in production environments due to security risks. Instead, explicitly list the specific origins of your frontend applications.
Within this module you might run into this problem and solve it using flutter_cors.
Never disable CORS checks in production
Disabling CORS checks is acceptable for this module, but you should never do it in a production environment.
Read the Mozilla Developer Docs to learn more on Cross-Origin Resource Sharing (CORS).
16.14.6 Code Quality
Beyond specific security implementations, adopting sound development practices significantly contributes to a safer application. This includes leveraging language features like null safety to prevent common programming errors that could lead to vulnerabilities. A well-defined architectural pattern (e.g., BLoC, Provider, Riverpod) promotes code organization, testability, and separation of concerns, making it easier to identify and fix security flaws. Comprehensive unit and integration tests are crucial for verifying that security mechanisms function as intended and that data is handled correctly under various scenarios, reducing the risk of regressions and undiscovered vulnerabilities.
Furthermore, regularly updating dependencies to their latest stable versions is critical -- see Flutter Docs Security. These updates often include security patches for newly discovered vulnerabilities. Minimize the number of libraries used and carefully evaluate their quality, maintenance status, and the number and identity of their contributors or publishers.
While staying current, always specify a reasonable minimum SDK version (minSdkVersion for Android, iOS Deployment Target for iOS) to ensure your app runs on devices that still receive security updates from their respective OS vendors, thereby minimizing exposure to platform-level vulnerabilities.
16.14.7 Evaluation
Create a comprehensive test guide for your app that addresses both user experience (UX) and security concerns. Conduct regular tests to ensure ongoing ux and safety.
Mobile App Test Guide
Check the instructions of the Mobile App Test Guide.
In addition add security issues to your UX evaluation
- Measure user success objectives on UX security-related tasks.
- Test how users perform essential security actions, like managing permissions or changing passwords without confusion or heavy cognitive load.
16.14.8 Data Protection (Datenschutz)
Data protection, or "Datenschutz" in German, refers to the legal and ethical handling of personal data. Compliance with regulations like GDPR (General Data Protection Regulation) is crucial, especially if your app targets users in the EU. Read and understand the key principles of GDPR.
Practical Implications for Apps:
- Privacy Policy: Clearly state what data your app collects, why, how it's used, and with whom it's shared. Make it easily accessible within the app.
- User Consent: Obtain explicit consent before collecting or processing personal data, especially for non-essential data. Provide clear opt-in/opt-out mechanisms.
- Data Subject Rights: Allow users to view, correct, delete and export their data.
- Data Minimization: Only request permissions and collect data that is strictly necessary for your app's core functionality.
16.14.9 Security vs. Usability
- Clear Communication: Explain security features and data practices in plain language. Avoid jargon.
- Intuitive Controls: Make privacy settings and security options easy to find and understand. Test it with real users.
- Feedback: Provide clear feedback when security actions are taken (e.g., "Password changed successfully," "Data deleted").
- Error Handling: Implement user-friendly error messages for security failures, guiding users on how to resolve issues without revealing sensitive details. Don't show exceptions to your users, i.e. do not provide any information about your systems, databases etc.
16.14.10 Further Reading
- The Connection Between System Security and User Experience Design - Enhancing Both for Better Performance
- Here’s How Recent Cybersecurity Lapses Are Impacting Consumer Trust and Behavior
16.15 Self-Assessment
Fachgespräch 1 — Check Your Understanding
These questions test conceptual understanding, not implementation details. You should be able to answer them in your own words without looking anything up.
Flutter architecture & rendering
- Why does Flutter not use native platform widgets? What advantage does this give, and what trade-off does it introduce?
- Explain the Flutter layer model. What role does the engine play, and what does the framework add on top?
Widget tree & composition
- What does "everything is a widget" mean in practice? How does this differ from typical C++ GUI concepts?
- What is the difference between a
StatelessWidgetand aStatefulWidget? When would you use each?
State & the declarative paradigm
- Explain
UI = f(state)in your own words. How does this differ from directly modifying UI elements? - What is the difference between ephemeral state and app state? Give one concrete example of each.
- What triggers a widget rebuild in Flutter?
Async & the event loop
- Dart runs on a single-thread event loop. What does this mean for your code, and why does blocking this thread freeze the UI?
- What happens if you call an async function inside
build()withoutawait? Why is this a problem?
Dart vs. C++
- How does Dart's null safety differ from C++ pointer handling? What class of bugs does it prevent?
- What is the difference between
finalandconstin Dart? Why doesconstin front of a widget matter for performance? - How does privacy work in Dart? Why is there no
privatekeyword?
Project structure
- What is the responsibility of the
data,domain, anduifolders in a clean Flutter project structure? - Why should you define your theme early, and why should widgets reference
Theme.of(context)instead of hardcoded values?