firebase realtime database 을 활용해서 수집 되는 정보를 저장해 둡니다.
2. 앱을 구동하면
기본 화면을 조회 합니다. 앞에 알림이 나오기는 하지만, 클릭하면 넘어 갑니다.
사용하시기전에 정보받기를 먼저 한번 해 주세요.
메뉴는
1) 정보갱신(제주 맛집) : 앞에서 설명한 데이터를 온라인으로 받아 옵니다. 데이터 통신을 사용해야 하기 때문에 자주 하지는 않고, 필요시 마다 한번식 내려 받아서 보시면 됩니다.
firebase 의 데이터는 매일 아침 9시에 추가 수집을 하고 있어서 보실때 한번씩 받아오기를 해서 보면 최신 자료를 보실 수 있습니다. 2) 정보갱신(제주버스정류소) : 맛집 인근 1Km 주변에 위치한 버스정류소정보를 수집하기 위해서 사용합니다. 해당 자료는 (http://www.jeju.go.kr/help/open.htm?act=view&seq=967654) 에서 수집된 api 을 활용해서 작성되었습니다.
3) 앱 알림은 처음 시작때 보이는 간단한 알림 정보를 표시 합니다.
4) 이 앱은 : 지금 보시는 페이지를 보여 주도록 되어 있습니다.
3. 찾아보기
찾아보는 방법은
1) 카테고리 선택 3단으로 구성된 카테고리를 선택합니다. 대분류 (한식, 양식 ...) 중분류 (해물,생선, 회 ...) 소분류 를 다 선택할 수 도 있고, 대분류, 중분류만 선택할 수 도 있습니다.
2) 찾는 내용으로 검색 상호를 알고 있거나, 지역명 (한림, 구좌, 성산포 ...) 등으로 입력할 수 도 있습니다. 찾는 내용을 입력하고 돋보기 버튼을 클릭하면 해당 내용과 연관된 자료를 리스트로 보여 줍니다.
3) 지도 버튼 리스트에 나와 있는 것들이 한개 또는 여러개 있을 경우 해당 정보를 지도에 표시해 줍니다.
4. 지도로 보기
지도로 보기는 구글맵 지도를 이용해서 조회된 내용에 해당 하는 위치늘 지도로 볼 수 있습니다.
지도에서 확대/축소를 이용해서 지도의 스케일을 조정할 수 있고, 내 위치 (권한 허용이 된 경우)버튼을 이용해서 지도에 나의 위치를 표시하여 찾는 곳과의 거리를 가늠해 볼 수 있습니다.
5. 상세보기
가고 싶은 곳을 찾았다면, 지도 아래 링클를 클릭해서 해당 맛집에 대한 블로그의 내용을 열어 볼 수 있습니다.
블로그의 내용과 해당 위치가 일치 하지 않을 수 도 있지만, 블로그의 내용을 스크랩 해서 주소 정보를 받아 오고 있어서 잘 찾아 보시면 블로그에서 해당 맛집의 정보를 보실 수 있을 겁니다. (?)
개별 조회를 하는 경우 주변에 나타나는 버스정류소를 클릭하면 버스정류소의 이름과 해당 정류소에 도착예정인 버스 정보를 하단에 보여 줍니다.
6. 기타 이 앱은 스크랩핑을 이용해서 맛집 정보를 수집하고 있으므로 해당 맛집을 찾아 다니시는 수고를 하시는 분들께, 일종의 링크 공유정도이 기능을 구현하고 있습니다. 네이버나 다음에서 검색해서 찾을 수 있는 정보를 한번에 모아서 보는 정도의 기능입니다.
이런 정도의 기능 제공이 불편하시면 6k2emg@gmail.com 으로 알려 주시면 앱을 내리도록 하겠습니다.
#스하리1000명프로젝트, ¿Perdido en Corea? Incluso si no hablas coreano, esta aplicación te ayuda a moverte fácilmente. Simplemente hable su idioma: traduce, busca y muestra resultados en su idioma. ¡Genial para viajeros! Admite más de 10 idiomas, incluidos inglés, japonés, chino, vietnamita y más. ¡Pruébalo ahora! https://play.google.com/store/apps/details?id=com.billcoreatech.opdgang1127
조금 지나긴 했지만, 이슈가 되었던 요소수, 그걸 판매하는 주유소 정보를 공공데이터 포털에서 제공하기 시작했다. 현재 (2021.12.20 기준)는 111개 주유소의 정보만 제공이 되고 있는 것 같으나, 일단 그걸 이용해서 데이터 제공을 하는 앱을 구성해 보았다. 이번 앱은 이미 만들었던 앱에 retrofit 서비스 호출을 구성하여 데이터를 읽어 오는 부분만 구성해 보았다.
가는길에 앱 메인
이 앱은 현재 가는길에 들려야 하는 곳을 찾아서 기록해 두기 위해서 만들었던 앱이다. 집 가는 길에, 학교 가는 길에, 어디 가는 길에... 들렸다 오라는 엄마의 , 여보의 말을 기억해야 하나... 깜밖 거리는 나를 위해서...
본론으로 와서 공공데이터 포털에서 데이터 활용 신청을 해 보자.
공공데이터 포털
data.go.kr 을 접속해 보면 위와 같이 요소수 중점 유통 주유소 재고현황 API에 대한 활용 정보가 제공되고 있음을 알 수 있다. 저 팝업을 클릭해 들어가 보면 활용에 대한 정보를 볼 수 있다.
api 활용 안내
활용신청을 클릭해 보자. 신청 단계는 사용신청 사항을 입력해서 확인을 진행하게 되면 바로 처리가 되고 있어서 자세한 설명을 접어 두고 신청 후 화면을 보면 아래와 같이 일반 인증키가 2개 보이는데, source 작성 시에는 decoding 된 것을 가져다가 source 작성 시에 encoding 해서 전달하는 방식으로 처리를 하면 좋을 것 같다.
api 활용 승인
api 호출시 파라미터 설명
api 구조체 설명
아래로 내려보면 데이터 구조를 설명하는 부분들이 나와 있으므로 이것을 근간으로 해서 data 구조 class 을 선언해 보자.
/** * 2021.11.26 요소수 주요소 정보 */ public class AdBlueBean { @SerializedName("currentCount") int currentCount; @SerializedName("data") ArrayList<InventoryModelBean> data ; @SerializedName("matchCount") int matchCount ; @SerializedName("page") int page ; @SerializedName("perPage") int perPage; @SerializedName("totalCount") int totalCount ;
public void setPage(int page) { this.page = page; }
public int getPage() { return page; }
public void setPerPage(int perPage) { this.perPage = perPage; }
public int getPerPage() { return perPage; }
public void setMatchCount(int matchCount) { this.matchCount = matchCount; }
public int getMatchCount() { return matchCount; }
public void setCurrentCount(int currentCount) { this.currentCount = currentCount; }
public int getCurrentCount() { return currentCount; }
public void setTotalCount(int totalCount) { this.totalCount = totalCount; }
public int getTotalCount() { return totalCount; }
public void setData(ArrayList<InventoryModelBean> data) { this.data = data; }
public ArrayList<InventoryModelBean> getData() { return data; } }
위 그림에 inventory_api 을 보면 위 AdBlueBean과 같이 구조체를 가지고 있고 실제 상세 자료는 inventory_model 형식의 데이터 구조를 가지고 있다. 그래서 일단은 AdBlueBean class을 선언하고 model 데이터는 아래처럼 InventoryBean class을 구성하였다.
/** * 2021.11.26 요소수 재고량 정보 */ public class InventoryModelBean { @SerializedName("addr") String addr ; // 주유소 주소 @SerializedName("code") String code ; // 주유소 코드 @SerializedName("inventory") String inventory ; // 재고량 @SerializedName("lat") String lat ; // 위도 @SerializedName("lng") String lng ; // 경도 @SerializedName("name") String name ; // 주유소 이름 @SerializedName("openTime") String openTime ; // 영업 시간 @SerializedName("price") String price ; // 가격 @SerializedName("regDt") String regDt ; // 업데이트 일시 @SerializedName("tel") String tel ; // 전화번호 @SerializedName("color") String color ; // 잔량 수량 구간
public void setAddr(String addr) { this.addr = addr; }
public String getAddr() { return addr; }
public void setCode(String code) { this.code = code; }
public String getCode() { return code; }
public void setInventory(String inventory) { this.inventory = inventory; }
public String getInventory() { return inventory; }
public void setLat(String lat) { this.lat = lat; }
public String getLat() { return lat; }
public void setLng(String lng) { this.lng = lng; }
public String getLng() { return lng; }
public void setName(String name) { this.name = name; }
public String getName() { return name; }
public void setOpenTime(String openTime) { this.openTime = openTime; }
public String getOpenTime() { return openTime; }
public void setPrice(String price) { this.price = price; }
public String getPrice() { return price; }
public void setRegDt(String regDt) { this.regDt = regDt; }
public String getRegDt() { return regDt; }
public void setTel(String tel) { this.tel = tel; }
public String getTel() { return tel; }
public void setColor(String color) { this.color = color; }
public String getColor() { return color; }
}
@SerializedName는 api로 제공받는 데이터 구조체가 json 형식으로 받게 될 것인데, Gson을 이용해 데이터를 받을 때 연결되는 정보라는 정도로 이해를 하면 될 것 같다. 실제 source 코드에서 사용되는 변수명과 같은 경우가 되더라도 데이터를 연결해 주기 위해서는 api을 제공하는 쪽에서 제시한 이름으로 일치시켜 주어야 한다.
public class MapsFragment extends Fragment implements OnCompleteListener<Void>, OnBackPressedListener, OnMapReadyCallback, GoogleMap.OnMapLongClickListener, GoogleMap.OnMarkerClickListener {
GoogleMap mMap;
....
RetrofitApi service;
....
Context context;
public static MapsFragment newInstance(String keyWord, float latitude, float longitude) { MapsFragment fragment = new MapsFragment(); Bundle args = new Bundle(); ... return fragment; }
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... }
// 사용을 위해서 선언 retrofitBlue = new Retrofit.Builder() .baseUrl(serviceURL) // 기본 url 은 공공데이터 포털에서 확인 가능 .addConverterFactory(GsonConverterFactory.create()) .build(); // interface 와 연결하기 위해서 선언 serviceBlue = retrofitBlue.create(RetrofitApi.class);
for(int i=1 ; i < 13 ; i++) { // 서비스 호출을 통해서 데이터을 구해옴 현재는 페이지다 10건씩 수신 하도록 선언 serviceBlue.getAllAdBlueData(serviceKey, i, 10) .enqueue(new Callback<AdBlueBean>() { @Override public void onResponse(Call<AdBlueBean> call, Response<AdBlueBean> response) { Log.e(TAG, "Total=" + response.body().getTotalCount()); Log.e(TAG, "Current=" + response.body().getCurrentCount()); for (InventoryModelBean inventoryModelBean : response.body().getData()) { ... // 가져온 데이터를 화면에 보여 주도록 설정 infoBinding.textName.setText(inventoryModelBean.getName()); infoBinding.textPhone.setText(inventoryModelBean.getTel()); infoBinding.textPrice.setText(inventoryModelBean.getPrice()+"원"); infoBinding.textInventory.setText(inventoryModelBean.getInventory()+"L"); LatLng sydney = new LatLng(Double.parseDouble(inventoryModelBean.getLat()), Double.parseDouble(inventoryModelBean.getLng())); googleMap.addMarker(new MarkerOptions().position(sydney).title(inventoryModelBean.getName()) .icon(BitmapDescriptorFactory.fromBitmap(createDrawableFromView(context, infoBinding.getRoot())))); } }
source에 필요해 보이는 부분들만 발췌를 했는데, retrofitBlue라는 설정을 통해서 retrofit 사용을 위한 기본 설정을 하고 service을 호출해서 앞에서 구현한 interface을 호출해서 서비스 호출을 통해서 데이터를 가져오면 된다.
response 가 json으로 오기 때문에 앞에서 선언했던 AdBlueBean을 이용해서 데이터를 받아서 분석해서 사용하면 된다. 어ㅣ예시에서는 for 구문을 통해서 12번 데이터를 받아오는 것처럼 되어 있는데. 현재까지 알려진 것으로는 api 가 제공하는 데이터가 110개 정도 되고 한 번에 10개의 데이터를 제공하고 있기 때문에 반복해서 전제 자료를 받아오기 위함이다.
이렇게 해서 데이터가 오는 걸 보면 다음과 같이 구현이 되었다.
데이터 조회 처리 앱 구동 예시
map에 custom marker을 구현해서 표시가 되고 있지만, 아직은 모양이 이쁘지는 않은 것 같다. 이 부분은 좀 더 고민을 해 볼 필요가 있을 것 같다.
앱을 만들면서 지도가 필요한 경우 카카오맵도 해 봤고, 구글맵도 구현해 보기는 했다. 카카오맵을 사용하면 나름 카카오의 지원(?)을 받아 이런 저런 것들을 해 볼 수 있다.
우선 좋은 점은 카카오가 지원하는 다른 서비스들과 연동이 수월하다. 하다 못해 카카오 내비를 호출해서 바로 길찾기를 지원받을 수 있으니... 다만, onestore 에 앱을 등록하는 것이 번거롭다(?) 여러번 시도를 했는데, 어떤 방법으로 앱을 검증 하는 지는 모르겠으나, 번번히 실패 ... 그래서 구글맵으로 수정앱을 등록해보니, 한번에 패스~
그래서 일단 구글맵에 지도를 표현해 보도록 하겠다.
일단 기본적으로 구현이 수월한 방법... android studio 에서 new - acitivity - gallery 를 선택하면 ...
액티비티 선택
여러 종류의 activity 가 있는데, 그 중에서 지도가 들어있는 maps 을 선택하면 기본적인 설정은 android studio 가 해 준다.
그 다음은 manifest 파일에 meta 정보를 설정하는 것이 나온다. meta에 google-api-key 을 설정해 주어야 하는데,
cloud platform 의 console 화면에서 사용자 인증 정보에 들어가서 api 키를 추가 하거나 기존에 등록했던 것에서 가져 오면 된다.
google dash board
이미 등록해 놓은 것이 있으니 그걸 하나 선택해서 상세 내용을 볼껀데,
api 세부 설정
이 내용에서 한가지 생각해 볼 것은 android 앱을 만들고 있기 때문에 애플리케이션 제한사항을 선택할 때 꼭 안드로이드를 선택해야만 할 것 같은 생각이 들었던 경험이 있는 데, 그 때는 주의해야할 부분이 인증서 해쉬값을 등록해 줄 때 debug 용 release 용 두개를 각각 설정해 주어야 하고, 그것들을 다 project에 찾아서 설정해 주어야 했던 것 같은 기억이 있다.
그래서 이번에는 애플리케이션 제한사항은 설정하지 않았다. 다만, api 제한 사항을 선택해서 maps sdk 와 firebase 부분만 선택해서 사용하는 것으로 설정하고, api key는 오른쪽 상단에 있는 apikey을 복사해서 manifest 파일에 등록해 주었다.
저 제한사항 둘 중 하나를 선택해 주지 않으면 메일이 자주 온다. google 에서 궁시렁 궁시렁...
아무튼 이렇게 설정이 끝나고 나면 이제 activity 를 구성해 볼 차례 인데, 기본적으로 maps activity 을 선택 했기 때문에 기본 code 작업은 되어 있게 된다.
화면을 만들다 보면 간혹 뭔가를 선택해야 하는 경우가 발생하게 된다. 그럴 때 콤보라고 쓰고 스피너라고 읽는 layout item에 대한 이야기를 잠시해 두고 넣어가고자 한다. 이유는 간혹 작업을 하면서 사용하게 되는데, 나도 인간인지라 깜빡깜빡해서 사용할 때마다 찾아봐야 한다는 것이다. ㅋ~
db에서 데이터를 읽어서 ArrayList에 넣고 그 값을 이용해서 spinner을 위한 adapter을 선언해 주는 것이다. 여기서 또 보고 가야 할 것은 simple_spinner_dropdown_item을 선언하는 것인데, 이건 spinner을 모양을 선택해 주는 것이니, 참고하고, 다른 선언을 하고 싶은 경우는 찾아보고 변경해 주면 될 것 같다.
그럼... 선언한 화면이 어떻게 움직이는 지 봐야 할 듯...
실제로 구동하는 예시는 이런 모습으로 나타난다... 화면에는 spinner 가 3개가 있는데, 첫 번째 spinner 가 변할 때마다, 2번쨰을 다시 읽어오고, 2번째를 선택하면 3번쨰을 다시 읽어 오는 그런 모습으로 구동하게 된다.
@Override public void onCancelled(@NonNull DatabaseError error) {
} }) ; }
코드를 보면 split(">") 하기 전에 공백을 제거하는 처리를 먼저 했다. 데이터를 받아서 보니 글자들 사이에 들어가 있는 공백은 별 필요가 없는데, 그리고 나누어 기록을 했다. 그걸 하기 위해서 먼저 데이터를 저장할 dbHelper을 구현해 보았다.
/** * aClassCode, bClassCode, cClassCode : 코드 분류 1,2,3 차 * cntData : 위치별-상호별 중복 건수 * infoData : 상세 정보 y,x,place_name 으로 cntData 와 연계 * @param db */ @Override public void onCreate(SQLiteDatabase db) {
여러 가지 구현에 대한 이야기를 적어 놓고 있는데, 아직 적용을 해 보지 못해서 다 이해가 되는 것은 아니기는 하지만,
조금은 알 것 같기도 하고...
일단은 내가 만든 앱에서 알림이 뜨면 내 손목에 있는 워치에도 알림이 뜨기는 한다. 그리고 한 가지 꼭 기억하고 가야 할 것은 내 폰의 설정에서 알림 부분에 내가 만든 앱의 알림이 허용되어 있는지 봐야 한다. 또한 워치 앱에서도 (갤럭시 워치 4는 Galaxy Wearable)에서 알림 부분에 내 앱의 알림에 대한 설정이 허용되어 있는지 봐야 한다.