A real-world Flutter banking app demonstrating isolate-based multithreading for smooth UI performance
Features • Demo • Architecture • Isolate Techniques • Getting Started • Testing
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.
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
- 📜 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
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 |
- 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
Transaction History |
Isolate Mode Selector |
|
compute()
|
Isolate.run()
|
Isolate.spawn()
|
Try Main Thread mode to see UI freeze during scrolling - this demonstrates why isolates are crucial!
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
class Transaction {
final String id;
final String description;
final double amount;
final DateTime date;
final TransactionType type; // credit or debit
// ... more fields
}- Manages transaction state using
ChangeNotifier - Handles pagination and infinite scroll
- Switches between isolate modes
- Calculates total balance
- Provides search and filtering
- Implements 3 different isolate techniques
- Processes transactions in background
- Demonstrates message passing with Ports
- Manages worker isolate lifecycle
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
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
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
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)
- Main Thread causes visible UI freezing during scroll
- compute() and Isolate.run() are similar in performance
- Isolate.spawn() is 40% faster for repeated operations
- Initial spawn overhead is recovered after ~3 loads
- Flutter SDK 3.0 or higher
- Dart 3.0 or higher
# Clone the repository
git clone https://github.com/yourusername/isobank.git
cd isobank
# Install dependencies
flutter pub get
# Run the app
flutter run- Launch the app - Starts with
compute()mode - Tap the settings icon ⚙️ in the top right
- Select different isolate modes and observe:
- Processing time in the info banner
- Scroll smoothness
- UI responsiveness
- Try Main Thread mode to see the difference isolates make!
// 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));
}
});
}void _onScroll() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200) {
// Load more in background isolate - no UI jank!
context.read<TransactionViewModel>().loadMoreTransactions();
}
}IsoBank includes comprehensive tests covering all major components.
# 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 --watchtest/
├── 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
- Transaction creation and properties
- JSON serialization/deserialization
- Equality and hashing
- CopyWith functionality
- Enum handling
- Single transaction generation
- Bulk transaction generation
- Date range filtering
- Data consistency and validation
- compute() - Simple isolate processing
- Isolate.run() - Modern isolate API
- Isolate.spawn() - Long-lived worker isolate
- Worker lifecycle management
- Error handling
- Performance benchmarks
- State management with ChangeNotifier
- Loading and pagination
- Isolate mode switching
- Balance calculation
- Search and filtering
- Error handling
- Integration with isolate services
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();
});- Isolate Tests - Test both functionality and performance
- Mock-free - Tests use real isolates for accuracy
- Async Handling - Proper use of
async/await - Cleanup - Worker isolates are properly disposed
- Deterministic - Tests avoid timing-dependent assertions where possible
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!
Contributions are welcome! Feel free to:
- 🐛 Report bugs
- 💡 Suggest new isolate examples
- 📖 Improve documentation
- ⭐ Star this repo if you find it helpful!
This project is licensed under the MIT License - see the LICENSE file for details.
- 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
