본문 바로가기
Android

Android [AWS람다 메모앱] Retrofit2를 이용한 프론트엔드 CRUD, 로그인,회원가입,메인리사이클뷰,메모생성,조회,수정,삭제

by leopard4 2023. 2. 10.

XML 및 파일구성

앱기능

 

 

MemoApi

package com.leopard4.memoapp.api;

import com.leopard4.memoapp.model.Memo;
import com.leopard4.memoapp.model.MemoList;
import com.leopard4.memoapp.model.Res;

import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
import retrofit2.http.GET;
import retrofit2.http.Header;
import retrofit2.http.POST;
import retrofit2.http.PUT;
import retrofit2.http.Path;
import retrofit2.http.Query;

public interface MemoApi {

    // 내 메모 가져오는 API
    @GET("/memos")
    Call<MemoList> getMemoList(@Header("Authorization") String token, @Query("offset") int offset, @Query("limit") int limit);

    // 메모 생성 API
    @POST("/memos")
    Call<Res> createMemo(@Body Memo memo, @Header("Authorization") String token);

    // 메모 수정 API
    @PUT("/memos/{memoId}")
    Call<Res> updateMemo(@Path("memoId") int memoId, @Header("Authorization") String token, @Body Memo memo);
    // Call<Res>는 응답을 받을 때 사용하는 클래스 Res클래스 안에는 result라는 변수가 있음 PostMan에서 보던 result와 같음

    // 메모 삭제 API
    @DELETE("/memos/{memoId}")
    Call<Res> deleteMemo(@Path("memoId") int memoId, @Header("Authorization") String token);
}

 

UserApi

package com.leopard4.memoapp.api;

import com.leopard4.memoapp.model.User;
import com.leopard4.memoapp.model.UserRes;

import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.Header;
import retrofit2.http.POST;
import retrofit2.http.Query;

// 유저 관련 API 들을 모아놓은 인터페이스
public interface UserApi {

    // 회원가입 API 함수 작성
    @POST("/user/register") // API 명세서에 있는 경로를 작성한다.
    Call<UserRes> register(@Body User user); // Body 에 Json 으로 데이터를 보낸다. // Call<UserRes> 는 응답을 받을 클래스를 지정한다.

    // 로그인 API 함수 작성
    @POST("/user/login")// @의 의미는 레트로핏 라이브러리가 이 함수를 레트로핏 API 로 사용한다는 의미이다.
    Call<UserRes> login(@Body User user);

    // 로그아웃 API
    @POST("/user/logout")
    Call<UserRes> logout(@Header("Authorization") String token);

}

 

Res

package com.leopard4.memoapp.model;

public class Res {

    private String result;

    public String getResult() {
        return result;
    }

    public void setResult(String result) {
        this.result = result;
    }
}

Memo

package com.leopard4.memoapp.model;

import java.io.Serializable;

public class Memo implements Serializable {

    private int id;
    private String title;
    private String datetime;
    private String content;
    private String createdAt;
    private String updatedAt;

    public Memo() {
    }
    public Memo(String title, String datetime, String content) {
        this.title = title;
        this.datetime = datetime;
        this.content = content;
    }

    public int getId() {
        return id;
    }

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

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDatetime() {
        return datetime;
    }

    public void setDatetime(String datetime) {
        this.datetime = datetime;
    }

    public String getContent() {
        return content;
    }

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

    public String getCreatedAt() {
        return createdAt;
    }

    public void setCreatedAt(String createdAt) {
        this.createdAt = createdAt;
    }

    public String getUpdatedAt() {
        return updatedAt;
    }

    public void setUpdatedAt(String updatedAt) {
        this.updatedAt = updatedAt;
    }
}

 

NetworkClient

package com.leopard4.memoapp.api;

import android.content.Context;

import com.leopard4.memoapp.config.Config;

import java.util.concurrent.TimeUnit;

