初心者エンジニアによるAndroidアプリ開発日記⑤

# まえがき
前回にレイアウトの大まかな実装を行うことができたので、今回はロジックの大枠を実装していきます。

前回は[こちら](https://qiita.com/hukusima65/items/5cf550cd345718f9d57d)

# 目次
* [概要](#概要)
* [アクティビティ実装概論](#アクティビティ実装概論)
   * [AppCompatActivityクラス](#appcompatactivityクラス)
   * [Rクラス](#rクラス)
* [実装：manifests](#実装manifests)
* [実装：TitleActivity](#実装titleactivity)
* [実装：QuizActivity](#実装quizactivity)
* [実装：IncorrectActivity](#実装incorrectactivity)
* [国旗リソース](#国旗リソース)
* [動作確認](#動作確認)
* [終わりに](#終わりに)

---

## 概要
この記事では、アプリ全体のロジックの実装内容を記載する。

## アクティビティ実装概論
ロジックの実装はkotlin+javaフォルダ内に作成します。Androidアプリ開発において、ロジックは**アクティビティ**と呼ばれ、アプリの基本的な挙動をそのメソッド群で保証する**AppCompatActivityクラス**を継承したインスタンスとして定義されます。

### AppCompatActivityクラス
AppCompatActivityクラスのメソッド群は**ライフサイクル**と呼ばれ、以下のようなものがあります。

* onCreate()
* onStart()
* onResume()
* onPause()
* onStop()
* onRestart()
* onDestroy()

それぞれ説明を記載しようと思いましたが、[こちらの記事](https://qiita.com/t_nana/items/cd66117d3424e045d084#:~:text=Activity%E3%81%AE%E9%81%B7)をそのままコピペすることになりそうです。わかりやすいので、そちらに目を通してもらえればと思います。

>とはいえ、今回登場するのはonCreate()のみです。

### Rクラス
画像や画面のXMLなどが配置されているresフォルダの中身は、アクティビティでは**Rクラス**というクラスで管理されます。

## 実装：manifests
タイトル画面、クイズ画面、不正解画面のアクティビティは、それぞれTitleActivity.kt、QuizActivity.kt、IncorrectActivity.ktというファイルで定義することとしたので、その設定をAndroidManifest.xmlに記述します。具体的には以下を、applicationタグ内に記述します。

```xml
        <activity
            android:name=".TitleActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity
            android:name=".QuizActivity"
            android:exported="true" />

        <activity
            android:name=".IncorrectActivity"
            android:exported="true" />
```
intent-filterタグがあるとアプリ起動時に実行されるアクティビティとなるので、TitleActivityのみこれをもちます。

## 実装：TitleActivity
```app/kotlin+java/com.example.flagquizapp/TitleActivity.kt
package com.example.flagquizapp

import android.content.Intent
import android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity

class TitleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_title)

        // スタートボタンをクリックしたときにクイズ画面へ遷移
        val startButton = findViewById<Button>(R.id.startButton)
        startButton.setOnClickListener {
            // クイズ画面（MainActivity）へ遷移
            val intent = Intent(this, QuizActivity::class.java)
            startActivity(intent)
        }
    }
}
```
packageやimportはテンプレにもともとあるかと思います。ライブラリなどを使用する際にはimportに追記する必要がありますが、タイトル画面では不要なのでテンプレのままで問題ないでしょう。 <br>

TitleActivityはAppCompatActivityの子クラスであり、AppCompatActivityのメソッドであるonCreate()をoverride(上書き)しています。 <br>

savedInstanceStateとはBundle型の値であり、その名の通りActivityの状態を保持するためのもので、Createされたタイミングではnullです。

## 実装：QuizActivity
ここの実装にはChatGPTにかなり頼りました。このアクティビティでは国旗画像や国名を使用するため、先に[resフォルダを実装](#その他の実装)してからQuizActivityの実装に取り掛かりました。なお、画像表示にはGlideというライブラリを使用しようとしています。

>なお、[動作確認](#動作確認)では画像が表示されませんでした…。ライブラリがうまく使えていないことが原因ではないかと考えています。

```app/kotlin+java/com.example.flagquizapp/QuizActivity.kt
package com.example.flagquizapp

import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import kotlin.random.Random
import com.bumptech.glide.Glide

class QuizActivity : AppCompatActivity() {
    // 国旗のURLと国名を格納する配列を宣言
    private lateinit var flagUrls: Array<String>
    private lateinit var countryNames: Array<String>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_quiz)

        // リソースから国旗のURLと国名を読み込む
        flagUrls = resources.getStringArray(R.array.flag_urls)
        countryNames = resources.getStringArray(R.array.country_names)

        // クイズ用にランダムで1つの国旗を選択
        val correctIndex = Random.nextInt(flagUrls.size)
        val correctFlagUrl = flagUrls[correctIndex]
        val correctCountryName = countryNames[correctIndex]

        // Glideを使って国旗画像を表示
        val flagImage: ImageView = findViewById(R.id.flagImage)
        Glide.with(this)
            .load(correctFlagUrl)
            .into(flagImage)

        // 1つ正解と3つのランダムな不正解を選択肢として準備
        val options = generateOptions(correctIndex)

        // ボタンに選択肢をセット
        val option1: Button = findViewById(R.id.option1)
        val option2: Button = findViewById(R.id.option2)
        val option3: Button = findViewById(R.id.option3)
        val option4: Button = findViewById(R.id.option4)

        option1.text = options[0]
        option2.text = options[1]
        option3.text = options[2]
        option4.text = options[3]

        // ボタンがクリックされたときの処理
        option1.setOnClickListener { checkAnswer(options[0], correctCountryName) }
        option2.setOnClickListener { checkAnswer(options[1], correctCountryName) }
        option3.setOnClickListener { checkAnswer(options[2], correctCountryName) }
        option4.setOnClickListener { checkAnswer(options[3], correctCountryName) }
    }

    // 正解1つとランダムな不正解3つを含む選択肢を生成する関数
    private fun generateOptions(correctIndex: Int): List<String> {
        val options = mutableListOf<String>()
        options.add(countryNames[correctIndex])  // 正解を追加

        // ランダムな不正解を3つ追加
        while (options.size < 4) {
            val randomIndex = Random.nextInt(countryNames.size)
            if (randomIndex != correctIndex && !options.contains(countryNames[randomIndex])) {
                options.add(countryNames[randomIndex])
            }
        }

        // 選択肢の順番をシャッフル
        options.shuffle()
        return options
    }

    // 回答が正解かどうかを判定する関数
    private fun checkAnswer(selectedAnswer: String, correctAnswer: String) {
        if (selectedAnswer == correctAnswer) {
            // 正解なら次のクイズ画面へ移動
            startActivity(Intent(this, QuizActivity::class.java))
        } else {
            // 不正解なら不正解画面へ移動
            val intent = Intent(this, IncorrectActivity::class.java)
            startActivity(intent)
        }
    }
}
```

importでは、4択の選択肢作成のためのRandomと今回（おそらく）うまく使えなかったGlideを追記しています。 <br>

QuizActivityでは、まずランダムに正解の国名と国旗画像を取得し、選択肢はランダムで4つのボタンのうち1つに割り当てます。 <br>

国旗画像の表示はGlideを用いていますが、現状うまくいっていません…。対応中です。 <br>

正解の選択肢を設定したのち、不正解となる選択肢を3つ追加で用意し、選択肢をシャッフルします。

## 実装：IncorrectActivity
TitleActivityと本質的に同じです。今回はひとまず大枠を実装したいので、スコア表示は一旦後回しです。

```app/kotlin+java/com.example.flagquizapp/IncorrectActivity.kt
package com.example.flagquizapp

import android.content.Intent
import android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity

class IncorrectActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_incorrect)

        // タイトルへ戻るボタンをクリックしたときにクイズ画面へ遷移
        val startButton = findViewById<Button>(R.id.returnToTitleButton)
        startButton.setOnClickListener {
            // タイトル画面（TitleActivity）へ遷移
            val intent = Intent(this, TitleActivity::class.java)
            startActivity(intent)
        }
    }
}
```

## 国旗リソース
国旗の画像は[外務省のHP](https://www.mofa.go.jp/mofaj/kids/kokki/list.html)から拝借します。著作権的なことは気にしなくてよいみたいでした（[参考](https://www.mofa.go.jp/mofaj/comment/faq/mofa/hp.html#04)）。外務省HPのHTMLを覗くと画像のURLを確認できるので、今回は画像の登録をURLによって行います。国名と国旗URLは対応して並んでいます。

```app/res/values/countrynames.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="country_names">
        <item>インド共和国</item>
        <item>インドネシア共和国</item>
        <item>カンボジア王国</item>
                        :
        <item>レソト王国</item>
    </string-array>
</resources>
```

```app/res/values/flagurls.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="flag_urls">
        <item>https://www.mofa.go.jp/mofaj/kids/img/flags/in.svg</item>
        <item>https://www.mofa.go.jp/mofaj/kids/img/flags/id.svg</item>
        <item>https://www.mofa.go.jp/mofaj/kids/img/flags/kh.svg</item>
                                            :
        <item>https://www.mofa.go.jp/mofaj/kids/img/flags/ls.svg</item>
    </string-array>
</resources>
```
>この辺りはPythonの力に頼りました。

## 動作確認
動作確認の前に、activity_quiz.xmlでImageViewが存在しないsample国旗を参照しているので、その部分のみ削除しておきます。
▷ボタンによりエミュレータが起動し、無事タイトル画面が表示されましたが…

![スクリーンショット 2024-10-03 045954.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3890153/9176043b-f5ac-72ed-74ae-d19bb849594e.png)

クイズ画面にて国旗画像が表示されませんでした。国旗表示に用いているGlideがうまく使えていないのだと思います。

## 終わりに
今回ロジックの大枠の実装を行いまいたが、国旗の画像を表示させることができませんでした。現在ライブラリの設定を見直しているところなので、うまくいったところでそれを次回の内容としたいと思います（場合によってはGlideを使わないかもしれませんが…）。 <br>

ロジック実装の残りは、①国旗画像の表示、②不正解画面でのスコア表示となりそうです。

次回は[こちら](https://qiita.com/hukusima65/items/b91e39301bb3640a9ea7)

### 参考
* [https://qiita.com/t_nana/items/cd66117d3424e045d084#:~:text=Activity%E3%81%AE%E9%81%B7](https://qiita.com/t_nana/items/cd66117d3424e045d084#:~:text=Activity%E3%81%AE%E9%81%B7)
* [http://androidhacker.blog94.fc2.com/blog-entry-13.html](http://androidhacker.blog94.fc2.com/blog-entry-13.html)
* [https://appdev-room.com/android-bundle#:~:text=savedInsta](https://appdev-room.com/android-bundle#:~:text=savedInsta)