Today's

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

모바일 앱(안드로이드)

안드로이드 앱 만들기 : 공공데이터 포털 활용 해 보기. (2)

Billcorea 2021. 12. 2. 23:03
반응형

조금 지나긴 했지만, 이슈가 되었던 요소수, 그걸 판매하는 주유소 정보를 공공데이터 포털에서 제공하기 시작했다.  현재 (2021.12.20 기준)는 111개 주유소의 정보만 제공이 되고 있는 것 같으나, 일단 그걸 이용해서 데이터 제공을 하는 앱을 구성해 보았다.  이번 앱은 이미 만들었던 앱에 retrofit 서비스 호출을 구성하여 데이터를 읽어 오는 부분만 구성해 보았다. 

 

가는길에 앱 메인

 이 앱은 현재 가는길에 들려야 하는 곳을 찾아서 기록해 두기 위해서 만들었던 앱이다.  집 가는 길에,  학교 가는 길에, 어디 가는 길에... 들렸다 오라는 엄마의 , 여보의 말을 기억해야 하나... 깜밖 거리는 나를 위해서...

 

본론으로 와서 공공데이터 포털에서 데이터 활용 신청을 해 보자.

공공데이터 포털

data.go.kr 을 접속해 보면 위와 같이 요소수 중점 유통 주유소 재고현황 API에 대한 활용 정보가 제공되고 있음을 알 수 있다. 저 팝업을 클릭해 들어가 보면 활용에 대한 정보를 볼 수 있다. 

 

api 활용 안내

 

활용신청을 클릭해 보자. 신청 단계는 사용신청 사항을 입력해서 확인을 진행하게 되면 바로 처리가 되고 있어서 자세한 설명을 접어 두고 신청 후 화면을 보면 아래와 같이 일반 인증키가 2개 보이는데,  source 작성 시에는 decoding 된 것을 가져다가 source 작성 시에 encoding 해서 전달하는 방식으로 처리를 하면 좋을 것 같다. 

 

api 활용 승인

 

 

api 호출시 파라미터 설명
api 구조체 설명

아래로 내려보면 데이터 구조를 설명하는 부분들이 나와 있으므로 이것을 근간으로 해서 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

 

안드로이드 앱 만들기 API 연동을 위한 retrofit 구현 이야기.

안드로이드 폰에서 Restful 호출을 위해서 StringRequest 을 사용해 보기도 했지만, Retrofit 을 알고 나서는 간편하게 잘 쓰게 되었다. data.go.kr 에서 제공하는 공공데이터를 이용해서 앱을 만들어 보고

billcorea.tistory.com

이 글에서도 잠깐 설명을 하기는 했었는데,  지금 보니 조금 허접한 느낌이 드는 건 뭘까?

 

일단, 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을 구현해서 표시가 되고 있지만, 아직은 모양이 이쁘지는 않은 것 같다. 이 부분은 좀 더 고민을 해 볼 필요가 있을 것 같다.

 

이 걸로 오늘 앱 만들기...

 

 

 

반응형