국비교육(22-23)

78일차(1)/Android App(41) : Http POST방식 요청하기(4) - JSONArray 활용

서리/Seori 2023. 1. 30. 16:25

78일차(1)/Android App(41) : Http POST방식 요청하기(4) - JSONArray 활용

 

 

 

- 이전 예제 코드리뷰

 

AsyncTask

- 앱을 run하면 onCreate가 순식간에 실행되었다가 종료되면서 앱이 활성화된다.

- 이후 여기에 등록한 리스너(전송버튼)를 누르면 어떤 객체로 실행의 흐름이 들어온다.

- 위의 파란색 스레드도 UI 스레드이다. 앱이 처음에 활성화될때 사용된 스레드와 같은 스레드이다.

- UI는 이 메인스레드 안에서만 업데이트할 수 있다.

 

- 진행에 시간이 소요되는 불확실한 작업을 한다면? -> 원칙적으로 메인스레드에서는 하면 안된다.

- 일반적으로 메인 스레드는 사용자의 입력에 반응해서 빠르게 반응해야 하는데, 그러지 못하기때문에...

  그런 작업은 새 스레드에서 해야한다.

 

- 새 스레드에서 이런 작업을 해준다.

- AsyncTask가 왜 필요한지, 어떻게 사용하는지 알아두기!

 


 

 

- 현재 서버컴퓨터 안에 스프링부트로 웹서버 앱을 만들어서 사용하고 있다.

 

- 클라이언트가 될 수 있는 것은?

 → Window 데스크탑 / Mac OS 데스크탑 / 안드로이드 폰

- 각각 모두에 웹브라우저가 있다. 즉 서버 컴퓨터에 요청할 능력이 있다.

 

 

- 웹브라우저가 아닌 우리가 만든 MyAndroid 어플리케이션에서 브라우저처럼 요청할 수 있는지?

 

- 데이터 전송, 받아오기, 기기의 데이터 수정, 파일 업로드... 등등의 작업을 하려고 한다.

- 때로는 웹서버와 연동해서 로그인 처리도 할 수 있다.

- 브라우저는 폼이 만들어져 있기 때문에 요청을 편리하게 할 수 있는데,

 앱을 만드는 입장에서는 이런 작업을 따로 코딩해주어야 한다.

 

- 웹서버에 요청하는 방법은? 특정서버에 어떻게 http 요청을 하고 응답 데이터를 받아올 것인가?

→ HttpURLConnection 객체를 통해서 가능하다. 

 

- UI 스레드가 아닌 서브 스레드에서 작업한다.

- http://xxx/xxx/getData 가 저 위치에 들어간다.

- 브라우저가 아니므로 이 요청하려는 내용을 넣어서 객체를 생성하고, 이를 통해 openConnection() 한다.

 

- 원하는 요청 방식(get/post)를 정해서 요청할 수 있다.

- 정상적인 응답(200번) 이 되면 반복문을 돌면서 응답한 값을 읽어올 수 있다.

 

- Utility를 사용해서도 이런 방식으로 작성할 수 있다.

- 대부분의 코드는 유틸리티에 들어가 있다. 이것을 받아와서 쓸 수 있으면 된다.

- get 요청 : sendGetRequest()

  post 요청 : sendPostRequest()

- 결과를 특정 메소드에서 받아볼 수 있게끔 한다.

 

- 코틀린으로도 이런 코드를 작성 가능하다.

- 유틸리티를 코틀린으로 바꾸어서 적용하기.

(자체 코틀린 변환을 사용하면 static class를 가지고있는 경우 object로 바꾸어 버린다.)

 

- 이 예제를 돌리기 위해서는 스프링부트 서버가 필요하다.

- 해당 경로에 대한 안드로이드 컨트롤러도 만들어야 한다.

- 스프링 웹 서버를 돌려두고 안드로이드 run!

- 이 버튼을 누르면 이곳으로 실행순서가 들어오고, msg에 담긴 값이 맵에 담겨서 반환된다.

 

 

