반응형
게시된 내용은 작성자가 공부한 내용을 정리하여 기록하였습니다. 일부 빠지거나 부족한 부분이 있을 수 있습니다. 최대한 편집 없이 기록하였습니다.
출처 블로그
사용 도구 : STS 4 (Spring Tools Suite), AWS EC2, MariaDB, Windows, Android Studio
아래와 같은 앱을 만들겠습니다.
이름, 나이, 주소를 넣고 ADD를 누르면 DB에 추가 됩니다.
수정을 눌러서 이름, 나이, 주소를 수정합니다.
삭제를 눌러서 데이터를 삭제합니다.
1. 안드로이드 프로젝트 생성
안드로이드 프로젝트를 생성하겠습니다. Empty Activity를 선택합니다.
그리고 프로젝트 이름을 정하고, Java를 선택합니다.
2. 설정 추가
Lombok, RecyclerView, Retrofit, Gson 라이브러리를 추가합니다.
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
// Lombok
compileOnly 'org.projectlombok:lombok:1.18.8'
annotationProcessor 'org.projectlombok:lombok:1.18.8'
// RecyclerView
implementation "androidx.recyclerview:recyclerview:1.1.0"
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
// Gson
implementation 'com.google.code.gson:gson:2.8.5'
}
AndroidManifest.xml에 인터넷 권한을 추가합니다.
<uses-permission android:name="android.permission.INTERNET"/>
3. 레이아웃 만들기
activity_main.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"
android:orientation="vertical"
tools:context=".MainActivity">
<EditText
android:id="@+id/et_name"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:hint="NAME"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/et_age"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:hint="AGE"
app:layout_constraintStart_toEndOf="@+id/et_name"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/et_address"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:hint="ADDRESS"
app:layout_constraintStart_toEndOf="@+id/et_age"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:layout_width="69dp"
android:layout_height="46dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:text="Add"
android:id="@+id/btn_add"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="68dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:text="MemberList"
android:textColor="#000"
android:textSize="25sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/member_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
</androidx.constraintlayout.widget.ConstraintLayout>
member_info.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="wrap_content"
android:layout_margin="3dp"
android:background="@drawable/border_shadow"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:id="@+id/info_layout">
<TextView
android:id="@+id/info_id"
android:layout_width="50dp"
android:layout_height="40dp"
android:text="ID"
android:gravity="center"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/info_name"
android:layout_width="60dp"
android:layout_height="40dp"
android:text="NAME"
android:gravity="center"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/info_id"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/info_age"
android:layout_width="60dp"
android:layout_height="40dp"
android:text="AGE"
android:gravity="center"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/info_name"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/info_address"
android:layout_width="75dp"
android:layout_height="40dp"
android:text="ADDRESS"
android:gravity="center"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/info_age"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/info_created"
android:layout_width="80dp"
android:layout_height="40dp"
android:text="CREATED"
android:gravity="center"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/info_address"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/info_update"
android:layout_width="31dp"
android:layout_height="18dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:background="#f0f0f0"
android:text="수정"
android:textSize="8sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/info_delete"
android:layout_width="31dp"
android:layout_height="18dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:background="#f0f0f0"
android:text="삭제"
android:textSize="8sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/info_update" />
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/update_layout"
android:layout_width="match_parent"
android:layout_height="50dp"
android:visibility="gone"
>
<TextView
android:id="@+id/update_id"
android:layout_width="50dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="7dp"
android:gravity="center"
android:text="ID"
android:textSize="10sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/update_name"
android:layout_width="60dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="7dp"
android:gravity="center"
android:text="NAME"
android:textSize="10sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/update_id"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/update_age"
android:layout_width="60dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="7dp"
android:gravity="center"
android:text="AGE"
android:textSize="10sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/update_name"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/update_address"
android:layout_width="75dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="7dp"
android:gravity="center"
android:text="ADDRESS"
android:textSize="10sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/update_age"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/update_created"
android:layout_width="20dp"
android:layout_height="40dp"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="7dp"
android:gravity="center"
android:text="CREATED"
android:textSize="10sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="@+id/update_address"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.416" />
<Button
android:id="@+id/update_btn"
android:layout_width="31dp"
android:layout_height="35dp"
android:layout_marginStart="4dp"
android:layout_marginLeft="4dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="4dp"
android:layout_marginRight="4dp"
android:layout_marginBottom="8dp"
android:background="#f0f0f0"
android:text="수정"
android:textSize="8sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="@+id/update_created"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
drawable 폴더에
border_shadow.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<shape android:shape="rectangle">
<solid android:color="#CABBBBBB"/>
<corners android:radius="2dp" />
</shape>
</item>
<item
android:left="1dp"
android:right="1dp"
android:top="1dp"
android:bottom="2dp">
<shape android:shape="rectangle">
<solid android:color="@android:color/white"/>
<corners android:radius="2dp" />
</shape>
</item>
</layer-list>
4. Object 정의
스프링 프로젝트에서 사용했던 Member 객체를 그대로 사용합니다.
Member.Java
package com.example.retrofixtest;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Member {
private long id;
private String name;
private int age;
private String address;
private Date createdAt;
public long getId() {
return id;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getAddress() {
return address;
}
public Date getCreatedAt(){
return createdAt;
}
}
5. Interface 정의
아래 BASE_URL에 EC2의 퍼블릭 IP를 넣어주세요.
DataService.java
package com.example.retrofixtest;
import java.util.List;
import java.util.Map;
import okhttp3.OkHttpClient;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Path;
public class DataService {
private String BASE_URL = ""; // TODO REST API 퍼블릭 IP로 변경
Retrofit retrofitClient =
new Retrofit.Builder()
.baseUrl(BASE_URL)
.client(new OkHttpClient.Builder().build())
.addConverterFactory(GsonConverterFactory.create())
.build();
SelectAPI select = retrofitClient.create(SelectAPI.class);
InsertAPI insert = retrofitClient.create(InsertAPI.class);
UpdateAPI update = retrofitClient.create(UpdateAPI.class);
DeleteAPI delete = retrofitClient.create(DeleteAPI.class);
}
interface SelectAPI{
@GET("select/{id}")
Call<Member> selectOne(@Path("id") long id);
@GET("select")
Call<List<Member>> selectAll();
}
interface InsertAPI{
@POST("insert")
Call<Member> insertOne(@Body Map<String, String> map);
}
interface UpdateAPI{
@POST("update/{id}")
Call<Member> updateOne(@Path("id") long id, @Body Map<String, String> map);
}
interface DeleteAPI{
@POST("delete/{id}")
Call<ResponseBody> deleteOne(@Path("id") long id);
}
6. Adapter 정의
콜백을 객체로 받지 않는 Delete 요청 같은 경우에는 콜백 타입이 String이 아닌 ResponseBody 타입으로 받습니다.
MemberAdapter.java
package com.example.retrofixtest;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.recyclerview.widget.RecyclerView;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class MemberAdapter extends RecyclerView.Adapter<MemberAdapter.ViewHolder> {
private List<Member> data;
private Context context;
private DataService dataService;
MemberAdapter(List<Member> data, Context context, DataService dataService) {
this.data = data;
this.context = context;
this.dataService = dataService;
}
@Override
public MemberAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
return new MemberAdapter.ViewHolder(inflater.inflate(R.layout.member_info, parent, false));
}
@Override
public int getItemCount() {
return data.size() ;
}
@Override
public void onBindViewHolder(final MemberAdapter.ViewHolder holder, final int position) {
holder.info_id.setText(String.valueOf(data.get(position).getId()));
holder.info_name.setText(data.get(position).getName());
holder.info_age.setText(String.valueOf(data.get(position).getAge()));
holder.info_address.setText(data.get(position).getAddress());
holder.info_created.setText(dateParser(data.get(position).getCreatedAt()));
holder.info_update.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
holder.update_id.setText(String.valueOf(data.get(position).getId()));
holder.update_name.setText(data.get(position).getName());
holder.update_age.setText(String.valueOf(data.get(position).getAge()));
holder.update_address.setText(data.get(position).getAddress());
holder.update_created.setText(data.get(position).getCreatedAt().toString());
holder.info_layout.setVisibility(View.GONE);
holder.update_layout.setVisibility(View.VISIBLE);
}
});
holder.info_delete.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
dataService.delete.deleteOne(data.get(position).getId()).enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
data.remove(position);
notifyItemRemoved(position);
Toast.makeText(context, "아이템 삭제 완료", Toast.LENGTH_SHORT).show();
}
@Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
t.printStackTrace();
}
});
}
});
holder.update_btn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Map<String, String> map = new HashMap();
map.put("name", holder.update_name.getText().toString());
map.put("age", holder.update_age.getText().toString());
map.put("address", holder.update_address.getText().toString());
dataService.update.updateOne(data.get(position).getId(), map).enqueue(new Callback<Member>() {
@Override
public void onResponse(Call<Member> call, Response<Member> response) {
data.set(position, response.body());
notifyDataSetChanged();
Toast.makeText(context, "아이템 수정 완료", Toast.LENGTH_SHORT).show();
holder.info_layout.setVisibility(View.VISIBLE);
holder.update_layout.setVisibility(View.GONE);
}
@Override
public void onFailure(Call<Member> call, Throwable t) {
t.printStackTrace();
}
});
}
});
}
public String dateParser(Date date){
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM/dd hh:mm");
return simpleDateFormat.format(date);
}
public class ViewHolder extends RecyclerView.ViewHolder {
ConstraintLayout info_layout, update_layout;
TextView info_id, info_name, info_age, info_address, info_created, update_id, update_created;
Button info_update, info_delete, update_btn;
EditText update_name, update_age, update_address;
ViewHolder(View itemView) {
super(itemView);
// 뷰 영역
info_layout = itemView.findViewById(R.id.info_layout);
info_id = itemView.findViewById(R.id.info_id);
info_name = itemView.findViewById(R.id.info_name);
info_age = itemView.findViewById(R.id.info_age);
info_address = itemView.findViewById(R.id.info_address);
info_created = itemView.findViewById(R.id.info_created);
info_update = itemView.findViewById(R.id.info_update);
info_delete = itemView.findViewById(R.id.info_delete);
// 수정 영역
update_layout = itemView.findViewById(R.id.update_layout);
update_id = itemView.findViewById(R.id.update_id);
update_name = itemView.findViewById(R.id.update_name);
update_age = itemView.findViewById(R.id.update_age);
update_address = itemView.findViewById(R.id.update_address);
update_created = itemView.findViewById(R.id.update_created);
update_btn = itemView.findViewById(R.id.update_btn);
}
}
}
7. MainActivity 정의
MainActivity.java
package com.example.retrofixtest;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class MainActivity extends AppCompatActivity {
DataService dataService = new DataService();
List<Member> members;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final RecyclerView member_list = findViewById(R.id.member_list);
final EditText et_name = findViewById(R.id.et_name);
final EditText et_age = findViewById(R.id.et_age);
final EditText et_address = findViewById(R.id.et_address);
member_list.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
dataService.select.selectAll().enqueue(new Callback<List<Member>>() {
@Override
public void onResponse(Call<List<Member>> call, Response<List<Member>> response) {
Log.d("test", response.body().toString());
members = response.body();
setAdapter(member_list);
}
@Override
public void onFailure(Call<List<Member>> call, Throwable t) {
t.printStackTrace();
}
});
Button btn_add = findViewById(R.id.btn_add);
btn_add.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
Map<String, String> map = new HashMap();
map.put("name", et_name.getText().toString());
map.put("age", et_age.getText().toString());
map.put("address", et_address.getText().toString());
dataService.insert.insertOne(map).enqueue(new Callback<Member>() {
@Override
public void onResponse(Call<Member> call, Response<Member> response) {
members.add(response.body());
setAdapter(member_list);
Toast.makeText(MainActivity.this, "유저 등록 완료", Toast.LENGTH_SHORT).show();
et_name.setText("");
et_age.setText("");
et_address.setText("");
}
@Override
public void onFailure(Call<Member> call, Throwable t) {
t.printStackTrace();
}
});
}
});
}
void setAdapter(RecyclerView member_list){
member_list.setAdapter(new MemberAdapter(members, this, dataService));
}
}
이렇게 안드로이드 앱에서 데이터를 추가/수정/삭제/조회할 수 있는 과정을 모두 알아봤습니다.
이렇게 배운 내용을 통해서 재활 운동 앱 서비스를 만들었습니다. 환자의 재활 상태를 DB에 저장하여 관리하고, 축적된 데이터를 활용하여 환자들이 효율적인 재활이 가능한 앱 서비스입니다.
반응형
'IT > Spring' 카테고리의 다른 글
[리액터] 리액터 개요 (0) | 2022.07.11 |
---|---|
Spring boot와 MariaDB를 JPA로 연동 (0) | 2022.03.20 |
[안드로이드 앱 서버 만들기] 4. AWS EC2 인스턴스에 배포 (0) | 2021.07.06 |
[안드로이드 앱 서버 만들기] 3. MariaDB와 연동 그리고 JPA로 CRUD 메소드 만들기 (0) | 2021.07.06 |
[안드로이드 앱 서버 만들기] 2. AWS EC2와 MariaDB 생성 (0) | 2021.07.06 |