flutter_diff_viewer 1.4.0 copy "flutter_diff_viewer: ^1.4.0" to clipboard
flutter_diff_viewer: ^1.4.0 copied to clipboard

A production-grade Flutter package providing GitHub/GitLab-style content comparison and diff viewing. Supports side-by-side, unified, and stacked layouts with line, word, and character-level diff granularity.

Flutter Diff Viewer #

pub.dev License: MIT Flutter

A production-grade Flutter package providing GitHub/GitLab-style content comparison and diff viewing. Built with Clean Architecture, SOLID principles, and designed for enterprise use.


Features #

  • 🔄 GitHub-style side-by-side diff view
  • 📄 Unified diff view (single column with +/- indicators)
  • 📱 Stacked diff view (old above, new below — optimized for mobile)
  • 🔤 Word-level inline highlighting — shows exactly which words changed
  • 🔡 Character-level inline highlighting — maximum precision
  • 🔢 Line numbers in the gutter
  • 📊 Change summary bar (+N additions, -N deletions)
  • 🧭 Change navigation (Previous / Next change buttons)
  • 📂 Collapsible unchanged sections with configurable context lines
  • 🎨 Light and dark themes (GitHub-style out of the box)
  • 🎯 Fully customizable — every UI section has a builder callback
  • 🔌 Pluggable diff algorithm — swap in your own engine
  • 🔍 Added Whitespace Highlighting — visually marks added/extra spaces (·) and tabs ()
  • ↔️ Synchronized Horizontal Scrolling — all line rows scroll together smoothly across each panel
  • Accessibility — semantic labels, screen reader support, color-independent indicators
  • PerformantListView.builder handles 50,000+ line documents
  • 🖥️ Responsive — auto-selects layout based on screen width
  • 🌍 Localization — all user-visible strings are customizable

Installation #

Add to your pubspec.yaml:

dependencies:
  flutter_diff_viewer: ^1.4.0

Then run:

flutter pub get

Quick Start #

import 'package:flutter_diff_viewer/flutter_diff_viewer.dart';

FlutterDiffViewer(
  oldContent: 'Hello World\nLine two',
  newContent: 'Hello Dart\nLine two\nNew line',
)

That's it! The widget automatically:

  1. Calculates the diff asynchronously
  2. Selects the appropriate layout for the screen size
  3. Renders with the default GitHub-style light theme

Examples #

Side-by-Side View #

FlutterDiffViewer(
  oldContent: oldText,
  newContent: newText,
  oldLabel: 'v1.2',
  newLabel: 'v1.3',
  configuration: FlutterDiffViewerConfiguration(
    layout: DiffLayout.sideBySide,
    theme: FlutterDiffViewerTheme.light(),
    typography: DiffTypography.defaults(),
    spacing: DiffSpacing.defaults(),
    localizations: DiffLocalizations.defaults(),
  ),
)

Unified View #

FlutterDiffViewer(
  oldContent: oldText,
  newContent: newText,
  configuration: FlutterDiffViewerConfiguration(
    layout: DiffLayout.unified,
    showLineNumbers: true,
    showSummary: true,
    theme: FlutterDiffViewerTheme.light(),
    typography: DiffTypography.defaults(),
    spacing: DiffSpacing.defaults(),
    localizations: DiffLocalizations.defaults(),
  ),
)

Dark Mode #

FlutterDiffViewer(
  oldContent: oldText,
  newContent: newText,
  theme: FlutterDiffViewerTheme.dark(),
)

Adaptive (auto-detects from Flutter theme) #

FlutterDiffViewer(
  oldContent: oldText,
  newContent: newText,
  configuration: FlutterDiffViewerConfiguration.adaptive(context),
)

Configuration #

FlutterDiffViewerConfiguration controls all layout and feature options:

