Dart: Dart and Flutter Collaboration

Last updated: 2026-08-26

Dart is the heart of Flutter — a single language that drives UI, logic, and native communication.

1. What You Will Learn


2. A Developer's Real Story

(1) Pain Point: CLI and Flutter App Each Implementing Separate Business Logic

Bob's DataPipeline has two front-ends: a CLI tool (for developers) and a Flutter dashboard (for SaaS customers to view reports). The two systems implemented their own data processing logic, leading to: fixing a tax calculation bug required changes in two places; missing one change once caused the CLI report to be correct but the dashboard to be wrong, resulting in customer complaints.

(2) Solution: Shared Logic Package

Extract the core business logic into a pure Dart package, which both the CLI and Flutter depend on. Fixing a bug only requires a change in one place.

100%
graph TD
  A[DataPipeline Core<br/>pure Dart] --> B[CLI App]
  A --> C[Flutter Dashboard]
  C --> D[MethodChannel]
  D --> E[iOS Native]
  D --> F[Android Native]
  C --> G[FFI]
  G --> H[C Library]
TEXT 📖 Display only
> **Output:** Run locally in DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly due to SDK versions.

(3) Benefits


3. Dart's Role in the Flutter Architecture

(1) Architecture Layers

Layer Technology Responsibility
UI Layer Flutter Widget Interface rendering and interaction
Logic Layer Dart (pure) Business logic, data processing
Platform Layer Native (Kotlin/Swift) Platform-specific functions
Communication Layer MethodChannel / FFI Dart ↔ Native bridge

▶ Example

TEXT 📖 Display only
> **Output:** Run locally in DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly due to SDK versions.

: Dart logic in a Flutter project

DART
// This code runs in BOTH CLI and Flutter environments
// lib/data_pipeline_core.dart

class OrderAnalyzer {
  final double taxRate;

  OrderAnalyzer({this.taxRate = 0.08});

  double calculateTax(double amount) => amount * taxRate;
  double calculateTotal(double amount) => amount * (1 + taxRate);

  Map<String, double> groupByCategory(List``<Order>`` orders) {
    final result = <String, double>{};
    for (final order in orders) {
      result.update(order.category, (v) => v + order.amount, ifAbsent: () => order.amount);
    }
    return result;
  }
}

class Order {
  final String id;
  final double amount;
  final String category;

  Order({required this.id, required this.amount, required this.category});
}
TEXT 📖 Display only
> **Output:** Run locally in DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly due to SDK versions.

4. Shared Business Logic Package Design

(1) Pure Dart Package Structure

▶ Example

TEXT 📖 Display only
> **Output:** Run locally in DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly due to SDK versions.

: Shared package structure

TEXT 📖 Display only
packages/
  data_pipeline_core/
    lib/
      src/
        models/
          order.dart
          product.dart
          customer.dart
        services/
          analyzer.dart
          aggregator.dart
          transformer.dart
        utils/
          formatters.dart
          validators.dart
      data_pipeline_core.dart  # Barrel export
    test/
      analyzer_test.dart
      aggregator_test.dart
    pubspec.yaml
```text


```text
> **Output:** Run locally in DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly due to SDK versions.

▶ Example

TEXT 📖 Display only
> **Output:** Run locally in DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly due to SDK versions.

: Barrel export

DART
// lib/data_pipeline_core.dart
// Barrel file - exports all public APIs

// Models
export 'src/models/order.dart';
export 'src/models/product.dart';
export 'src/models/customer.dart';

// Services
export 'src/services/analyzer.dart';
export 'src/services/aggregator.dart';
export 'src/services/transformer.dart';

// Utils
export 'src/utils/formatters.dart';
export 'src/utils/validators.dart';
TEXT 📖 Display only
> **Output:** Run locally in DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly due to SDK versions.

▶ Example

TEXT 📖 Display only
> **Output:** Run locally in DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly due to SDK versions.

: pubspec.yaml for pure Dart package

YAML
name: data_pipeline_core
description: Core business logic for DataPipeline - pure Dart, no Flutter dependency
version: 1.0.0

environment:
  sdk: ^3.0.0

dependencies:
  json_annotation: ^4.8.0

dev_dependencies:
  test: ^1.24.0
  json_serializable: ^6.7.0
  build_runner: ^2.4.0

▶ Example

TEXT 📖 Display only
> **Output:** Run locally in DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly due to SDK versions.

: Two consumers

