Skip to content

A practical Flutter demo showcasing real-world multithreading with isolates, compute, and parallel UI rendering — featuring a dummy banking app called IsoBank.

Notifications You must be signed in to change notification settings

Raks-Javac/flutter_multithread_demo

Repository files navigation

🏦 IsoBank - Flutter Isolates Demo

A real-world Flutter banking app demonstrating isolate-based multithreading for smooth UI performance

Flutter Dart License


IsoBank Demo

FeaturesDemoArchitectureIsolate TechniquesGetting StartedTesting


📖 Overview

IsoBank is a Flutter application that demonstrates real-world use cases of multithreading and concurrency in Flutter using isolates. The app simulates a banking transaction history with infinite scrolling, showcasing how different isolate techniques affect performance and UI smoothness.

Why This Matters

In production Flutter apps, heavy computations on the main thread cause UI jank (jerky animations, frozen screens). IsoBank demonstrates:

  • How to offload heavy work to background isolates
  • Different isolate APIs and when to use each one
  • Performance comparisons between approaches
  • Real-world patterns you can copy into your apps

✨ Features

🎯 Core Functionality

  • 📜 Transaction History: Infinite scrolling list of 500+ dummy bank transactions
  • 💰 Real-time Balance: Automatic calculation of total balance from transactions
  • 🔄 Pull-to-Refresh: Reload transaction data with smooth animations
  • 🎨 Beautiful UI: Modern banking interface with Material Design 3

🚀 Isolate Demonstrations

IsoBank implements 4 different processing modes you can switch between:

Mode Icon Description Use Case
compute() Flutter's simple isolate helper One-off computations, easiest to use
Isolate.run() 🎯 Modern Dart isolate API Short-lived tasks, clean syntax
Isolate.spawn() 🚀 Long-lived worker isolate Repeated operations, best performance
Main Thread ⚠️ No isolate (comparison) Shows UI jank when blocking main thread

📊 Performance Metrics

  • Processing Time: See exact milliseconds each approach takes
  • Visual Comparison: Switch modes to feel the difference in UI smoothness
  • Real-world Data: 50 transactions per page with complex JSON processing

🎥 Demo

App Screenshots

Transaction List

Transaction History
Infinite scrolling with real-time balance

Isolate Mode Selector

Isolate Mode Selector
Switch between different isolate techniques

Switching Between Isolate Modes

compute()

  • Creates new isolate per load
  • Simple API
  • ~40-60ms per page

Isolate.run()

  • Modern syntax
  • Short-lived
  • ~35-55ms per page

Isolate.spawn()

  • Reusable worker
  • No spawn overhead
  • ~20-35ms per page

Try Main Thread mode to see UI freeze during scrolling - this demonstrates why isolates are crucial!


🏗 Architecture

IsoBank follows MVVM (Model-View-ViewModel) pattern for clean separation of concerns:

lib/
├── models/
│   └── transaction.dart          # Transaction data model
├── views/
│   └── transaction_list_view.dart # UI components
├── viewmodels/
│   └── transaction_viewmodel.dart # Business logic & state
├── services/
│   └── transaction_isolate_service.dart # Isolate implementations
├── utils/
│   └── transaction_generator.dart # Dummy data generation
└── main.dart                     # App entry point

📦 Key Components

1. Transaction Model (models/transaction.dart)

class Transaction {
  final String id;
  final String description;
  final double amount;
  final DateTime date;
  final TransactionType type; // credit or debit
  // ... more fields
}

2. Transaction ViewModel (viewmodels/transaction_viewmodel.dart)

  • Manages transaction state using ChangeNotifier
  • Handles pagination and infinite scroll
  • Switches between isolate modes
  • Calculates total balance
  • Provides search and filtering

3. Isolate Service (services/transaction_isolate_service.dart)

  • Implements 3 different isolate techniques
  • Processes transactions in background
  • Demonstrates message passing with Ports
  • Manages worker isolate lifecycle

🔬 Isolate Techniques Explained

1️⃣ compute() - Simplest Approach

