조금 지나긴 했지만, 이슈가 되었던 요소수, 그걸 판매하는 주유소 정보를 공공데이터 포털에서 제공하기 시작했다. 현재 (2021.12.20 기준)는 111개 주유소의 정보만 제공이 되고 있는 것 같으나, 일단 그걸 이용해서 데이터 제공을 하는 앱을 구성해 보았다. 이번 앱은 이미 만들었던 앱에 retrofit 서비스 호출을 구성하여 데이터를 읽어 오는 부분만 구성해 보았다.
이 앱은 현재 가는길에 들려야 하는 곳을 찾아서 기록해 두기 위해서 만들었던 앱이다. 집 가는 길에, 학교 가는 길에, 어디 가는 길에... 들렸다 오라는 엄마의 , 여보의 말을 기억해야 하나... 깜밖 거리는 나를 위해서...
본론으로 와서 공공데이터 포털에서 데이터 활용 신청을 해 보자.
data.go.kr 을 접속해 보면 위와 같이 요소수 중점 유통 주유소 재고현황 API에 대한 활용 정보가 제공되고 있음을 알 수 있다. 저 팝업을 클릭해 들어가 보면 활용에 대한 정보를 볼 수 있다.
활용신청을 클릭해 보자. 신청 단계는 사용신청 사항을 입력해서 확인을 진행하게 되면 바로 처리가 되고 있어서 자세한 설명을 접어 두고 신청 후 화면을 보면 아래와 같이 일반 인증키가 2개 보이는데, source 작성 시에는 decoding 된 것을 가져다가 source 작성 시에 encoding 해서 전달하는 방식으로 처리를 하면 좋을 것 같다.
아래로 내려보면 데이터 구조를 설명하는 부분들이 나와 있으므로 이것을 근간으로 해서 data 구조 class 을 선언해 보자.
import com.google.gson.annotations.SerializedName;
import java.util.ArrayList;
/**
* 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을 구성하였다.
import com.google.gson.annotations.SerializedName;
/**
* 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을 제공하는 쪽에서 제시한 이름으로 일치시켜 주어야 한다.
이제 통신을 구성하기 위해서 retrofit 에 대한 설정을 상기해 보자.
https://billcorea.tistory.com/26
이 글에서도 잠깐 설명을 하기는 했었는데, 지금 보니 조금 허접한 느낌이 드는 건 뭘까?
일단, gradle(Module) 파일에서 설정은
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'
이렇게 3줄이 들어가야 하는 데, gson 은 데이터를 json 형식으로 받을 거라서 필요하고, simplexml 은 데이터를 xml 형식으로 받을 때 필요하나, 이번에는 사용하지 않기 때문에 없어도 뭐...
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Headers;
import retrofit2.http.Query;
/**
* http 호출을 위한 API
* 앱 등록 및 사용자 설정 필수 category
*
*/
public interface RetrofitApi {
/**
* 2021.11.26 요소수 데이터 수집
*/
@GET("/api/uws/v1/inventory")
Call<AdBlueBean> getAdBlueData(@Query(value="serviceKey", encoded = true) String serviceKey,
@Query("page") int iPage,
@Query("perPage") int perPage,
@Query(value="cond[addr::LIKE]", encoded = true) String addressLike
);
/**
* 전체 목록을 수집
* @param serviceKey
* @param iPage
* @param perPage
* @return
*/
@GET("/api/uws/v1/inventory")
Call<AdBlueBean> getAllAdBlueData(@Query(value="serviceKey", encoded = true) String serviceKey,
@Query("page") int iPage,
@Query("perPage") int perPage
);
}
서비스 호출을 하기 위한 interface을 구현해 보았는데, 실상은 getAllBlueData 함수만 사용하였다. 위에 있는 getAdBlueData 함수는 조회 조건에 addr에 like 검색을 지원하는 호출이 필요할 때 사용해 볼 수 있다.
이제 activity에서 호출을 해 보자.
....
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
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);
...
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
context = container.getContext();
...
binding = FragmentMapsBinding.inflate(getLayoutInflater());
...
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
SupportMapFragment mapFragment =
(SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.map);
...
// 사용을 위해서 선언
retrofitBlue = new Retrofit.Builder()
.baseUrl(serviceURL) // 기본 url 은 공공데이터 포털에서 확인 가능
.addConverterFactory(GsonConverterFactory.create())
.build();
// interface 와 연결하기 위해서 선언
serviceBlue = retrofitBlue.create(RetrofitApi.class);
...
}
private void doFindAllAdBlue(GoogleMap googleMap) {
infoBinding = AdblueInfoBinding.inflate(getLayoutInflater());
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()))));
}
}
@Override
public void onFailure(Call<AdBlueBean> call, Throwable t) {
Log.e(TAG, "error=" + t.toString());
}
});
}
}
...
}
source에 필요해 보이는 부분들만 발췌를 했는데, retrofitBlue라는 설정을 통해서 retrofit 사용을 위한 기본 설정을 하고 service을 호출해서 앞에서 구현한 interface을 호출해서 서비스 호출을 통해서 데이터를 가져오면 된다.
response 가 json으로 오기 때문에 앞에서 선언했던 AdBlueBean을 이용해서 데이터를 받아서 분석해서 사용하면 된다. 어ㅣ예시에서는 for 구문을 통해서 12번 데이터를 받아오는 것처럼 되어 있는데. 현재까지 알려진 것으로는 api 가 제공하는 데이터가 110개 정도 되고 한 번에 10개의 데이터를 제공하고 있기 때문에 반복해서 전제 자료를 받아오기 위함이다.
이렇게 해서 데이터가 오는 걸 보면 다음과 같이 구현이 되었다.
map에 custom marker을 구현해서 표시가 되고 있지만, 아직은 모양이 이쁘지는 않은 것 같다. 이 부분은 좀 더 고민을 해 볼 필요가 있을 것 같다.
이 걸로 오늘 앱 만들기...
'모바일 앱(안드로이드)' 카테고리의 다른 글
안드로이드 앱 만들기 : 알림 구현 방법 Snackbar 와 Toast 차이 (3) | 2021.12.10 |
---|---|
안드로이드 앱 만들기 : 리사이클뷰(RecycleView) 에 광고 추가 (0) | 2021.12.08 |
안드로이드 앱 만들기 : 구글맵 활용 하기, 제주 맛집 리스트 (2) (2) | 2021.11.30 |
안드로이드 앱 만들기 : 스피너 ? spinner ? 콤보 ? 따라하기 (2) | 2021.11.29 |
안드로이드 앱 만들기 : 제주 맛집 리스트 (1) (7) | 2021.11.28 |