Today's

길을 나서지 않으면 그 길에서 만날 수 있는 사람을 만날 수 없다

모바일 앱(안드로이드)

안드로이드 앱 만들기 도전 2일차 geofences 을 활용한 앱

Billcorea 2021. 8. 2. 09:09
반응형

앞에 이야기를 보고 준비를 잘 했다면 이제 하나씩 만들어 보자.

카카오 지도 준비는 되었으니, 이제 카카오 개발자 페이지에서 주변 정보를 수집할 방법에 대하여 생각해 보자, 구글에서도 place API 을 지원하고 있으나, 사용에 부담이 되는 것은 아무래도 간혹 나오는 영문 데이터 떄문이다, 아직은 한국적인 느낌이 들지 않는다.

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

먼저 manifasts 파일에 인터넷 사용을 위한 권한등록을 한다. 데이터는 retrofit API을 이용해서 받아올 꺼니까

    implementation 'com.squareup.retrofit2:retrofit:2.7.2'
    implementation 'com.squareup.retrofit2:converter-gson:2.7.2'
    implementation 'com.squareup.retrofit2:converter-simplexml:2.1.0'

다음은 gradle 파일에 retrofit 사용을 위해서 implementation 을 선언한다. 나는 데이터를 json 방식으로 받아서 처리를 할 것이라서 simplexml 을 없어도 되나. 데이터를 혹시나 xml 구조로 받아야 하는 경우가 있을 때는 기술할 필요가 있다.

이제 카카오개발자 페이지에서 새로 앱을 만들기 등록을 하고, API 키를 받는다...(저 그림의 키는 일부이니 붙여넣기를 해도 소용이 없을 듯...)

그리고 플랫폼에 android 앱에 대한 정보를 하나 등록을 해야 하고, 키 해시는 앱을 실행해서 나오는 키 해시 값을 등록해 주어야 카카오 지도를 활용할 수 있다.

    public PackageInfo getPackageInfo(final Context context, int flag) {
        try {
            return context.getPackageManager().getPackageInfo(context.getPackageName(), flag);
        } catch (PackageManager.NameNotFoundException e) {
            Log.w(TAG, "Unable to get PackageInfo", e);
        }
        return null;
    }

    public String getKeyHash(Context context) {
        PackageInfo packageInfo = getPackageInfo(context, PackageManager.GET_SIGNATURES);
        if (packageInfo == null)
            return null;

        for (Signature signature : packageInfo.signatures) {
            try {
                MessageDigest md = MessageDigest.getInstance("SHA");
                md.update(signature.toByteArray());
                return Base64.encodeToString(md.digest(), Base64.NO_WRAP);
            } catch (NoSuchAlgorithmException e) {
                Log.e("getKeyHash", "Unable to get MessageDigest. signature=" + signature, e);
            }
        }
        return null;
    }

구글링을 해보면 다 나오겠지만, 위와 같이 코드를 넣어 실행 결과를 받아보면 해시값을 알 수 있다.

이제 주변 정보를 얻기 위해서. 카카오의 로컬 서비스를 호출할 준비를 해 보자.

https://developers.kakao.com/docs/latest/ko/local/dev-guide

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

아직 까지는 (2021.08.현재) rest 호출만 지원하고 있다 하니, 앞에서 준비할 것 처럼 http 통신을 해서 결과를 수집해야 할 것 같다.

내가 사용할 방법은 키워드를 통해서 주변을 찾아 보는 것이다. 

 

이런 내용이 있는 위치 부터 읽어 본다.  그리고 응답을 받아서 관리하기 위해서 아래와 같이 응답 구조체 class 을 만들었다.

package ...

import com.google.gson.annotations.SerializedName;

import java.util.ArrayList;

public class ResponseBean {
    @SerializedName("meta")
    Meta meta ;
    @SerializedName("documents")
    ArrayList<Documents> documents ;

    public Meta getMeta() {
        return meta;
    }

    public ArrayList<Documents> getDocuments() {
        return documents;
    }

    public void setMeta(Meta meta) {
        this.meta = meta;
    }

    public void setDocuments(ArrayList<Documents> documents) {
        this.documents = documents;
    }

    public class Meta {
        @SerializedName("same_name")
        SameName sameName ;
        @SerializedName("pageable_count")
        int pageableCount ;
        @SerializedName("total_count")
        int totalCount ;
        @SerializedName("is_end")
        boolean isEnd ;

        public SameName getSameName() {
            return sameName;
        }

        public int getPageableCount() {
            return pageableCount;
        }

        public int getTotalCount() {
            return totalCount;
        }

        public boolean isEnd() {
            return isEnd;
        }

        public void setSameName(SameName sameName) {
            this.sameName = sameName;
        }

        public void setPageableCount(int pageableCount) {
            this.pageableCount = pageableCount;
        }