Code Example:

Future<List<Transaction>> processWithCompute(int count) async {
  return await compute(_generateTransactionsIsolate, count);
}

static List<Transaction> _generateTransactionsIsolate(int count) {
  // Heavy processing here
  return TransactionGenerator.generateTransactions(count);
}

When to use:

  • ✅ Simple one-off computations
  • ✅ Quick prototyping
  • ✅ Flutter-specific code

Pros:

  • Easiest to use
  • Auto-manages isolate lifecycle
  • Part of Flutter framework

Cons:

  • Creates new isolate each time (overhead)
  • Only for Flutter apps

2️⃣ Isolate.run() - Modern Dart

Code Example:

Future<List<Transaction>> processWithIsolateRun(int count) async {
  return await Isolate.run(() {
    final transactions = TransactionGenerator.generateTransactions(count);
    transactions.sort((a, b) => b.date.compareTo(a.date));
    return transactions;
  });
}

When to use:

  • ✅ Short-lived computations
  • ✅ Pure Dart packages (non-Flutter)
  • ✅ Modern Dart 2.19+ projects

Pros:

  • Clean, modern syntax
  • Works in pure Dart
  • Better than compute for non-UI code

Cons:

  • Still creates new isolate per call

3️⃣ Isolate.spawn() - High Performance

Code Example:

class TransactionWorker {
  final Isolate _isolate;
  final ReceivePort _receivePort;
  final SendPort _sendPort;

  // Spawn worker once
  static Future<TransactionWorker> spawn() async {
    final initPort = ReceivePort();
    final isolate = await Isolate.spawn(_workerEntryPoint, initPort.sendPort);
    final sendPort = await initPort.first as SendPort;
    // ... setup communication
  }

  // Reuse worker many times
  Future<List<Transaction>> processTransactions(int count) {
    _sendPort.send(_WorkerRequest(count: count));
    return completer.future;
  }
}

When to use:

  • ✅ Repeated computations
  • ✅ Long-running background tasks
  • ✅ Streaming data processing
  • ✅ Maximum performance needed

Pros:

  • Reusable - no spawn overhead after creation
  • Fastest for repeated operations
  • Stateful - can maintain worker state

Cons:

  • More complex setup
  • Manual port management
  • Need to handle worker lifecycle

🚦 Performance Comparison

Based on testing with 50 transactions per page:

Mode First Load Subsequent Loads UI Smoothness
compute() ~50ms ~45ms ⭐⭐⭐⭐ Smooth
Isolate.run() ~45ms ~40ms ⭐⭐⭐⭐ Smooth
Isolate.spawn() ~60ms* ~25ms ⭐⭐⭐⭐⭐ Smoothest
Main Thread ~40ms ~40ms ⭐ Janky!

*First load includes worker spawn time (~35ms overhead)

Key Insights

  1. Main Thread causes visible UI freezing during scroll
  2. compute() and Isolate.run() are similar in performance
  3. Isolate.spawn() is 40% faster for repeated operations
  4. Initial spawn overhead is recovered after ~3 loads

📱 Getting Started

Prerequisites

  • Flutter SDK 3.0 or higher
  • Dart 3.0 or higher

Installation

# Clone the repository
git clone https://github.com/yourusername/isobank.git
cd isobank

# Install dependencies
flutter pub get

# Run the app
flutter run

Try It Yourself

  1. Launch the app - Starts with compute() mode
  2. Tap the settings icon ⚙️ in the top right
  3. Select different isolate modes and observe:
    • Processing time in the info banner
    • Scroll smoothness
    • UI responsiveness
  4. Try Main Thread mode to see the difference isolates make!

🧪 Code Highlights

Message Passing with Ports

// Worker isolate entry point
static void _workerEntryPoint(SendPort initPort) {
  final workerPort = ReceivePort();
  initPort.send(workerPort.sendPort);

  workerPort.listen((message) {
    if (message is _WorkerRequest) {
      // Process request
      final result = processData(message.count);
      // Send response back
      mainSendPort.send(_WorkerResponse(result));
    }
  });
}