import okhttp3.OkHttpClient;
import okhttp3.logging.HttpLoggingInterceptor;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class NetworkClient {

    public static Retrofit retrofit;

    public static Retrofit getRetrofitClient(Context context){
        if(retrofit == null){
            // 통신 로그 확인할때 필요한 코드
            HttpLoggingInterceptor loggingInterceptor =
                    new HttpLoggingInterceptor();
            loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); // 실배포시는 NONE으로 설정 (해킹문제)

            // 네트워크 연결관련 코드
            OkHttpClient httpClient = new OkHttpClient.Builder()
                    .connectTimeout(1, TimeUnit.MINUTES)
                    .readTimeout(1, TimeUnit.MINUTES)
                    .writeTimeout(1, TimeUnit.MINUTES)
                    .addInterceptor(loggingInterceptor)
                    .build();
            // 네트워크로 데이터를 보내고 받는
            // 레트로핏 라이브러리 관련 코드
            retrofit = new Retrofit.Builder()
                    .baseUrl(Config.DOMAIN)
                    .client(httpClient)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit;
    }


}

 

 

매인액티비티

package com.leopard4.memoapp;

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

import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Toast;

import com.leopard4.memoapp.adapter.MemoAdapter;
import com.leopard4.memoapp.api.MemoApi;
import com.leopard4.memoapp.api.NetworkClient;
import com.leopard4.memoapp.api.UserApi;
import com.leopard4.memoapp.config.Config;
import com.leopard4.memoapp.model.Memo;
import com.leopard4.memoapp.model.MemoList;
import com.leopard4.memoapp.model.Res;
import com.leopard4.memoapp.model.UserRes;

import java.util.ArrayList;

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

public class MainActivity extends AppCompatActivity {

    Button btnAdd;
    ProgressBar progressBar;
    String accessToken;

    RecyclerView recyclerView;
    MemoAdapter adapter;
    ArrayList<Memo> memoArrayList = new ArrayList<>();

    ProgressDialog dialog;

    // 페이징 처리를 위한 변수
    int offset = 0;
    int limit = 2;
    int count = 0;

    int secondDeleteIndex;


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


        // 억세스토큰이 저장되어있으면,
        // 로그인한 유저이므로 메인액티비티를 실행하고,

        // 그렇지 않으면, 회원가입 액티비티를 실행하고,
        // 메인액티비티는 종료!
        SharedPreferences sp = getSharedPreferences(Config.PREFERENCE_NAME, MODE_PRIVATE);
        accessToken = sp.getString(Config.ACCESS_TOKEN, "");
        Log.i("MEMO_APP",accessToken);

        if (accessToken.isEmpty()) {
            // 회원가입 액티비티 실행
            Intent intent = new Intent(this, RegisterActivity.class);
            startActivity(intent);
            finish();
            return;
        }
        // 회원가입/로그인 유저면, 메인액티비티 실행
        btnAdd = findViewById(R.id.btnAdd);
        progressBar = findViewById(R.id.progressBar);

