概要
簡易コーポレイトサイトアプリをAndroidで作成していきます。
APIの連携、バリデーション機能の実装を行います。
API連携
送信ボタンをクリックしてAPIを叩くようにします。
http通信のための準備
http通信をできるようにするため、ライブラリを追加します。
今回はOkhttp3を使った方法です。
gradleからインポート
gradleにOkHttp3のライブラリの依存を追加します。
build.gradleを開いて、dependenciesに下記を追加します。
dependencies {
+ implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.10'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3'
implementation 'androidx.navigation:navigation-ui-ktx:2.5.3'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
追加すると上部に「Sync Now」が表示されるので、こちらをクリックして同期させます。
manifestsに追記
Androidはデフォルトではアプリの通信を許可していないようなので、AndroidManifests.xmlにインターネットへの接続許可を追加します。
src > main > manifests > AndroidManifests.xmlを開いて追加します。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
+ <uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AndroidWebsite"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
</application>
</manifest>
ContactFragment.kt編集
ボタンをクリックしたらAPIを叩くように実装します。
「app」> 「java」 > 「come.example.androidwebsite」 で ContactFrament.ktを開いて編集します。(設定よって表記が微妙に異なり.ktの拡張子が隠れていることもあります)
onCreateView()でそれぞれの値を取得します。
また、それぞれの値の初期化も追加します。
class ContactFragment : Fragment() {
+ private var nameEditText: EditText? = null
+ private var emailEditText: EditText? = null
+ private var bodyEditText: EditText? = null
+ private lateinit var sendButton: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
- return inflater.inflate(R.layout.fragment_contact, container, false)
+ val view = inflater.inflate(R.layout.fragment_contact, container, false)
+ val nameEditText = view.findViewById<EditText>(R.id.etName)
+ val emailEditText = view.findViewById<EditText>(R.id.etEmail)
+ val bodyEditText = view.findViewById<EditText>(R.id.etBody)
+ val sendButton = view.findViewById<Button>(R.id.button)
+ return view
}
private var nameEditText: EditText? = null
private var emailEditText: EditText? = null
private var bodyEditText: EditText? = null
private lateinit var sendButton: Button
val view = inflater.inflate(R.layout.fragment_contact, container, false)
val nameEditText = view.findViewById<EditText>(R.id.etName)
val emailEditText = view.findViewById<EditText>(R.id.etEmail)
val bodyEditText = view.findViewById<EditText>(R.id.etBody)
val sendButton = view.findViewById<Button>(R.id.button)
return view
赤文字エラーはカーソルをあわせて「Import」を実行してエラーを解決します。
clickイベントを設定していきます。
ボタンをクリックしたら、ログに押されたと表示されるようにします。
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_contact, container, false)
val nameEditText = view.findViewById<EditText>(R.id.etName)
val emailEditText = view.findViewById<EditText>(R.id.etEmail)
val bodyEditText = view.findViewById<EditText>(R.id.etBody)
val sendButton = view.findViewById<Button>(R.id.btn)
+ sendButton.setOnClickListener {
+ Log.d("click","押された")
+ }
return view
}
sendButton.setOnClickListener {
Log.d("click","押された")
}
エミュレータを起動して、送信ボタンをクリックしてみます。
ログに「押された」と表示されているので動作しているようです。
パラメータ準備
okHttpクライアントを作成します。
メティアタイプの設定と、APIのURLの指定もします。
package com.example.simpleweb
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
/**
* A simple [Fragment] subclass.
* Use the [ContactFragment.newInstance] factory method to
* create an instance of this fragment.
*/
class ContactFragment : Fragment() {
// TODO: Rename and change types of parameters
+ private val client = OkHttpClient.Builder().build()
+ private val MEDIA = "application/x-www-form-urlencoded; charset=utf-8".toMediaType()
+ private val urlStr = "https://script.google.com/macros/s/デプロイID/exec"
private var nameEditText: EditText? = null
private var emailEditText: EditText? = null
private var bodyEditText: EditText? = null
private lateinit var sendButton: Button
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_contact, container, false)
nameEditText = view.findViewById(R.id.etName)
emailEditText = view.findViewById(R.id.etEmail)
bodyEditText = view.findViewById(R.id.etBody)
sendButton = view.findViewById(R.id.btn)
sendButton.setOnClickListener {
Log.d("click","押された")
}
return view
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* @param param1 Parameter 1.
* @param param2 Parameter 2.
* @return A new instance of fragment ContactFragment.
*/
// TODO: Rename and change types and number of parameters
@JvmStatic
fun newInstance(param1: String, param2: String) =
ContactFragment().apply {
arguments = Bundle().apply {
}
}
}
}
private val client = OkHttpClient.Builder().build()
private val MEDIA = "application/x-www-form-urlencoded; charset=utf-8".toMediaType()
private val urlStr = "https://script.google.com/macros/s/デプロイID/exec"
赤文字エラーはImportで解決します。
ログを表示しているところでAPI送信を実行します。
送信データを作成し、リクエストを作成します。
送信を実行し、レスポンスが正常に返ってきたら、Logにレスポンスを表示させます。
エラーの場合も設定しておきます。
こちらも赤文字はImportで解決しますが、Importの選択肢が複雑なので、下記コードの上部のimportを確認しながらImport実行するか、importのコードもコピーして貼り付けてしまってもいいかと思います。
package com.example.simpleweb
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
import java.io.IOException
//~~~~省略~~~~~~~
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_contact, container, false)
nameEditText = view.findViewById(R.id.etName)
emailEditText = view.findViewById(R.id.etEmail)
bodyEditText = view.findViewById(R.id.etBody)
sendButton = view.findViewById(R.id.btn)
sendButton.setOnClickListener {
+ val sendData = "name="+nameEditText?.text.toString()+"&&email="+emailEditText?.text.toString()+"&&body="+bodyEditText?.text.toString()
+ val request = Request.Builder()
+ .url(urlStr)
+ .post(sendData.toRequestBody(MEDIA))
+ .build()
+
+ client.newCall(request).enqueue(object : Callback {
+
+ override fun onResponse(call: Call, response: Response) {
+ val responseBody = response.body?.string().orEmpty()
+ val responseBodyJson = JSONObject(responseBody)
+ Handler(Looper.getMainLooper()).post {
+ Log.d("レスポンス", responseBodyJson.getString("message"))
+ }
+
+ }
+
+ override fun onFailure(call:Call,e: IOException){
+ Log.e("ERROR",e.toString())
+ }
+ })
}
return view
}
val sendData = "name="+nameEditText?.text.toString()+"&&email="+emailEditText?.text.toString()+"&&body="+bodyEditText?.text.toString()
val request = Request.Builder()
.url(urlStr)
.post(sendData.toRequestBody(MEDIA))
.build()
client.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
val responseBody = response.body?.string().orEmpty()
val responseBodyJson = JSONObject(responseBody)
Handler(Looper.getMainLooper()).post {
Log.d("レスポンス", responseBodyJson.getString("message"))
}
}
override fun onFailure(call:Call,e: IOException){
Log.e("ERROR",e.toString())
}
})
確認
エミュレータを起動して、それぞれ正しい値を入力して実行してみます。
ログにレスポンスが返ってきているのが確認できます。
「success!」はAPI側から返ってきているのできちんとAPI連携できたことがわかります。
トースト
レスポンスをトーストで表示させます。下記のように書き換えます。
import android.widget.Toast
//~~~省略~~~~
client.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
val responseBody = response.body?.string().orEmpty()
val responseBodyJson = JSONObject(responseBody)
Handler(Looper.getMainLooper()).post {
- Log.d("レスポンス", responseBodyJson.getString("message"))
+ Toast.makeText(requireContext(),responseBodyJson.getString("message"),Toast.LENGTH_SHORT).show()
}
}
override fun onFailure(call: Call, e: IOException) {
Log.e("ERROR", e.toString())
}
})
バリデーション
バリデーションを設定していきます。
それぞれエラーメッセージの表示まではできているので、入力中にエラーメッセージの出し分けをするようにします。
まず、それぞれのエラーメッセージを取得します。
+ import android.widget.TextView
//~~~省略~~~~
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_contact, container, false)
val nameEditText = view.findViewById<EditText>(R.id.etName)
val emailEditText = view.findViewById<EditText>(R.id.etEmail)
val bodyEditText = view.findViewById<EditText>(R.id.etBody)
val sendButton = view.findViewById<Button>(R.id.btn)
+ val nameError = view.findViewById<TextView>(R.id.nameErr)
+ val emailError = view.findViewById<TextView>(R.id.emailErr)
+ val bodyError = view.findViewById<TextView>(R.id.bodyErr)
val nameError = view.findViewById<TextView>(R.id.nameErr)
val emailError = view.findViewById<TextView>(R.id.emailErr)
val bodyError = view.findViewById<TextView>(R.id.bodyErr)
name必須バリデーション
名前は必須のチェックのみを実装します。
nameEditTextにsetOnKeyListenerを設定し、キーイベントでnameEditTextの値がからかどうかを判断。
emptyの場合はエラーメッセージにvisibilityをVISBLE、それ以外はINVISIBLEにします。
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_contact, container, false)
val nameEditText = view.findViewById<EditText>(R.id.etName)
val emailEditText = view.findViewById<EditText>(R.id.etEmail)
val bodyEditText = view.findViewById<EditText>(R.id.etBody)
val sendButton = view.findViewById<Button>(R.id.btn)
val nameError = view.findViewById<TextView>(R.id.nameErr)
val emailError = view.findViewById<TextView>(R.id.emailErr)
val bodyError = view.findViewById<TextView>(R.id.bodyErr)
+ nameEditText.apply {
+ setOnKeyListener{_, keyCode, keyEvent ->
+ if(nameEditText.text.toString().isEmpty()){
+ nameError.visibility = View.VISIBLE
+ }else{
+ nameError.visibility = View.INVISIBLE
+ }
+ return@setOnKeyListener false
+ }
+ }
sendButton.setOnClickListener {
nameEditText.apply {
setOnKeyListener{_, keyCode, keyEvent ->
if(nameEditText.text.toString().isEmpty()){
nameError.visibility = View.VISIBLE
}else{
nameError.visibility = View.INVISIBLE
}
return@setOnKeyListener false
}
}
emailバリデーション
emailはバリデーションパターンを正規表現で設定しておきます。
それを、name同様にsetOnKeyListernerでmatchするか判断してエラー文言の出し分けをします。
+ val PARAM_EMAIL = Regex("^[a-z0-9.]+@[a-z0-9.]+\\.[a-z]+\$")
nameEditText.apply {
setOnKeyListener{_, keyCode, keyEvent ->
if(nameEditText.text.toString().isEmpty()){
nameError.visibility = View.VISIBLE
}else{
nameError.visibility = View.INVISIBLE
}
return@setOnKeyListener false
}
}
+ emailEditText.apply {
+ setOnKeyListener { _, keyCode, keyEvent ->
+ if(emailEditText.text.toString().matches(PARAM_EMAIL)){
+ emailError.visibility = View.INVISIBLE
+ }else{
+ emailError.visibility = View.VISIBLE
+ }
+ return@setOnKeyListener false
+ }
+ }
sendButton.setOnClickListener {
emailEditText.apply {
setOnKeyListener { _, keyCode, keyEvent ->
if(emailEditText.text.toString().matches(PARAM_EMAIL)){
emailError.visibility = View.INVISIBLE
}else{
emailError.visibility = View.VISIBLE
}
return@setOnKeyListener false
}
}
Bodyバリデーション
BodyもEmail同様に。
val PARAM_EMAIL = Regex("^[a-z0-9.]+@[a-z0-9.]+\\.[a-z]+\$")
+ val PARAM_BODY = Regex("^.{1,10}\$")
nameEditText.apply {
setOnKeyListener{_, keyCode, keyEvent ->
if(nameEditText.text.toString().isEmpty()){
nameError.visibility = View.VISIBLE
}else{
nameError.visibility = View.INVISIBLE
}
return@setOnKeyListener false
}
}
emailEditText.apply {
setOnKeyListener { _, keyCode, keyEvent ->
if(emailEditText.text.toString().matches(PARAM_EMAIL)){
emailError.visibility = View.INVISIBLE
}else{
emailError.visibility = View.VISIBLE
}
return@setOnKeyListener false
}
}
+ bodyEditText.apply {
+ setOnKeyListener { _, keyCode, keyEvent ->
+ if(bodyEditText.text.toString().matches(PARAM_BODY)){
+ bodyError.visibility = View.INVISIBLE
+ }else{
+ bodyError.visibility = View.VISIBLE
+ }
+ return@setOnKeyListener false
+ }
+ }
bodyEditText.apply {
setOnKeyListener { _, keyCode, keyEvent ->
if(bodyEditText.text.toString().matches(PARAM_BODY)){
bodyError.visibility = View.INVISIBLE
}else{
bodyError.visibility = View.VISIBLE
}
return@setOnKeyListener false
}
}
ボタン
エラーが出ている間はAPI送信しないように、エラーが出ているときはトーストで「入力内容を確認してください。」と表示させるようにします。
sendButton.setOnClickListener {
+ if(bodyError.visibility == View.INVISIBLE && nameError.visibility == View.INVISIBLE && emailError.visibility == View.INVISIBLE) {
val sendData =
"name=" + nameEditText?.text.toString() + "&&email=" + emailEditText?.text.toString() + "&&body=" + bodyEditText?.text.toString()
val request = Request.Builder()
.url(urlStr)
.post(sendData.toRequestBody(MEDIA))
.build()
client.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
val responseBody = response.body?.string().orEmpty()
val responseBodyJson = JSONObject(responseBody)
Handler(Looper.getMainLooper()).post {
Toast.makeText(requireContext(),responseBodyJson.getString("message"),Toast.LENGTH_SHORT).show()
}
}
override fun onFailure(call: Call, e: IOException) {
Log.e("ERROR", e.toString())
}
})
+ }else{
+ Toast.makeText(requireContext(),"入力内容を確認してください。",Toast.LENGTH_SHORT).show()
+ }
}
sendButton.setOnClickListener {
if(bodyError.visibility == View.INVISIBLE && nameError.visibility == View.INVISIBLE && emailError.visibility == View.INVISIBLE) {
val sendData = "name="+nameEditText?.text.toString()+"&&email="+emailEditText?.text.toString()+"&&body="+bodyEditText?.text.toString()
val request = Request.Builder()
.url(urlStr)
.post(sendData.toRequestBody(MEDIA))
.build()
client.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
val responseBody = response.body?.string().orEmpty()
val responseBodyJson = JSONObject(responseBody)
Handler(Looper.getMainLooper()).post {
Toast.makeText(requireContext(),responseBodyJson.getString("message"),Toast.LENGTH_SHORT).show()
}
}
override fun onFailure(call:Call,e: IOException){
Log.e("ERROR",e.toString())
}
})
}else{
Toast.makeText(requireContext(),"入力内容を確認してください。",Toast.LENGTH_SHORT).show()
}
}
動作確認
エミュレータを起動し、入力中にエラーメッセージが表示されるか、送信ボタンを押したら期待通りトーストが表示されるかを確認します。
また適切な値を入力するとエラーメッセージが消えることも確認します。
これで全機能の実装が完了しました。