        public void setTotalCount(int totalCount) {
            this.totalCount = totalCount;
        }

        public void setEnd(boolean end) {
            isEnd = end;
        }
    }

    public class Documents {
        @SerializedName("place_name")
        String placeName ;
        @SerializedName("distance")
        String distance ;
        @SerializedName("place_url")
        String placeUrl ;
        @SerializedName("category_name")
        String categoryName ;
        @SerializedName("address_name")
        String addressName ;
        @SerializedName("road_address_name")
        String roadAddressName ;
        @SerializedName("id")
        String id ;
        @SerializedName("phone")
        String phone;
        @SerializedName("category_group_code")
        String categoryGroupCode ;
        @SerializedName("category_group_name")
        String categoryGroupName ;
        @SerializedName("x")
        String posX ;
        @SerializedName("y")
        String posY ;

        public String getAddressName() {
            return addressName;
        }

        public String getCategoryGroupCode() {
            return categoryGroupCode;
        }

        public String getCategoryGroupName() {
            return categoryGroupName;
        }

        public String getCategoryName() {
            return categoryName;
        }

        public String getDistance() {
            return distance;
        }

        public String getId() {
            return id;
        }

        public String getPhone() {
            return phone;
        }

        public String getPlaceName() {
            return placeName;
        }

        public String getPlaceUrl() {
            return placeUrl;
        }

        public double getPosX() {
            return Double.parseDouble(posX);
        }

        public double getPosY() {
            return Double.parseDouble(posY);
        }

        public String getRoadAddressName() {
            return roadAddressName;
        }

        public void setAddressName(String addressName) {
            this.addressName = addressName;
        }

        public void setCategoryGroupCode(String categoryGroupCode) {
            this.categoryGroupCode = categoryGroupCode;
        }

        public void setCategoryGroupName(String categoryGroupName) {
            this.categoryGroupName = categoryGroupName;
        }

        public void setCategoryName(String categoryName) {
            this.categoryName = categoryName;
        }

        public void setDistance(String distance) {
            this.distance = distance;
        }

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

        public void setPhone(String phone) {
            this.phone = phone;
        }

        public void setPlaceName(String placeName) {
            this.placeName = placeName;
        }

        public void setPlaceUrl(String placeUrl) {
            this.placeUrl = placeUrl;
        }

        public void setPosX(String posX) {
            this.posX = posX;
        }

        public void setPosY(String posY) {
            this.posY = posY;
        }

        public void setRoadAddressName(String roadAddressName) {
            this.roadAddressName = roadAddressName;
        }
    }

    public class SameName {
        @SerializedName("region")
        String[] region ;
        @SerializedName("keyword")
        String keyWord ;
        @SerializedName("selected_region")
        String selectedRegion;

        public String getSelectedRegion() {
            return selectedRegion;
        }

        public String getKeyWord() {
            return keyWord;
        }

        public String[] getRegion() {
            return region;
        }

        public void setRegion(String[] region) {
            this.region = region;
        }

        public void setKeyWord(String keyWord) {
            this.keyWord = keyWord;
        }

        public void setSelectedRegion(String selectedRegion) {
            this.selectedRegion = selectedRegion;
        }
    }
}

부분 쪼개서 class 로 나누는 것도 좋기는 하나, 뭐 그러나 저러나 비슷한 것 같아서... 그냥 파일 하나에 다 담아서 정리를 했다. 그 다음은 이제 저 class 을 이용해서 호출하는 api 을 하나 만들어 보자 (2021.08.21 아래 소스 일부 수정)

package ...

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Headers;
import retrofit2.http.Query;

