Web

Android에서 SpringBoot JPA 서버와 연동하기

kakaroo 2022. 2. 17. 21:42
반응형

article logo

 

SpringBoot에서 JPA Repository 를 활용해 CRUD를 구현했습니다.

https://kakaroo.tistory.com/49

 

Spring Boot - Eclipse Maven으로 Spring Web Layer 실습

아래 기등록한 포스트는 IntelliJ + Gradle 로 구현하였기에 Eclipse + Maven 환경으로 다시 해 보겠습니다. (공부삼아.. ) https://kakaroo.tistory.com/39 2 - Spring Boot - JPA 구현 by Spring Web Layer Spri..

kakaroo.tistory.com

 

이제, Android UI에서 input 을 서버에 주고 서버의 결과를 화면에 출력하는 것을 만들어 보겠습니다.

기본적인 UI는 아래처럼 JPA repository 데이터를 Post/Put/Get/Delete 할 수 있게 간단한 UI를 생성합니다.

간단한 Android UI
간단한 Android UI

 

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/et_title"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="16dp"
        android:layout_marginBottom="8dp"
        android:inputType="text"
        android:hint="Title"
        app:layout_constraintBottom_toTopOf="@+id/et_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/et_content"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="16dp"
        android:layout_marginBottom="8dp"
        android:inputType="text"
        android:hint="Content"
        app:layout_constraintBottom_toTopOf="@+id/et_author"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/et_title" />

    <EditText
        android:id="@+id/et_author"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="16dp"
        android:layout_marginBottom="8dp"
        android:inputType="text"
        android:hint="Author"
        app:layout_constraintBottom_toTopOf="@+id/radio_post"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/et_content" />

    <RadioButton
        android:id="@+id/radio_post"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginBottom="8dp"
        android:text="Post"
        app:layout_constraintBottom_toTopOf="@+id/et_getId"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/et_author" />

    <RadioButton
        android:id="@+id/radio_put"
        android:layout_width="0dp"
        android:layout_height="48dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:text="Put"
        app:layout_constraintStart_toEndOf="@+id/radio_post"
        app:layout_constraintTop_toBottomOf="@+id/et_author" />

    <Button
        android:id="@+id/bt_post_put"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="16dp"
        android:text="POST/PUT"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/et_author" />

    <EditText
        android:id="@+id/et_getId"
        android:layout_width="64dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:inputType="number"
        android:hint="ID"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/radio_post" />

    <Button
        android:id="@+id/bt_get"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="12dp"
        android:layout_marginStart="8dp"
        android:text="Get"
        app:layout_constraintBottom_toBottomOf="@+id/et_getId"

        app:layout_constraintHorizontal_chainStyle="spread"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintStart_toEndOf="@+id/et_getId"
        app:layout_constraintTop_toTopOf="@+id/et_getId"
        app:layout_constraintVertical_bias="0.0" />

    <Button
        app:layout_constraintHorizontal_chainStyle="spread"
        app:layout_constraintHorizontal_weight="1"
        android:layout_marginHorizontal="12dp"
        android:id="@+id/bt_delete"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:text="Delete"
        app:layout_constraintBottom_toBottomOf="@+id/bt_get"
        app:layout_constraintStart_toEndOf="@+id/bt_get"
        app:layout_constraintTop_toTopOf="@+id/bt_get" />

    <Button
        android:id="@+id/bt_getall"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginHorizontal="12dp"
        android:text="GET ALL"
        app:layout_constraintBottom_toBottomOf="@+id/bt_get"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_chainStyle="spread"
        app:layout_constraintHorizontal_weight="1"
        app:layout_constraintStart_toEndOf="@+id/bt_delete"
        app:layout_constraintTop_toTopOf="@+id/bt_get"
        app:layout_constraintVertical_bias="0.0" />

    <TextView
        android:id="@+id/tv_result"
        android:layout_width="51dp"
        android:layout_height="16dp"
        android:layout_marginStart="16dp"
        android:layout_marginTop="48dp"
        android:layout_marginEnd="16dp"
        android:text="Result"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/et_getId" />



</androidx.constraintlayout.widget.ConstraintLayout>

 

Layout View에 action을 주기위해 View binding을 설정합니다.

<build.gradle>

viewBinding {
    enabled = true
}

 

import com.kakaroo.jpatestkotlin.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {
    //전역변수로 binding 객체선언
    private var mBinding: ActivityMainBinding? = null

    // 매번 null 체크를 할 필요 없이 편의성을 위해 바인딩 변수 재 선언
    private val binding get() = mBinding!!

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //setContentView(R.layout.activity_main)
        mBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
    }
}
View Binding?
안드로이드 개발 초기에는 findViewById를 이용해서 xml의 뷰와 변수를 연결시켜주는 그런 작업을 해야 했습니다.
한 두 개 정도는 상관없지만, 복잡한 UI (여기서는 간단하지만...)에는 한 페이지에 들어가는 뷰가 아주 많게 됩니다. 
view binding은 이 findViewById를 대체할 수 있는 기능이다.

 

 

Failed to connect to localhost/127.0.0.1:8080

이미 JPA 를 이용하기 위해 서버를 local 로 열었으므로 안드로이드로 동일 IP로 연결시 위 error가 발생합니다.

이 때는 localhost가 아니라 10.0.2.2 를 써보시면 될 것 같습니다. 

 

 

< Post >

 

Result of Post method
Result of Post method

 

< Get >

Result of Get method
Result of Get method

 

< Get All >

Result of Get All method
Result of Get All method

