簡単なアプリを作りました
アプリを起動時 ボタンを押すとおみくじ結果画面に遷移します
おみくじ結果画面 3秒ほど「Fortune Telling...」と表示され、結果が表示されます
githubで公開しています Kotlin学習にどうぞ! https://github.com/butorisa/fortune-app
※画像には表示されていませんが、利用しているAPIのリンク・占い提供元のリンクを3枚目画面の下部に表示するようにしました
アプリの概要
- 開発端末OS:Android 8.1.0(Oreo)
- 利用API:Web ad Fortune 無料版API
HTTP通信ライブラリ:OkHttp3
MainActivity
- ActionBar(正確にはToolBar)のみ
- 画面部分はFragmentで描画
- EntryFragment
- 起動時の画面(1枚目)
- MainActivityの上から描画
- FortuneResultFragmentに遷移するボタンを保持
- FortuneResultFragment
- FortuneResultBean
- 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のコード設計、デザインパターンも意識しながらコーディングできるようになりたい!