Flutter / Dart cheat sheet¶
This is a short reference for common Dart and Flutter basics. It focuses on the patterns you will use often in a small or medium Flutter project, with examples that match typical app code rather than toy syntax.
this. in Dart¶
In Dart, this. is usually only needed when it improves clarity. The two common cases are constructor field initialization and name conflicts between a field and a local variable or parameter.
Use this. in constructor parameters¶
This shorthand assigns the constructor arguments directly to the instance fields. It is the most common and most idiomatic use of this. in Dart.
Use this. when a parameter shadows a field¶
In rename, name refers to the method parameter, while this.name refers to the field stored on the object. Use this. here to make the distinction explicit.
Skip this. when it adds no value¶
When there is no naming conflict, this.name is usually unnecessary noise. Dart code normally omits it unless it helps disambiguate something.
Rule of thumb¶
- Use
this.in constructor parameter shorthand. - Use
this.when a local name conflicts with a field name. - Otherwise, prefer the shorter field access.
Leading underscore _name¶
In Dart, a leading underscore makes a declaration private to its library. In practice, that usually means private to the current file unless you are using part files.
These names can be used inside the same library, but not imported from another file. This is Dart’s normal way to mark implementation details as internal.
Good uses for _¶
Use a leading underscore for code that should stay local to one file, such as:
- helper methods used only in that file
- private widget state classes
- file-local variables
- internal widgets that are not reused elsewhere
When not to use _¶
Keep a declaration public if other files should depend on it. That usually includes shared model classes, reusable widgets, and services or loaders used across features.
Rule of thumb¶
If another file should use it, do not prefix it with _. If it is an internal detail of one file, prefix it with _.
Interfaces in Dart¶
Dart does not use a separate interface keyword the way Java or C# often does. Instead, every class defines an interface, and abstract classes are the normal way to express an explicit contract.
Every class exposes an interface¶
class Animal {
void speak() {}
}
class Dog implements Animal {
@override
void speak() {
print('woof');
}
}
This example shows that Dog can implement the interface defined by Animal. The important detail is that implements gives you the contract, not the code.
extends reuses behavior¶
Use extends when you want to inherit existing behavior. AssetLoader gets the log method automatically because it reuses the implementation from BaseLoader.
implements defines a contract¶
class BenchLoader {
Future<void> load() async {}
}
class AssetBenchLoader implements BenchLoader {
@override
Future<void> load() async {
// custom implementation
}
}
Use implements when you want a type contract without inheriting behavior. Once you implement a class, you must provide your own implementation for its members.
Prefer abstract classes for explicit interfaces¶
abstract class BenchLoader {
Future<BenchFile> load();
}
class AssetBenchLoader implements BenchLoader {
@override
Future<BenchFile> load() async {
// load asset
}
}
This is the most common explicit-interface style in Dart codebases. The abstract class clearly communicates that callers depend on a contract, while concrete implementations decide how the work happens.
Rule of thumb¶
- Use
extendswhen you want shared behavior. - Use
implementswhen you want a contract without inherited code. - Use an
abstract classwhen you want the contract to be explicit and readable.
Project structure in Flutter¶
There is no single perfect folder layout for every Flutter app. The right choice depends mostly on project size and how many features are evolving at once.
Small app¶
This is fine for a spike, a prototype, or a very small tool. The main goal at this stage is to keep navigation simple and avoid structure that pretends the app is larger than it is.
Growing app¶
lib/
main.dart
app/
app.dart
theme.dart
features/
benchmark/
benchmark_page.dart
benchmark_painter.dart
bench_model.dart
bench_loader.dart
Feature-first grouping is usually the easiest layout once the project starts growing. Related files stay together, which makes the codebase easier to navigate and reduces the need for giant global folders.
Layer-first structure can work, but gets messy faster¶
This layout can feel clean at first, but it often spreads one feature across too many folders. As the app grows, you spend more time jumping between directories to understand one screen or flow.
Rule of thumb¶
- For a small app, keep the structure simple.
- For a growing app, prefer feature-first grouping.
- Avoid introducing architecture that the current app size does not need yet.
Practical Flutter habits¶
Most Flutter code stays easier to maintain when responsibilities are obvious. The goal is not perfect architecture. The goal is to keep state, rendering, loading, and models easy to find and reason about.
Keep widgets small and obvious¶
If a widget is doing too many jobs, split it along real responsibilities. A common split is:
- the page owns state and layout
- the painter draws
- the model parses and stores typed data
This makes each class easier to test, review, and change without dragging unrelated logic with it.
Keep state close to where it is used¶
Do not introduce app-wide state management just because it exists. If state only matters to one page or one local flow, local widget state is often the simplest and best choice.
Prefer typed models over raw maps¶
Passing typed objects through the app is easier to read and safer to refactor than moving Map<String, dynamic> around everywhere. Types also make parsing, validation, and rendering responsibilities clearer.
Separate loading, state, and drawing¶
For a benchmark-style screen, a clean split looks like this:
- the loader reads JSON
- the page stores UI state
- the painter draws shapes
Avoid making the painter load files or parse data. Drawing code is easier to maintain when it only draws.
Make nullability explicit¶
If a value can be absent, model that directly with a nullable type. Only use ! when you are genuinely certain the value is non-null at that point in the code.
Prefer clear code over clever code¶
This is completely fine if it is easier to read than a more compact style. In Flutter code, straightforward setup is usually better than clever chaining when clarity matters more than brevity.
Keep async work out of build()¶
Do not start file loading or heavy work directly inside build(). Use lifecycle methods such as initState(), keep the async work in a separate method, and update the UI with setState() when the result is ready.
Keep build() declarative¶
A good build() method mostly decides what to show for the current state. For example, it might choose between loading UI, an error message, or the final canvas view.
Keep one main responsibility per class¶
Names should match responsibilities. A class such as BenchmarkPage should own page state and layout, BenchmarkPainter should focus on drawing, and BenchFile or BenchObject should represent parsed data.
Do not over-package early¶
A small learning app usually does not need many dependencies or layers. Start with the Flutter SDK and a few clear files, then add libraries only when they solve a real problem you already have.
Quick examples¶
These are short examples of shapes you will see often in real Flutter and Dart code.
Constructor shorthand¶
This shows the compact constructor form when all you need is to assign an argument to a field.
File-private helper¶
The leading underscore keeps this helper private to the current library, which is a good fit when no other file should call it.
Abstract interface¶
This defines the contract without choosing an implementation yet. It is useful when multiple loaders could exist later.
Public shared model¶
This stays public because shared model types are often used across files, not hidden as local implementation details.
Summary¶
Most Dart and Flutter basics become easier once you keep a few simple distinctions in mind. Use this. only when it helps, use _ for file-local details, prefer abstract classes for explicit contracts, keep project structure proportional to app size, and separate state, loading, models, and drawing so each class has a clear job.