スキル一覧に戻る
pretodev

manageprovidercommand

by pretodev

0🍴 0📅 2026年1月21日
GitHubで見るManusで実行

SKILL.md


name: ManageProviderCommand description: Guidelines for creating and maintaining command providers, which handle mutations and side effects in Flutter/Riverpod.

Commands are use cases that handle mutations and side effects. They connect business rules with infrastructure and have triple states: loading, success, and error.

When to Use

  • Perform mutations (create, update, delete)
  • Handle async operations with loading/success/error feedback
  • Connect business rules with infrastructure layers

Location

Path: lib/features/<feature>/providers/<action>_command.dart

CommandMixin Requirement

All commands must use CommandMixin<T> from lib/shared/providers/command_provider_base_mixin.dart.

MethodPurpose
invalidState()Returns error state for initial build
emitLoading()Sets loading state with ref.mounted check
emitData(T data)Sets success state, returns Ok(data)
emitError<E>(E error, [StackTrace?])Sets error state, returns Err(error)
emitResult(Result<T>, [StackTrace?])Handles Result pattern automatically

Mixin Implementation

// lib/shared/providers/command_provider_base_mixin.dart
import 'package:odu_core/odu_core.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

AsyncValue<T> invalidState<T>() => AsyncError<T>(
  StateError('Not called yet'),
  StackTrace.current,
);

mixin CommandMixin<T> on $Notifier<AsyncValue<T>> {
  Result<T> emitError<E extends Exception>(E error, [StackTrace? stackTrace]) {
    if (ref.mounted) {
      state = AsyncError(error, stackTrace ?? StackTrace.current);
    }
    return Err(error, stackTrace ?? StackTrace.current);
  }

  Result<T> emitData(T data) {
    if (ref.mounted) {
      state = AsyncData(data);
    }
    return Ok(data);
  }

  void emitLoading() {
    if (ref.mounted) {
      state = const AsyncLoading();
    }
  }

  Result<T> emitResult(Result<T> result, [StackTrace? stackTrace]) {
    if (ref.mounted) {
      state = switch (result) {
        Ok<T>(value: final data) => AsyncData(data),
        Err<T>(value: final error) => AsyncError(
          error,
          stackTrace ?? StackTrace.current,
        ),
      };
    }
    return result;
  }
}

Structure

@riverpod
class AddExercise extends _$AddExercise with CommandMixin<Exercise> {
  @override
  AsyncValue<Exercise> build() {
    return invalidState();
  }

  Future<void> call(DailyTraining training, Exercise exercise) async {
    emitLoading();
    final repository = ref.read(trainingRepositoryProvider);
    training.addExercise(exercise);
    final result = await repository.store(training).map((_) => exercise);
    emitResult(result);
  }
}

Naming Conventions

ElementConventionExample
File name*_command.dartadd_exercise_command.dart
Class nameAction verbAddExercise, UpdateUser
MethodAlways call()Future<void> call(...)

Return Types

Future<void> - Fire and Forget

Future<void> call(DailyTraining training, Exercise exercise) async {
  emitLoading();
  final result = await repository.store(training).map((_) => exercise);
  emitResult(result);
}

FutureResult<T> - Caller Needs Result

FutureResult<Exercise> call(
  DailyTraining training, {
  required PositionedExercise exercise,
}) async {
  emitLoading();
  training.setExercise(exercise);
  final result = await ref
      .read(trainingRepositoryProvider)
      .store(training)
      .map((_) => exercise.value);
  return emitResult(result);
}

Command with Unit Return Type

@riverpod
class MergeExercises extends _$MergeExercises with CommandMixin<Unit> {
  @override
  AsyncValue<Unit> build() => invalidState();

  Future<void> call(
    DailyTraining training, {
    required List<PositionedExercise> exercises,
  }) async {
    emitLoading();
    training.mergeExercises(exercises);
    final result = await ref
        .read(trainingRepositoryProvider)
        .store(training)
        .map((_) => unit);
    emitResult(result);
  }
}

Maintenance and Modification

When modifying existing commands:

  1. Preserve State Logic: Ensure emitLoading() is called at the start and emitResult() (or equivalent) at the end.
  2. Update Parameters: If changing the call method signature, ensure all call sites are updated.
  3. Refactor Responsibilities: If a command is growing too large or handling multiple distinct actions, split it into separate command providers.
  4. Check Dependencies: When adding new dependencies (e.g., a new repository), use ref.read() within the call method, not ref.watch().

Anti-Patterns

❌ Business Logic in Provider

// BAD: Validation belongs in entity
Future<void> call(DailyTraining training, Exercise exercise) async {
  if (exercise.name.isEmpty) {
    emitError(ValidationException('Name required'));
    return;
  }
  // ...
}

❌ Multiple Responsibilities

// BAD: One action per provider
class ExerciseManager extends _$ExerciseManager with CommandMixin<void> {
  Future<void> add(...) async { ... }
  Future<void> update(...) async { ... }
  Future<void> delete(...) async { ... }
}

❌ Command Without CommandMixin

// BAD: Missing mixin and invalidState()
class AddExercise extends _$AddExercise {
  @override
  AsyncValue<Exercise> build() => AsyncData(Exercise.empty());
}

❌ Manual State Switching

// BAD: Use emitResult() instead
state = switch (result) {
  Ok() => AsyncData(exercise),
  Err(value: final err) => AsyncError(err, StackTrace.current),
};

Code Smells

SmellSolution
Business logic in providerMove to entity domain methods
Multiple actions per providerCreate separate commands
Missing loading stateCall emitLoading()
ref.watch() in methodsUse ref.read()
AsyncData in buildUse invalidState()
Manual state switchingUse emitResult()

Code Generation

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'your_command.g.dart';
dart run build_runner build -d

スコア

総合スコア

50/100

リポジトリの品質指標に基づく評価

SKILL.md

SKILL.mdファイルが含まれている

+20
LICENSE

ライセンスが設定されている

0/10
説明文

100文字以上の説明がある

0/10
人気

GitHub Stars 100以上

0/15
最近の活動

3ヶ月以内に更新がある

0/10
フォーク

10回以上フォークされている

0/5
Issue管理

オープンIssueが50未満

+5
言語

プログラミング言語が設定されている

+5
タグ

1つ以上のタグが設定されている

0/5

レビュー

💬

レビュー機能は近日公開予定です