< Put >

requestbody를 넣지 않았더니 400 Bad Request 가 발생했습니다.

수정한 결과 정상적으로 처리됩니다.

Result of Put method
Result of Put method

 

< Delete >

Result of Delete method
Result of Delete method

 

URL connection, request, response 처리하는 코드

		// URL 객체 생성
        val url = URL(page)
        // 연결 객체 생성
        val httpConn: HttpURLConnection = url.openConnection() as HttpURLConnection

        // Post 파라미터
        //val params = ("param=1"
        //        + "¶m2=2" + "sale")

        // 결과값 저장 문자열
        val sb = StringBuilder()

        // 연결되면
        if (httpConn != null) {
            Log.i(Common.LOG_TAG, page + " connection succesfully")
            result += "Connection successfully!" + "\n"
            // 응답 타임아웃 설정
            httpConn.setRequestProperty("Accept", "application/json")
            httpConn.setRequestProperty("Content-type", "application/json; utf-8");
            httpConn.setConnectTimeout(Common.HTTP_CONNECT_TIMEOUT)
            // POST 요청방식
            httpConn.setRequestMethod(Common.HTTP_REQ_METHOD_LIST[method])

            // 포스트 파라미터 전달
            //httpConn.getOutputStream().write(params.toByteArray(charset("utf-8")))

            //--------------------------
            //   서버로 값 전송
            //--------------------------
            if(bNeedRequestParam) {
                var json : String = ""
                // build jsonObject
                val jsonObject = JSONObject()
                jsonObject.accumulate("title", binding.etTitle.text.toString())
                jsonObject.accumulate("content", binding.etContent.text.toString())
                jsonObject.accumulate("author", binding.etAuthor.text.toString())

                // convert JSONObject to JSON to String
                json = jsonObject.toString()

                // OutputStream으로 POST 데이터를 넘겨주겠다는 옵션.
                httpConn.doOutput = true
                // InputStream으로 서버로 부터 응답을 받겠다는 옵션.
                httpConn.doInput = true

                val os : OutputStream = httpConn.outputStream
                os.write(json.toByteArray(Charsets.UTF_8))
                os.flush()
                os.close()
            }

            // url에 접속 성공하면 (200)
            var status : Int = try {
                httpConn.responseCode
            } catch (e: IOException) {
                // HttpUrlConnection will throw an IOException if any 4XX
                // response is sent. If we request the status again, this
                // time the internal status will be properly set, and we'll be
                // able to retrieve it.
                Log.e(Common.LOG_TAG, "URL responseCode error :$e")
                httpConn.responseCode
            }
            result += "Respose code:" + status + "\n"

            if(status == HttpURLConnection.HTTP_OK) {
                // 결과 값 읽어오는 부분
                val br = BufferedReader(
                    InputStreamReader(
                        httpConn.inputStream, "utf-8"
                    )
                )
                var line: String?

                while (br.readLine().also { line = it } != null) {
                    sb.append(line)
                }
                // 버퍼리더 종료
                br.close()
                Log.i(Common.LOG_TAG, "결과 문자열 :$sb")

                if(!bJsonResponseParam) {
                    result + sb.toString()
                } else {    //return 값이 JSON 이다.
                    //{"id":2,"title":"Title1","content":"Content1","author":"Author1"}
                    // 안드로이드에서 기본적으로 제공하는 JSONObject로 JSON을 파싱
                    // getAll인 경우 JsonArray 형태이므로
                    if(method == Common.HTTP_GET_ALL) {
                        val jsonArray = JSONTokener(sb.toString()).nextValue() as JSONArray
                        for (i in 0 until jsonArray.length()) {
                            val jsonObject = jsonArray.getJSONObject(i)
                            val title = jsonObject.getString("title")
                            val content = jsonObject.getString("content")
                            val author = jsonObject.getString("author")
                            result += "title: "+ title + "\ncontent: " + content + "\nauthor: " + author +"\n"
                        }
                    } else {
                        val jsonObject = JSONObject(sb.toString())
                        val title = jsonObject.getString("title")
                        val content = jsonObject.getString("content")
                        val author = jsonObject.getString("author")
                        result += "title: "+ title + "\ncontent: " + content + "\nauthor: " + author +"\n"
                    }
                }
            }
            // 연결 끊기
            httpConn.disconnect()
        }

        //백그라운드 스레드에서는 메인화면을 변경 할 수 없음
        // runOnUiThread(메인 스레드영역)
        runOnUiThread {
            Toast.makeText(applicationContext, "응답$sb", Toast.LENGTH_SHORT).show()
        }
    } catch (e: Exception) {
        Log.e(Common.LOG_TAG, "HttpURLConnection error :$e")
        result += "HttpURLConnection error! $e"
    } finally {
        binding.tvResult.setText(result)
    }
}
th.start()

 

 

 

Android에서 json parsing 방법은 아래를 참조하였습니다.

ref : https://devsnote.com/writings/2338

 

뎁스노트 | Kotlin에서 외부 라이브러리 사용하지 않고 JSON 데이터 파싱하는 3가지 방법

심플한 JSON, 배열로 된 JSON, 그리고 중첩된 JSON에 데이터에 대해 파싱하는 방법을 설명합니다.   심플한 JSON JSON 예:  {

devsnote.com

 

Source : https://github.com/kakarooJ/JPA-Android-byKotlin

 

GitHub - kakarooJ/JPA-Android-byKotlin

Contribute to kakarooJ/JPA-Android-byKotlin development by creating an account on GitHub.

github.com

 

반응형