辞書データと正解文字列
まずは辞書データと正解文字列の状態管理をRiverpodで行っていきましょう。entities/correct_answer.dart
の内容を以下のように変更してください。
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'correct_answer.g.dart';
(keepAlive: true)
List<String> wordDictionary(WordDictionaryRef ref) => [];
(keepAlive: true)
class CorrectAnswer extends _$CorrectAnswer {
String build() => '';
void update(String value) => state = value;
}
// List<String> wordDictionary = [];
// var correctAnswer = '';
今まで使っていたグローバル変数の wordDictionary
をここではコメントアウトしていますが、実際にコメントアウトするとコンパイルエラーになるかと思います。
これはProviderを生成すると wordDictionary
というシンボルが衝突する(Providerの元になる関数とグローバル変数)ためです。途中でアプリを実行できる状態を保ちたい場合は、適宜 wordDictionary
をリネームしてください。VS Codeの機能を使ってリネームすると、変数の利用箇所も自動的に変更されるので便利です(右クリックのメニューから「シンボルの名前変更」を選択してください)。
本書では riverpod_generator
を活用してProviderを作っていきます。ただ上のコードを書いただけでは機能せず、Providerをコマンドにより生成する必要があります。
ターミナルでプロジェクトのルートディレクトリに移動して、以下のコマンドを実行してください。
dart run build_runner watch
build_runner
の watch
コマンドは、一度実行しておくとファイルの変更を検知して必要に応じて成果物を自動生成してくれます。
もしくはVS Codeの拡張機能を利用しても構いません。
期待通りProviderが生成されたら、次は利用側を変更していきます。
main.dart
を開いて、以下のように変更してください。
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final wordsString = await rootBundle.loadString('assets/words.txt');
final container = ProviderContainer(overrides: [
wordDictionaryProvider.overrideWithValue(wordsString.split('\n')),
]);
_decideAnswer(container);
runApp(
ProviderScope(
parent: container,
child: const App(),
),
);
}
void _decideAnswer(ProviderContainer container) {
final dictionary = container.read(wordDictionaryProvider);
final index = _random.nextInt(dictionary.length);
container.read(correctAnswerProvider.notifier).update(dictionary[index]);
debugPrint('answer: ${dictionary[index]}');
}
wordDictionaryProvider
の宣言部分( wordDictionary()
)では、その実装は []
(空のリストリテラル)としていました。辞書データは不変のため、後から変更できる形にしていないので、アプリ開始時に何もしなければ空のままになってしまいます。
そこで、辞書データをファイルから読み込んだ後、ProviderContainer
を独自に宣言し、overrideを使って実装を上書きします。
ProviderContainer
はFlutterアプリではあまり使いませんが、これをいつもWidgetツリーのルートに置いておく ProviderScope
に渡してやると、中身が継承されるのでそれ以下のツリーから参照できるようになります。
correctAnswer
も correctAnswerProvider
を使うように変更されています。
correctAnswerProvider
は可変のものとして宣言されており、update()
を使って変更することが可能です。これを利用して、正解の選定方法は変えずに状態のみ更新しています。
回答の状態
次に回答の状態を書き換えていきます。 entities/answer_state.dart
を次のように変更してください。
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:wordle_clone/entities/entities.dart';
part 'answer_state.g.dart';
// ...
// 定数、型定義などはそのまま
// ...
int currentCursor = 0;
int answerCount = 0;
// ここから追加
(keepAlive: true)
class CurrentAnswer extends _$CurrentAnswer {
AnswerState build() => createInitialAnswerState();
List<List<AnswerPanelState>> get _stateClone => [
for (final row in super.state) [...row]
];
void update(int answerCount, int cursor, AnswerPanelState newPanel) {
final currentState = _stateClone;
currentState[answerCount][cursor] = newPanel;
state = currentState;
}
void updateRow(int answerCount, AnswerRowState newState) {
final currentState = _stateClone;
currentState[answerCount] = newState;
state = currentState;
}
}
(keepAlive: true)
class AnswerCount extends _$AnswerCount {
int build() => 0;
void increment() => state += 1;
void update(int value) => state = value;
}
(keepAlive: true)
class CurrentCursor extends _$CurrentCursor {
int build() => 0;
void increment() => state += 1;
void update(int value) => state = value;
}
currentCursor
、answerCount
はそのまま可変のProvider currentCursorProvider
answerCountProvider
に置き換えました。
後のロジックのリファクタリングにも影響しますが、これらを更新するときの操作は以下の二通りしか無いので、予めそのようなメソッドを用意しておきます。
- 1だけ増加させる(increment)
- 任意の値に変更する(update)
また、もともと answer_panels.dart
に実態を持っていた全体の回答の状態 answerState
も、currentAnswerProvider
としてここで管理することにしました。
初期化処理は createInitialAnswerState()
をそのまま利用、そして更新処理は以下の二通りを用意しています。
- 任意の行・列に該当する回答の変更(update)
- 任意の行全体の変更(updateRow)