Infinite Scroll with Isolates

void _onScroll() {
  if (_scrollController.position.pixels >= 
      _scrollController.position.maxScrollExtent - 200) {
    // Load more in background isolate - no UI jank!
    context.read<TransactionViewModel>().loadMoreTransactions();
  }
}

🧪 Testing

IsoBank includes comprehensive tests covering all major components.

Running Tests

# Run all tests
flutter test

# Run tests with coverage
flutter test --coverage

# Run specific test file
flutter test test/models/transaction_test.dart

# Run tests in watch mode (requires external tool)
flutter test --watch

Test Structure

test/
├── models/
│   └── transaction_test.dart           # Transaction model tests
├── utils/
│   └── transaction_generator_test.dart # Data generation tests
├── services/
│   └── transaction_isolate_service_test.dart # Isolate service tests
└── viewmodels/
    └── transaction_viewmodel_test.dart # ViewModel & integration tests

Test Coverage

✅ Model Tests (test/models/transaction_test.dart)

  • Transaction creation and properties
  • JSON serialization/deserialization
  • Equality and hashing
  • CopyWith functionality
  • Enum handling

✅ Generator Tests (test/utils/transaction_generator_test.dart)

  • Single transaction generation
  • Bulk transaction generation
  • Date range filtering
  • Data consistency and validation

✅ Service Tests (test/services/transaction_isolate_service_test.dart)

  • compute() - Simple isolate processing
  • Isolate.run() - Modern isolate API
  • Isolate.spawn() - Long-lived worker isolate
  • Worker lifecycle management
  • Error handling
  • Performance benchmarks

✅ ViewModel Tests (test/viewmodels/transaction_viewmodel_test.dart)

  • State management with ChangeNotifier
  • Loading and pagination
  • Isolate mode switching
  • Balance calculation
  • Search and filtering
  • Error handling
  • Integration with isolate services

Example Test: Isolate Performance

test('Isolate.spawn should be faster for repeated operations', () async {
  final service = TransactionIsolateService();
  
  // Create worker once
  final worker = await service.createWorker();
  
  final stopwatch = Stopwatch()..start();
  
  // Reuse worker multiple times
  for (int i = 0; i < 5; i++) {
    await worker.processTransactions(50);
  }
  
  stopwatch.stop();
  final workerTime = stopwatch.elapsedMilliseconds;
  
  // Worker should be faster due to reuse
  expect(workerTime, lessThan(500)); // Reasonable threshold
  
  worker.dispose();
});

Test Best Practices

  1. Isolate Tests - Test both functionality and performance
  2. Mock-free - Tests use real isolates for accuracy
  3. Async Handling - Proper use of async/await
  4. Cleanup - Worker isolates are properly disposed
  5. Deterministic - Tests avoid timing-dependent assertions where possible

📚 Code Reference

ISOLATE_PATTERNS.dart

The lib/ISOLATE_PATTERNS.dart file is a copy-paste ready reference guide containing:

  • ✅ All three isolate patterns with working examples
  • ✅ Decision tree for choosing the right pattern
  • ✅ Common pitfalls and how to avoid them
  • ✅ Performance tips and best practices
  • ✅ Real-world usage examples

Use this file as a template when implementing isolates in your own projects!


🎓 Learning Resources

Official Documentation

Related Articles


🤝 Contributing

Contributions are welcome! Feel free to:

  • 🐛 Report bugs
  • 💡 Suggest new isolate examples
  • 📖 Improve documentation
  • ⭐ Star this repo if you find it helpful!

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.


🙏 Acknowledgments

  • Flutter team for excellent isolate APIs
  • The Dart community for concurrency patterns
  • Material Design for beautiful UI components

Built with ❤️ using Flutter

Made to demonstrate real-world isolate usage in production apps

⬆ Back to Top

About

A practical Flutter demo showcasing real-world multithreading with isolates, compute, and parallel UI rendering — featuring a dummy banking app called IsoBank.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published