ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Android Studio - Retrofit 이용한 서버와의 통신
    Android Studio 2021. 2. 28. 01:58

    이 포스팅에서는 이틀동안 삽질했던........ ^ㅠ... 서버와의 통신방법에 대해서 작성해보고자 한다. 

    우선 지금 개발을 진행하는 팀에는 서버를 담당하는 팀원이 있어서, 팀원이 짜준 api를 보고 안드로이드(클라이언트)쪽에서 그 양식에 맞춰 정보를 전달하고, 응답을 받아오는 과정을 포스팅하고자 한다. 

     

     implementation 'com.squareup.retrofit2:retrofit:2.5.0'
     implementation 'com.google.code.gson:gson:2.8.2'
     implementation 'com.squareup.retrofit2:converter-gson:2.4.0'

    우선, app 레벨의 build.gradle파일에 Retrofit 사용을 위해 세줄의 코드를 추가해준다. compile이라고 쳤더니 warning이 떠서 안드로이드 스튜디오가 제안한대로 implementation으로 바꿨더니 warning없이 잘 돌아간다

    혹시 인터넷 권한 설정이 안 되어 있다면 AndroidManifest.xml파일에 추가해주어야 한다. 

    <permission android:name="android.permission.INTERNET"/>

    그러고 나서 서버 팀원이 올려준 위키를 보면 각 기능의 메소드, 경로, Request Header, Request Body, Response 이렇게 나와있다. 우선 메소드, 경로를 보고 API 인터페이스 파일을 안드로이드 스튜디오 상에서 만들어준다. 

    public interface ServiceApi {
        @Headers({Request Header})
        @POST("서버팀원이 올려준 경로")
        Call<응답클래스이름> userJoin(@Body 보낼 클래스데이터 이름);
    }

    api구성은 이런식으로 했다. 아마 개발환경마다 경로 이런건 다 다를테니 저런 내용이 들어간다는 것을 한국어로 기록해두었다. 우리팀 실제 코드 내용은 아래와 같았다. 만약 API 더 추가할거라면 이 인터페이스에 쭉 써주면 된다

    public interface ServiceApi {
        @Headers({"Content-Type: application/json"})
        @POST("/user/signup")
        Call<JoinResponse> userJoin(@Body JoinData data);
    }

    그 다음 보낼 데이터(JoinData), 받을 데이터(JoinResponse)를 정의해주었다. 

    public class JoinData {
        @SerializedName("id")
        private String id;
    
        @SerializedName("password")
        private String password;
    
        @SerializedName("name")
        private String name;
    
        @SerializedName("email")
        private String email;
    
        public JoinData(String id, String password, String name, String email) {
            this.id=id;
            this.password=password;
            this.name=name;
            this.email=email;
        }
    }

    우리팀의 경우 서버쪽에서 정의해준 api가 id, password, name, email이어서 위와 같이 JoinData(클라이언트가 서버에 보내는 데이터)를 정의해주었다. 

    public class JoinResponse {
    
        @SerializedName("status")
        private int status;
    
        @SerializedName("success")
        private boolean success;
    
        @SerializedName("message")
        private String message;
    
        public class Data{
            @SerializedName("userId")
            private int userId;
        }
    
        public int getStatus() {
            return status;
        }
    
        public String getMessage() {
            return message;
        }
    
    }

    응답의 경우, Data 안에 userId라는 다른 요소가 있어서, Data의 경우 클래스 형태로 정의해주었다. 그 외 getStatus와 getMessage는 CreateAccount쪽에서 쓰는 메서드라서 정의해놨다. 굳이 get/set다 정의해둘 필요는 없고 본인이 필요한 메서드들만 정의해두면 될 것 같다..!

     

    그 다음 RetrofitClient정의를 해주었다. 

    public class RetrofitClient {
        private final static String BASE_URL = BuildConfig.SERVER_URL;
        private static Retrofit retrofit = null;
    
        private RetrofitClient() {
        }
    
        public static Retrofit getClient() {
            if(retrofit == null) {
                retrofit = new Retrofit.Builder()
                        .baseUrl(BASE_URL)
                        .addConverterFactory(GsonConverterFactory.create())
                        .build();
            }
            return retrofit;
        }
    }

    저 BASE_URL은 저번 포스팅에서 mapkey숨겼던 것처럼 local.properties에 숨겨두었다. 혹시 몰라서 숨겨둔 거긴 한데, 만약 굳이 숨길 필요 없다면 아래처럼 그대로 써주면 된다. 서버와의 협업을 하는 중이라면 서버쪽에서 라우팅 주소 xxx.xxx.xxx.xxx이런 식으로 주고, 포트번호 xxxx이런식으로 위키에 쓰여있다(혹은 직접 말로 했거나...) 그걸 바탕으로 아래처럼 써주면 된다.

    private final static String BASE_URL = "http://xxx.xxx.xxx.xxx:포트번호"

    아 그리고 구글링 해보니 저 뒤에 /를 붙여야한다 이런 이야기가 있던데, 나의 경우 API 인터페이스파일에서 경로가 /user/signup이렇게 되어있어서 에러가 나지는 않았다. 이건 팀 상황마다 다를듯 하다. 

    혹시 local properties에 숨기는 방법이 궁금하다면 전 포스팅 참고...

     

    ▼▼▼

    bubblebubble.tistory.com/m/6

     

    Android Studio - Google Map API 협업 / Git에 업로드

    이 포스팅에서는 Android Studio에서 사용한 Google Map API 키를 Git에 올라가지 않게끔 처리하는 방법과 어떻게 팀원과 API Key를 공유했는지 작성하려 한다. 물론 이게 정답일지는 모르겠는데, 못 찾겠

    bubblebubble.tistory.com

     


    그러고 나서, 회원가입 화면의 .java파일에서 마저 코딩을 해주면 된다. 코드를 다 올리기엔 좀 길기도 하고, 포스팅 보시는 분마다 코드가 다 다를테니 Retrofit 통신에 사용되는 부분만 잘라왔다. 

    button.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if((id_input.length() == 0) ||(pw_input.length() == 0) || (username_input.length() == 0) || (email_input.length() == 0)){
                        Toast.makeText(getApplicationContext(), "please enter all information",Toast.LENGTH_SHORT).show();
                    } else {
                        id = id_input.getText().toString();
                        pw = pw_input.getText().toString();
                        username = username_input.getText().toString();
                        email = email_input.getText().toString();
                        startJoin(new JoinData(id,pw,username,email));
                    }
                }
            });

    우선 저 버튼이 회원가입 버튼이다. 그래서 저걸 눌렀을때, 공백이 아니라면 넘어가게끔 해주었다. (유효성 검사는 서버측에서 해줌) 저 else부분을 보면 startJoin함수를 호출하고 있다. 

    private void startJoin(JoinData data) {
            serviceApi.userJoin(data).enqueue(new Callback<JoinResponse>() {
                @Override
                public void onResponse(Call<JoinResponse> call, Response<JoinResponse> response) {
                    JoinResponse result = response.body(); 
                    //서버로부터의 응답을 위에서 정의한 JoinResponse객체에 담는다.
                    Toast.makeText(CreateAccount.this, result.getMessage(), Toast.LENGTH_SHORT).show(); 
                    // getMessage를 통해 성공시 서버로부터 회원가입 성공이라는 메시지를 받음
                    if(result.getStatus() == 200) {
                        finish();  //getStatus로 받아온 코드가 200(OK)면 회원가입 프래그먼트 종료
                    }
                }
    
                @Override
                public void onFailure(Call<JoinResponse> call, Throwable t) {
                    Toast.makeText(CreateAccount.this, "Sign up Error", Toast.LENGTH_SHORT).show();
                    Log.e("Sign up Error", t.getMessage());
                    t.printStackTrace();
                }
            });
        }

    startJoin함수의 모양은 위와 같다. 이부분에서 참 삽질을 많이 했어서, 내가 겪었었던 에러들을 간단하게 기록해두려 한다. (근데 대부분 내 부주의......로 싱겁게 끝난 에러다. 혹시 본인이 실수한 건 없는지 꼭 다시 확인하기)

     

    1. 서버 연결 실패 에러(ECONNREFUSED)

    java.net.ConnectException: failed to connect after 10000ms: isConnected failed: ECONNREFUSED (Connection refused)

    처음에 이런 에러가 떴었다... 서버 잘 작동하고 서버에 문제가 없어서 뭐가 문제지 하고 헤맸는데, 일단 해결방법/원인은아래와 같았다. 

    -> 대부분 url 잘못이라고 한다. 구글에 검색해보면 스택오버플로에 수많은 질문이 나온다. 대부분 로컬에서 pc / 에뮬레이터를 한 컴퓨터에서 돌리다 보니 발생. 아마 이 경우는 구글링으로 쉽게 해결했을것이다...

    -> 로컬서버 안쓰고 팀에 서버 담당 팀원이 있는 경우...

    나처럼... ^^ 포트번호 실수도 있다. node.js기준으로 기본 포트가 3000이고 DB쪽이 3306이다. 나는 3306이라고 들어서 쭉 그걸로 했었는데 위의 에러가 계속 났었다. 혹시 3306이라고 들었을 경우 3306이 맞는지 확인할것... 그 외에, 라우팅 주소가 잘못된건 아닌지 확인해보면 된다. 서버 잘돌아가면 대부분의 실수는....요청하는 클라이언트 잘못이다......^ㅠ

     

     

    2. onResponse부분의 null

    Attempt to invoke virtual method  java.lang.String JoinResponse.getMessage()' on a null object reference CreateAccount.java:~~

    위와 같은 에러는 내가 name을 nickname이라고 착각하고 ㅋㅋㅋㅋㅋㅋ 해서 나왔다. 서버로부터의 response가 null이라고 나와서 위의 에러가 뜨는 것인데, 서버가 잘 돌아가고 있다면 클라이언트측에서 서버가 알려준 양식과 다르게 쓰지 않았는지 점검이 필요하다. 나는 JoinResponse부분만 문제가 있나 한참 살폈는데, 보내는 데이터 즉 joinData부분에서 name을 nickname이라고 써놨었다. 이거 고쳤더니 바로 성공하고, 서버쪽 DB에도 잘 들어가는거 확인했다. 

     

    결론은 클라이언트 측에서 에러난거 없는지 서버 담당자가 올려준 문서와 비교하며 잘 확인할것...!  ^----^!

     

     

    참고한 블로그 : m.blog.naver.com/zion830/221661486117

     

    AWS를 활용한 안드로이드 앱 (6) 안드로이드에서 로그인, 회원가입 구현하기, Retrofit 라이브러리로

    이 글은 본인의 AWS 이용기를 정리한 글로, 글 전체의 목차는 https://blog.naver.com/zion830/22135330...

    blog.naver.com

     

Designed by Tistory.