Skip to main content
Consistent code style makes AppFlowy’s codebase easier to read, maintain, and contribute to. This guide covers style conventions for both Dart/Flutter and Rust code.

Dart/Flutter Code Style

Dart Style Guide

AppFlowy follows the official Dart Style Guide.

Analysis Options

Linting rules are configured in analysis_options.yaml:
include: package:flutter_lints/flutter.yaml

linter:
  rules:
    - require_trailing_commas
    - prefer_collection_literals
    - prefer_final_fields
    - prefer_final_in_for_each
    - prefer_final_locals
    - sized_box_for_whitespace
    - use_decorated_box
    - unnecessary_parenthesis
    - avoid_unnecessary_containers
    - always_declare_return_types
    - sort_constructors_first
    - unawaited_futures

Formatting

1

Use dartfmt

Format code with dartfmt (built into flutter format):
cd appflowy_flutter
flutter format .
2

Enable format on save

In VS Code (settings.json):
{
  "editor.formatOnSave": true,
  "[dart]": {
    "editor.formatOnSave": true
  }
}

Key Conventions

Naming

Use UpperCamelCase for class names:
class DocumentBloc { }
class UserProfile { }
class AppFlowyEditor { }

Trailing Commas

Always use trailing commas for better formatting:
Widget build(BuildContext context) {
  return Column(
    children: [
      Text('Hello'),
      Text('World'),
    ],  // trailing comma
  );
}

Prefer Final

Use final for variables that don’t change:
final documentId = '123';
final userProfile = getUserProfile();

Return Types

Always declare return types explicitly:
Future<Document> loadDocument(String id) async {
  // ...
}

Widget buildTitle() {
  return Text('Title');
}

BLoC Pattern

AppFlowy uses the BLoC pattern for state management:
class DocumentBloc extends Bloc<DocumentEvent, DocumentState> {
  DocumentBloc({
    required this.documentId,
  }) : super(DocumentState.initial()) {
    on<DocumentEvent>(_onDocumentEvent);
  }
  
  final String documentId;
  
  Future<void> _onDocumentEvent(
    DocumentEvent event,
    Emitter<DocumentState> emit,
  ) async {
    // Handle event
  }
}
Key principles:
  • Events are immutable and describe actions
  • States are immutable and describe UI state
  • BLoCs handle business logic, not UI

Widget Structure

Organize widgets consistently:
class MyWidget extends StatelessWidget {
  const MyWidget({
    super.key,
    required this.title,
    this.subtitle,
  });
  
  // 1. Fields
  final String title;
  final String? subtitle;
  
  // 2. Build method
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        _buildTitle(),
        if (subtitle != null) _buildSubtitle(),
      ],
    );
  }
  
  // 3. Private helper methods
  Widget _buildTitle() {
    return Text(title);
  }
  
  Widget _buildSubtitle() {
    return Text(subtitle!);
  }
}

File Organization

1

One class per file

Each file should contain one main public class.
2

File naming

Use snake_case for file names:
document_bloc.dart
user_profile_widget.dart
3

Import ordering

Order imports as follows:
// 1. Dart SDK imports
import 'dart:async';
import 'dart:convert';

// 2. Flutter imports
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

// 3. Third-party package imports
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

// 4. AppFlowy imports
import 'package:appflowy/workspace/domain/document.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';

Comments and Documentation

Use /// for public API documentation:
/// Loads a document by its ID.
///
/// Returns a [Future] that completes with the [Document]
/// or throws a [DocumentNotFoundError].
Future<Document> loadDocument(String id) async {
  // ...
}

Rust Code Style

Rust Style Guide

AppFlowy follows the official Rust Style Guide.

Rustfmt Configuration

Formatting is configured in rust-lib/rustfmt.toml:
max_width = 100
tab_spaces = 2
newline_style = "Auto"
match_block_trailing_comma = true
use_field_init_shorthand = true
use_try_shorthand = true
reorder_imports = true
reorder_modules = true
remove_nested_parens = true
merge_derives = true
edition = "2024"

Formatting

1

Use rustfmt

Format code with rustfmt:
cd rust-lib
cargo fmt
2