        recyclerView = findViewById(R.id.recyclerView);
        recyclerView.setHasFixedSize(true);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);

            }
            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                // 스크롤이 끝에 도달했는지 확인
                int lastPosition = ((LinearLayoutManager)recyclerView.getLayoutManager()).findLastCompletelyVisibleItemPosition();
                int totalCount = recyclerView.getAdapter().getItemCount();
                if (lastPosition + 1 == totalCount ) {
                    // 스크롤이 끝에 도달했을 때, 데이터를 추가로 가져옴

                    if (count == limit) { // 카운트가 총갯수이므로 총갯수와 같으면 더이상 가져올 데이터가 없음
                        addNetworkData();
                    }
                }
            }

        });

        // 메모생성버튼
        btnAdd.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MemoAddActivity.class);
                startActivity(intent);
            }
        });

    }

    private void addNetworkData() {
        progressBar.setVisibility(View.VISIBLE);

        Retrofit retrofit = NetworkClient.getRetrofitClient(this);

        MemoApi api = retrofit.create(MemoApi.class);

        Call<MemoList> call = api.getMemoList("Bearer " + accessToken, offset, limit);
        call.enqueue(new Callback<MemoList>() {
            @Override
            public void onResponse(Call<MemoList> call, Response<MemoList> response) {
                progressBar.setVisibility(View.GONE);

                if (response.isSuccessful()) {
                    // 성공시, 메모리스트를 어레이리스트에 저장
                    MemoList memoList = response.body();

                    memoArrayList.addAll(memoList.getItems());
                    // 어댑터 갱신
                    adapter.notifyDataSetChanged();
                    // 오프셋을 다음 페이지로 변경
                    count = memoList.getCount();
                    offset = offset + count;


                } else {
                    // 실패
                    progressBar.setVisibility(View.GONE);
                    Toast.makeText(MainActivity.this, "메모를 불러오는데 실패했습니다.", Toast.LENGTH_SHORT).show();
                    return;
                }
            }

            @Override
            public void onFailure(Call<MemoList> call, Throwable t) {
                progressBar.setVisibility(View.GONE);

            }
        });

    }

    private void getNetworkData() {
        progressBar.setVisibility(View.VISIBLE);

        Retrofit retrofit = NetworkClient.getRetrofitClient(this);

        MemoApi api = retrofit.create(MemoApi.class);

        // 오프셋 초기화는, 함수 호출하기 전에!!
        offset = 0; // 오프셋 초기화
        count = 0; // 카운트 초기화

        Call<MemoList> call = api.getMemoList("Bearer " + accessToken, offset, limit);
        call.enqueue(new Callback<MemoList>() {
            @Override
            public void onResponse(Call<MemoList> call, Response<MemoList> response) {
                progressBar.setVisibility(View.GONE);

                if (response.isSuccessful()) {
                    // 성공시, 메모리스트를 어레이리스트에 저장
                    MemoList memoList = response.body();

                    memoArrayList.clear(); // 기존 배열리스트가 존재하지 않게 초기화

                    memoArrayList.addAll(memoList.getItems());
                    // 메모리스트를 어댑터에 연결
                    adapter = new MemoAdapter(MainActivity.this, memoArrayList);
                    recyclerView.setAdapter(adapter);

                    // 오프셋 처리하는 코드
                    count = memoList.getCount(); // 전체 메모 개수(페이징처리용)
                    offset  = offset + count; // 다음 페이지를 위한 오프셋


                } else {
                    // 실패
                    progressBar.setVisibility(View.GONE);
                    Toast.makeText(MainActivity.this, "메모를 불러오는데 실패했습니다.", Toast.LENGTH_SHORT).show();
                    return;
                }
            }

            @Override
            public void onFailure(Call<MemoList> call, Throwable t) {
                progressBar.setVisibility(View.GONE);

            }
        });

    }
    @Override
    public boolean onCreateOptionsMenu(@NonNull Menu menu){
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
    public boolean onOptionsItemSelected(@NonNull MenuItem item){
        int itemId = item.getItemId();

        if(itemId == R.id.menuLogout){

            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setTitle("로그아웃");
            builder.setMessage("로그아웃 하시겠습니까?");
            builder.setPositiveButton("예", new DialogInterface.OnClickListener() {
                @Override
                public void onClick(DialogInterface dialog, int which) {

                    showProgress("로그아웃 중입니다.");

                    Retrofit retrofit = NetworkClient.getRetrofitClient(MainActivity.this);
                    UserApi api = retrofit.create(UserApi.class);

                    Call<UserRes> call = api.logout("Bearer " + accessToken);

                    call.enqueue(new Callback<UserRes>() {
                        @Override
                        public void onResponse(Call<UserRes> call, Response<UserRes> response) {
                            dismissProgress();
                            if(response.isSuccessful()){
                                // 쉐어드 프리퍼런스에 저장한 토큰을 초기화!!
                                SharedPreferences sp = getApplication().getSharedPreferences(Config.PREFERENCE_NAME, MODE_PRIVATE);
                                SharedPreferences.Editor editor = sp.edit();
                                editor.putString(Config.ACCESS_TOKEN, "");
                                editor.apply();

                                // 기획 : 앱종료
//                                finish();

                                // 기획 : 로그아웃하면, 로그인 화면을 띄우도록
                                Intent intent = new Intent(MainActivity.this, LoginActivity.class);
                                startActivity(intent);

                                finish();

                            }else{
                                // 로그아웃 실패
                                Toast.makeText(MainActivity.this, "로그아웃에 실패했습니다.", Toast.LENGTH_SHORT).show();
                            }
                        }

                        @Override
                        public void onFailure(Call<UserRes> call, Throwable t) {
                            dismissProgress();
                            Toast.makeText(MainActivity.this, "로그아웃에 실패했습니다.", Toast.LENGTH_SHORT).show();
                        }
                    });
                }
            });
            builder.setNegativeButton("아니오", null);
            builder.show();

        }

        return super.onOptionsItemSelected(item);

    }
    void showProgress(String message){
        dialog = new ProgressDialog(this);
        dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        dialog.setMessage(message);
        dialog.show();
    }

    // 로직처리가 끝나면 화면에서 사라지는 함수
    void dismissProgress(){
        dialog.dismiss();
    }

    @Override
    protected void onResume() {
        super.onResume();
        // 네트워크로부터 내 메모를 가져온다.
        getNetworkData();
    }

    public void deleteMemo(int deleteIndex) {

        secondDeleteIndex = deleteIndex;
        // 네트워크로 메모 삭제하는 코드 작성
        Retrofit retrofit = NetworkClient.getRetrofitClient(MainActivity.this);
        MemoApi api = retrofit.create(MemoApi.class); // 인터페이스 객체 생성

        Memo memo = memoArrayList.get(deleteIndex);

        SharedPreferences sp = getApplication().getSharedPreferences(Config.PREFERENCE_NAME, MODE_PRIVATE);
        String accessToken = sp.getString(Config.ACCESS_TOKEN, "");

        Call<Res> call = api.deleteMemo(memo.getId(), "Bearer " + accessToken);
        call.enqueue(new Callback<Res>() {
            @Override
            public void onResponse(Call<Res> call, Response<Res> response) {
                if(response.isSuccessful()){
                    // 삭제 성공
                    Toast.makeText(MainActivity.this, "메모를 삭제했습니다.", Toast.LENGTH_SHORT).show();
                    // 삭제한 메모를 리스트에서 제거
                    memoArrayList.remove(secondDeleteIndex);
                    // 어댑터에 변경사항 알림
                    adapter.notifyDataSetChanged();
                }else{
                    // 삭제 실패
                    Toast.makeText(MainActivity.this, "메모를 삭제하는데 실패했습니다.", Toast.LENGTH_SHORT).show();
                }
            }

            @Override
            public void onFailure(Call<Res> call, Throwable t) {
                Toast.makeText(MainActivity.this, "메모를 삭제하는데 실패했습니다.", Toast.LENGTH_SHORT).show();
            }
        }
        );



    }
}

