Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f837998
Refactor controller and registry: Remove ControllerRegistry and relat…
PlugFox Nov 11, 2025
eed2543
Update version to 1.0.0-dev and add Mutex implementation for concurre…
PlugFox Nov 11, 2025
8a931a5
Add linked, queue, and simple mutex implementations for concurrency c…
PlugFox Nov 11, 2025
12e099c
Enhance LinkedMutex: Ensure task completion is checked before finaliz…
PlugFox Nov 11, 2025
f790640
Fix LinkedMutex: Remove error handling in lock method and ensure head…
PlugFox Nov 11, 2025
4c44925
Cleanup test_race_condition.dart: Remove unnecessary whitespace for i…
PlugFox Nov 11, 2025
3a0c8a7
Remove race condition test: Delete test_race_condition.dart as it is …
PlugFox Nov 11, 2025
2ccbfb8
Add mutex tests: Implement comprehensive unit tests for Mutex functio…
PlugFox Nov 11, 2025
4bfba5c
Add library declaration and timeout for mutex tests
PlugFox Nov 11, 2025
7281c6b
Increase timeout duration for mutex tests to improve reliability
PlugFox Nov 11, 2025
1be520c
Update version and environment constraints in pubspec.yaml
PlugFox Feb 6, 2026
266df05
Refactor concurrency handling in Controller
PlugFox Feb 6, 2026
822c855
format
PlugFox Feb 6, 2026
677c92e
Update dependencies and environment constraints in pubspec.yaml and p…
PlugFox Feb 6, 2026
6512986
Enhance handle<T>() method to support type-safe return values across …
PlugFox Feb 6, 2026
45208de
Add select method tests for concurrent controller with various scenarios
PlugFox Feb 6, 2026
379e9e0
Remove unused parameter warning in _SimpleTestObserver constructor
PlugFox Feb 6, 2026
42f17c5
Update Flutter version to 3.38.0 in setup actions and workflows
PlugFox Feb 6, 2026
be0af6e
Update Dart SDK constraint to support version 3.9.0 in pubspec files
PlugFox Feb 6, 2026
dbb4abe
Refactor _FakeControllerBase to remove 'base' keyword for Dart compat…
PlugFox Feb 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/actions/setup/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ description: Sets up the Flutter environment

inputs:
flutter-version:
description: 'The version of Flutter to use'
description: "The version of Flutter to use"
required: false
default: '3.24.3'
default: "3.38.0"
pub-cache:
description: 'The name of the pub cache variable'
description: "The name of the pub cache variable"
required: false
default: control

Expand All @@ -32,7 +32,7 @@ runs:
- name: 🚂 Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '${{ inputs.flutter-version }}'
flutter-version: "${{ inputs.flutter-version }}"
channel: "stable"

- name: 📤 Restore Pub modules
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/checkout.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
- name: 🚂 Setup Flutter and dependencies
uses: ./.github/actions/setup
with:
flutter-version: 3.24.3
flutter-version: 3.38.0

- name: 👷 Install Dependencies
timeout-minutes: 1
Expand Down Expand Up @@ -76,7 +76,7 @@ jobs:

- name: 📥 Upload test report
uses: actions/upload-artifact@v4
if: (success() || failure()) && ${{ github.actor != 'dependabot[bot]' }}
if: ${{ (success() || failure()) && github.actor != 'dependabot[bot]' }}
with:
name: test-results
path: reports/tests.json
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ log.pana.json
coverage/
/test/**/*.json
/test/.test_coverage.dart
reports/

# Temp
/tmp
Expand All @@ -50,4 +51,7 @@ coverage/
.fvm/flutter_sdk

# Generated files
*.*.dart
*.*.dart

# LLMS
.claude/
63 changes: 63 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,66 @@
## 1.0.0-dev.1

### Features

- **ADDED**: Generic `handle<T>()` method - now supports return values
- Example: `Future<User> fetchUser(String id) => handle<User>(() async { ... return user; })`
- Type-safe return values from controller operations
- Works with all concurrency strategies (sequential, concurrent, droppable)

### Breaking Changes

- **REMOVED**: `base` modifier from `Controller`, `StateController`, and concurrency handler mixins
- Controllers no longer require `final` or `base` in user code
- Provides more flexibility for inheritance patterns
- **CHANGED**: `Controller.handle()` now has a default concurrent implementation
- No longer abstract - controllers can be used without choosing a concurrency mixin
- Operations execute concurrently by default
- Provides zone for error catching, HandlerContext, observer notifications, and callbacks
- **CHANGED**: Concurrency handler mixins simplified to wrap `super.handle()` with `Mutex`
- `SequentialControllerHandler` - wraps handle with mutex for sequential execution
- `DroppableControllerHandler` - wraps handle with mutex and drops new operations if busy
- `ConcurrentControllerHandler` - **deprecated** (base behavior is already concurrent)
- **CHANGED**: `Controller.handle()` signature now includes `error` and `done` callbacks
- Before: `handle(handler, {name, meta})`
- After: `handle<T>(handler, {error, done, name, meta})`
- **CHANGED**: `DroppableControllerHandler` now returns `null` when dropping operations
- Handle method returns `Future<T?>` to support nullable return values
- Dropped operations return `null` instead of throwing errors
- This is the expected behavior for droppable operations

### Added

- **ADDED**: Base `handle()` implementation in `Controller` class
- Concurrent execution by default
- Zone-based error catching (including unawaited futures)
- HandlerContext for debugging
- Observer notifications
- Error and done callbacks
- **ADDED**: `Mutex` is now the core primitive for concurrency control
- Can be used directly in controllers for custom concurrency patterns
- Sequential and droppable mixins built on top of Mutex

### Improved

- **IMPROVED**: Reduced code complexity
- Concurrency handler mixins reduced from ~300 lines to ~90 lines
- Removed `_ControllerEventQueue` class (no longer needed)
- Simplified architecture easier to understand and maintain
- **IMPROVED**: More flexibility in concurrency control
- Use mixins for simple cases (sequential/droppable)
- Use Mutex directly for custom patterns
- Mix and match strategies in the same controller

### Migration Guide

See [MIGRATION.md](MIGRATION.md) for detailed migration instructions from 0.x to 1.0.0.

**Quick migration:**
- Remove `base` from your controller classes
- `ConcurrentControllerHandler` can be removed (controllers are concurrent by default)
- `SequentialControllerHandler` and `DroppableControllerHandler` work the same way
- For custom concurrency, use `Mutex` directly instead of creating custom mixins

## 0.2.0

- **ADDED**: `HandlerContext` to handlers, available at zone and observer.
Expand Down
270 changes: 270 additions & 0 deletions IDEAS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
# Future Improvements and Ideas

This document contains ideas and recommendations for future improvements to the Control library.

## Architecture Improvements

### Phase 1: MVP (Implemented in v1.0.0)

#### 1. Remove `base` Modifiers ✅
**Problem:** The `base` modifier forces users to use `final` or `base` on their controller classes, which creates unnecessary restrictions.

**Solution:** Remove `base` from:
- `Controller` class
- `StateController` class
- Concurrency handler mixins

**Benefits:**
- More flexibility for users
- No forced inheritance patterns
- Simpler API

#### 2. Base `handle()` Implementation ✅
**Problem:** Currently `handle()` is abstract in `Controller`, forcing users to choose a concurrency strategy via mixins.

**Solution:** Implement base `handle()` in `Controller` with concurrent behavior by default. The method provides:
- Zone for error catching (including unawaited futures)
- HandlerContext for debugging
- Observer notifications
- error/done callbacks
- isProcessing tracking

**Benefits:**
- No forced mixin selection
- Concurrent by default (most common case)
- Mixins become optional

#### 3. Simplify Concurrency Handler Mixins ✅
**Problem:** Current mixins have ~100 lines each with duplicated error handling logic.

**Solution:** Make mixins simple wrappers around `super.handle()` + `Mutex`:

```dart
mixin SequentialControllerHandler on Controller {
final Mutex _$mutex = Mutex();

@override
Future<void> handle(...) =>
_$mutex.synchronize(() => super.handle(...));
}
```

**Benefits:**
- Reduces code from ~300 lines to ~90 lines
- Eliminates duplication
- Mixins are now just 10-15 lines each
- Easier to understand and maintain

### Phase 2: Enhancements

#### 4. Generic `handle<T>()` ✅ **(Implemented in v1.0.0-dev.1)**
**Status:** Implemented

**Implementation:**

```dart
Future<T> handle<T>(Future<T> Function() handler, {...});

// Usage
Future<User> fetchUser(String id) => handle<User>(() async {
final user = await api.getUser(id);
setState(state.copyWith(user: user));
return user; // Can return values!
});
```

**Benefits:**
- ✅ More flexible API
- ✅ Better composition
- ✅ Type-safe return values
- ✅ Works with all concurrency strategies

**Breaking Change:** `DroppableControllerHandler` now throws `StateError` when operations are dropped instead of silently completing.

#### 5. `tryLock()` Method for Mutex ⭐
**Proposal:** Add non-blocking lock attempt:

```dart
abstract class Mutex {
/// Attempts to acquire lock without waiting
/// Returns unlock function if successful, null if already locked
void Function()? tryLock();
}

// Usage - droppable pattern without mixin
void operation() {
final unlock = _mutex.tryLock();
if (unlock == null) return; // Already running, drop
try {
// critical section
} finally {
unlock();
}
}
```

**Benefits:**
- Enables droppable pattern without mixin
- More control over locking behavior
- No waiting if lock unavailable

#### 6. `isIdle` Getter ⭐
**Proposal:** Add convenience getter:

```dart
abstract class Controller {
bool get isProcessing;
bool get isIdle => !isProcessing; // Opposite of isProcessing
}
```

**Benefits:**
- More readable in UI code
- Natural language

#### 7. Extension Methods 💡
**Proposal:** Add convenience extensions:

```dart
extension MutexControllerExtension on Mutex {
Future<void> handleWith(
Controller controller,
Future<void> Function() handler, {...}
) => synchronize(() => controller.handle(handler, ...));
}

// Usage
void operation() => _mutex.handleWith(this, () async {...});
```

**Benefits:**
- Shorter, more readable code
- Composable helpers

#### 8. Debounce/Throttle Utilities 💡
**Proposal:** Add common patterns:

```dart
class ControllerUtils {
static Future<void> Function() debounce(
Duration duration,
Future<void> Function() action,
) {...}

static Future<void> Function() throttle(
Duration duration,
Future<void> Function() action,
) {...}
}

// Usage
class SearchController extends StateController<SearchState> {
late final _debouncedSearch = ControllerUtils.debounce(
const Duration(milliseconds: 300),
_performSearch,
);

void search(String query) => handle(_debouncedSearch);
}
```

**Benefits:**
- Common use cases covered
- Less boilerplate
- Reusable patterns

### Phase 3: Polish (Future)

#### 9. Integration Tests
Add comprehensive integration tests for:
- Concurrent behavior
- Sequential behavior
- Droppable behavior
- Mixed strategies
- Error handling across all strategies

#### 10. Performance Benchmarks
Add benchmarks comparing:
- Different concurrency strategies
- Mutex implementations
- Handler overhead

#### 11. Enhanced Documentation
- Add dartdoc examples to all public APIs
- Create cookbook with common patterns
- Add diagrams showing concurrency flows
- Video tutorials

#### 12. Performance Optimizations
- Cache `isProcessing` flag in sequential handler
- Optimize zone creation
- Consider object pooling for handler contexts

## API Stability

### Stable APIs (keep unchanged)
- `StateController.state`
- `StateController.setState()`
- `Controller.addListener()`
- `Controller.dispose()`
- `Mutex.lock()`
- `Mutex.synchronize()`

### Evolving APIs (may change)
- Handler mixins (simplified in 1.0.0)
- `handle()` signature (may become generic)
- Observer interface (may add more hooks)

## Breaking Changes for 1.0.0

### Removed
- `base` modifiers on classes and mixins

### Changed
- `Controller.handle()` now has a default implementation (concurrent)
- Concurrency handler mixins simplified (now just wrap `super.handle()` + mutex)
- `ConcurrentControllerHandler` is now redundant (base behavior)

### Migration
See MIGRATION.md for detailed migration guide from 0.x to 1.0.0

## Naming Considerations

### Current Names (Keep)
- `handle()` - short, clear, conventional
- `Controller` - standard pattern name
- `StateController` - descriptive
- `Mutex` - well-known CS term

### Potential Alternatives (Not Recommended)
- `handle()` → `execute()` / `runHandler()` - too verbose
- `Controller` → `Bloc` / `Manager` - different patterns
- `Mutex` → `Lock` - less precise

## Questions for Community

1. Should `handle()` be generic `<T>` or stay `<void>`?
2. Is `tryLock()` needed or is checking `locked` sufficient?
3. Should debounce/throttle be in core or separate package?
4. What other concurrency patterns are needed? (semaphore, rwlock, etc.)

## References

- [Mutex tests](test/unit/mutex_test.dart) - comprehensive test coverage
- [Controller tests](test/unit/state_controller_test.dart) - concurrency tests
- [Example app](example/lib/main.dart) - real-world usage

## Contributing

Feel free to:
- Open issues discussing these ideas
- Submit PRs implementing phase 2/3 features
- Share your use cases and patterns
- Suggest new ideas

---

**Last Updated:** 2026-02-06
**Status:**
- Phase 1 (MVP) - ✅ Implemented in v1.0.0-dev.1
- Phase 2 Item 4 (Generic handle) - ✅ Implemented in v1.0.0-dev.1
Loading