ホーム画面のタップから DB・クラウド保存まで、すべての工程を省略なく図解
1 ホーム画面でルーティンをタップ
やっていること:
routineListProvider でルーティン一覧を表示(21行目)_onRoutineTap() が呼ばれる(100行目)ref.read(activeWorkoutProvider.notifier).startWorkout(data) を実行(119行目)Navigator.push で WorkoutScreen に画面遷移(121行目)RoutineWithExercisesフリートレーニングの場合(127-141行目):
showExerciseSelectSheet() で種目選択シートを表示startFreeWorkout(selected) に渡す2 状態の初期化(メモリ上にワークアウトを構築)
処理の流れ:
ActiveWorkoutState を作り、各種目に3セットの空の SetData を用意_repository.getLastWorkoutSets(exerciseId) → 前回のセット_repository.getPersonalBest1RM(exerciseId) → 過去最高の推定1RMcopyWith)。これにより「いつの間にか値が変わっていた」というバグを防ぐ。3つの入れ子構造:
| クラス名 | 役割 | 含むデータ |
|---|---|---|
SetData | 1セット分 | weight, reps, isPR + computed: hasData, estimated1rm |
ActiveExerciseData | 1種目分 | exerciseId, name, sets[], previousSets[], personalBest1rm |
ActiveWorkoutState | ワークアウト全体 | routineId, routineName, startedAt, exercises[], currentExerciseIndex |
WorkoutSummary | 完了時サマリー | routineName, date, durationSeconds, totalVolume, exerciseCount, totalSets, prCount |
computed プロパティ(自動計算):
3 前回記録の取得(DB → メモリ)
SQL的にやっていること:
4 ワークアウト画面で種目を確認
画面の構成要素:
種目カードタップ時:
5 種目詳細画面で重量・回数を入力
画面の構成要素:
入力時のデータの流れ:
「前回の値を入力」ボタン:
その他の操作:
updateReps() — 回数を更新(346-354行目)addSet() — セット追加(384-388行目)removeSet() — セット削除(276-282行目)reorderSet() — セット並べ替え(297-306行目)6 「トレーニング完了」をタップ ─ PR判定
PR判定の流れ:
_repository.getPersonalBest1RM(exerciseId) を呼ぶestimated1rm を計算weight * (1 + reps / 30)(Epley式)isPR = true7 サマリーデータの組み立て
8 Repository でDB形式に変換
処理の流れ:
workoutId = uuid.v4() — ユニークなIDを発行durationSeconds = now - startedAthasData チェック(空セットは保存しない!)変換マッピング(メモリ → DB):
| メモリ上(ドメイン) | → | DB形式(Companion) |
|---|---|---|
| ActiveWorkoutState | → | WorkoutsCompanion |
| ActiveExerciseData | → | WorkoutExercisesCompanion |
| SetData | → | WorkoutSetsCompanion |
フィールド対応の詳細:
| メモリ | → | DB | テーブル |
|---|---|---|---|
| (自動生成) | → | id (UUID) | workouts |
| state.routineName | → | routineName | workouts |
| state.routineId | → | routineId (FK) | workouts |
| userId (引数) | → | userId (FK) | workouts |
| state.startedAt | → | date | workouts |
| now - startedAt | → | durationSeconds | workouts |
| state.totalVolume | → | totalVolume | workouts |
| state.totalFilledSetCount | → | totalSets | workouts |
| prCount集計 | → | prCount | workouts |
| (自動生成) | → | id (UUID) | workout_exercises |
| exercise.name | → | exerciseName | workout_exercises |
| exercise.exerciseId | → | exerciseId (FK) | workout_exercises |
| ループ変数 i | → | sortOrder | workout_exercises |
| (自動生成) | → | id (UUID) | workout_sets |
| setData.weight | → | weight | workout_sets |
| setData.reps | → | reps | workout_sets |
| setData.estimated1rm | → | estimated1rm | workout_sets |
| setData.isPR | → | isPR | workout_sets |
9 SQLite にトランザクションで一括保存
3つのテーブルに順番に INSERT:
10 バックグラウンドでクラウド同期
条件: userId != null かつ syncService != null の場合のみ実行
ログインしていないユーザーはローカル保存のみで同期しない。
処理の流れ:
unawaited() でバックグラウンド実行(123行目)syncService.syncUser() でユーザーを登録syncService.syncWorkout() でワークアウトを同期3ステップの同期:
Step 1: Workout → Session(39-49行目)
| ローカル (SQLite) | → | リモート (PostgreSQL) |
|---|---|---|
| workout.id | → | Session.id |
| workout.routineName | → | Session.routineName |
| workout.date | → | Session.date (Timestamp) |
| workout.durationSeconds | → | Session.durationSeconds |
| workout.createdAt | → | Session.createdAt (Timestamp) |
| workout.routineId | → | Session.routineId (optional) |
Step 2: WorkoutExercise → SessionExercise(52-63行目)
| ローカル (SQLite) | → | リモート (PostgreSQL) |
|---|---|---|
| ex.id | → | SessionExercise.id |
| ex.workoutId | → | SessionExercise.sessionId |
| ex.exerciseName | → | SessionExercise.exerciseName |
| ex.sortOrder | → | SessionExercise.sortOrder |
| ex.exerciseId | → | SessionExercise.exerciseId (optional) |
Step 3: WorkoutSet → SetRecord(66-78行目)
| ローカル (SQLite) | → | リモート (PostgreSQL) |
|---|---|---|
| set.id | → | SetRecord.id |
| set.workoutExerciseId | → | SetRecord.sessionExerciseId |
| set.setNumber | → | SetRecord.setNumber |
| set.weight | → | SetRecord.weight |
| set.reps | → | SetRecord.reps |
| set.estimated1rm | → | SetRecord.estimated1rm (optional) |
提供されるメソッド:
syncSession() → Session テーブルに upsertsyncSessionExercise() → SessionExercise テーブルに upsertsyncSetRecord() → SetRecord テーブルに upsertupsertUser() → User テーブルに upsertupsert とは: そのIDのデータがなければ INSERT、あれば UPDATE。つまり「あれば上書き、なければ新規作成」。同じワークアウトを2回同期しても重複しない。
最終的な保存先:
11 完了画面の表示とメモリクリア
completeWorkout() の最後:
戻り値の WorkoutSummary は WorkoutExerciseScreen で受け取り、WorkoutSummaryScreen に渡して完了画面を表示する。
データの最終状態:
+ Provider の依存関係
+ DB テーブル関係(ER図)
+ 画面遷移フロー
以上が、タップから保存までの全工程です
各ステップをタップすると詳細が開きます
筋トレメモ データフロー完全図解 — 2026-03-28