Flutterでクイズアプリケーションを作成する方法

まず、簡単なクイズアプリケーションを作成する。

![スクリーンショット 2023-06-14 10.46.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3268288/98998cea-d03a-beb9-e9d5-f1c7733411ec.png)
選択肢のリストの項目をクリックすると次の問題に進む。
![スクリーンショット 2023-06-14 10.46.34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3268288/fbdf00cf-f10a-01e9-1255-93d20365c4f8.png)
次の問題が表示される。

ユーザーが選択肢を選んだときの正誤判定や、すべての問題を解き終えたときのフィードバックは本記事では取り扱っていない。

# 1. 問題文、選択肢、正解を保持するための設計（question.dartの作成）
### 1-1. Questionクラスの作成
まず、それぞれの問題、その選択肢、正解を関連付けて保持するためのQuestionクラスを定義する。
```dart
class Question {
  String question;
  List<String> choices;
  int answerIndex;

  Question(this.question, this.choices, this.answerIndex);
}
```
このクラスには、問題文、選択肢リスト、正解のインデックスが含まれる。問題文はString型、選択肢リストはString型のリストであり、正解は選択肢リストの中のインデックスとすることで整数型で保持する。

### 1-2 Questionクラスの使用例
```dart
Question q1 = Question(
 'What is the capital of France?', ['Paris', 'Berlin', 'London', 'Madrid'], 0
);
```
Questionクラスを用いて、上記のように新しい問題を作成する。この例では、「フランスの首都は何か？」という問題文に対し、「パリ、ベルリン、ロンドン、マドリード」の選択肢があり、その中で最初の選択肢（インデックス0）が正解となる。

### 1-3 question.dartの作成
Questionクラスをquestion.dartというファイル名で保存する。


# 2. 問題を表示するシンプルなFlutterアプリケーションの作成(main.dartの作成)
画面に問題と選択肢を表示する簡単なアプリを作成する。これらの問題と選択肢は前述のcreateQuestion関数を使って作成する。

5つの問題をリストに追加し、それらを順番に表示する。選択肢をクリックすると次の問題に進むようにする。5つの問題を順番に表示するために、現在の問題のインデックスを管理する必要がある。

```dart
// main.dart

import 'package:flutter/material.dart';
import 'question.dart'; // Importing Question class 

void main() {
  runApp(QuestionApp());
}

class QuestionApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Question App',
      home: QuizScreen(),
    );
  }
}

class QuizScreen extends StatefulWidget {
  QuizScreen({Key? key}) : super(key: key);

  @override
  _QuizScreenState createState() => _QuizScreenState();
}

class _QuizScreenState extends State<QuizScreen> {
  late List<Question> questions;
  int currentIndex = 0;

  void _incrementCounter(){
    setState((){
      currentIndex = (currentIndex + 1) % questions.length;
    });
  }

  @override
  void initState() {
    super.initState();
    questions = [
      Question('What is the capital of France?', ['Paris', 'Berlin', 'London', 'Madrid'], 0),
      Question('What is the capital of Germany?', ['Paris', 'Berlin', 'London', 'Madrid'], 1),
      Question('What is the capital of UK?', ['Paris', 'Berlin', 'London', 'Madrid'], 2),
      Question('What is the capital of Spain?', ['Paris', 'Berlin', 'London', 'Madrid'], 3),
      Question('What is the capital of Italy?', ['Paris', 'Berlin', 'London', 'Madrid', 'Rome'], 4),
    ];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Question App'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              questions[currentIndex].question,
              style: Theme.of(context).textTheme.headline5,
            ),
            ...questions[currentIndex].choices.map((choice) => ListTile(
                  title: Center(
                    child: Text(choice),
                  ),
                  onTap: _incrementCounter,
                )).toList(),
          ],
        ),
      ),
    );
  }
}

```
ここで、'question.dart'をインポートすることで、Questionクラスが利用できるようになる。

**1. QuestionAppクラス**
これはStatelessWidgetを継承するクラスで、アプリケーションのメインウィジェットとなる。アプリケーションのホーム画面としてQuizScreenを設定している。

**2. QuizScreenクラス**
StatefulWidgetを継承するクラスで、状態（ここでは現在の問題）を持つウィジェットである。

**3. _QuizScreenStateクラス**
QuizScreenウィジェットの状態を管理するクラスである。クイズの問題リスト(questions)と現在の問題のインデックス(currentIndex)を保持する。initStateメソッドによって、初期化時に5つの問題がquestionsリストに追加される。

**3.1. initStateメソッド**
initStateメソッドで5つの問題を作成し、questionsリストに追加する。各問題はcreateQuestion関数を使用して作成され、問題文、選択肢のリスト、正解のインデックスを引数として渡す。

**3.2. buildメソッド**
buildメソッドでは、アプリケーションのUIを定義する。Scaffoldウィジェットを使用してアプリケーションの基本的なレイアウト（AppBarとBody）を作成する。Body部分では現在の問題とその選択肢を表示する。選択肢はListTileウィジェットとして表示され、タップすると次の問題に進むようにonTapコールバックが設定されている。

**3.2.1. Textウィジェット**
Textウィジェットは、現在の問題（questions[currentIndex].question）を表示する。ススタイルはTheme.of(context).textTheme.headline5を適用し、アプリのテーマに従った見た目が適用される。

**3.2.2. 選択肢のリスト**
questions[currentIndex].choices.map((choice) => ListTile(title: Text(choice), onTap: () {...}))という表現は、選択肢を一つずつ取り出し（.map((choice) => ...)）、それぞれの選択肢にListTileウィジェットを適用している。

ListTileウィジェットはリスト内の各項目に適用し、そのタイトルに選択肢のテキストを配置する。

onTap: () {...}は、ユーザーがその選択肢をタップしたときの振る舞いを定義しする。現在は、どの選択肢がタップされても次の問題に移動する。これはsetState関数を用いてcurrentIndexを更新することで実現される。また、% questions.lengthにより質問リストの範囲を超えないようにしている。

**3.2.3. スプレッド演算子...**
 ... と .toList() という表現はスプレッド演算子と呼ばれ、リスト内の要素を別のリストに展開する。これにより、各選択肢が個別のListTileとしてColumnウィジェット内に配置される。

これらを組み合わせることで、質問とその選択肢が画面上に表示され、ユーザーが選択肢をタップすると次の質問に移動するという動作を実現している。