관리 메뉴

나만을 위한 블로그

[Android] 레트로핏 예제 - 레트로핏으로 앱에서 CRUD하는 방법 < 1 > 본문

Android

[Android] 레트로핏 예제 - 레트로핏으로 앱에서 CRUD하는 방법 < 1 >

참깨빵위에참깨빵 2020. 10. 6. 21:14
728x90
반응형

1. 레트로핏이란?

 

[Android] Retrofit(레트로핏)이란?

참고한 사이트 : http://devflow.github.io/retrofit-kr/ Retrofit - 한글 문서 A type-safe HTTP client for Android and Java devflow.github.io https://galid1.tistory.com/617 Java - Retrofit이란? (retrofi..

onlyfor-me-blog.tistory.com

2. 레트로핏 예제 - 서버에서 값 가져와 앱에서 보여주기

 

[Android] 레트로핏 예제 - 서버에서 값 가져와 앱에서 보여주기

Retrofit, Volley와 관련해서 연계된 포스팅을 작성할 계획이다. 글을 작성하면서 공부하지 않고 넘겼던 부분이 있다면 공부하기 위해서 시리즈를 기획하게 됐다. 순서는 아래와 같다. 레트로핏이 ��

onlyfor-me-blog.tistory.com

3. 레트로핏으로 앱에서 CRUD하는 방법 < 1 >

4. 레트로핏으로 앱에서 CRUD하는 방법 < 2 >

 

 

아래에서 설명하는 내용은 모두 개인의 주관이며, 코드 작성법 또한 정석이 아니다. 이런 방식으로도 쓴다는 것을 보여주고 나 혼자 공부하기 위해 기록하는 글이다. 무조건 이 글이 정답은 아닌 것에 주의하자.

이번 포스팅은 앱에서 CRUD하는 예제다.

분량이 많으니 1편에선 INSERT와 SELECT, 2편에선 UPDATE와 DELETE하는 것을 포스팅하려고 한다.

전에 사용한 DB와 테이블, 앱 화면은 같은 것을 사용한다. 사용하는 툴도 똑같다.

 

 

< 진행 순서 >

 

 

1. PHP 파일 코딩

2. 안드로이드 XML, 자바 파일 코딩

 

 

1) PHP 파일 코딩

 

 

먼저 CRUD하는 기능을 각각 가진 PHP 파일을 아래와 같이 만든다.

 

INSERT 쿼리문을 수행하는 파일 = example_insert.php

UPDATE 쿼리문을 수행하는 파일 = example_update.php

DELETE 쿼리문을 수행하는 파일 = example_delete.php

SELECT 쿼리문을 수행하는 파일 = example_select.php

 

이 중 SELECT 쿼리문을 수행하는 파일은 이전 글에 이미 적었기 때문에, 맨 마지막에 기존 내용을 수정해 진행하겠다.

먼저 INSERT 쿼리를 수행하는 example_insert.php 부터 코딩해보자.

 

<?php

if ($_SERVER['REQUEST_METHOD'] == 'POST')
{
    header("Content-type:application/json");

    require_once 'example_con.php';

    $name = $_POST['name'];
    $hobby = $_POST['hobby'];

    $sql = "INSERT INTO person(name, hobby)
            VALUES('$name', '$hobby')";
    
    if (mysqli_query($con, $sql))
    {
        $response['success'] = true;
        $response['message'] = "추가 완료";
    }
    else
    {
        $response['success'] = false;
        $response['message'] = "추가 실패";
    }
}
else
{
    $response['success'] = false;
    $response['message'] = "POST로 오지 않음";
}

echo json_encode($response);

파일 첫 줄의 if문은 REQUEST_METHOD가 POST여야 INSERT 쿼리문이 작동하고, 그게 아니라면 INSERT 쿼리문이 작동하지 않게 하는 코드다. 이 코드가 없어도 작동은 정상적으로 이뤄진다.

require_once 밑을 보면 이전 포스팅에선 $_GET을 썼지만, 이번엔 POST로 요청하기 때문에 $_POST를 쓴 것을 볼 수 있다.

이 파일을 포스트맨으로 돌린 다음 확인해보자. name은 Min, hobby는 movie를 넣었다.

HTTP 코드가 200이니 문제없이 정상적으로 실행됐다는 것을 알 수 있다.

이제 DB를 확인해보자.

 

 

입력한 내용들이 정상적으로 DB에 추가됐다.

이제 UPDATE 쿼리문을 수행하는 example_update.php 파일을 코딩한다.

 

<?php

header("Content-type:application/json");

require_once 'example_con.php';

$id = $_POST['id'];
$name = $_POST['name'];
$hobby = $_POST['hobby'];

$sql = "UPDATE person SET name = '$name', hobby = '$hobby' WHERE id = '$id'";

