Flutter / Dart, widgets, and local ownership / Lesson 1
Dart values and an explicit Flutter host
Separate language guarantees, project configuration, and target-platform responsibilities before writing a screen.
On this page
Lesson goals
- Record the execution boundary
- Express absence in the type
- Make normalization a pure function
Record the execution boundary
Flutter uses Dart to describe an application while its engine, framework, embedding, and platform toolchains perform different jobs. A Dart function can be understood without an Android emulator; a Material widget requires Flutter libraries and a widget host. A signed iOS application additionally requires the supported Apple tooling and signing configuration. Record the SDK selected by the project, target platform, dependency lockfile, and build mode when investigating behavior. A command copied from an unrelated project cannot establish that these boundaries are compatible. The course presents fragments as unexecuted illustrations because no Flutter or Dart SDK is available in the authoring environment.
Express absence in the type
A non-nullable String must contain a string; String? can also contain null. This distinction gives the analyzer information before a widget ever renders. Prefer checking a local nullable value and allowing flow analysis to promote it over adding a not-null assertion to silence the analyzer. An empty string and an absent string may have different meanings: a user who entered only spaces has supplied input that still needs validation. Decide which layer normalizes whitespace and which layer rejects a missing value. Using dynamic to avoid a type error merely moves uncertainty into runtime behavior.
Make normalization a pure function
The first fragment converts optional display input into a deterministic label. Its output depends only on its argument, and it does not read a controller, locale, or network connection. This makes boundary cases easy to discuss independently of UI testing. The fallback is a product decision rather than a universal null-handling rule. An account identifier, for example, should usually be rejected when missing instead of silently becoming Guest. Pure functions are useful seams, but they should preserve meaningful domain distinctions and avoid concealing validation failures behind attractive default text.
String displayName(String? raw) {
final value = raw?.trim();
return value == null || value.isEmpty ? 'Guest' : value;
}
void main() {
print(displayName(null));
print(displayName(' Mira '));
}
Guest
Mira
The null-aware call keeps absence explicit; the branch handles both missing and blank presentation input. These are predicted results, not recorded SDK execution.
Distinguish final from immutable contents
A final variable cannot be assigned a second value, but a mutable object referenced by that variable can still change. The second fragment deliberately creates a read-only list view using List.unmodifiable before handing values across an ownership boundary. Copying or freezing a collection does not recursively freeze every object inside it. For a list of mutable models, ownership must also cover the models themselves. Prefer small immutable value types for information passed into widgets. Name quantities with units, such as priceCents, because the type int alone does not distinguish money from an item count.
void main() {
final draft = <String>['Map', 'Notes'];
final published = List<String>.unmodifiable(draft);
draft.add('Camera');
print(published.join(', '));
print(draft.length);
}
Map, Notes
3
The published collection preserves the copied membership even though the original list gains a third item. Mutating published would throw at runtime.
Read analyzer feedback before runtime symptoms
Static analysis identifies unsupported member access, incompatible assignments, and many lifecycle mistakes before a device run. A warning is evidence about a particular expression, not an instruction to add casts indiscriminately. Trace the value from its source and ask whether the declared contract is correct. A parsed JSON field is not trustworthy merely because the receiving model has a typed field. Validate its runtime shape at the decoding boundary. Keep intentionally invalid examples outside normal application sources so that an educational failure cannot accidentally become a release failure.
Use evidence that matches the claim
Checking JSON structure for this course establishes content consistency, not Dart compilation or mobile compatibility. In your own isolated project, distinguish a language unit test from a widget test and from a target-platform integration test. Record what was exercised and what remained assumed. A passing pure calculation does not prove font rendering, permission prompts, or native plugin behavior. When a dependency or SDK changes, review its documented constraints before attributing a regression to application logic. Small reproducible inputs and explicit expected values make a report actionable without requiring private data or credentials.
Common errors and fixes
A nullable value is used as String
- Why it happens
- The contract permits null but the call assumes presence.
- How to fix it
- Check and handle absence before using the value.
- Prevent it next time
- Test absent, blank, and ordinary inputs separately.
A final list changes unexpectedly
- Why it happens
- final protects assignment, not mutable contents.
- How to fix it
- Define collection ownership and expose a defensive immutable copy where needed.
- Prevent it next time
- Review nested model mutability too.
An illustrative fragment is treated as a tested application
- Why it happens
- The host, SDK, and platform checks are missing.
- How to fix it
- State the missing execution requirements and test in an isolated compatible host.
- Prevent it next time
- Attach execution context to reproducible reports.
Check your understanding
1. Should displayName be reused to normalize an order identifier?
Show worked solution
No. Guest is a presentation fallback and would invent a plausible identifier. A separate parser should reject absence, validate the identifier format, and return an explicit success or failure. Reuse operations only when their domain contracts agree.
2. Why can an unmodifiable list still expose mutable state?
Show worked solution
Its membership is protected, but an element might reference a mutable object shared elsewhere. Use immutable elements or copy the relevant nested values. Verify both attempts to replace list entries and changes to an element owned by another component.
Key takeaways
- Record the execution boundary
- Use evidence that matches the claim