에딧액티비티

package com.leopard4.memoapp;

import androidx.appcompat.app.AppCompatActivity;

import android.app.DatePickerDialog;
import android.app.ProgressDialog;
import android.app.TimePickerDialog;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.Toast;

import com.leopard4.memoapp.api.MemoApi;
import com.leopard4.memoapp.api.NetworkClient;
import com.leopard4.memoapp.config.Config;
import com.leopard4.memoapp.model.Memo;
import com.leopard4.memoapp.model.Res;

import java.util.Calendar;

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

public class MemoEditActivity extends AppCompatActivity {

    EditText editTitle;
    EditText editContent;
    Button btnSave, btnDate, btnTime;

    private ProgressDialog dialog;
    String datetime;

    private String date;
    private String time;

    int memoId;


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

        // 액티비티 재실행시 멤버변수가 초기화되는지 확인 // 초기화됨
        Log.i("RESET?",date + time);

        editTitle = findViewById(R.id.editTitle);
        editContent = findViewById(R.id.editContent);
        btnSave = findViewById(R.id.btnSave);
        btnDate = findViewById(R.id.btnDate);
        btnTime = findViewById(R.id.btnTime);

        Memo memo = (Memo) getIntent().getSerializableExtra("memo");

        memoId = memo.getId();
        editTitle.setText(memo.getTitle());
        editContent.setText(memo.getContent());
        // "2023-08-03T11:30:00"
        // "2023-08-03T11:30"
        String[] dateArray = memo.getDatetime().substring(0,15+1).split("T");
        date = dateArray[0];
        time = dateArray[1];