FlutterDiffViewerConfiguration(
  // Layout
  layout: DiffLayout.auto,           // auto, sideBySide, unified, stacked
  sideBySideBreakpoint: 768.0,       // px width threshold for auto layout

  // Features
  showHeader: true,
  showLineNumbers: true,
  showIndicators: true,              // +/- indicator column
  showSummary: true,                 // additions/deletions count
  showChangeNavigation: true,
  enableTextSelection: true,
  synchronizedScrolling: true,       // sync side-by-side panels
  collapseUnchangedLines: true,

  // Comparison
  granularity: DiffGranularity.word, // line, word, character, auto
  ignoreWhitespace: false,
  caseSensitive: true,
  contextLines: 3,                   // unchanged lines around each change
  useIsolateForLargeDocuments: true, // compute() for >1000 lines

  // Styling
  theme: FlutterDiffViewerTheme.light(),
  typography: DiffTypography.defaults(),
  spacing: DiffSpacing.defaults(),
  localizations: DiffLocalizations.defaults(),
)

Themes #

Built-in themes #

FlutterDiffViewerTheme.light()  // GitHub light style
FlutterDiffViewerTheme.dark()   // GitHub dark style

Custom theme #

FlutterDiffViewer(
  oldContent: oldText,
  newContent: newText,
  theme: FlutterDiffViewerTheme.light().copyWith(
    addedBackgroundColor: Color(0xFFE8F5E9),
    removedBackgroundColor: Color(0xFFEDE7F6),
    addedTextColor: Color(0xFF2E7D32),
    removedTextColor: Color(0xFF6A1B9A),
  ),
)

Word and Character Diff #

// Word-level highlighting
FlutterDiffViewer(
  oldContent: 'The quick brown fox',
  newContent: 'The fast red cat',
  configuration: FlutterDiffViewerConfiguration(
    granularity: DiffGranularity.word,
    theme: FlutterDiffViewerTheme.light(),
    typography: DiffTypography.defaults(),
    spacing: DiffSpacing.defaults(),
    localizations: DiffLocalizations.defaults(),
  ),
)

// Character-level highlighting (maximum precision)
FlutterDiffViewer(
  oldContent: 'colour',
  newContent: 'color',
  configuration: FlutterDiffViewerConfiguration(
    granularity: DiffGranularity.character,
    theme: FlutterDiffViewerTheme.light(),
    typography: DiffTypography.defaults(),
    spacing: DiffSpacing.defaults(),
    localizations: DiffLocalizations.defaults(),
  ),
)

Controller #

Use FlutterDiffViewerController for programmatic navigation:

final controller = FlutterDiffViewerController();

// In your widget tree
FlutterDiffViewer(
  oldContent: oldText,
  newContent: newText,
  controller: controller,
)

// Navigate programmatically
controller.nextChange();
controller.previousChange();
controller.goToChange(2);
controller.scrollToLine(50);

// Collapse/expand
controller.collapseAll();
controller.expandAll();

// Read state
print('${controller.currentChangeIndex} of ${controller.totalChanges}');

// Always dispose if you create it yourself
@override
void dispose() {
  controller.dispose();
  super.dispose();
}

Custom Builders #

Every UI section can be replaced with your own widget:

FlutterDiffViewer(
  oldContent: oldText,
  newContent: newText,

  // Custom header
  headerBuilder: (context, oldLabel, newLabel, config) {
    return MyCustomHeader(old: oldLabel, new_: newLabel);
  },

  // Custom line renderer
  lineBuilder: (context, line, config) {
    return MyCustomDiffLine(line: line);
  },

  // Custom collapsed section
  collapsedSectionBuilder: (context, count, onExpand, config) {
    return TextButton(
      onPressed: onExpand,
      child: Text('Show $count hidden lines'),
    );
  },

  // Custom empty state
  emptyStateBuilder: (context, config) {
    return const Center(child: Text('✅ Files are identical'));
  },

  // Custom error state
  errorBuilder: (context, error, config) {
    return Center(child: Text('Error: $error'));
  },
)

Custom Diff Engine #

Replace the built-in Myers LCS engine with your own:

class MyDiffEngine implements DiffEngine {
  @override
  DiffResult compare(
    String oldContent,
    String newContent,
    DiffComparisonOptions options,
  ) {
    // Your custom algorithm here
    return DiffResult(...);
  }
}

FlutterDiffViewer(
  oldContent: oldText,
  newContent: newText,
  diffEngine: MyDiffEngine(),
)

Localization #

Provide custom strings for any language:

FlutterDiffViewer(
  oldContent: oldText,
  newContent: newText,
  configuration: FlutterDiffViewerConfiguration(
    theme: FlutterDiffViewerTheme.light(),
    typography: DiffTypography.defaults(),
    spacing: DiffSpacing.defaults(),
    localizations: DiffLocalizations(
      oldVersionLabel: 'Aktuell',
      newVersionLabel: 'Geändert',
      addedLabel: 'Hinzugefügt',
      removedLabel: 'Entfernt',
      noChangesLabel: 'Keine Änderungen',
      nextChangeLabel: 'Nächste Änderung',
      previousChangeLabel: 'Vorherige Änderung',
      showMoreLabel: 'Zeile anzeigen',
      collapseLabel: 'Einklappen',
      loadingLabel: 'Berechnung…',
      errorLabel: 'Fehler beim Berechnen',
      additionsCountLabel: 'Hinzufügungen',
      deletionsCountLabel: 'Löschungen',
      modifiedLabel: 'Geändert',
      unchangedLabel: 'Unverändert',
      changeOfLabel: 'von',
    ),
  ),
)

Responsive Layout #

DiffLayout.auto automatically selects:

  • Side-by-side when availableWidth >= sideBySideBreakpoint (default: 768px)
  • Unified on narrower screens

Override the breakpoint:

FlutterDiffViewerConfiguration(
  layout: DiffLayout.auto,
  sideBySideBreakpoint: 600.0, // switch at 600px instead
  theme: FlutterDiffViewerTheme.light(),
  typography: DiffTypography.defaults(),
  spacing: DiffSpacing.defaults(),
  localizations: DiffLocalizations.defaults(),
)

Accessibility #

The package never relies solely on color to convey meaning:

  • Change indicators: +, -, ~, (in addition to color)
  • Semantic labels on every line: "Added: content", "Removed: content"
  • Screen reader support via Flutter's Semantics framework
  • All interactive elements have tooltips

Performance #

  • Uses ListView.builder — only visible rows are rendered
  • Diff calculation runs asynchronously (Future-based)
  • For documents >1000 lines with useIsolateForLargeDocuments: true, calculation runs in a separate isolate via compute()
  • Staged diff: line-level first, then word/character only for changed lines

Measured: 500-line documents calculate in <20ms on average hardware. Performance with very large documents (50,000+ lines) depends on the device and number of changes.


Architecture #

This package is built with Clean Architecture:

Presentation  →  Domain  ←  Data
                 (pure Dart)
  • Domain: Entities, use cases, repository interfaces — zero Flutter/Material deps
  • Data: Diff engines, repository implementations, models
  • Presentation: Widgets, controller, themes, builders

API Reference #

See the pub.dev documentation for the full API reference.

Key classes #

Class Description
FlutterDiffViewer Main widget — the package entry point
FlutterDiffViewerConfiguration Immutable configuration object
FlutterDiffViewerTheme Color theme with light/dark factories
DiffTypography Text style customization
DiffSpacing Layout spacing customization
DiffLocalizations User-visible string customization
FlutterDiffViewerController Programmatic navigation and scroll control
DiffEngine Abstract interface for custom diff algorithms
DiffResult The output of a diff calculation
DiffLine A single row in the diff view
DiffSegment An inline highlighted text segment

Roadmap #

  • ❌ Syntax highlighting for code files
  • ❌ Image diff support
  • ❌ Inline comment support (GitHub PR review style)
  • ❌ Export diff as HTML/PDF
  • ❌ Web worker support for Flutter Web

Contributing #

Contributions are welcome! Please read our contributing guidelines and submit pull requests to our GitHub repository.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/my-feature)
  3. Write tests for your changes
  4. Ensure all tests pass (flutter test)
  5. Submit a pull request

License #

This package is licensed under the MIT License.

3
likes
150
points
322
downloads

Documentation

Documentation
API reference

Publisher

unverified uploader

Weekly Downloads

A production-grade Flutter package providing GitHub/GitLab-style content comparison and diff viewing. Supports side-by-side, unified, and stacked layouts with line, word, and character-level diff granularity.

Homepage
Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter

More

Packages that depend on flutter_diff_viewer