- 만약 html 폼이었다면 msg 에는 input요소의 name="msg" 로 지정된 값이 담길 것이다.

- input에 입력한 문자열을 읽어내는 형식이었을 것이다.

 

- spring의 컨트롤러 자체는 이전에 만든 컨트롤러와 다르지 않다.

- 유틸 클래스 내부에서 위와 같은 html 폼을 제출한 것과 같은 효과를 내준 것이다.

 

- 앱의 요청에 대한 일반적인 응답은 보통 xml or JSON 문자열이다.

- @Responsebody 어노테이션을 붙이고 + map/list/dto 로 리턴해주기

 

- 웹브라우저로 직접 요청할 수도 있다. 그러면 이렇게 응답해준다.

- 안드로이드 앱에서 이런 요청을 하게 되면 안드로이드는 이런 JSON 문자열을 응답받게 된다.

- 이것을 응답받아서 어떻게 사용할 것인지?

 

- JSON문자열은 본래 javascript 표기법을 모방했기 때문에, javascript에서 다루기 쉽다.

 (자동으로 object로 변환되어 들어온다)

- 하지만 java, kotlin에 친화적이지는 않다. 그러면 어떻게 적용할 수 있을까?

 

- 응답한 문자열을 JSONObject 객체를 생성하면서 담아오고,

  obj.getXXX 메소드로 들어있는 XXX타입의 값을 불러내 추출하여 사용한다.

 

 


 

 

- JSON 문자열의 형식을 보면 아래와 같다.

- 유형1: { "num":1, "name":"kim" }

- 유형2: [ "kim", "lee", "park" ]

- 유형3: { "num":2, "name":"park", "hobby": [ "game","reading","movie" ] }

- 유형4: [ { }, { }, { }, ... ]

 

- 중괄호 { }, 대괄호 [ ] 로 JSON 문자열을 표기한다.

- 3번:특정 키값으로 여러개의 값을 가지고 있을 수 있다.

- 4번: object 형태가 여러개 있을 수 있다.

- 유형1, 4를 가장 많이 사용한다.

 

<학습목표>

1. Spring Boot 서버에서 원하는 유형의 json 문자열을 출력하기

-> 간단하다. 어노테이션+dto/map/list 리턴

 controller의 return type에 따라서 출력되는 json문자열의 형식이 정해진다.

Map, Dto=> { }

List => [ ]

 

2. android app에서 다양한 유형의 json 문자열 활용하는 방법 익히기

{ } => JSONObject 타입 객체로 접근

[ ] => JSONArray 타입 객체로 접근

 

- 안드로이드에서도 받아서 활용하기

 

 

MainActivity

package com.example.step17httprequest3

import android.os.Bundle
import android.util.Log
import android.widget.Button
import android.widget.EditText
import androidx.appcompat.app.AppCompatActivity
import org.json.JSONArray
import org.json.JSONObject

class MainActivity : AppCompatActivity() , Util.RequestListener {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //EditText 객체의 참조값 얻어오기
        val editText=findViewById<EditText>(R.id.inputMsg)
        //Button 객체의 참조값 얻어와서 리스너 등록
        val sendBtn=findViewById<Button>(R.id.sendBtn)
        sendBtn.setOnClickListener {
            //EditText에 입력한 문자열을 읽어와서
            val msg=editText.text.toString()
            Util.sendPostRequest(
                    999,
                    "http://192.168.0.34:9000/boot07/api/send",
                    mapOf("msg" to msg), //"msg" 라는 키값으로 입력한 메세지를 담은 Map
                    this)
        }
        val getListBtn=findViewById<Button>(R.id.getListBtn);
        getListBtn.setOnClickListener {
            Util.sendGetRequest(
                    1000,
                    "http://192.168.0.34:9000/boot07/api/list",
                    mapOf("pageNum" to "1"),
                    this
            )
        }
    }

    override fun onSuccess(requestId: Int, result: Map<String, Any?>?) {
        if(requestId == 999){
            //웹서버가 응답한 json문자열 { }
            val jsonStr = result?.get("data").toString()
            Log.d("#### json 문자열 ####", jsonStr)
            val obj=JSONObject(jsonStr)
            //key 값을 이용해서 Boolean, String, Int 값을 추출할 수 있다.
            val isSuccess=obj.getBoolean("isSuccess")
            val response=obj.getString("response")
            val num=obj.getInt("num")
        }else if (requestId == 1000){
            val jsonStr= result?.get("data").toString();
            Log.d("#### json 문자열 ####", jsonStr);
            val arr=JSONArray(jsonStr);
            //반복문 돌면서 i 값을 0에서부터 JSONArray 의 방의 사이즈 -1 까지 변화시킨다.
            for(i in 0..arr.length()-1){
                val tmp=arr.getString(i);
                Log.d("json array", tmp)
            }
        }
    }

    override fun onFail(requestId: Int, result: Map<String, Any?>?) {

    }
}

 

