안드로이드에서 Serverless REST API 호출하기
읽기 전
- 불필요한 코드나 잘못 작성된 내용에 대한 지적은 언제나 환영합니다. (예시 코드는 works on my machine 상태라 다르게 작동될 수 있습니다.)
- Udemy강의(AWS Serverless APIs & Apps - A Complete Introduction)를 기반으로 작성되었습니다. 필자의 프로젝트 요구사항에 맞춰 일부 변형하였으니 자세한 이해를 원하신다면 결제해서 수강해보시길 권장드립니다. 좋은 강의라고 생각합니다.
항상 할인 중이니 저렴한 가격에 줍줍하세요 - 안드로이드에서 Serverless REST API에 데이터 조회/삽입/삭제 요청합니다.
이번 글에서 할 일
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에 저장된 모든 값이 조회되었다.
조회하고자 하는 UserId값을 입력하여 해당 UserId 속성을 갖는 레코드의 데이터가 하단 테스트뷰에 출력되었다.
POST 메소드
레코드 정보들을 입력하여 POST 메소드 요청을 하면 하단 텍스트뷰에 제대로 삽입되었다는 메세지가 출력된다.
DynamoDB에 제대로 값이 삽입되어 있음을 확인하였다.
DELETE 메소드
UserId 값에 삭제하고자 하는 값을 입력한 뒤 DELETE 메소드 요청을 하면 하단 테스트뷰에 삭제된 UserId 값이 출력된다.
DynamoDB에 제대로 값이 삭제되었음을 확인하였다.
마무리
- 이로써 AWS API Gateway, AWS Lambda, AWS DynamoDB를 활용하여 Serverless REST API를 제작하고 Android Application에서 호출하는 과정까지 모두 끝냈습니다. 제작하면서 항상 웹 콘솔에서 직관적으로 데이터를 확인할 수 있다는 측면에서 DynamoDB가 확실한 이점을 가지고 있지만 엄밀하게 사전 정의된 데이터를 사용한다면 익숙한 문법을 가진 RDS와의 연동이 더 좋은 방법일 수 있지 않았을까라는 생각이 들었습니다.
- 아무튼 AWS 서비스 관련 자료와 특히 Serverless 자료들은 많은 블로그와 자체 레퍼런스에서 소개하고 있습니다만 아예 저처럼 생기초부터 모르는 (개)초보들이 참고하기에는 조금 허들이 있다고 느껴 정리하게 되었습니다. 특히 node.js와 웹 애플리케이션 위주로 다들 사용하고 계셔서 python을 사용하려는 입장에선 더욱 동기부여가 되었습니다.
- 굉장히 초보적인 포스팅이었기에 오류가 군데군데 있을 수 있으니 의견 주시면 지속적으로 반영하겠습니다.
'Cloud > AWS' 카테고리의 다른 글
Serverless | AWS Lambda에서 Firebase Notification 전송 (18) | 2021.01.11 |
---|---|
Serverless | AWS Lambda Layer 외부 모듈 import하기 (0) | 2021.01.08 |
Serverless | AWS API Gateway + Lambda + DynamoDB를 활용한 REST API 구축 #005 (0) | 2020.07.30 |
Serverless | AWS API Gateway + Lambda + DynamoDB를 활용한 REST API 구축 #004 (0) | 2020.07.27 |
Serverless | AWS API Gateway + Lambda + DynamoDB를 활용한 REST API 구축 #003 (2) | 2020.07.26 |