/**
 * http 호출을 위한 API
 *     앱 등록 및 사용자 설정 필수 category
 *
 * query	            String	검색을 원하는 질의어	O
 * category_group_code	String	카테고리 그룹 코드 * 결과를 카테고리로 필터링을 원하는 경우 사용	X
 * x	                String	중심 좌표의 X값 혹은 longitude * 특정 지역을 중심으로 검색하려고 할 경우 radius와 함께 사용 가능	X
 * y	                String	중심 좌표의 Y값 혹은 latitude * 특정 지역을 중심으로 검색하려고 할 경우 radius와 함께 사용 가능	X
 * radius	            Integer	중심 좌표부터의 반경거리. 특정 지역을 중심으로 검색하려고 할 경우 중심좌표로 쓰일 x,y와 함께 사용 * 단위 meter, 0~20000 사이의 값	X
 * rect	                String	사각형 범위내에서 제한 검색을 위한 좌표. 지도 화면 내 검색시 등 제한 검색에서 사용 가능 * 좌측 X 좌표,좌측 Y 좌표, 우측 X 좌표, 우측 Y 좌표 형식	X
 * page	                Integer	결과 페이지 번호 * 1~45 사이의 값 (기본값: 1)	X
 * size	                Integer	한 페이지에 보여질 문서의 개수 * 1~15 사이의 값 (기본값: 15)	X
 * sort	                String	결과 정렬 순서, distance 정렬을 원할 때는 기준 좌표로 쓰일 x, y와 함께 사용 * distance 또는 accuracy (기본값: accuracy)	X
 *
 * category_group_code  String  카테고리 코드 O
 * x                    String             중심 좌표의 X값 혹은 longitude * 특정 지역을 중심으로 검색하려고 할 경우 radius와 함께 사용 가능.  (x,y,radius) 또는 rect 필수
 * y                    String             중심 좌표의 Y값 혹은 latitude * 특정 지역을 중심으로 검색하려고 할 경우 radius와 함께 사용 가능.  (x,y,radius) 또는 rect 필수
 * radius               Integer 중심 좌표부터의 반경거리. 특정 지역을 중심으로 검색하려고 할 경우 중심좌표로 쓰일 x,y와 함께 사용. 단위 meter, 0~20000 사이의 값  (x,y,radius) 또는 rect 필수
 * rect                 String  사각형 범위내에서 제한 검색을 위한 좌표 * 지도 화면 내 검색시 등 제한 검색에서 사용 가능 * 좌측 X 좌표, 좌측 Y 좌표, 우측 X 좌표, 우측 Y 좌표 형식 * x, y, radius 또는 rect 필수 X
 * page                 Integer 결과 페이지 번호 * 1~45 사이의 값 (기본값: 1)  X
 * size                 Integer 한 페이지에 보여질 문서의 개수 * 1~15 사이의 값 (기본값: 15) X
 * sort                 String  결과 정렬 순서, distance 정렬을 원할 때는 기준좌표로 쓰일 x, y 파라미터 필요 * distance 또는 accuracy (기본값: accuracy) X
 */
public interface RetrofitApi {
    @Headers("Authorization:KakaoAK 647a2bd........0b9d8")
    @GET("/v2/local/search/keyword.json")
    Call<ResponseBean> getKeywordData (@Query(value="query", encoded = true) String strAddr,
                                       @Query("x") double x,
                                       @Query("y") double y,
                                       @Query("radius") int radius,
                                       @Query("page") int page
                                );

    @Headers("Authorization:KakaoAK 647a2........970b9d8")
    @GET("/v2/local/search/category.json")
    @GET("/v2/local/search/category.json")
    Call<ResponseBean> getCategoryData (@Query("category_group_code") String categoryCode,
                                        @Query("x") double x,
                                        @Query("y") double y,
                                        @Query("radius") int radius,
                                        @Query("rect") String rect,
                                        @Query("page") int page);
    );
}

여기서 ResponseBean 은 위에서 말한 class 이름이고, @Headers 에 들어 있는 문장에 키값은 위에서 받은 rest api 키 이니 참고 하시길...  자 이제 호출해 볼까 ?  아래와 같이 화면의 버튼 event 등에서 getData을 호출하면 된다. 

넘어가는 파라미터는 검색할 때 사용할 strKeyword 그리고 중심이 되는 x, y 좌표(Longitude, Latitude)값, 그리고 반경(radius), 그리고 받아올 페이지의 시작값(iPage) 

페이지의 시작값은 난 무조건 1페이지 분량만 받아서 할 거라서 1로 설정해서 받아왔지만, 주변 검색을 더 하고 싶으면 페이지 번호를 계속 변경해서 받아오면 된다.  그 페이지가 마지막 인지 여부를 응답구조체 중에서 meta is_end 이 값을 보고 알 수 있다.

    public void getData(String strKeyword, double x, double y, int radius, int iPage) {
        Log.e(TAG, "(" + x + "," + y + ")" + strKeyword);
        service.getKeywordData(strKeyword, x, y, radius, iPage).enqueue(new Callback<ResponseBean>() {
            @Override
            public void onResponse(Call<ResponseBean> call, Response<ResponseBean> response) {
                Log.i(TAG, "code=" + response.code() + "" ) ;
                try {
                    for (ResponseBean.Documents documents : response.body().getDocuments()) {
                        Log.e(TAG, documents.getPlaceName());
                        MapPoint mapPoint = MapPoint.mapPointWithGeoCoord(documents.getPosY(), documents.getPosX());
                        addMarker(mapPoint, documents.getPlaceName());
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onFailure(Call<ResponseBean> call, Throwable t) {

                Log.e(TAG, t.toString()) ;
                t.printStackTrace();

            }
        });
    }

그리고 결과를 받아 왔다면 addMarker 함수를 호출해서 지도에 마커들을 출력하면 끝...

 

다른 것들은 이제 다음에...

반응형