AppFlowy Docs

Style Guides

[Ed.] I have placed some @@@ in the text where I need Nathan's input. You can easily search for "@@@" to find them.

General Coding Conventions

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 ./ fmt. It's a good habit to run ./ 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).

Line length

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:

Coding for correctness

Beyond formatting, there are a few other tips that are worth following.

Prefer exhaustive matches

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.)

Use "TODO" comments for things you don't want to forget

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.

Naming conventions

  • Classes, enums, typedefs, and extensions name should be in UpperCamelCase.
class HomeLayout
enum IntegrationEnv {
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 :)

Specify types for class members

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.

Use Cascade Notation @@@I think this applies to both Rust and Dart

__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
// Do - With Cascade Notation

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.

Remove Unused Resources

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.

Maintain hard-coded values, strings, colors in a separate file

Create a separate file to store strings, colors, constants. so it’s easy to access them.

Rust Coding Conventions

Using crates from

It is allowed to use crates from, though external dependencies should not be added gratuitously. All such crates must have a suitably permissive license.

Flutter/Dart Coding Conventions


  • 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.';

Don't use new

Dart 2 makes the new keyword optional. Even in Dart 1, its meaning was never clear because factory constructors mean a new 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.
Widget build(BuildContext context) {
return Row(
children: [
child: Text('Increment'),
Widget build(BuildContext context) {
return new Row(
children: [
new RaisedButton(
child: new Text('Increment'),
new Text('Click!'),

Use if condition instead of conditional expression

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.
Widget getText(BuildContext context) {
return Row(
children: [
Platform.isAndroid ? Text("Android") : null,
Widget getText(BuildContext context) {
return Row(
if (Platform.isAndroid) Text("Android")

Avoid large widget trees

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(),

Const Widgets

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.
Usage of const

Use Trailing Commas

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.
Usage of traiing commas

Render Grids and Lists Lazily

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

Flutter Analyze

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.

Flutter Pub Version Checker (Android Studio Plugin)

@@@ 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.

Custom Error Screen: handle “RED SCREEN OF DEATH”

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(
child: Text(
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';