국비교육(22-23)

79일차(1)/Android App(42) : Http 요청으로 Oracle DB 출력하기

서리/Seori 2023. 2. 2. 23:58

79일차(1)/Android App(42) : Http 요청으로 Oracle DB 출력하기

 

 

- 안드로이드 앱에서 DB에 있는 Todo 리스트 가져오기

- 안드로이드 앱에서 DB 테이블에 데이터 추가, 삭제하기

 

 

새 모듈 생성

 

 

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">

    <ListView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:id="@+id/listView"/>
    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="할일 입력..."
        android:id="@+id/inputText"/>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="추가"
            android:id="@+id/addBtn"/>
    </LinearLayout>

</LinearLayout>

 

- sqLite 예제에서 사용한 layout 복사해옴

 

- 이전 예제에서는 내부 DB를 사용한 것이고,

 이번 예제에서는 같은 기능을 SpringBoot의 oracle DB를 활용해서 해볼 것!

 

- step13에서 할일을 입력해서 ListView에 추가했던 것처럼 원격지 DB에 들어갈 수 있도록 코딩.

삭제- 오래 클릭시 들어오는 요청으로삭제

 

 

- 안드로이드 앱과 서버 간의 대화를 구현하기.

 

- SQLite에 UI를 다루는 기본동작은 있다. (이 경우 dbHelper는 필요없다)

- 원격지 서버를 쓴다는 것은 사용하는 모든 클라이언트가 같은 테이블을 공유한다는 것.

- 똑같이 동작하되 원격지 DB를 쓰도록 수정!

 


 

- 앱과 웹서버 사이의 소통 방식

 

 

- 출시된 어떤 안드로이드 앱이 있다고 가정하기

- 이 앱에 띄우는 정보는 원격지 웹서버의 어떤 DB에 있는 정보이다.

 

- 이 멀리 떨어진 DB에 어떻게 접근할 것인가? 바로 화면에 띄울 수는 없다.

- 그래서 웹서버 어플리케이션이있다. 이 서버를 spring Boot로 만들어준다.

- 안드로이드 앱에서 필요한 정보가 있다면 이 웹서버 앱에 요청해준다. 그러면 서버에서 응답을 준다.

 

 

- 웹브라우저가 아니기 때문에 응답을 줄때 html, css등은 의미가 없다.

 대신 json, xml 형식 문자열로 응답한다!

- 앱에서 이것을 파싱한다. 파싱은 문자열 안에서 원하는 내용만 쏙쏙 빼내는 것을 의미한다.

 

- 앱과 웹서버 사이의 대화방식을 학습!

 

- 오라클 Todo Table 을 웹서버 연동

 

- 안드로이드 앱에서 리퀘스트를 보내는 핵심객체는 HttpURLConnection 객체이다.

 (브라우저가 아니므로 요청해주는 객체가 필요하다)

- 응답되는 객체를 추출해서 사용한다.

- jsp페이지를 포워드 이동해서 응답하는게 아니라 JSON형식의 문자열을 응답한다는 것 빼고는

 웹서버 입장에서는 앱에서 요청하든 브라우저에서 요청하든 다를 것이 없다.

- get/post 와 같은 전송 방식이 중요한 것이지, 어떤 객체를 사용해서 요청하는지 등을 구별할 수는 없다.

- HttpURLConnection 객체를 사용하면 웹브라우저에서 폼 전송하거나/링크를 누른 효과를 똑같이 낼 수 있다.

 


 

- Util 클래스 안에 들어있는 메소드

 

- 키보드 숨기는 메소드 (Util.hideKeyboard 로 this 전달하면 사용가능)

 

- 포커스 해제하는 메소드(releaseFocus) 등

 

- 이렇게 자주 사용하는 기능을 Util로 만들어두면 앱을 금방 만들 수 있다.

 

 

- get, post방식 요청 메소드도 Util 안에 들어있다.

- 요청 id, 요청 url, 파라미터 값, 리스너를 전달하면 요청할 수 있다.

 

 

 

 