$result = mysqli_query($con, $sql);

여기선 name, hobby와 id까지 같이 POST로 가져오는 걸 볼 수 있다.

UPDATE, DELETE 쿼리문을 짤 때 주의할 것은 이렇게 id를 같이 넣어주지 않으면 name이 Min이고 hobby가 movie인 데이터들을 전부 입력한 값으로 바꿔버리거나 삭제해버린다.

그래서 UPDATE나 DELETE를 할 때는 꼭 id를 가져와서, 그것의 조건에 맞는 row의 데이터만 바꾸도록 해야 한다. 나중에 큰 불상사가 생길 수도 있다.

이번엔 name에 Mib, hobby에 movig을 넣어보겠다. id는 MySQL 접속 툴이나 쉘에서 확인해보면 되니 Min으로 저장돼있는 row의 id와 수정할 이름, 취미 문자열을 입력해주자.

 

제대로 Mib과 movig이란 글자로 바뀐 걸 볼 수 있다.

이제 DELETE 쿼리문을 수행하는 example_delete.php 파일을 코딩한다.

 

<?php

$id = $_POST['id'];

require_once 'example_con.php';

$sql = "DELETE FROM person WHERE id = '$id'";

$result = mysqli_query($con, $sql);

if ($result)
{
    echo "Success";
}
else
{
    echo "Failed";
}

이렇게 하면 포스트맨으로 id를 넣고 POST로 돌릴 경우 Success라는 문자열이 출력될 것이다. 확인해보자.

 

 

됐다. 이제 DB에서도 삭제됐는지 확인해보자.

 

 

깔끔하게 삭제됐다.

마지막으로 이전 포스팅에서 사용했던 SELECT 쿼리문을 사용하는 example_select.php의 내용을 수정한다.

 

<?php

header("Content-type:application/json");

require_once 'example_con.php';

$name = $_GET['name'];
$hobby = $_GET['hobby'];

$sql = mysqli_query($con, "SELECT * FROM person");

$response = array();

while($row = mysqli_fetch_assoc($sql))
{
    array_push($response, array(
        'id' => $row['id'],
        'name' => $row['name'],
        'hobby' => $row['hobby']
    ));
}

echo json_encode($response);

이렇게 코딩하고 포스트맨으로 결과를 확인하면 아래와 같이 JSON 형식으로 출력되는 걸 볼 수 있다.

 

PHP 파일 준비는 끝났다. 이제 앱을 코딩해보자.

 

 

2) 안드로이드 XML, 자바 파일 코딩

 

 

먼저 ApiInterface.java에 추상 메서드를 추가해준다.

SELECT 쿼리를 사용하는 메서드 또한 내용이 바뀌었으니 수정해야 한다.

 

import java.util.List;

import retrofit2.Call;
import retrofit2.http.Field;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Query;

public interface ApiInterface
{
    @GET("example_select.php")
    Call<List<Person>> getNameHobby();

    @FormUrlEncoded
    @POST("example_insert.php")
    Call<Person> insertPerson(
            @Field("name") String name,
            @Field("hobby") String hobby
    );
}

 

POST 방식으로 서버에 요청할 때는 @Field라는 어노테이션을 쓰는 것에 주의하자. GET일 때는 @Query 어노테이션을 사용한다.

다음은 리사이클러뷰 어댑터다.

 

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

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.List;

public class PersonAdapter extends RecyclerView.Adapter<PersonAdapter.PersonViewHolder>
{
    private Context context;
    private List<Person> lists;
    private ItemClickListener itemClickListener;

    public PersonAdapter(Context context, List<Person> lists, ItemClickListener itemClickListener)
    {
        this.context = context;
        this.lists = lists;
        this.itemClickListener = itemClickListener;
    }

    @NonNull
    @Override
    public PersonAdapter.PersonViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType)
    {
        View view = LayoutInflater.from(context).inflate(R.layout.person_item, parent, false);
        return new PersonViewHolder(view, itemClickListener);
    }

    @Override
    public void onBindViewHolder(@NonNull PersonAdapter.PersonViewHolder holder, int position)
    {
        Person person = lists.get(position);

        holder.name_text.setText(person.getName());
        holder.hobby_text.setText(person.getHobby());
    }

    @Override
    public int getItemCount()
    {
        return lists.size();
    }

    public class PersonViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener
    {
        public LinearLayout linearLayout;
        public TextView name_text, hobby_text;
        ItemClickListener itemClickListener;

        public PersonViewHolder(@NonNull View view, ItemClickListener itemClickListener)
        {
            super(view);
            linearLayout = view.findViewById(R.id.linear_layout);
            name_text = view.findViewById(R.id.name_text);
            hobby_text = view.findViewById(R.id.hobby_text);

            this.itemClickListener = itemClickListener;
            linearLayout.setOnClickListener(this);
        }

        @Override
        public void onClick(View view)
        {
            itemClickListener.onItemClick(view, getAdapterPosition());
        }
    }

    public interface ItemClickListener
    {
        void onItemClick(View view, int position);
    }

}

 