YAML
# CLI app pubspec.yaml
dependencies:
  data_pipeline_core:
    path: ../packages/data_pipeline_core
  args: ^2.4.2

# Flutter app pubspec.yaml
dependencies:
  data_pipeline_core:
    path: ../packages/data_pipeline_core
  flutter:
    sdk: flutter
Principle Description
No Flutter dependency pubspec.yaml does not depend on flutter
No direct use of dart:io Use abstract interfaces for file/network operations
No UI code Pure data processing logic
Complete testing Testable without the Flutter runtime

5. MethodChannel

(1) Dart ↔ Native Communication

▶ Example

TEXT 📖 Display only
> **Output:** Run locally in DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly due to SDK versions.

: MethodChannel calling native

DART
import 'package:flutter/services.dart';

class NativeService {
  static const _channel = MethodChannel('com.datapipeline/native');

  // Call native method
  Future``<String>`` getDeviceId() async {
    try {
      final deviceId = await _channel.invokeMethod``<String>``('getDeviceId');
      return deviceId ?? 'unknown';
    } on PlatformException catch (e) {
      print('Failed to get device ID: ${e.message}');
      return 'error';
    }
  }

  // Call with arguments
  Future``<bool>`` saveToFile(String path, String content) async {
    try {
      final result = await _channel.invokeMethod``<bool>``('saveToFile', {
        'path': path,
        'content': content,
      });
      return result ?? false;
    } on PlatformException catch (e) {
      print('Failed to save: ${e.message}');
      return false;
    }
  }
}
TEXT 📖 Display only
> **Output:** Run locally in DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly due to SDK versions.

(2) Native-side Implementation

▶ Example

TEXT 📖 Display only
> **Output:** Run locally in DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly due to SDK versions.

: Android (Kotlin) side

KOTLIN
// Android implementation
class MainActivity : FlutterActivity() {
    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        MethodChannel(flutterEngine.dartExecutor.binaryMessenger,
            "com.datapipeline/native").setMethodCallHandler { call, result ->
            when (call.method) {
                "getDeviceId" -> {
                    val deviceId = Settings.Secure.getString(
                        contentResolver, Settings.Secure.ANDROID_ID)
                    result.success(deviceId)
                }
                "saveToFile" -> {
                    val path = call.argument<String>("path")
                    val content = call.argument<String>("content")
                    // Save file logic
                    result.success(true)
                }
                else -> result.notImplemented()
            }
        }
    }
}
TEXT 📖 Display only
> **Output:** Run locally in DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly due to SDK versions.
Communication Method Direction Data Type Use Case
MethodChannel Dart → Native Standard types Calling native methods
EventChannel Native → Dart Stream Native event streams
BasicMessageChannel Bidirectional String/Bytes Bidirectional messages

6. FFI — Calling C Libraries

▶ Example

TEXT 📖 Display only
> **Output:** Run locally in DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly due to SDK versions.

: dart:ffi calling a C function

DART
import 'dart:ffi';
import 'package:ffi/ffi.dart';

// C function signature: double process_data(double* data, int length)
typedef ProcessDataNative = Double Function(Pointer``<Double>``, Int32);
typedef ProcessDataDart = double Function(Pointer``<Double>``, int);

void main() {
  // Load dynamic library
  final dylib = DynamicLibrary.open('libdatapipeline.so');

  // Look up function
  final processData = dylib.lookupFunction<ProcessDataNative, ProcessDataDart>('process_data');

  // Prepare data
  final dataPtr = calloc``<Double>``(5);
  for (var i = 0; i < 5; i++) {
    dataPtr[i] = (i + 1) * 100.0;
  }

  // Call C function
  final result = processData(dataPtr, 5);
  print('Result from C: $result');

  // Free memory
  calloc.free(dataPtr);
}
TEXT 📖 Display only
> **Output:** Run locally in DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly due to SDK versions.
FFI Concept Description
DynamicLibrary Load dynamic link libraries
lookupFunction Look up a C function
`Pointer````` Points to native memory
calloc / free Allocate/free native memory
NativeType C type mapping

7. Bob's Scenario: DataPipeline Dual Front-end Architecture

▶ Example

TEXT 📖 Display only
> **Output:** Run locally in DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly due to SDK versions.

: Shared core + dual front-ends

DART
// packages/data_pipeline_core/lib/src/services/analyzer.dart

class OrderAnalyzer {
  final double taxRate;

  OrderAnalyzer({this.taxRate = 0.08});