        btnDate.setText(date+"");
        btnTime.setText(time+"");

        // 0. 날짜선택 버튼을 누르면 년,월,일선택
        btnDate.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                // 오늘 날짜 가져오기
                Calendar current = Calendar.getInstance();
                current.get(Calendar.YEAR);

                new DatePickerDialog(MemoEditActivity.this, new DatePickerDialog.OnDateSetListener() {
                    @Override
                    public void onDateSet(DatePicker datePicker, int i, int i1, int i2){
                        Log.i("MEMO_APP", "년도: " + i + ", 월: " + i1 + ", 일: " + i2);
                        // i: 년도, i1: 월(0~11), i2: 일

                        int month = i1 + 1;
                        String monthStr;
                        if (month < 10) {
                            monthStr =  "0" + month;
                        } else {
                            monthStr = "" + month;
                        }
                        String dayStr;
                        if (i2 < 10) {
                            dayStr = "0" + i2;
                        } else {
                            dayStr = "" + i2;
                        }

                        date = i + "-" + monthStr + "-" + dayStr;
                        btnDate.setText(date);
                    }



                },
                        current.get(Calendar.YEAR),
                        current.get(Calendar.MONTH),
                        current.get(Calendar.DAY_OF_MONTH)
                ).show();

            }
        });

        // 0-1. 시간선택 버튼을 선택하면 시,분 선택
        btnTime.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Calendar current = Calendar.getInstance();

                new TimePickerDialog(
                        MemoEditActivity.this,
                        new TimePickerDialog.OnTimeSetListener() {
                            @Override
                            public void onTimeSet(android.widget.TimePicker timePicker, int i, int i1) {
                                Log.i("MEMO_APP", "시: " + i + ", 분: " + i1);
                                // i: 시, i1: 분
                                String hourStr;
                                if (i < 10) {
                                    hourStr = "0" + i;
                                } else {
                                    hourStr = "" + i;
                                }
                                String minuteStr;
                                if (i1 < 10) {
                                    minuteStr = "0" + i1;
                                } else {
                                    minuteStr = "" + i1;
                                }
                                time = hourStr + ":" + minuteStr;

                                btnTime.setText(time);
                            }
                        },
                        current.get(Calendar.HOUR_OF_DAY),
                        current.get(Calendar.MINUTE),
                        true
                ).show();
            }
        });

        btnSave.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 1. 입력한 제목과 내용을 가져온다.
                String title = editTitle.getText().toString().trim();
                String content = editContent.getText().toString().trim();
                if(date == null || time == null){
                    Toast.makeText(MemoEditActivity.this, "날짜와 시간을 선택해주세요.", Toast.LENGTH_SHORT).show();
                    return;
                }
                datetime = date + " " + time;
                Log.i("TOTAL_DATE",datetime);

                // 2. 제목과 내용이 비어있는지 체크한다.
                if (title.length() == 0 || content.length() == 0) {
                    Toast.makeText(MemoEditActivity.this, "제목과 내용을 입력해주세요.", Toast.LENGTH_SHORT).show();
                    return;
                }

                // 네트워크 호출하는 코드
                // 로딩 다이얼로그를 보여준다.
                showProgress("메모를 수정하고 있습니다.");

                Retrofit retrofit = NetworkClient.getRetrofitClient(MemoEditActivity.this);
                MemoApi api = retrofit.create(MemoApi.class);
                Memo memo = new Memo( title, datetime, content);

                // 토큰이 있는지 확인
                SharedPreferences sp = getSharedPreferences(Config.PREFERENCE_NAME, MODE_PRIVATE);
                String accessToken = sp.getString(Config.ACCESS_TOKEN, "");

                Call<Res> call = api.updateMemo(memoId ,"Bearer " + accessToken, memo); // <Res>로 리턴하라
                call.enqueue(new Callback<Res>() {
                    @Override
                    public void onResponse(Call<Res> call, Response<Res> response) {
                        // 3-3. 로딩 다이얼로그를 사라지게 한다.
                        dismissProgress();
                        if (response.isSuccessful()) {   // 응답코드가 200~300 사이인 경우
                            // 3-4. 메모 목록 화면으로 이동한다.
                            // 실무에서는 네트워크 절약을위해 back버튼을 누르면 result코드에따라 런처를 이용해서 메인화면으로 이동
                            finish();
                        } else {
                            Toast.makeText(MemoEditActivity.this, "서버와 통신 중 오류가 발생했습니다.", Toast.LENGTH_SHORT).show();
                        }
                    }
                    @Override
                    public void onFailure(Call<Res> call, Throwable t) {
                        Toast.makeText(MemoEditActivity.this, "서버와 통신 중 오류가 발생했습니다.", Toast.LENGTH_SHORT).show();
                        dismissProgress();
                    }
                });
            }
        });


    }

    void showProgress(String message){
        dialog = new ProgressDialog(this);
        dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        dialog.setMessage(message);
        dialog.show();
    }

    // 로직처리가 끝나면 화면에서 사라지는 함수
    void dismissProgress(){
        dialog.dismiss();
    }

}