어댑터의 onCreateViewHolder()에 사용되는 person_item.xml의 내용은 아래처럼 한다.

이름, 취미만 출력시킬 것이기 때문에 단순하게 짰다.

 

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <LinearLayout
        android:id="@+id/linear_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:weightSum="2">

        <TextView
            android:id="@+id/name_text"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="이름"
            android:textSize="20sp"
            android:gravity="center"/>

        <TextView
            android:id="@+id/hobby_text"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="취미"
            android:textSize="20sp"
            android:gravity="center"/>

    </LinearLayout>

</LinearLayout>

 

모델 클래스도 수정한다. 테이블 상에서의 id값을 받아야 하고, PHP 파일에서 success와 message를 사용했으니 이것에 맞는 변수와 게터세터도 추가해야 한다.

 

import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;

public class Person
{
    @Expose
    @SerializedName("id") private int id;

    @Expose
    @SerializedName("name") private String name;

    @Expose
    @SerializedName("hobby") private String hobby;

    @Expose
    @SerializedName("success") private Boolean success;

    @Expose
    @SerializedName("message") private String message;

    public int getId()
    {
        return id;
    }

    public void setId(int id)
    {
        this.id = id;
    }

    public String getName()
    {
        return name;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public String getHobby()
    {
        return hobby;
    }

    public void setHobby(String hobby)
    {
        this.hobby = hobby;
    }

    public Boolean getSuccess()
    {
        return success;
    }

    public void setSuccess(Boolean success)
    {
        this.success = success;
    }

    public String getMessage()
    {
        return message;
    }

    public void setMessage(String message)
    {
        this.message = message;
    }
    
}

 

메인 액티비티의 경우 리사이클러뷰를 넣을 것이기 때문에 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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:weightSum="3">

        <EditText
            android:id="@+id/edit_name"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="이름 입력"/>

        <EditText
            android:id="@+id/edit_hobby"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:hint="취미 입력"/>

        <Button
            android:id="@+id/add_btn"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="추 가"
            android:textSize="20sp"/>

    </LinearLayout>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/person_recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"/>

</LinearLayout>

 

자바 파일도 이에 맞춰서 코딩해준다.

 

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

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

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class MainActivity extends AppCompatActivity
{
    public static final String TAG = "MainActivity";

    EditText edit_name, edit_hobby;
    Button add_btn;
    String name, hobby;
    RecyclerView recyclerView;
    PersonAdapter adapter;
    PersonAdapter.ItemClickListener itemClickListener;
    List<Person> list = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        recyclerView = (RecyclerView) findViewById(R.id.person_recyclerview);
        edit_name = (EditText) findViewById(R.id.edit_name);
        edit_hobby = (EditText) findViewById(R.id.edit_hobby);
        add_btn = (Button) findViewById(R.id.add_btn);

        recyclerView.setHasFixedSize(true);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.addItemDecoration(new DividerItemDecoration(getApplicationContext(), DividerItemDecoration.VERTICAL));

        selectPerson();

        itemClickListener = new PersonAdapter.ItemClickListener()
        {
            @Override
            public void onItemClick(View view, int position)
            {
                int id = list.get(position).getId();
                String name = list.get(position).getName();
                String hobby = list.get(position).getHobby();
                Log.e(TAG, "id : " + id + ", name : " + name + ", hobby : " + hobby);
            }
        };

        add_btn.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View view)
            {
                name = edit_name.getText().toString();
                hobby = edit_hobby.getText().toString();
            }
        });
    }

    private void selectPerson()
    {
        ApiInterface apiInterface = ApiClient.getApiClient().create(ApiInterface.class);
        Call<List<Person>> call = apiInterface.getNameHobby();
        call.enqueue(new Callback<List<Person>>()
        {
            @Override
            public void onResponse(@NonNull Call<List<Person>> call, @NonNull Response<List<Person>> response)
            {
                if (response.isSuccessful() && response.body() != null)
                {
                    onGetResult(response.body());
                }
            }

            @Override
            public void onFailure(@NonNull Call<List<Person>> call, @NonNull Throwable t)
            {
                Log.e("selectPerson()", "에러 : " + t.getMessage());
            }
        });
    }

    private void onGetResult(List<Person> lists)
    {
        adapter = new PersonAdapter(this, lists, itemClickListener);
        adapter.notifyDataSetChanged();
        recyclerView.setAdapter(adapter);

        list = lists;
    }
}