  AnalysisResult analyze(List``<Order>`` orders) {
    final completed = orders.where((o) => o.status == 'completed').toList();
    final revenue = completed.fold``<double>``(0, (s, o) => s + o.amount);
    final tax = revenue * taxRate;

    final byCategory = <String, double>{};
    for (final o in completed) {
      byCategory.update(o.category, (v) => v + o.amount, ifAbsent: () => o.amount);
    }

    return AnalysisResult(
      totalOrders: orders.length,
      completedOrders: completed.length,
      revenue: revenue,
      tax: tax,
      revenueByCategory: byCategory,
    );
  }
}

class AnalysisResult {
  final int totalOrders;
  final int completedOrders;
  final double revenue;
  final double tax;
  final Map<String, double> revenueByCategory;

  AnalysisResult({
    required this.totalOrders,
    required this.completedOrders,
    required this.revenue,
    required this.tax,
    required this.revenueByCategory,
  });

  double get totalWithTax => revenue + tax;
  double get averageOrderValue => completedOrders > 0 ? revenue / completedOrders : 0;
}

class Order {
  final String id;
  final double amount;
  final String status;
  final String category;

  Order({required this.id, required this.amount, required this.status, required this.category});
}

// CLI consumer
void main() {
  final analyzer = OrderAnalyzer(taxRate: 0.08);
  final orders = [
    Order(id: 'ORD-001', amount: 1500.0, status: 'completed', category: 'Electronics'),
    Order(id: 'ORD-002', amount: 890.0, status: 'completed', category: 'Clothing'),
  ];

  final result = analyzer.analyze(orders);
  print('Revenue: \$${result.revenue.toStringAsFixed(2)} USD');
  print('Tax: \$${result.tax.toStringAsFixed(2)} USD');
}

// Flutter consumer would use the same OrderAnalyzer
// but render results in a Widget instead of print()
TEXT 📖 Display only
> **Output:** Run locally in DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly due to SDK versions.

8. Complete Example: DataPipeline Shared Architecture

DART
// ============================================
// DataPipeline Shared Architecture
// Pure Dart core + CLI consumer + Flutter consumer
// ============================================

// ---- Core Library (pure Dart) ----

// lib/src/models/order.dart
class Order {
  final String id;
  final double amount;
  final String status;
  final String category;
  final String region;

  const Order({
    required this.id,
    required this.amount,
    required this.status,
    required this.category,
    this.region = 'US',
  });

  double get tax => amount * _taxRate(region);
  double get total => amount + tax;
  String get formatAmount => '\$${amount.toStringAsFixed(2)} USD';

  static double _taxRate(String region) => switch (region) {
    'US' => 0.08,
    'EU' => 0.20,
    'UK' => 0.15,
    _ => 0.10,
  };
}

// lib/src/services/analyzer.dart
class OrderAnalyzer {
  const OrderAnalyzer();

  AnalysisResult analyze(List``<Order>`` orders) {
    final completed = orders.where((o) => o.status == 'completed').toList();
    final revenue = completed.fold``<double>``(0, (s, o) => s + o.amount);
    final tax = completed.fold``<double>``(0, (s, o) => s + o.tax);
    final total = revenue + tax;

    final byCategory = <String, CategoryResult>{};
    for (final o in completed) {
      byCategory.update(
        o.category,
        (v) => v.addOrder(o),
        ifAbsent: () => CategoryResult.fromOrder(o),
      );
    }

    return AnalysisResult(
      totalOrders: orders.length,
      completedOrders: completed.length,
      revenue: revenue,
      tax: tax,
      total: total,
      byCategory: byCategory,
    );
  }
}

class CategoryResult {
  final int count;
  final double revenue;

  const CategoryResult({required this.count, required this.revenue});

  factory CategoryResult.fromOrder(Order order) =>
      CategoryResult(count: 1, revenue: order.amount);

  CategoryResult addOrder(Order order) =>
      CategoryResult(count: count + 1, revenue: revenue + order.amount);

  double get average => count > 0 ? revenue / count : 0;
}

class AnalysisResult {
  final int totalOrders;
  final int completedOrders;
  final double revenue;
  final double tax;
  final double total;
  final Map<String, CategoryResult> byCategory;

  const AnalysisResult({
    required this.totalOrders,
    required this.completedOrders,
    required this.revenue,
    required this.tax,
    required this.total,
    required this.byCategory,
  });

  double get averageOrderValue => completedOrders > 0 ? revenue / completedOrders : 0;
}