- JSONArray 객체가 있다.

 

- 서버에서 { } 문자열을 응답했다면 JSONObject 객체를 생성하는 것이 맞고,

 [ ] 문자열을 응답했다면 JsonArray 객체를 생성하면 된다.

 

- 각각 다른 곳에 들어가면 에러가 난다. 1:1 대응관계를 기억하기!

 

- 백엔드 서버 개발자이면서 안드로이드 앱개발자라면 이 서버연동-데이터전송 내용을 잘 알아야 한다.

 

 


 

AndroidController

package com.sy.boot07.api;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class AndroidController {
	/*
	 * JSON 문자열 
	 * 1. @ResponseBody 어노테이션
     * 2. Map 혹은 List 혹은 Dto 를 리턴하면 자동으로 JSON 문자열로 변환 되어서 응답된다.
	 */
	@RequestMapping("/api/send")
	@ResponseBody
	public Map<String, Object> send(String msg){
		
		System.out.println(msg);
		Map<String, Object> map=new HashMap<>();
		map.put("isSuccess", true);
		map.put("response", "hello client!");
		map.put("num", 1);
		
		return map;
	}
	
	@RequestMapping("/api/list")
	@ResponseBody
	public List<String> list(int pageNum){
		List<String> names=new ArrayList<>();
		names.add("바나나");
		names.add("딸기");
		names.add("복숭아");
		return names;
	}
}

 

- Spring에서 안드로이드 컨트롤러 메소드 추가

 

 

- 주소창에 입력해보면 이렇게 출력된다.

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    android:orientation="vertical">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/inputMsg"
        android:hint="서버에 할말 입력" />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/sendBtn"
        android:text="전송"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/getListBtn"
        android:text="목록 받아오기"/>

</LinearLayout>

 

- 안드로이드에서 목록을 받아오는 버튼을 추가

 

- 제너릭<> 은 String이므로 1도 "1"로 문자로 넣어주어야 한다.

- 목록 보기 버튼을 누르면, 이쪽으로 실행순서가 들어오고 이 경로요청이 들어갈 것이다.

 

- else if 추가 : id가 1000일 경우

- { }JSONObject[ ] 이면 JSONArray

 

- array 타입이면 인덱스를 통해서 접근한다.

 (object 는 키 값을 통해서 접근했다.)

 

- Array안에 Object가 들어있으면 JSONObject 타입을 리턴한다.(getJSONObject)

 

- for 반복문 돌면서 담아준다. i값은 0에서부터 length-1 이다.

- getString(i) 로 하나씩 반복문을 돈다.  i값을 인덱스로 활용한다.

 

- 안드로이드 로그에 이렇게 추출된다.

 

- 유형3: { "num":2, "name":"park", "hobby": [ "game","reading","movie" ] }

- 유형3 같은 경우에는 jsonArray를 사용

 

- 유형4: [ { }, { }, { }, ... ]  

- array안에 object가 있는 경우에는 getJSONObject 를 사용하면 된다.