- doInBackground 메소드 안에서 get 방식 파라미터를 구성하고, 값을 누적시켜 저장

- num, name, addr 정보를 전달할 때,

이것을 Map에 담아서 ?num=1&name=kim&addr=seoul 이 되도록 하기

 

- 이 메소드의 while문 부분은 이런 문자열을 만들어내는 코드이다.

 

 

- http://localhost:9000/member 뒤에 저 내용을 붙여준다.

- GET 방식 요청 파라미터. map에 담긴 내용을 변화시켜서 뒤에 갖다붙이는 효과(링크를 누르는 효과와 같다)

- 요청하는 방법은? httpURLConnection 사용

 

 

- 서버가 문자열에 응답하면서(반복문을 돌면서) 출력하는 문자열을

하나하나 연결연산자로 연결하는것은 비효율적이므로, StringBuilder 에 누적시킨 후 한번에 가지고 온다.

 

- 여기서 가져오는 문자열을 json으로 활용하면 편하다!

- utility에서 "data"라는 키값으로 Map에 담고, 성공이라면 onSuccess() 를 호출하면서 map 넣어주고,

 실패하면 onFail() 호출하면서 map 넣어주기

 


 

- 담은 문자열을 어떻게 꺼내는가?

 

- 서버가 응답하는 문자열의 형식

 

 

@ResponseBody

public Map xxx() 이 있다고 하면,

컨트롤러에서 Map 또는 Dto를 리턴하면 아래와 같은 형식의 문자열이 응답한다.