여기까지 하고 앱을 빌드하면 아래와 같은 화면이 나온다.

 

 

성공적으로 DB에 있는 데이터들을 불러와서 리사이클러뷰에 뿌려주는 걸 볼 수 있다.

이제 editText에 값을 입력 후 버튼을 누르면 추가될 수 있도록 메서드를 추가하고 코드를 수정해준다.

 

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

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

import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class MainActivity extends AppCompatActivity
{
    public static final String TAG = "MainActivity";

    EditText edit_name, edit_hobby;
    Button add_btn;
    String name, hobby;
    RecyclerView recyclerView;
    PersonAdapter adapter;
    PersonAdapter.ItemClickListener itemClickListener;
    List<Person> list = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        recyclerView = (RecyclerView) findViewById(R.id.person_recyclerview);
        edit_name = (EditText) findViewById(R.id.edit_name);
        edit_hobby = (EditText) findViewById(R.id.edit_hobby);
        add_btn = (Button) findViewById(R.id.add_btn);

        recyclerView.setHasFixedSize(true);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.addItemDecoration(new DividerItemDecoration(getApplicationContext(), DividerItemDecoration.VERTICAL));

        selectPerson();

        itemClickListener = new PersonAdapter.ItemClickListener()
        {
            @Override
            public void onItemClick(View view, int position)
            {
                int id = list.get(position).getId();
                String name = list.get(position).getName();
                String hobby = list.get(position).getHobby();
                Log.e(TAG, "id : " + id + ", name : " + name + ", hobby : " + hobby);
            }
        };

        add_btn.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View view)
            {
                name = edit_name.getText().toString();
                hobby = edit_hobby.getText().toString();
                insertPerson(name, hobby);
            }
        });
    }

    private void selectPerson()
    {
        ApiInterface apiInterface = ApiClient.getApiClient().create(ApiInterface.class);
        Call<List<Person>> call = apiInterface.getNameHobby();
        call.enqueue(new Callback<List<Person>>()
        {
            @Override
            public void onResponse(@NonNull Call<List<Person>> call, @NonNull Response<List<Person>> response)
            {
                if (response.isSuccessful() && response.body() != null)
                {
                    onGetResult(response.body());
                }
            }

            @Override
            public void onFailure(@NonNull Call<List<Person>> call, @NonNull Throwable t)
            {
                Log.e("selectPerson()", "에러 : " + t.getMessage());
            }
        });
    }

    private void onGetResult(List<Person> lists)
    {
        adapter = new PersonAdapter(this, lists, itemClickListener);
        adapter.notifyDataSetChanged();
        recyclerView.setAdapter(adapter);

        list = lists;
    }

    // ↓ 추가된 부분
    private void insertPerson(String name, String hobby)
    {
        ApiInterface apiInterface = ApiClient.getApiClient().create(ApiInterface.class);
        Call<Person> call = apiInterface.insertPerson(name, hobby);
        call.enqueue(new Callback<Person>()
        {
            @Override
            public void onResponse(@NonNull Call<Person> call, @NonNull Response<Person> response)
            {
                if (response.isSuccessful() && response.body() != null)
                {
                    Boolean success = response.body().getSuccess();
                    if (success)
                    {
                        onSuccess(response.body().getMessage());
                    }
                    else
                    {
                        onError(response.body().getMessage());
                    }
                }
            }

            @Override
            public void onFailure(@NonNull Call<Person> call, @NonNull Throwable t)
            {
                Log.e("insertPerson()", "에러 : " + t.getMessage());
            }
        });
    }

    private void onError(String message)
    {
        Log.e("insertPerson()", "onResponse() 에러 : " + message);
    }

    private void onSuccess(String message)
    {
        Log.e("insertPerson()", "onResponse() 성공 : " + message);
        setResult(RESULT_OK);
        finish();
    }
}

 

그럼 이제 앱이 어떻게 동작하는지 확인해보자.

이름, 취미를 입력하고 추가 버튼을 누른 다음 다시 액티비티로 돌아오면 추가된 데이터가 리사이클러뷰에 보여야 한다.

 

 

됐다. 이제 데이터 추가와 조회가 완벽하게 되는 앱이 만들어졌다.

그리고 아이템 클릭 리스너를 정의해서 로그로 id, name, hobby를 출력하게 하는 코드를 봤을 것이다.

이것도 정상적으로 작동하는지 로그를 통해 확인해보자. 가장 마지막에 추가한 아이템을 클릭하겠다.

 

성공적으로 출력된다. 이걸로 레트로핏을 통한 INSERT와 SELECT 관련 포스팅은 끝이다.

UPDAT와 DELETE 쿼리를 위한 액티비티와 메서드를 만드는 과정은 다음 포스팅에서 설명하겠다.

반응형
Comments