BETA

Kotlin入門 おみくじアプリを作ってみた

投稿日:2020-08-30
最終更新:2020-08-30

簡単なアプリを作りました

アプリを起動時 ボタンを押すとおみくじ結果画面に遷移します

おみくじ結果画面 3秒ほど「Fortune Telling...」と表示され、結果が表示されます
 
githubで公開しています Kotlin学習にどうぞ!
https://github.com/butorisa/fortune-app

※画像には表示されていませんが、利用しているAPIのリンク・占い提供元のリンクを3枚目画面の下部に表示するようにしました

アプリの概要

  • MainActivity
    • ActionBar(正確にはToolBar)のみ
    • 画面部分はFragmentで描画
  • EntryFragment
    • 起動時の画面(1枚目)
    • MainActivityの上から描画
    • FortuneResultFragmentに遷移するボタンを保持
  • FortuneResultFragment
    • おみくじ結果描画画面(2、3枚目)
    • 画面描画時に占いAPIを実行して、結果の12星座運勢からランダムな結果を表示
    • API実行はCoroutineで非同期実行
    • おみくじ結果が表示されるまで[GO BACK]ボタンは動作しない
    • ボタン押下で前画面へ戻る(EntryFragment指定はしていない)
  • FortuneResultBean
    • 占いAPIレスポンスをマッピングするクラス
  • RestApiClient
    • 占いAPI呼び出しクラス(バックエンド)
    • 引数にURLを取り、APIを実行、レスポンスjson(String型)を返す
    • BeanクラスへのマッピングはRestApiClient呼び出し元で行う

MainActivity

MainActivityはActionBarしかない空っぽの画面なのでEntryFragmentを表示するだけ
アプリテーマを「NoActionBar」にしているのでActionBarの設定コードを書いている

    /**  
     * Activity描画  
     */  
    @RequiresApi(Build.VERSION_CODES.O)  
    override fun onCreate(savedInstanceState: Bundle?) {  
        super.onCreate(savedInstanceState)  
        setContentView(R.layout.activity_main)  
        // actionbar  
        val toolbar = findViewById<Toolbar>(R.id.toolbar_fortune)  
        toolbar.title = "pretty chipper"  
        setSupportActionBar(toolbar)  

        // EntryFragment描画  
        val transaction = supportFragmentManager.beginTransaction()  
        transaction.addToBackStack(null)  
        transaction.replace(R.id.container, EntryFragment())  
        transaction.commit()  
    }  

EntryFragment

inflater.inflate() でFragmentのxmlファイルをバインド
ボタンに押下時の処理を設定

    /**  
     * EntryFragment描画  
     */  
    override fun onCreateView(  
        inflater: LayoutInflater,  
        container: ViewGroup?,  
        savedInstanceState: Bundle?  
    ): View? {  
        super.onCreateView(inflater, container, savedInstanceState)  
        val view = inflater.inflate(R.layout.fragment_entry, container, false)  

        // ボタン押下でFortuneResultFragmentに移動  
        view?.findViewById<Button>(R.id.button_telling_fortune)?.setOnClickListener {  
            val transaction = fragmentManager?.beginTransaction()  
            transaction?.addToBackStack(null)  
            transaction?.replace(R.id.container, FortuneResultFragment())  
            transaction?.commit()  
        }  
        return view  
    }  

FortuneResultFragment