{"key":value, "key2":value2", ...}

- App에서 위 형식의 문자열을 활용하려면 JSONObject 객체를 사용하면 된다.

 생성자의 인자에 저 내용을 넣어주어야 한다.

 

 

- 파란색 박스: 서버에서 사용하는 내용 (응답시키는 방법)

- 분홍색 박스: App에서 사용하는 내용 (어떤객체로 가져와서 파싱하는가)

 ( parsing : 특정 키 값으로 저장된 데이터를 빼내는 것)

- 이들의 연결고리가 되는 것은 JSON문자열이다!

 


 

- List도 사용할 수 있다. 컨트롤러에서 List<String> 을 리턴하면 아래와 같은 형식의 문자열이 응답된다.

 

@ResponseBody

public List<String> xxx()

[ "data1", "data2", data3", .... ]

 

- App에서 위와 같은 형식의 문자열을 활용하려면 JSONArray 객체를 사용하면 된다.

 


 

 

- List<> 제너릭이 Dto, map 인 경우

 

@ResponseBody

public List<Dto> xxx()

[ {"key" : valure }, {}, {}, ... ]

 

- 컨트롤러에서 List<Dto> or List<Map> 을 리턴하면 위와 같은 형식의 문자열이 응답된다.

 

- JsonArray의 0,1,2번 방 안에있는것이 JSONObject 이다.

- 출력되는 형태에 따라서 적절한 JSON 메소드 사용하기

 

 

- 이후에는 로그인, 로그아웃기능 구현 예정

- 어떤 인증(로그인)을 받아야만 사용할 수 있는 서버의 데이터도 있다.

 

- 브라우저도 없는데 어떻게 로그인 기능을 사용할까?

- httpSession 객체를 사용. session을 활용하는 방법으로 로그인해볼 것!

 


 

- 안드로이드에서 DB에 있는 Todo 리스트 가져오기

 

Android Manifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!--
        1. 개인정보
        2. 위치정보
        3. 비용이 발생할 가능성이 있는 작업
        4. 배터리를 많이 소모하는 등등의 작업

        위의 작업을 app에서 한다면 허가를 얻어내야 한다.

        인터넷 자원을 사용하겠다는 permission
    -->
    <uses-permission android:name="android.permission.INTERNET"/>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyAndroid"
        android:usesCleartextTraffic="true"> <!-- https가 아닌 http 요청도 가능하게 하는 설정 -->
        <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>
        </activity>
    </application>

</manifest>

 

 

- Android Manifest.xml 에 인터넷 사용 permission 추가

 

- 앱 설치시 개인정보 사용, 위치정보 사용, 저장소 사용, 배터리 소모, .. 등등의 정보를 요청하는 것은

여기에 permission으로 명시해놓은 내용만 가능하다! 이 내용만 접근할 수 있다.

 

- usesCleartextTraffic : https가 아닌 http 요청도 가능하게 하는 설정 

 


 

TodoAdapter

- 이전에는 코틀린으로 만들었던 것을 java로 만들어보기

- listview_cell 의 레이아웃을 가지고 있다.

 

listview_cell.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/text_content"
        android:textSize="20sp"
        android:text="내용입니다..."
        android:layout_margin="10dp"/>
    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/text_regdate"
        android:textColor="#999999"
        android:gravity="right"
        android:text="등록일입니다..."
        android:layout_margin="10dp"/>
</LinearLayout>

 

TodoDto

package com.example.step17example;

public class TodoDto {
    //필드
    private int num;
    private String content;
    private String regdate;
    //디폴트 생성자
    public TodoDto () {}

    public TodoDto(int num, String content, String regdate) {        
        this.num = num;
        this.content = content;
        this.regdate = regdate;
    }

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getRegdate() {
        return regdate;
    }

    public void setRegdate(String regdate) {
        this.regdate = regdate;
    }


}

 

TodoAdapter

package com.example.step17example;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import java.util.List;

public class TodoAdapter extends BaseAdapter {

    //필드
    private Context context;
    private int layoutRes;
    private List<TodoDto> list;

    //생성자
    public TodoAdapter(Context context, int layoutRes, List<TodoDto> list) {
        this.context = context; //액티비티의 참조값
        this.layoutRes = layoutRes; //listView의 cell layout 리소스 아이디
        this.list = list; //listView에 출력할 데이터를 가지고 있는 모델
    }

    //모델의 개수 리턴
    @Override
    public int getCount() {
        return list.size();
    }
    //position에 해당하는 모델을 리턴
    @Override
    public Object getItem(int position) {
        return list.get(position);
    }
    //position에 해당하는 아이템의 아이디(primary key)
    @Override
    public long getItemId(int position) {
        //position에 해당하는 TodoDto의 번호를 리턴하는 것
        return list.get(position).getNum();
    }
    //position에 해당하는 cell View를 리턴해주어야 한다. (listView가 호출할 예정인 메소드)
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        //만일 view를 새로 만들어야 한다면
        if(convertView == null){
            //여기서 만들고 (res/layout/listView_cell.xml 문서를 전개해서)
            //해당 문서의 리소스 아이디는 생성자의 인자로 전달되어서 필드에 저장해 놓은 상태
            convertView=LayoutInflater.from(context).inflate(layoutRes, parent, false);
        }
        //View에 모델이 가지고 있는 데이터를 출력하고
        TodoDto dto=list.get(position);
        //listView_cell.xml 안에 있는 TextView의 참조값 얻어와서 문자열 출력
        TextView text_content=convertView.findViewById(R.id.text_content);
        TextView text_regdate=convertView.findViewById(R.id.text_regdate);
        text_content.setText(dto.getContent());
        text_regdate.setText(dto.getRegdate());
        //View를 리턴해준다.
        return convertView;
    }
}

 

- BaseAdapter 상속해주고 메소드 4개 override

- 필드와 생성자 만들기

- 생성자에는 context(액티비티), 레이아웃 리소스의 int값, 모델(List<TodoDto>)를 전달해주어야 한다.

 

- getItem은 TodoDto type을 리턴하는데, 이는 object 타입도 될 수 있으므로

  object를 리턴하는 메소드에서 리턴값이 될 수 있다.

 

- position의 primary key를 리턴해준다.

- position에 해당하는 num을 리턴하는 것. getNum()

 

- 원래는 long 타입을 리턴해야 하는데, int 타입을 리턴하는 것은 가능하다! long이 더 넓은 범위이므로

 

- 리스트뷰는 최소한의 cell 갯수만 만들고 나머지는 재활용하는 구조

- 처음에는 값이 null이다. 안에서 값을 만들어주어야 한다.

 

- View에 데이터를 출력하고, textView의 참조값을 얻어와서 문자열을 출력한다.

 

 

- 이 아답타를 listView에 연결한다. 그러면 ListView에서 cellview를 출력해줄 것!

 


 

- activity_main.xml 에 있는 UI의 참조값을 얻어와서 MainActivity에서 작업하기!

 

MainActivity (최종)

package com.example.step17example;

import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.EditText;

import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;

import com.example.step17example.databinding.ActivityMainBinding;

import org.json.JSONArray;
import org.json.JSONObject;

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

/*
    view binding 사용하는 방법

    1. build.gradle 파일에 아래 설정 추가
        buildFeatures {
            viewBinding = true
        }

    2. 우상단의 sync now 링크를 눌러서 설정이 적용되도록 한다.
    3. layout xml 문서의 이름대로 클래스가 자동으로 만들어진다.
       예를 들어 activity_main.xml 문서면 ActivityMainBinding 클래스
                activity_sub.xml 문서면 ActivitySubBinding 클래스
 */
public class MainActivity extends AppCompatActivity implements Util.RequestListener, AdapterView.OnItemLongClickListener {
    //자주 사용하는 문자열은 static final 상수로 만들어두고 사용하면 편리하다.
    public static final String BASE_URL="http://192.168.0.34:9000/boot07/";
    //필드
    List<TodoDto> list;
    TodoAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //setContentView(R.layout.activity_main);

        //R.layout.activity_main.xml 문서를 전개해서 View를 만들기
        ActivityMainBinding binding=ActivityMainBinding.inflate(getLayoutInflater());
        //전개된 layout에서 root를 얻어내서 화면 구성을 한다.(여기서는 LinearLayout 이다.)
        setContentView(binding.getRoot());

        //아답타에 넣어줄 모델 목록
        list=new ArrayList<>();
        //listView에 연결할 아답타
        adapter=new TodoAdapter(this, R.layout.listview_cell, list);
        //listView에 아답타 연결하기
        binding.listView.setAdapter(adapter);


        //UI의 참조값 얻어오기
        EditText inputText=findViewById(R.id.inputText);

        //버튼의 참조값 얻어오기, 리스너 등록
        binding.addBtn.setOnClickListener(v -> {
            //EditText에 입력한 문자열을 읽어와서
            String content = binding.inputText.getText().toString();
            Map<String, String> map=new HashMap<>();
            map.put("content", content);
            //원격지 웹서버에 post 방식으로 전송하기
            Util.sendPostRequest(AppConstants.REQUEST_TODO_INSERT,
                                AppConstants.BASE_URL+"/todo/insert",
                                map,
                                this);
        });

        //ListView를 길게 누르고 있을 때 리스너 등록
        binding.listView.setOnItemLongClickListener(this);

    }

    @Override
    protected void onStart() {
        super.onStart();
        //MainActivity가 활성화되는 시점에 원격지 서버의 데이터를 받아와서 listView에 출력하기
        Util.sendGetRequest(AppConstants.REQUEST_TODO_LIST,
                AppConstants.BASE_URL+"/todo/list",
                null,
                this);
    }

    @Override
    public void onSuccess(int requestId, Map<String, Object> result) {
        //응답된 json 문자열 읽어오기
        String jsonStr=(String)result.get("data");

        //만일 할일 추가 요청에 대한 응답이라면
        if(requestId == AppConstants.REQUEST_TODO_INSERT){
            //jsonStr 은 {"isSuccess":true} 형식의 json 문자열
            Log.d("MainActivity onSuccess()", jsonStr);
            //성공이면 목록을 다시 요청해서 UI가 업데이트되도록 한다.
            Util.sendGetRequest(AppConstants.REQUEST_TODO_LIST,
                    AppConstants.BASE_URL+"/todo/list",
                    null,
                    this);

        }else if(requestId == AppConstants.REQUEST_TODO_LIST){
            //기존 목록은 일단 삭제
            list.clear();
            //jsonStr 은 [{"num":x, "content":"x", "regdate":"x"}, {},{},{},...] 형식의 문자열
            try {
                JSONArray arr=new JSONArray(jsonStr);
                //JSONArray 객체의 방의 개수만큼 반복문 돌면서
                for(int i=0; i<arr.length(); i++){
                    //JSONObject 객체를 하나씩 얻어낸다.
                    JSONObject tmp=arr.getJSONObject(i);
                    //JSONObject 에는 할일 하나가 들어있다. => TodoDto로 변경하면 된다.
                    TodoDto dto=new TodoDto();
                    dto.setNum(tmp.getInt("num"));
                    dto.setContent(tmp.getString("content"));
                    dto.setRegdate(tmp.getString("regdate"));
                    //TodoDto 객체를 list에 누적시킨다.
                    list.add(dto);
                }
                //모두 누적시켰으면 모델이 변경되었다고 아답타에 알려서 ListView가 업데이트 되도록 한다.
                adapter.notifyDataSetChanged();
            } catch (Exception e) {
                Log.e("MainActivity onSuccess()", e.getMessage());
            }
        }else if(requestId == AppConstants.REQUEST_TODO_DELETE){
            //성공이면 목록을 다시 요청해서 UI가 업데이트되도록 한다.
            Util.sendGetRequest(AppConstants.REQUEST_TODO_LIST,
                    AppConstants.BASE_URL+"/todo/list",
                    null,
                    this);
        }
    }

    @Override
    public void onFail(int requestId, Map<String, Object> result) {
        //만일 할일 추가 요청에 대한 실패라면
        if(requestId==AppConstants.REQUEST_TODO_INSERT){

        }
    }

    //listView의 cell을 오랫동안 클릭하면 실행되는 메소드
    @Override
    public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {

        new AlertDialog.Builder(this).setTitle("알림")
                .setMessage("삭제하시겠습니까?")
                .setPositiveButton("네", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        /*
                            view는 클릭한 cell의 view
                            position은 클릭한 cell의 인덱스
                            id는 클릭한 cell 모델의 primary key 값
                         */
                        //삭제할 할일의 primary key 값을 Map에 담고
                        Map<String, String> map = new HashMap<>();
                        map.put("num", Long.toString(id));
                        //Util을 사용해서 삭제
                        Util.sendPostRequest(AppConstants.REQUEST_TODO_DELETE,
                                AppConstants.BASE_URL+"/todo/delete",
                                map,
                                MainActivity.this);
                    }
                })
                .setNegativeButton("아니오", null)
                .create()
                .show();


        return false;

    }
}

 

 

 