매모어댑터

package com.leopard4.memoapp.adapter;

import static android.content.Context.MODE_PRIVATE;

import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.cardview.widget.CardView;
import androidx.recyclerview.widget.RecyclerView;


import com.leopard4.memoapp.MainActivity;
import com.leopard4.memoapp.MemoEditActivity;
import com.leopard4.memoapp.R;
import com.leopard4.memoapp.api.MemoApi;
import com.leopard4.memoapp.api.NetworkClient;
import com.leopard4.memoapp.config.Config;
import com.leopard4.memoapp.model.Memo;
import com.leopard4.memoapp.model.Res;

import java.util.ArrayList;

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

public class MemoAdapter extends RecyclerView.Adapter<MemoAdapter.ViewHolder>{

    Context context;
    ArrayList<Memo> memoList;

    int deleteIndex;

    int id;
    private ProgressDialog dialog;

    public MemoAdapter(Context context, ArrayList<Memo> memoList) {
        this.context = context;
        this.memoList = memoList;
    }

    @NonNull
    @Override
    public MemoAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.memo_row, parent, false);
        return new MemoAdapter.ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MemoAdapter.ViewHolder holder, int position) {
        Memo memo = memoList.get(position);
        holder.txtTitle.setText(memo.getTitle());
        // "2023-08-03T11:30:00"
        // "2023-08-03 11:30:00"
        // "2023-08-03 11:30"
        String date = memo.getDatetime().replace("T", " ").substring(0, 15+1);
        holder.txtDate.setText(date);
        holder.txtContent.setText(memo.getContent());

    }

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

    public class ViewHolder extends RecyclerView.ViewHolder {
        TextView txtTitle;
        TextView txtDate;
        TextView txtContent;
        ImageView imgDelete;

        CardView cardView;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);

            txtTitle = itemView.findViewById(R.id.txtTitle);
            txtDate = itemView.findViewById(R.id.txtDate);
            txtContent = itemView.findViewById(R.id.txtContent);
            imgDelete = itemView.findViewById(R.id.imgDelete);
            cardView = itemView.findViewById(R.id.cardView);

            cardView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    int position = getAdapterPosition();
                    Memo memo = memoList.get(position);

                    Intent intent = new Intent(context, MemoEditActivity.class);
                    intent.putExtra("memo", memo);

                    context.startActivity(intent);

                }

            });

            // ok 버튼을 클릭하면 메모 삭제하는 api
            imgDelete.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {

                    Log.i("DELETE_INDEX", deleteIndex+"" );

                    AlertDialog.Builder builder = new AlertDialog.Builder(context);
                    builder.setTitle("메모 삭제");
                    builder.setMessage("메모를 삭제하시겠습니까?");
                    builder.setPositiveButton("확인", (dialog, which) -> {

                        deleteIndex = getAdapterPosition();
                        // 함수로 뺀건 네트워크 데이터를 아끼기위함인듯함
                        ((MainActivity)context).deleteMemo(deleteIndex);

                    });
                    builder.setNegativeButton("취소", null);
                    builder.show();

                }
            });

        }
    }
    void showProgress(String message){
        dialog = new ProgressDialog(context);
        dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        dialog.setMessage(message);
        dialog.show();
    }

    // 로직처리가 끝나면 화면에서 사라지는 함수
    void dismissProgress(){
        dialog.dismiss();
    }
}

