description |
---|
[Ed.] I have placed some @@@ in the text where I need Nathan's input. You can easily search for "@@@" to find them. |
The conventions in this section apply to all languages and scripts used in the AppFlowy project. You will find specifications for each language below, if applicable.
Instead, formatting should be done using ./x.py fmt. It's a good habit to run ./x.py fmt before every commit, as this reduces conflicts later.
In the past, files began with a copyright and license notice. Please omit this notice for new files licensed under the standard terms (dual MIT/Apache-2.0).
Lines should be at most 120 characters. It's even better if you can keep things to 80.
Ignoring the line length limit. Sometimes – in particular for tests – it can be necessary to exempt yourself from this limit. In that case, you can add a comment towards the top of the file like so:
Beyond formatting, there are a few other tips that are worth following.
Using _ in a match is convenient, but it means that when new variants are added to the enum, they may not get handled correctly. Ask yourself: if a new variant were added to this enum, what's the chance that it would want to use the _ code, versus having some other treatment? Unless the answer is "low", then prefer an exhaustive match. (The same advice applies to if let and while let, which are effectively tests for a single variant.)
TODOs are a very useful tool to remind yourself of bits while working on a feature. We recommend that you insert a TODO comment for something that you want to get back to before you submit your PR.
TODOs are to be used locally while working on a feature and must be dealt with and removed before checking in the code.
fn do_something() {
if something_else {
unimplemented!(); // TODO write this
}
}
These are very practical and you can see a list of them in the VS Code 'Problems' tab.
- Classes, enums, typedefs, and extensions name should be in
UpperCamelCase.
class HomeLayout
enum IntegrationEnv {
dev,
pro,
}
typedef NaviAction = void Function();
- Libraries, packages, directories, and source files name should be in
snake_case(lowercase_with_underscores).
library firebase_dynamic_links;
import ‘socket/socket_manager.dart’;
- Variables, constants, parameters, and named parameters should be in
lowerCamelCase.
var item;
const testValue = 14.28;
final urlScheme = RegExp(‘^([a-z]+):’);
void sum(int testValue) {…}
@@@ Nathan FooWe have to choose one of these two ideologies. Me, personally, I'm a type who writes the types. It makes the code easier to understand. I know that the newer mentality is to not have them because the compiler can figure it out. But where I disagree is that just because the compiler can figure it out, I don't see why we should make the next programmer have to figure it out. We'll do whatever you want :)
Always specify the type of a member when it’s value type is known. Avoid using var
when possible.
int item = 10;
final User user = User();
String name = ‘pooja’;
const int timeOut = 20;
sum(int testValue) { // …}
DO follow a consistent rule for var and final on local variables. Most local variables shouldn't have type annotations and should be declared using just var or final. There are two rules in wide use for when to use one or the other:
Use final for local variables that are not reassigned and var for those that are.
Use var for all local variables, even ones that aren't reassigned. Never use final for locals. (Using final for fields and top-level variables is still encouraged, of course.)
Either rule is acceptable, but pick one and apply it consistently throughout your code. That way when a reader sees var, they know whether it means that the variable is assigned later in the function.
__Cascade notation allows you to perform a sequence of operations on the same object. It saves your number of steps and needs for a temporary variable.
Demo d1 = new Demo();
Demo d2 = new Demo();
// Don't - Without Cascade Notation
d1.setA(20);
d1.setB(30);
d1.showVal();
// Do - With Cascade Notation
d2..setA(10)
..setB(15)
..showVal();
Use expression function bodies @@@ Nathan FooI think this applies to both Rust and Dart
For functions that contain just one expression, you can use an expression function. The =>
(arrow) notation is used for expression functions.
num get x => center.x;
set x(num value) => center = Point(value, center.y);
Place reusable functions in a class, so it's easy to access them. Also, For Dialog components, if you are using a package to wrap their logic with your own custom widget, so it would be easy to change the package if necessary later.
Especially when you are ready to push your changes, you’ll need to remove resources that aren't used in the application. These could be image assets, for example. Removing unused resources and compressing images will help us reduce the size of the application.
Create a separate file to store strings, colors, constants. so it’s easy to access them.
It is allowed to use crates from crates.io, though external dependencies should not be added gratuitously. All such crates must have a suitably permissive license.
- PREFER using interpolation to compose strings and values.
Generally, we are used to using long chains of+
to build a string out of literals and other values. That does work in Dart, but it’s almost always cleaner and shorter to use interpolation:
// Do
'Hello, $name! You are ${year - birth} years old.';
- AVOID using curly braces in interpolation when not needed.
If you’re interpolating a simple identifier not immediately followed by more alphanumeric text, the{}
should be omitted.
// Don't
'Hello, ' + name + '! You are ' + (year - birth).toString() + ' years old.';
Dart 2 makes the
new
keyword optional. Even in Dart 1, its meaning was never clear because factory constructors mean anew
invocation may still not actually return a new object.The language still permits
new
in order to make migration less painful, but consider it deprecated and remove it from your code.
//Do
Widget build(BuildContext context) {
return Row(
children: [
RaisedButton(
child: Text('Increment'),
),
Text('Click!'),
],
);
}
//Don't
Widget build(BuildContext context) {
return new Row(
children: [
new RaisedButton(
child: new Text('Increment'),
),
new Text('Click!'),
],
);
}
Many times we need to render a widget based on some condition in Row and Column. If conditional expression return null in any case then we should use the if condition only.
//Don't
Widget getText(BuildContext context) {
return Row(
children: [
Text("Hello"),
Platform.isAndroid ? Text("Android") : null,
]
);
}
//Do
Widget getText(BuildContext context) {
return Row(
children:
[
Text("Hello"),
if (Platform.isAndroid) Text("Android")
]
);
}
Split your code into smaller widgets instead. Dividing your code into small reusable widgets not only promotes its reusability but also its readability.
In the example below, EventItem
is a separate widget that will load individual events. EventItem
is usually stored in a separate file.
itemBuilder: (ctx, i) => ChangeNotifierProvider.value(
value: events[i],
child: EventItem(),
),
Whenever you have widgets that don’t change when the state changes, you should declare them as constants. This will prevent them from being rebuilt, hence improving performance.
This is similar to caching widgets that won’t get rebuilt. Some of the widgets that can be declared as const
include Text
, Padding
, and Icons
, to mention a few. If the widgets contain dynamic information, they shouldn't be declared as constants.
Since your widget tree can grow quickly, you need a way to format your code in a way that makes it easy to read. Adding trailing commas and using your IDE’s formatting shortcuts leads to more readability.
Use the lazy methods, with callbacks, when building large grids or lists. That way only the visible portion of the screen is built at startup time.
Some ways to lazy load data
- FutureBuilder
- lazy_load_scrollview package
It’s important to check the code with the analyzer. Run flutter analyze
in your terminal to check your code. This tool is a wrapper around the dartanalyzer tool. It performs static analysis of your code.
flutter analyze
Note that the Continuous Integration tasks will run flutter analyze on your PRs and will refuse tasks with warnings or errors.
@@@ Is there a VS Code version o this? More importantly, what's our procedure? Do only you upgrage the versions?
Plugin for checking the latest Pub packages versions. It will automatically run inspection in your pubspec.yaml file to check all dependencies and compare versions with the latest versions from the Pub package repository. The highlighted ones need an update.
Use ErrorWidget that renders an exception’s message. This widget is used when a build method fails, to help determine where the problem lies. Exceptions are also logged to the console, which you can read using flutter logs
. The console will also include additional information such as the stack trace for the exception.
It is possible to override this widget. Refer to this article for more information.
import 'package:flutter/material.dart';
void main() {
ErrorWidget.builder = (FlutterErrorDetails details) {
bool inDebug = false;
assert(() { inDebug = true; return true; }());
// In debug mode, use the normal error widget which shows
// the error message:
if (inDebug)
return ErrorWidget(details.exception);
// In release builds, show a yellow-on-blue message instead:
return Container(
alignment: Alignment.center,
child: Text(
'Error!',
style: TextStyle(color: Colors.yellow),
textDirection: TextDirection.ltr,
),
);
};
// Here we would normally runApp() the root widget, but to demonstrate
// the error handling we artificially fail:
return runApp(Builder(
builder: (BuildContext context) {
throw 'oh no, an error';
},
));
}