- 생성자의 인자들을 각각의 자리에 잘 넣어준다.

- context 타입은 Activity 안에서는 activity를 사용하면 된다.

 

 

- 현재는 데이터가 없다. 이렇게 출력된다.

- 이 안드로이드 화면에서 값을 입력하면 읽어와서 서버에서 할일을 DB에 저장하도록 만들 것!

 

 

package com.example.step17example;

import android.os.Bundle;
import android.widget.ListView;

import androidx.appcompat.app.AppCompatActivity;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //ListView 의 참조값 얻어오기
        ListView listView=findViewById(R.id.listView);
        //아답타에 넣어줄 모델 목록
        List<TodoDto> list=new ArrayList<>();
        //ListView 에 연결할 아답타 객체 생성
        TodoAdapter adapter=new TodoAdapter(this, R.layout.listview_cell, list);
        //ListView 에 아답타 연결하기
        listView.setAdapter(adapter);
    }
}

- 처음에 findViewById() 로 작성했던 것인데, 뷰바인딩 형식으로 바꿔볼 것!

 

 

- step08의 gradle.build에 가서 buildFeatures 설정을 복사해서 step17example에 갖다붙이고, 동기화해준다!

- 그러면 ViewBinding을 사용할 준비가 된 것이다!

 

- 다시 MainActivity 로 돌아가보면 ActivityMainBinding이라는 클래스가 하나 생겨있다.

 