// ---- CLI Consumer ----
void main() {
  final analyzer = OrderAnalyzer();
  final orders = [
    Order(id: 'ORD-001', amount: 1500.0, status: 'completed', category: 'Electronics'),
    Order(id: 'ORD-002', amount: 890.0, status: 'completed', category: 'Clothing'),
    Order(id: 'ORD-003', amount: 3200.0, status: 'pending', category: 'Electronics'),
    Order(id: 'ORD-004', amount: 2100.0, status: 'completed', category: 'Electronics', region: 'EU'),
  ];

  final result = analyzer.analyze(orders);

  print('=== DataPipeline Analytics ===');
  print('Orders:    ${result.completedOrders}/${result.totalOrders}');
  print('Revenue:   \$${result.revenue.toStringAsFixed(2)} USD');
  print('Tax:       \$${result.tax.toStringAsFixed(2)} USD');
  print('Total:     \$${result.total.toStringAsFixed(2)} USD');
  print('Average:   \$${result.averageOrderValue.toStringAsFixed(2)} USD');

  print('\nBy Category:');
  final sorted = result.byCategory.entries.toList()
    ..sort((a, b) => b.value.revenue.compareTo(a.value.revenue));
  for (final entry in sorted) {
    final cat = entry.value;
    print('  ${entry.key}: ${cat.count} orders, '
        '\$${cat.revenue.toStringAsFixed(2)} USD (avg: \$${cat.average.toStringAsFixed(2)})');
  }

  print('\n--- Platform Compatibility ---');
  print('Core logic:  Pure Dart (CLI + Flutter + Web)');
  print('CLI:         dart:io + args package');
  print('Flutter:     Widgets + MethodChannel + FFI');
}
TEXT 📖 Display only
> **Output:** Run locally in DartPad or with `dart run`. All examples in the Dart course are based on Dart 3.x / Flutter 3.x; results may vary slightly due to SDK versions.

Output:

TEXT 📖 Display only
=== DataPipeline Analytics ===
Orders:    3/4
Revenue:   $4490.00 USD
Tax:       $798.00 USD
Total:     $5288.00 USD
Average:   $1496.67 USD

By Category:
  Electronics: 2 orders, $3600.00 USD (avg: $1800.00)
  Clothing: 1 orders, $890.00 USD (avg: $890.00)

--- Platform Compatibility ---
Core logic:  Pure Dart (CLI + Flutter + Web)
CLI:         dart:io + args package
Flutter:     Widgets + MethodChannel + FFI

❓ FAQ

Q: Can a pure Dart package run on Flutter Web? A: Yes, as long as dart:io is not used. The web platform does not support dart:io; use abstract interfaces (like the http package's Client) to replace direct file/network operations.

Q: Is there a size limit for data transfer via MethodChannel? A: There is no hard limit, but large objects require serialization/deserialization, which has poor performance. For large data, consider using FFI or temporary files.

Q: Does FFI support iOS? A: Yes. On iOS, load .dylib or .framework. Flutter 3.0+ FFI supports all major platforms.

Q: When should I use MethodChannel vs. FFI? A: MethodChannel is suitable for calling platform APIs (sensors, push notifications, file saving); FFI is suitable for calling C libraries (image processing, encryption, database engines).

Q: Does testing a shared package require a Flutter environment? A: No. A pure Dart package can be tested with dart test alone and does not depend on Flutter. This is a major benefit of extracting a shared package.

Q: How can I ensure a shared package doesn't accidentally introduce Flutter dependencies? A: Don't depend on flutter in pubspec.yaml, and don't import flutter packages. In CI, run tests with dart test (not flutter test) to verify.

Q: Should the DataPipeline core package use a monorepo or a separate repository? A: A monorepo is recommended (using Melos or path dependencies) for easier synchronization between core package and consumers. A separate repository is suitable for open-source distribution.


📖 Summary


📝 Exercises

  1. Basic (Difficulty ⭐): Create a pure Dart package (dart create -t package-simple) containing a formatUSD function. Reference and call it from both a CLI and a Flutter project.
  2. Intermediate (Difficulty ⭐⭐): Design the DataPipeline core package structure: 3 model classes + 2 service classes + 1 barrel export. Ensure pubspec.yaml has no Flutter dependency, and write tests runnable with dart test.
  3. Challenge (Difficulty ⭐⭐⭐): Implement an abstract DataStorage interface (pure Dart), then implement it using dart:io (CLI) and SharedPreferences (Flutter) separately. The core package should depend only on the interface, with concrete implementations injected by consumers.

← Previous Lesson | Next Lesson →

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