Dart Proposals Conditional Parts and `concrete` to End Cross-Platform Implementation Boilerplate
Dart language #4827:First version of conditional part feature specification PR is great, especially useful for Flutter cross-platform plugins, FFI, and platform adaptation layers.
The proposal mainly brings two changes:
- Conditional Parts: Make Dart's
partwork like existing conditionalimport, allowing selection of different implementation files based on Android/iOS, Web, etc. compile environments - New
concretemodifier: Allow a class to predeclare a method with a concrete implementation, but the actual method body is completed by platform-specificaugmentcode
In other words, Dart wants different platforms to share the same static type and API checking results, only replacing the concrete implementation at compile time, which is very Kotlin-like, hahaha.
Currently the PR is still in Open state, the specification is marked as Draft, added 624 lines of proposal documentation, and has not yet officially modified Dart SDK code, but it's definitely worth looking forward to.
For example, Dart currently supports:
import 'channel_stub.dart'
if (dart.library.io) 'channel_io.dart'
if (dart.library.js_interop) 'channel_web.dart';
The problem is that channel_io.dart and channel_web.dart belong to different libraries. If you have a Channel class that maintains private state like _name, _handle, and want IO and Web to implement its close() method separately, the existing solution requires splitting the interface and implementation classes, then calling through factories or proxies. Dart does not guarantee that the APIs of the two conditional import files are completely consistent.
With the new proposal, you can write directly:
// channel.dart
part 'channel_stub.dart'
if (dart.library.io) 'channel_io.dart'
if (dart.library.js_interop) 'channel_web.dart';
abstract class Channel {
final String _name;
Channel(this._name);
concrete void close();
}
Then in the native platform file:
// channel_io.dart
part of 'channel.dart';
import 'dart:io';
augment class Channel {
augment void close() {
stdout.writeln('Closing $_name');
}
}
There are actually three changes here:
channel_io.dartstill belongs to thechannel.dartlibrary, so it can directly access_nameaugmentsupplements the method body of the originalChannelclass without creating extra implementation classes- Platform parts can have their own imports, e.g., only reference
dart:ioon IO platforms
Why introduce concrete specifically? I think this is the most interesting point of the proposal. For example, in Dart, an abstract class:
abstract class Channel {
void close();
}
means close() is an abstract method, so if a subclass calls super.close();, it will cause a compile error because there is no callable method body in the parent class.
But if the parent's method body is provided via Conditional Part, the Analyzer doesn't read the platform implementation when analyzing public declarations, so how does it know whether close() ends up being abstract?
So the proposal introduces:
abstract class Channel {
concrete void close();
}
This explicitly tells the Analyzer: this method has a concrete implementation, can be inherited by subclasses, and also allows
super.close().
At compile time for a specific platform, it checks whether the corresponding augment actually fills in the method body; if not, it reports a compile error. Again, Kotlin vibes.
Additionally, concrete supports non-const generative constructors, allowing platform parts to provide corresponding initialization logic.
Most importantly, platforms can change implementations but cannot casually alter the type structure. To ensure consistent static analysis results across platforms, the proposal imposes relatively strict restrictions on Conditional Parts:
| Behavior | Allowed in Conditional Part? |
|---|---|
| Filling in implementation for existing methods | Allowed |
| Accessing private members of the same library | Allowed |
| Independent imports of platform dependencies | Allowed |
| Adding top-level declarations, including private ones | Forbidden |
| Introducing new member declarations to a class | Forbidden |
Adding extends, implements, with |
Forbidden |
| Adding enum values | Forbidden |
| Modifying default values of optional parameters | Forbidden |
| Completing const constructors in Conditional Part | Forbidden |
Although new member declarations cannot be introduced, it still allows certain conforming
augmentvariable declarations to complete existing properties and provide underlying storage, not completely prohibiting platform-differentiated instance state.
In other words, the Analyzer can first analyze only the public declarations, obtaining stable type relationships, method signatures, enum sets, and constant results. Then, when the compiler selects a specific platform, it applies the augment from the corresponding part to the declarations, allowing implementation completeness and type checks. This ensures that different platforms cannot secretly change the static structure of the public API.
This is very useful for Flutter, for example:
- Flutter native and Web dual-platform plugins: Place native and JS implementations directly in the same Dart API, reducing abstract classes, factories, and proxy code
- FFI, JS Interop wrappers: Public classes maintain consistent methods and private state, while platform parts are responsible for calling the corresponding underlying APIs
- Code generation: Use Augmentations to generate implementation code for different environments without changing the external interface
So I'm looking forward to this direction, especially as Dart increasingly needs to simultaneously handle Flutter Native, Web, Wasm, and various platform extensions, maintaining a consistent Dart API is important.
If Augmentations also land officially later, then Dart's part is no longer just a simple file-splitting mechanism, but can become a more complete cross-file, cross-platform implementation composition mechanism.
Perfect!
Top 2 from juejin.cn, machine-translated. The original thread is authoritative.
To be honest, Flutter's progress is much slower than expected.
It's taken this many years to only now do this; the enthusiasm to follow up probably won't be strong, will it?