[ view binding 사용하는 방법 ]
1. build.gradle 파일에 아래 설정 추가
    buildFeatures {
          viewBinding = true
    }
2. 우상단의 sync now 링크를 눌러서 설정이 적용되도록 한다.
3. layout xml 문서의 이름대로 클래스가 자동으로 만들어진다.
  예를 들어 activity_main.xml 문서면 ActivityMainBinding 클래스
  activity_sub.xml 문서면 ActivitySubBinding 클래스

 

 

 

- binding.getRoot() 는 여기서는 리니어 레이아웃을 말한다.

- 이것이 root이다. 이 레이아웃을 전개했을때 최상위에 있는 요소!

 

- 참조값을 얻어오는 것은 이렇게 쓸 수 있다.

 

- View binding을 활용하면 아이디 값으로 바로 접근가능하다.

- 아답타를 연결하면서 참조값까지 알아낼 수 있다.

 


 

- add 버튼에 리스너 등록하기

 

- binding 객체를 사용하면 EditText, 버튼의 참조값도 간편하게 접근해서 가져올 수 있다.

 

 

- Util의 리스너를 액티비티에 직접 등록해준다.

 

- 상수로 만들어놓고 사용하면 편하다.

- 경로가 달라지고, 배포 시점이 달라지더라도 이렇게 상수값으로 설정해놓으면 이것만 수정하면 된다.

- 이렇게 상수들만 넣어두는 클래스를 별도로 만들어 관리하기도 한다.

 

 

- AppConstants 클래스 생성

package com.example.step17example;

public class AppConstants {
    //자주 사용하는 문자열은 static final 상수로 만들어두고 사용하면 편리하다.
    public static final String BASE_URL="http://192.168.0.34:9000/boot07/api";
    public static final int REQUEST_TODO_INSERT=999;
    public static final int REQUEST_TODO_LIST=1000;
    public static final int REQUEST_TODO_DELETE=1001;
}

 

- INSERT 작업을 이렇게 만들어서 넣어준다.

- sendPostRequest() 에 999라고 넣어둔 코드도 상수화하여 넣어준다.

- 적당히 상수로 고쳐 작성하면 코드의 가독성이 좋아진다.

 

- 이렇게 리스너를 함수 형태로 만들면 이 안에서 this 가 MainActivity 를 가리킬 수 있다.

- 보통은 그냥 Activity.this 라고 써야한다.

 


 

 

- 만약 서버에 데이터가 있다면 onCreate되는 시점에 불러올 수도 있어야 한다!

- 이 불러오는 요청도 static final 상수로 만들어준다.

 (이 상수는 관례상 대문자, snake case로만 작성한다.)

- _ 이렇게 언더바로 쭉 연결하는것을 snake case라고 한다.

 

- onStop에 잠자고 있다가 다시 활성화될 수 있으므로,

 onCreate 대신 onStart에 이 작업을 넣어준다.

 

- 이 과정을 거쳐서 액티비티가 활성화된다.

이 각각의 단계에서 하고싶은 작업이 있으면 해당 단계의 메소드를 오버라이드 하면 된다.

 

- 홈, back버튼을 누르면 바로 onDestroy로 가는게 아니라 보통 onStop에 머물러 있는다.

- onStop 단계에 있을 때 앱을 재시작하면 onRestart를 거쳐서 다시 onStart로 들어간다.

- 어떤 작업을 처음 시작할 때, 재시작할 때 둘다 적용되게 하고 싶다면 onCreate가 아니라 onStart에서 해야 하는 이유!

 

- 부모의 메소드(super)를 호출해야 하는지 말아야 하는지는 그때그때 다르다...

 

- 원격지 서버에 get요청을 보내는 Util 안의 함수 사용하기!

 

- 이 MainActivity에서 http에 post방식, get방식 요청을 2개 하고 있다.

- 각각의 요청을 요청의 아이디를 통해서 구분할 수 있다.

 

- onSuccess, onFail 안에서 요청의 id를 사용해서 if~else로 분기해서 응답하기!

 


 

STS - Spring Boot 컨트롤러가 필요!

 

@RestController

- @ResponseBody를 생략할 수 있는 어노테이션이다.

- spring 5.0 부터 추가된 RestController 어노테이션을 붙이면, 

  json 문자열을 응답할때 @ResponseBody를 생략할 수 있다.

- responsebody가 기본으로 붙어있는 상태. 기본 응답방식이 JSON문자열이라고 생각하면 된다!

- 편하게 JSON으로 응답할 수 있도록 만들어짐

 

- json이나 xml로 바로 응답하겠다는 뜻이다. (jsp 페이지 포워드이동 X)

- 여기서 응답한 json 문자열을 안드로이드에서 활용한다.

 


 

TodoDto

package com.sy.boot07.api;

import org.apache.ibatis.type.Alias;

@Alias("todoDto")
public class TodoDto {
	//필드
	private int num;
	private String content;
	private String regdate;
	
	public TodoDto () {}

	public TodoDto(int num, String content, String regdate) {
		super();
		this.num = num;
		this.content = content;
		this.regdate = regdate;
	}

	public int getNum() {
		return num;
	}

	public void setNum(int num) {
		this.num = num;
	}

	public String getContent() {
		return content;
	}

	public void setContent(String content) {
		this.content = content;
	}

	public String getRegdate() {
		return regdate;
	}

	public void setRegdate(String regdate) {
		this.regdate = regdate;
	}
	
}

 

TodoDao 인터페이스

package com.sy.boot07.api;

import java.util.List;

import com.sy.boot07.api.TodoDto;

public interface TodoDao {	
	//추가
	public void insert(TodoDto dto);
	//리스트
	public List<TodoDto> getList();
	//삭제
	public void delete(int num);
}

 

TodoDaoImpl

package com.sy.boot07.api;

import java.util.List;

import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import com.sy.boot07.api.TodoDao;
import com.sy.boot07.api.TodoDto;

//dao에 붙여야 하는 어노테이션
@Repository
public class TodoDaoImpl implements TodoDao {

	//의존객체 주입(DI) 받기 
	@Autowired
	private SqlSession session;
	

	@Override
	public void insert(TodoDto dto) {
		/*
		 * Mapper's namespace => todo
		 * sql's id => insert
		 * parameterType => TodoDto
		 */
		session.insert("todo.insert", dto);
	}
	
	@Override
	public List<TodoDto> getList() {
		/*
		 * Mapper's namespace => getList
		 * sql's id => getList
		 * parameterType => 없음 
		 * resultType => TodoDto
		 */		
		return session.selectList("todo.getList");
	}


	@Override
	public void delete(int num) {
		/*
		 * Mapper's namespace => todo
		 * sql's id => insert
		 * parameterType => int (번호에 의한 삭제)
		 */
		session.delete("todo.delete", num);		
	}

}

- List<> 제너릭이 바로 result type이 된다.

 

 

- mapper 작성하기

 

- 이렇게 Dto에 typeAlias 설정을 해두면 간단한 코딩이 가능하다.

 

- 이것을 인식시키기 위해서는 어떤 패키지의 하위에있는 것들을 스캔할 것인지에 대한 설정이 필요하다.

 

 

TodoMapper

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="todo">

	<insert id="insert" parameterType="todoDto">	
		INSERT INTO todo
		(num, content, regdate)
		VALUES(todo_seq.NEXTVAL , #{content}, SYSDATE)
	</insert>
	
	<select id="getList" parameterType="todoDto" resultType="todoDto">
		SELECT num, content, regdate
		FROM todo
		ORDER BY num ASC
	</select>
	
	<delete id="delete" parameterType="int">
		DELETE FROM todo
		WHERE num=#{num}
	</delete>
	
</mapper>

 

- Mapper에서 지정한 이름을 Dao에서 호출하는 메소드에 들어가는 내용과 일치시켜 주어야 하고,

 파라미터 타입도 일치시켜야한다.(TodoDto)

 

 

- SELECT문의 경우, select된 행을 어디에 저장할지를 정해야 한다.

- 이 select된 row를 어디에 담을지 정하는 것resultType 이다

 

 

- 이 sql문만 봐서는 지금 행이 몇개인지 알 수 없다. 

- 사용자 입장에서 어떤 메소드로 어떻게 사용하느냐에 따라 결과가 달라질 수 있다.

 

 

- 메소드의 인자 String, Object 타입으로

- 할일을 content라는 파라미터 명으로 전달한다.

- Map을 content에서 리턴해주면 저런 문자열이 응답된다.

 

- 이것을 보고 출력될 JSON 문자열을 상상해보기!!

 


MainActivity 수정

 

 

- 이렇게 실행의 흐름이 들어온다.

 

- DB에서 목록을 받아와서 ListView에 뿌려야 한다.

- 첫번째요청은 List 요청이다

 

- 여기서 가져온 jsonStr이 json형식이 아닐 경우 exception이 발생한다.

→ try~catch문으로 묶어주기

 

- 출력되는 글자를 보고 어떤 메소드를 사용할지 정하면 된다.

 

- dto의 setter 메소드를 사용해서 담아준다.

- 이 dto에 담은 것을 위의 list에 넣어주면 된다.

→ 위의 list를 필드로 선언해야 한다!

 

 

- 이 시점에 리스트가 업데이트 된다.

- 아답타 필드도 업데이트해주기

 

- 이렇게 DB에 있는 목록이 안드로이드 기기에 출력된다.

- 우리가 사용하는 버스 도착정보앱 등도 이런 식으로 json 문자열로 날아온 값을 추출해서 보여주는 것이다.

 

- 이렇게 parsing으로 필요한 내용만 뽑아내서 UI에 출력하는 것!

 


 

- 입력하면 할일(content)을 DB 에 추가하기!

- 서버에 보내서 등록을 하고, 응답이 되면 목록을 새로 요청해야 한다.

 

 

- 이런 순서, 구조로 흐름이 진행된다!

- 목록 받아오기 요청을 다시 한다(4번) 

 

- 그런데 그러면 5번 블럭에서 목록을 출력하면서, 기존 목록에 내용이 누적될 수 있다.

- 맨 처음에 기존 목록을 삭제하는 코드가 필요하다!

- else if 블럭 안에 list.clear(); 코드 추가

 

 

- 값을 입력하면 이렇게 추가되고, 목록도 갱신된다.

 


 

- listView의 셀을 길게 누르고 있을 때 삭제되도록 하는 기능 추가

 

 

- LongClickListener 를 구현 (AdapterView 패키지에 들어있다)

 

- cellView에 변경하고 싶은 것이 있다면 저 view를 사용해도 된다.

 

- 2번인 '쇼핑' cell을 길게 누른다고 하면,  메소드의 인자에 이렇게 값이 전달된다.

 

public static final int REQUEST_TODO_DELETE=1001;

- 요청의 종류가 늘어났으므로 새로운 상수를 추가했다.

 

- 숫자를 문자로 바꾸어서 처리! Map<String,String>이므로 문자로 바꾸어준다.

 

 

- 삭제하기 전에 알림을 띄우는 기능 추가

 

- Dialoginterface 인터페이스가 자동완성된다.

 

- RequestId 값에 따라 else if 로 분기해서 메소드 추가. 삭제 후 새로운 리스트를 불러올 수 있도록!

 


 

- Spring에 이 삭제 작업을 처리할 컨트롤러가 필요하다

 

- mapper 수정

- Dto를 가져왔을 때는 필드의 이름과 일치시켜야 하지만,

 int와 같이 값 하나만 가지고 왔을 때는 #{num} 자리에 num이 아닌 어떤 값을 넣든 상관없다.

 

 

- 삭제여부를 묻는 창이 나타나고, 클릭하면 삭제되고 새로운 목록이 다시 나타난다.