0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Androidで簡易コーポレートサイト(アプリ)を作成する #3 -API連携とバリデーション編-

Last updated at Posted at 2023-03-13

概要

簡易コーポレイトサイトアプリをAndroidで作成していきます。
APIの連携、バリデーション機能の実装を行います。

API連携

送信ボタンをクリックしてAPIを叩くようにします。

http通信のための準備

http通信をできるようにするため、ライブラリを追加します。
今回はOkhttp3を使った方法です。

gradleからインポート

gradleにOkHttp3のライブラリの依存を追加します。
build.gradleを開いて、dependenciesに下記を追加します。
000001.jpg

build.gradle(Module:AndroidWebsite.app)
	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」が表示されるので、こちらをクリックして同期させます。
000010.jpg

manifestsに追記

Androidはデフォルトではアプリの通信を許可していないようなので、AndroidManifests.xmlにインターネットへの接続許可を追加します。
src > main > manifests > AndroidManifests.xmlを開いて追加します。

000020.jpg
AndroidManifest.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の拡張子が隠れていることもあります)
000030.jpg

onCreateView()でそれぞれの値を取得します。
また、それぞれの値の初期化も追加します。

ContactFragment.kt
    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イベントを設定していきます。
ボタンをクリックしたら、ログに押されたと表示されるようにします。

ContactFragment.kt
    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","押された")
}

エミュレータを起動して、送信ボタンをクリックしてみます。
ログに「押された」と表示されているので動作しているようです。
000040.jpg

パラメータ準備

okHttpクライアントを作成します。
メティアタイプの設定と、APIのURLの指定もします。

ContactFragment.kt
    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のコードもコピーして貼り付けてしまってもいいかと思います。

ContactFragment.kt
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連携できたことがわかります。
000050.jpg

トースト

レスポンスをトーストで表示させます。下記のように書き換えます。

ContactFragment.kt
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())
        }
    })

確認するとふわっと表示されているのが確認できます。
000060.jpg

バリデーション

バリデーションを設定していきます。
それぞれエラーメッセージの表示まではできているので、入力中にエラーメッセージの出し分けをするようにします。
まず、それぞれのエラーメッセージを取得します。

ContactFragment.kt
+  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にします。

ContactFragment.kt

        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するか判断してエラー文言の出し分けをします。

ContactFragment.kt
+       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同様に。

ContactFragment.kt
        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送信しないように、エラーが出ているときはトーストで「入力内容を確認してください。」と表示させるようにします。

ContactFragment.kt
        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()
    }
}

動作確認

エミュレータを起動し、入力中にエラーメッセージが表示されるか、送信ボタンを押したら期待通りトーストが表示されるかを確認します。
000070.jpg
また適切な値を入力するとエラーメッセージが消えることも確認します。
000080.jpg

これで全機能の実装が完了しました。

関連コンテンツ

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?