onCreateView()で画面描画時にCoroutineを使って非同期で占いAPIを実行
APIのURLには「yyyy/MM/dd」形式で現在日付をつけると本日の占い結果が返却される
APIレスポンスが返ってきたら、TextViewに占い結果を設定
レスポンスjsonを全てgetJSONObject()で取得するのは面倒なので、12星座の運勢のレスポンス部分だけBeanクラスを作りマッピング

    /**  
     * FortuneResultFragment描画  
     */  
    @RequiresApi(Build.VERSION_CODES.O)  
    override fun onCreateView(  
        inflater: LayoutInflater,  
        container: ViewGroup?,  
        savedInstanceState: Bundle?  
    ): View? {  
        super.onCreateView(inflater, container, savedInstanceState)  
        val view = inflater.inflate(R.layout.fragment_fortune_result, container, false)  

        /* Coroutine start */  
        this.lifecycleScope.launch {  
            // call API  
            withContext(Dispatchers.Default) {  
                val url = "http://api.jugemkey.jp/api/horoscope/free/" + getCurrentDate()  
                RestApiClient().requestGet(url)  
            }.let {  
                delay(2000)  
                // mapping json to bean  
                val fortuneResult = parseJson(getCurrentDate(), it)  
                // set TextView  
                view?.findViewById<TextView>(R.id.fortune_content)?.text =  
                    fortuneResult.get((0..11).random())?.content // TODO ランダムな星座を表示  

                // ボタン押下で前画面へ移動  
                view.findViewById<Button>(R.id.button_back_to_entry).setOnClickListener {  
                    fragmentManager?.popBackStack()  
                }  
            }  
        }  
        /* Coroutine end */  
        return view  
    }  

    /**  
     * 現在日付取得  
     * @return 現在日付(yyyy/MM/dd)  
     */  
    @RequiresApi(Build.VERSION_CODES.O)  
    fun getCurrentDate(): String {  
        val today = LocalDate.now()  
        val dateFormatter = DateTimeFormatter.ofPattern("yyyy/MM/dd")  
        return dateFormatter.format(today)  
    }  

    /**  
     * json→FortuneResultBean変換  
     * @param today:現在日付(yyyy/MM/dd)  
     * @param strJson:レスポンスjson  
     * @return 運勢一覧(FortuneResultBeanリスト)  
     */  
    private fun parseJson(today: String, strJson: String): List<FortuneResultBean> {  
        val json = JSONObject(strJson)  
        // remove "\" from json-property  
        val strResult = json.getJSONObject("horoscope").getString(today.replace("\\", ""))  
        return ObjectMapper().readValue(strResult, object: TypeReference<List<FortuneResultBean>>(){})  
    }  

FortuneResultBean

占いAPIの運勢部分のjsonをマッピングするBeanクラス
このBeanクラスを12星座分のリストにして受け取る

/**  
 * 占いAPIレスポンスBean  
 */  
class FortuneResultBean {  
    @JsonProperty("content")  
    var content: String? = null  

    @JsonProperty("money")  
    var money: String? = null  

    @JsonProperty("job")  
    var job: String? = null  

    @JsonProperty("love")  
    var love: String? = null  

    @JsonProperty("total")  
    var total: String? = null  

    @JsonProperty("item")  
    var item: String? = null  

    @JsonProperty("color")  
    var color: String? = null  

    @JsonProperty("day")  
    var day: String? = null  

    @JsonProperty("rank")  
    var rank: String? = null  

    @JsonProperty("sign")  
    var sign: String? = null  
}  

RestApiClient

OkHttp3を利用してGETリクエスト レスポンスボディをString型のまま呼び出し元へ返す

    /**  
     * GETリクエスト  
     * @param APIリクエストURL  
     * @param リクエストパラメータ  
     * @return APIレスポンス(String)  
     */  
    @RequiresApi(api = Build.VERSION_CODES.N)  
    fun requestGet(url: String, vararg param: String?): String {  
        val request = Request.Builder().url(url!!).build()  
        val httpClient = OkHttpClient()  
        try {  
            val response = httpClient.newCall(request).execute()  
            return response.body!!.string()  

        } catch (e: IOException) {  
            // TODO handle exception  
            e.printStackTrace()  
        }  
        // TODO Handle Exception  
        throw NullPointerException()  
    }  

    companion object {  
        private val JSON: MediaType = "application/json; charset=utf-8".toMediaTypeOrNull() as MediaType  
    }  

きれいに書くよう意識しました

今までとりあえず動くことだけ意識していたのですが、後で見返すと分かりづらい。。。
FragmentにあるボタンのクリックリスナーをActivityに書いていたり、API呼び出し関数にUI操作を書いていたり、不要な変数を宣言していたり…
このあたりをスッキリするようにコーディングしてみました
Androidのコード設計、デザインパターンも意識しながらコーディングできるようになりたい!

技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

だいたい急に挑戦してゴールにたどり着かずに飽きる日々です

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう