안드로이드에서 Serverless REST API 호출하기

읽기 전

  • 불필요한 코드나 잘못 작성된 내용에 대한 지적은 언제나 환영합니다. (예시 코드는 works on my machine 상태라 다르게 작동될 수 있습니다.)
  • Udemy강의(AWS Serverless APIs & Apps - A Complete Introduction)를 기반으로 작성되었습니다. 필자의 프로젝트 요구사항에 맞춰 일부 변형하였으니 자세한 이해를 원하신다면 결제해서 수강해보시길 권장드립니다. 좋은 강의라고 생각합니다. 항상 할인 중이니 저렴한 가격에 줍줍하세요
  • 안드로이드에서 Serverless REST API에 데이터 조회/삽입/삭제 요청합니다.

이번 글에서 할 일

AWS_Serverless_006_01

REST API를 안드로이드에서 호출하고 기능 체크를 하여 완성합니다.

안드로이드 코드 작성

MainActivity.xml 정의

이번 포스팅에 사용된 페이지 xml 코드로 최대한 간단하게 배치하기로 하였다.

<?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">
<TextView
android:id="@+id/tvUserId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:text="UserId" />
<EditText
android:id="@+id/edUserId"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="textPersonName"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvUserId" />
<TextView
android:id="@+id/tvPrevScore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="prevScore"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/edUserId" />
<EditText
android:id="@+id/edPrevScore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvPrevScore"
android:ems="10"
android:inputType="number" />
<TextView
android:id="@+id/tvPrevRank"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/edPrevScore"
android:text="prevRank"
tools:layout_editor_absoluteY="64dp" />
<EditText
android:id="@+id/edPrevRank"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvPrevRank"
android:ems="10"
android:inputType="number" />
<TextView
android:id="@+id/tvCurrScore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/edPrevRank"
android:text="currScore"
tools:layout_editor_absoluteY="129dp" />
<EditText
android:id="@+id/edCurrScore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvCurrScore"
android:ems="10"
android:inputType="number" />
<TextView
android:id="@+id/tvCurrRank"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/edCurrScore"
android:text="currRank"
tools:layout_editor_absoluteY="45dp" />
<EditText
android:id="@+id/edCurrRank"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvCurrRank"
android:ems="10"
android:inputType="number" />
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/edCurrRank"
android:orientation="horizontal">
<Button
android:id="@+id/btnGet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="GET" />
<Button
android:id="@+id/btnPost"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="POST" />
<Button
android:id="@+id/btnDelete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="DELETE" />
</LinearLayout>
<TextView
android:id="@+id/tvMainText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/linearLayout"
android:text="MAIN TEXT" />
</androidx.constraintlayout.widget.ConstraintLayout>

MainActivity.java 정의

API 호출하여 데이터를 다루는 데 사용한 액티비티 코드이다. GET 메소드의 경우 UserId 값을 입력하지 않으면 경로 파라미터에 all을 붙이고 입력된 경우 single 을 붙이고 입력받은 UserId 값을 쿼리 문자열로 더한 뒤 URL을 구성한다.