Check formatting

cargo fmt -- --check
3

Enable format on save

In VS Code (settings.json):
{
  "[rust]": {
    "editor.formatOnSave": true,
    "editor.defaultFormatter": "rust-lang.rust-analyzer"
  }
}

Clippy Linting

Use Clippy for additional linting:
cargo clippy -- -D warnings
CI/CD pipelines enforce Clippy warnings. Fix all warnings before submitting PRs.

Key Conventions

Naming

Use UpperCamelCase for types:
struct UserProfile { }
enum DocumentEvent { }
trait DocumentHandler { }

Error Handling

Use Result for fallible operations:
use anyhow::Result;

fn load_document(id: &str) -> Result<Document> {
  let doc = database.get(id)?;
  Ok(doc)
}

Option Handling

Prefer combinators over pattern matching:
let name = user.name.unwrap_or_default();
let length = text.as_ref().map(|t| t.len());

Struct Organization

pub struct Document {
  // 1. Public fields
  pub id: String,
  pub title: String,
  
  // 2. Private fields
  content: String,
  metadata: Metadata,
}

impl Document {
  // 1. Constructor(s)
  pub fn new(id: String, title: String) -> Self {
    Self {
      id,
      title,
      content: String::new(),
      metadata: Metadata::default(),
    }
  }
  
  // 2. Public methods
  pub fn update_content(&mut self, content: String) {
    self.content = content;
  }
  
  // 3. Private methods
  fn validate(&self) -> bool {
    !self.content.is_empty()
  }
}

Module Organization

1

File naming

Use snake_case for module files:
user_profile.rs
document_handler.rs
2

Module declaration

In lib.rs or mod.rs:
mod user_profile;
mod document_handler;

pub use user_profile::UserProfile;
pub use document_handler::DocumentHandler;
3

Import ordering

// 1. Standard library
use std::collections::HashMap;
use std::sync::Arc;

// 2. External crates
use anyhow::Result;
use serde::{Deserialize, Serialize};

// 3. Internal modules
use crate::user::UserProfile;
use crate::error::FlowyError;

Comments and Documentation

Use /// for public API documentation:
/// Loads a document by its ID.
///
/// # Arguments
///
/// * `id` - The unique document identifier
///
/// # Returns
///
/// Returns `Ok(Document)` on success or `Err(FlowyError)` if not found.
///
/// # Examples
///
/// ```
/// let doc = load_document("123")?;
/// ```
pub fn load_document(id: &str) -> Result<Document> {
  // ...
}

Async Code

Use async/await for asynchronous operations:
use tokio::time::sleep;

pub async fn load_document(id: &str) -> Result<Document> {
  // Simulate async operation
  sleep(Duration::from_millis(100)).await;
  
  let doc = database.get(id).await?;
  Ok(doc)
}

Testing

Organize tests clearly:
#[cfg(test)]
mod tests {
  use super::*;
  
  #[test]
  fn test_document_creation() {
    let doc = Document::new("123".to_string(), "Title".to_string());
    assert_eq!(doc.id, "123");
  }
  
  #[tokio::test]
  async fn test_async_load() {
    let doc = load_document("123").await.unwrap();
    assert!(!doc.title.is_empty());
  }
}

General Best Practices

Keep Functions Small

Functions should do one thing well. Aim for under 50 lines.

Avoid Deep Nesting

Use early returns and helper functions to reduce nesting.

Write Tests

Test new code and maintain existing test coverage.

Document Public APIs

All public functions and types should have documentation.

Pre-commit Checks

Before committing, run:
cd appflowy_flutter

# Format code
flutter format .

# Analyze code
flutter analyze

# Run tests
flutter test

CI/CD Enforcement

The following checks run automatically on all PRs:
  • Code formatting (Dart and Rust)
  • Linting (dartanalyzer, Clippy)
  • Unit tests
  • Integration tests
  • Build verification
PRs that fail these checks will not be merged. Fix all issues before requesting review.

Next Steps

Testing

Learn about testing practices

Contributing

Contribute to AppFlowy

Architecture

Understand the architecture

Building

Build AppFlowy from source

Build docs developers (and LLMs) love