addActivity

package com.leopard4.memoapp;

import androidx.appcompat.app.AppCompatActivity;

import android.app.DatePickerDialog;
import android.app.TimePickerDialog;
import android.app.ProgressDialog;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.DatePicker;
import android.widget.EditText;
import android.widget.Toast;

import com.leopard4.memoapp.api.MemoApi;
import com.leopard4.memoapp.api.NetworkClient;
import com.leopard4.memoapp.config.Config;
import com.leopard4.memoapp.model.Memo;
import com.leopard4.memoapp.model.Res;

import java.util.Calendar;

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

public class MemoAddActivity extends AppCompatActivity {

    EditText editTitle;
    EditText editContent;
    Button btnSave, btnDate, btnTime;

    private ProgressDialog dialog;
    String datetime;

    private String date;
    private String time;



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

        // 액티비티 재실행시 멤버변수가 초기화되는지 확인 // 초기화됨
        Log.i("RESET?",date + time);

        editTitle = findViewById(R.id.editTitle);
        editContent = findViewById(R.id.editContent);
        btnSave = findViewById(R.id.btnSave);
        btnDate = findViewById(R.id.btnDate);
        btnTime = findViewById(R.id.btnTime);

        // 0. 날짜선택 버튼을 누르면 년,월,일선택
        btnDate.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                // 오늘 날짜 가져오기
                Calendar current = Calendar.getInstance();
                current.get(Calendar.YEAR);