package com.example.myapplication;
import androidx.appcompat.app.AppCompatActivity;
import android.app.ProgressDialog;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.net.HttpURLConnection;
import java.net.URL;
public class MainActivity extends AppCompatActivity {
private TextView tvMainText;
private EditText edUserId, edPrevScore, edPrevRank, edCurrScore, edCurrRank;
private Button btnGet, btnPost, btnDelete;
private ProgressDialog progressDialog;
private static final String TAG = "basic REST API";
private String connMethod;
private String mURL = "https://imdto253jj.execute-api.ap-northeast-2.amazonaws.com/dev/basic-api-res";
private String API_KEY = "";
private String bodyJson;
private static final int LOAD_SUCCESS = 101;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnGet = findViewById(R.id.btnGet);
btnPost = findViewById(R.id.btnPost);
btnDelete = findViewById(R.id.btnDelete);
tvMainText = findViewById(R.id.tvMainText);
edUserId = findViewById(R.id.edUserId);
edPrevScore = findViewById(R.id.edPrevScore);
edPrevRank = findViewById(R.id.edPrevRank);
edCurrScore = findViewById(R.id.edCurrScore);
edCurrRank = findViewById(R.id.edCurrRank);
Button.OnClickListener ButtonListener = new Button.OnClickListener() {
@Override
public void onClick(View v) {
if (v==btnGet){
connMethod = "GET";
if (edUserId.getText().toString().equals("")) {
mURL = "https://imdto253jj.execute-api.ap-northeast-2.amazonaws.com/dev/basic-api-res/" + "all";
} else if(!edUserId.getText().toString().equals("")) {
mURL = "https://imdto253jj.execute-api.ap-northeast-2.amazonaws.com/dev/basic-api-res/" + "single?UserId=" + edUserId.getText().toString();
}
getJSON(mURL, connMethod);
} else if (v==btnPost) {
connMethod = "POST";
getJSON(mURL, connMethod);
} else if (v==btnDelete) {
if (edUserId.getText().toString().equals("")){
Toast.makeText(getApplicationContext(),"check UserId", Toast.LENGTH_SHORT);
} else {
connMethod = "DELETE";
getJSON(mURL, connMethod);
}
}
}
};
btnGet.setOnClickListener(ButtonListener);
btnPost.setOnClickListener(ButtonListener);
btnDelete.setOnClickListener(ButtonListener);
}
private final MyHandler mHandler = new MyHandler(this);
private static class MyHandler extends Handler {
private final WeakReference<MainActivity> weakReference;
public MyHandler(MainActivity mainactivity) {
weakReference = new WeakReference<MainActivity>(mainactivity);
}
@Override
public void handleMessage(Message msg) {
MainActivity mainactivity = weakReference.get();
if (mainactivity != null) {
switch (msg.what) {
case LOAD_SUCCESS:
String jsonString = (String)msg.obj;
mainactivity.tvMainText.setText(jsonString);
break;
}
}
}
}
public void getJSON(final String mUrl, final String connMethod) {
Thread thread = new Thread(new Runnable() {
public void run() {
String result;
try {
URL url = new URL(mUrl);
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setReadTimeout(3000);
httpURLConnection.setConnectTimeout(3000);
httpURLConnection.setDoInput(true);
httpURLConnection.setRequestMethod(connMethod);
httpURLConnection.setRequestProperty("Content-Type", "application/json");
httpURLConnection.setUseCaches(false);
if (connMethod.equals("POST")){
httpURLConnection.setDoOutput(true);
JSONObject body = new JSONObject();
body.put("prevScore", Integer.parseInt(edPrevScore.getText().toString()));
body.put("prevRank", Integer.parseInt(edPrevRank.getText().toString()));
body.put("currScore", Integer.parseInt(edCurrScore.getText().toString()));
body.put("currRank", Integer.parseInt(edCurrRank.getText().toString()));
bodyJson = body.toString();
DataOutputStream wr = new DataOutputStream(httpURLConnection.getOutputStream());
wr.write(bodyJson.getBytes("EUC-KR"));
wr.flush();
wr.close();
} else if (connMethod.equals("DELETE")){
httpURLConnection.setDoOutput(true);
JSONObject body = new JSONObject();
body.accumulate("UserId", edUserId.getText());
bodyJson = body.toString();
Log.d("bodyJson",bodyJson);
OutputStream wr = httpURLConnection.getOutputStream();
wr.write(bodyJson.getBytes("EUC-KR"));
wr.flush();
wr.close();
}
httpURLConnection.connect();
int responseStatusCode = httpURLConnection.getResponseCode();
InputStream inputStream;
if (responseStatusCode == HttpURLConnection.HTTP_OK) {
inputStream = httpURLConnection.getInputStream();
} else {
inputStream = httpURLConnection.getErrorStream();
}
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "EUC-KR");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
StringBuilder sb = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
sb.append(line);
}
bufferedReader.close();
httpURLConnection.disconnect();
result = sb.toString().trim();
} catch (Exception e) {
result = e.toString();
}
Message message = mHandler.obtainMessage(LOAD_SUCCESS, result);
mHandler.sendMessage(message);
}
});
thread.start();
}
}

안드로이드 어플리케이션 실행 후 REST API 호출

GET 메소드

우선 아무것도 입력하지 않아 all 경로 파라미터가 입력된 상태이며 그에 따라 하단 텍스트뷰에 DB에 저장된 모든 값이 조회되었다.

AWS_Serverless_006_02

조회하고자 하는 UserId값을 입력하여 해당 UserId 속성을 갖는 레코드의 데이터가 하단 테스트뷰에 출력되었다.

AWS_Serverless_006_03

POST 메소드

레코드 정보들을 입력하여 POST 메소드 요청을 하면 하단 텍스트뷰에 제대로 삽입되었다는 메세지가 출력된다.

AWS_Serverless_006_04

DynamoDB에 제대로 값이 삽입되어 있음을 확인하였다.

AWS_Serverless_006_05

DELETE 메소드

UserId 값에 삭제하고자 하는 값을 입력한 뒤 DELETE 메소드 요청을 하면 하단 테스트뷰에 삭제된 UserId 값이 출력된다.

AWS_Serverless_006_06

DynamoDB에 제대로 값이 삭제되었음을 확인하였다.

AWS_Serverless_006_07

마무리

  • 이로써 AWS API Gateway, AWS Lambda, AWS DynamoDB를 활용하여 Serverless REST API를 제작하고 Android Application에서 호출하는 과정까지 모두 끝냈습니다. 제작하면서 항상 웹 콘솔에서 직관적으로 데이터를 확인할 수 있다는 측면에서 DynamoDB가 확실한 이점을 가지고 있지만 엄밀하게 사전 정의된 데이터를 사용한다면 익숙한 문법을 가진 RDS와의 연동이 더 좋은 방법일 수 있지 않았을까라는 생각이 들었습니다.
  • 아무튼 AWS 서비스 관련 자료와 특히 Serverless 자료들은 많은 블로그와 자체 레퍼런스에서 소개하고 있습니다만 아예 저처럼 생기초부터 모르는 (개)초보들이 참고하기에는 조금 허들이 있다고 느껴 정리하게 되었습니다. 특히 node.js와 웹 애플리케이션 위주로 다들 사용하고 계셔서 python을 사용하려는 입장에선 더욱 동기부여가 되었습니다.
  • 굉장히 초보적인 포스팅이었기에 오류가 군데군데 있을 수 있으니 의견 주시면 지속적으로 반영하겠습니다.

+ Recent posts