               new DatePickerDialog(MemoAddActivity.this, new DatePickerDialog.OnDateSetListener() {
                    @Override
                    public void onDateSet(DatePicker datePicker, int i, int i1, int i2){
                        Log.i("MEMO_APP", "년도: " + i + ", 월: " + i1 + ", 일: " + i2);
                        // i: 년도, i1: 월(0~11), i2: 일

                        int month = i1 + 1;
                        String monthStr;
                        if (month < 10) {
                            monthStr =  "0" + month;
                        } else {
                            monthStr = "" + month;
                        }
                        String dayStr;
                        if (i2 < 10) {
                            dayStr = "0" + i2;
                        } else {
                            dayStr = "" + i2;
                        }

                        date = i + "-" + monthStr + "-" + dayStr;
                        btnDate.setText(date);
                    }



                    },
                    current.get(Calendar.YEAR),
                    current.get(Calendar.MONTH),
                    current.get(Calendar.DAY_OF_MONTH)
                    ).show();

            }
        });

        // 0-1. 시간선택 버튼을 선택하면 시,분 선택
        btnTime.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Calendar current = Calendar.getInstance();

                new TimePickerDialog(
                        MemoAddActivity.this,
                        new TimePickerDialog.OnTimeSetListener() {
                            @Override
                            public void onTimeSet(android.widget.TimePicker timePicker, int i, int i1) {
                                Log.i("MEMO_APP", "시: " + i + ", 분: " + i1);
                                // i: 시, i1: 분
                                String hourStr;
                                if (i < 10) {
                                    hourStr = "0" + i;
                                } else {
                                    hourStr = "" + i;
                                }
                                String minuteStr;
                                if (i1 < 10) {
                                    minuteStr = "0" + i1;
                                } else {
                                    minuteStr = "" + i1;
                                }
                                time = hourStr + ":" + minuteStr;

                                btnTime.setText(time);
                            }
                        },
                        current.get(Calendar.HOUR_OF_DAY),
                        current.get(Calendar.MINUTE),
                        true
                ).show();
            }
        });

        btnSave.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 1. 입력한 제목과 내용을 가져온다.
                String title = editTitle.getText().toString().trim();
                String content = editContent.getText().toString().trim();
                if(date == null || time == null){
                    Toast.makeText(MemoAddActivity.this, "날짜와 시간을 선택해주세요.", Toast.LENGTH_SHORT).show();
                    return;
                }
                datetime = date + " " + time;
                Log.i("TOTAL_DATE",datetime);

                // 2. 제목과 내용이 비어있는지 체크한다.
                if (title.length() == 0 || content.length() == 0) {
                    Toast.makeText(MemoAddActivity.this, "제목과 내용을 입력해주세요.", Toast.LENGTH_SHORT).show();
                    return;
                }

                // 네트워크 호출하는 코드
                // 로딩 다이얼로그를 보여준다.
                showProgress("메모를 저장하고 있습니다.");

                Retrofit retrofit = NetworkClient.getRetrofitClient(MemoAddActivity.this);
                MemoApi api = retrofit.create(MemoApi.class);
                Memo memo = new Memo( title, datetime, content);

                // 토큰을 가져온다.
                SharedPreferences sp = getSharedPreferences(Config.PREFERENCE_NAME, MODE_PRIVATE);
                String accessToken = sp.getString(Config.ACCESS_TOKEN, "");

                Call<Res> call = api.createMemo( memo , "Bearer " + accessToken); // <Res>로 리턴하라
                call.enqueue(new Callback<Res>() {
                    @Override
                    public void onResponse(Call<Res> call, Response<Res> response) {
                        // 3-3. 로딩 다이얼로그를 사라지게 한다.
                        dismissProgress();
                        if (response.isSuccessful()) {   // 응답코드가 200~300 사이인 경우
                            // 3-4. 메모 목록 화면으로 이동한다.
                            // 실무에서는 네트워크 절약을위해 back버튼을 누르면 result코드에따라 런처를 이용해서 메인화면으로 이동
                            finish();
                        } else {
                            Toast.makeText(MemoAddActivity.this, "서버와 통신 중 오류가 발생했습니다.", Toast.LENGTH_SHORT).show();
                        }
                    }
                    @Override
                    public void onFailure(Call<Res> call, Throwable t) {
                        Toast.makeText(MemoAddActivity.this, "서버와 통신 중 오류가 발생했습니다.", Toast.LENGTH_SHORT).show();
                        dismissProgress();
                    }
                });
            }
        });


    }

    void showProgress(String message){
        dialog = new ProgressDialog(this);
        dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
        dialog.setMessage(message);
        dialog.show();
    }

    // 로직처리가 끝나면 화면에서 사라지는 함수
    void dismissProgress(){
        dialog.dismiss();
    }

}