기본 콘텐츠로 건너뛰기

안드로이드 앱 만들기 : 옵디강 (제주맛집) 기능 추가 버스 정류소 위치


원본출처: 티스토리 바로가기

이전 포스팅에서 제주버스의 정보를 수집했다. 

https://billcorea.tistory.com/111

 

이제 그 정보를 나의 앱에 넣는 작업을 해 봐야겠다.  일단은 데이터를 저장할 table 을 구성해 보았다.  뭐 말그대로 앞전 포스팅에서 작성한 class 중에서 item 이 들어 있는 class 구조를 그대로 적용해 보면 될 것 같다.

 

import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log;  public class DBHelper extends SQLiteOpenHelper {      private static final String DB_NAME = "opdGangDB";     private static final int DB_Ver = 4;     private static final String TAG = "DBHelper";      public DBHelper(Context context) {         super(context, DB_NAME, null, DB_Ver);     }      /**      * aClassCode, bClassCode, cClassCode : 코드 분류 1,2,3 차      * cntData : 위치별-상호별 중복 건수      * infoData : 상세 정보 y,x,place_name 으로 cntData 와 연계      * @param db      */     @Override     public void onCreate(SQLiteDatabase db) {          StringBuffer sb = new StringBuffer();         sb.append("create table ClassCode");         sb.append("( _id integer primary key autoincrement, ");         sb.append("  classNameA text, ");         sb.append("  classNameB text, ");         sb.append("  classNameC text  ");         sb.append(" ) ");         db.execSQL(sb.toString());          sb = new StringBuffer();         sb.append("create table cntData");         sb.append("( _id integer primary key autoincrement, ");         sb.append("  y text, ");         sb.append("  x text, ");         sb.append("  place_name text, ");         sb.append("  atCnt number ");         sb.append(" ) ");         db.execSQL(sb.toString());          sb = new StringBuffer();         sb.append("create table infoData");         sb.append("( _id integer primary key autoincrement, ");         sb.append("  y text, ");         sb.append("  x text, ");         sb.append("  address_name text, ");         sb.append("  category_group_code text, ");         sb.append("  category_group_name text, ");         sb.append("  category_name text, ");         sb.append("  distance text, ");         sb.append("  id text, ");         sb.append("  phone text, ");         sb.append("  place_name text, ");         sb.append("  place_url text, ");         sb.append("  road_address_name text, ");         sb.append("  url text, ");         sb.append("  regTime text, ");         sb.append("  likeCnt text, ");         sb.append("  unlikeCnt text ");         sb.append(" ) ");         db.execSQL(sb.toString());          /**          *   제주 버스 정류소 정보 수집 2021.12.20 ~          */         sb = new StringBuffer();         sb.append("create table stationItem ");         sb.append("( _id integer primary key autoincrement, ");         sb.append(" dirTp text, ");         sb.append(" govNm text, ");         sb.append(" localX real, ");         sb.append(" localY real, ");         sb.append(" mobiNum text, ");         sb.append(" stationId text, ");         sb.append(" stationNm text, ");         sb.append(" upd text, ");         sb.append(" useYn text ");         sb.append(" ) ");         db.execSQL(sb.toString());     }      /**      * @param db      * @param oldVersion      * @param newVersion      */      @Override     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {          StringBuffer sb = new StringBuffer();          Log.e(TAG, "newVersion=" + newVersion) ;         switch (newVersion) {             case 4:                  sb = new StringBuffer();                 sb.append("create table stationItem ");                 sb.append("( _id integer primary key autoincrement, ");                 sb.append(" dirTp text, ");                 sb.append(" govNm text, ");                 sb.append(" localX real, ");                 sb.append(" localY real, ");                 sb.append(" mobiNum text, ");                 sb.append(" stationId text, ");                 sb.append(" stationNm text, ");                 sb.append(" upd text, ");                 sb.append(" useYn text ");                 sb.append(" ) ");                 db.execSQL(sb.toString());                  break;             case 3:                 db.execSQL("alter table infoData add column likeCnt text ");                 db.execSQL("alter table infoData add column unlikeCnt text ");                 break;         }      } }

sqlite 에서 데이터를 구성해 앱을 만들다 보면 나중에 추가 하거나 구조를 변경 하게 되는 경우가 있는데,  그 때 마다 data을 다 지우고 다시 만들면 사용자도 힘들고, 개발자도 힘들고 ... 그래서 db을 생성할 때 버전에 따라서 변하게 하는 것이 좋을 듯 하다.  그러면 이전 앱을 설치한 사용자도 upgrade 되면 새로 데이터를 넣지 않아도 되기 때문이다. 

 

이 앱은 벌써 데이터 구조를 3, 4 버전까지 늘렸다. 2 버전은 어디 갔는 지 모르겠지만... (사실은 한번 잘못 빌드를 했다가.. 실폐를 해서 ) 아무튼 새로 버전을 받더라도 사용자는 아무런 의심 없이 사용하게 될 것이다.

 

이번에는 이 데이터를 불러서 사용할 것들을 정리해 봐야겠다.

import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.util.Log;  import com.billcoreatech.opdgang1127.JejuBusInfo.StationItemBean; import com.billcoreatech.opdgang1127.JejuFD6.JejuFD6infoBean; import com.billcoreatech.opdgang1127.Utils.StringUtils;  public class DBHandler extends RuntimeException {      DBHelper helper ;     SQLiteDatabase db ;     String TAG = "DBHandler";      public DBHandler(Context context) {         helper = new DBHelper(context) ;         db = helper.getWritableDatabase() ;     }      public static DBHandler open (Context ctx) throws SQLException {         DBHandler handler = new DBHandler(ctx) ;         return handler ;     }      public void close() {         helper.close();     }     ...      /**      * 제주 버스 정류소 정보 2021.12.20 ~      * @param station      * @return      */     public long appendBusStation(StationItemBean station) {         long result = 0 ;         Cursor rs = selectBusStation(station.getStationId());         if (rs.moveToNext()) {             result = updateBusStation(station);         } else {             result = insertBusStation(station);         }         rs.close();         return result ;     }      /**      * 제주 버스 정류소 정보 2021.12.20 ~      * @param busStation      * @return      */     public Cursor selectBusStation(String busStation) {         StringBuffer sb = new StringBuffer();         sb.append("select * from stationItem ");         if (!"".equals(busStation)) {             sb.append(" where stationId = '" + busStation.trim() + "' ");         }         return  db.rawQuery(sb.toString(), null);     }      /**      * 주변 정류소 찾기      * @param x : 경도      * @param y : 위도      * @param roundValue : 주변범위 m      * @return      */     public Cursor selectBusStationArea(double x, double y, int roundValue) {         double x1 = x - StringUtils.LongitudeInDifference(y, roundValue);         double x2 = x + StringUtils.LongitudeInDifference(y, roundValue);         double y1 = y - StringUtils.LatitudeInDifference(roundValue) ;         double y2 = y + StringUtils.LatitudeInDifference(roundValue) ;         StringBuffer sb = new StringBuffer();         sb.append("select * from stationItem ");         sb.append(" where (localX >= " + x1 + " and localX <= " + x2 + ") ");         sb.append("   and (localY >= " + y1 + " and localY <= " + y2 + ") ");         sb.append("   and useYn = 'Y' ");         Log.e(TAG, sb.toString());         return  db.rawQuery(sb.toString(), null);     }      /**      * 제주 버스 정류소 정보 2021.12.20 ~      * @param station      * @return      */     public long insertBusStation(StationItemBean station) {         ContentValues values = new ContentValues();         values.put("dirTp", station.getDirTp());         values.put("govNm", station.getGovNm());         values.put("localX", station.getLocalX());         values.put("localY", station.getLocalY());         values.put("mobiNum", station.getMobiNum());         values.put("stationId", station.getStationId());         values.put("stationNM", station.getStationNm());         values.put("upd", station.getUpd()) ;         values.put("useYn", station.getUseYn()) ;          return db.insert("stationItem", null, values) ;     }      /**      * 제주 버스 정류소 정보 2021.12.20 ~      * @param station      * @return      */     public long updateBusStation(StationItemBean station) {         ContentValues values = new ContentValues();         values.put("dirTp", station.getDirTp());         values.put("govNm", station.getGovNm());         values.put("localX", station.getLocalX());         values.put("localY", station.getLocalY());         values.put("mobiNum", station.getMobiNum());         values.put("stationNM", station.getStationNm());         values.put("upd", station.getUpd()) ;         values.put("useYn", station.getUseYn()) ;          return db.update("stationItem", values, "stationId = '" + station.getStationId() + "' ", null) ;     }  }

이렇게 insert, update, select 을 구성했고, append 도 추가 했다. append 는 새로 데이터를 받아온 다음 그것을 저장하기 위해서, 

 

이제 데이터를 받아와서 저장하는 구현을 해 봐야지.

 

   @Override     public boolean onOptionsItemSelected(@NonNull MenuItem item) {         switch (item.getItemId()) {              case R.id.actionGetBusStation:                  fd6Binding = Updatejejufd6infoViewBinding.inflate(getLayoutInflater());                 fd6Binding.adView.loadAd(adRequest);                 AlertDialog.Builder builder1 = new AlertDialog.Builder(MainActivity.this, R.style.ThemeDialog)                         .setTitle(getString(R.string.titleUpdateBusStation) + "(last:" + sp.getString("lastUpdateBusStation", "1970-01-01") + ")")                         .setView(fd6Binding.getRoot())                         .setPositiveButton(getString(R.string.OK), new DialogInterface.OnClickListener() {                             @Override                             public void onClick(DialogInterface dialogInterface, int i) {                                 doGetBusStation();                             }                         })                         .setNegativeButton(getString(R.string.CLOSE), new DialogInterface.OnClickListener() {                             @Override                             public void onClick(DialogInterface dialogInterface, int i) {                              }                         });                 AlertDialog dialog1 = builder1.create();                 dialog1.show();                  break;           }         return super.onOptionsItemSelected(item);     }

option menu 를 선택 하면 아래 처럼 데이터를 수신 받고 그것을 이용해서 저장하는 for 문을 구성 하였다.

 

   /**      * 2021.12.20 제주 버스 정류소 정보 수집 기능 추가      */     private void doGetBusStation() {          binding.baseProgressBar.setVisibility(View.VISIBLE);         retrofit = new Retrofit.Builder()                 .baseUrl(RetrofitApi.baseURL)                 .client(new OkHttpClient())                 .addConverterFactory(SimpleXmlConverterFactory.create())                 .build();         service = retrofit.create(RetrofitApi.class);         String lastUpdateDate = sp.getString("lastUpdateBusStation", "1970-01-01");         service.getStationInfo(lastUpdateDate).enqueue(new Callback<StationBean>() {             @Override             public void onResponse(Call<StationBean> call, Response<StationBean> response) {                 Log.e(TAG, "response=" + response.code() + " Total="  + response.body().getBodyClass().getTotalCount()                           + "\n" + response.message());                 if (!"0".equals(response.body().getBodyClass().getTotalCount())) {                     dbHandler = DBHandler.open(getApplicationContext());                     for (StationItemBean item : response.body().getBodyClass().getItems()) {                         Log.e(TAG, item.getStationNm());                         long cnt = dbHandler.appendBusStation(item);                         Log.e(TAG, cnt + " " + item.getStationNm());                     }                     dbHandler.close();                     editor = sp.edit();                     editor.putString("lastUpdateBusStation", StringUtils.getToday());                     editor.commit();                 }                 binding.baseProgressBar.setVisibility(View.GONE);             }              @Override             public void onFailure(Call<StationBean> call, Throwable t) {                 Log.e(TAG, "ERROR=" + t.toString()) ;             }         });      }

 저번과 달라진 부분은 service 호출할 때 최종 변경일자를 구해서 전달하도록 수정한 부분이다. 그래서 retrofit api 에서도 다음과 같이 날자를 받아서 파라미터로 전달 하도록 구성을 변경 하였다. 

import retrofit2.Call; import retrofit2.http.GET; import retrofit2.http.Query;  public interface RetrofitApi {     String baseURL = "http://busopen.jeju.go.kr";     String getStation = "/OpenAPI/service/bis/Station";      @GET(getStation)     Call<StationBean> getStationInfo(@Query(value="last") String lastUpdate); }

이렇게 하면 저번 포스팅에서 보았던 제주버스api 에 최종 변경된 정보만 수집하는 모양으로 작업을 할 수 있다. 

이제 가져온 데이터가 앱에서 어떻게 활용이 되는 지 잠시 보도록 하겠다. 

 

버스 정류소가 표시된 지도

이렇게 앱에 버스 정류소를 표시할 수 있는 자료가 만들어 졌다.  다음은 뭐 해야하지 ?  내가 있는 위치에서 저 버스 정류소까지 가는 방법을 연구해 보아야 겠다.

 

그럼 오늘도 즐~ 코딩...

댓글

이 블로그의 인기 게시물

개인정보처리방침 안내

 billcoreaTech('https://billcoreatech.blogspot.com/'이하 'https://billcoreatech.blogspot')은(는) 「개인정보 보호법」 제30조에 따라 정보주체의 개인정보를 보호하고 이와 관련한 고충을 신속하고 원활하게 처리할 수 있도록 하기 위하여 다음과 같이 개인정보 처리방침을 수립·공개합니다. ○ 이 개인정보처리방침은 2021년 8월 26부터 적용됩니다. 제1조(개인정보의 처리 목적) billcoreaTech('https://billcoreatech.blogspot.com/'이하 'https://billcoreatech.blogspot')은(는) 다음의 목적을 위하여 개인정보를 처리합니다. 처리하고 있는 개인정보는 다음의 목적 이외의 용도로는 이용되지 않으며 이용 목적이 변경되는 경우에는 「개인정보 보호법」 제18조에 따라 별도의 동의를 받는 등 필요한 조치를 이행할 예정입니다. 1. 서비스 제공 맞춤서비스 제공을 목적으로 개인정보를 처리합니다. 제2조(개인정보의 처리 및 보유 기간) ① billcoreaTech은(는) 법령에 따른 개인정보 보유·이용기간 또는 정보주체로부터 개인정보를 수집 시에 동의받은 개인정보 보유·이용기간 내에서 개인정보를 처리·보유합니다. ② 각각의 개인정보 처리 및 보유 기간은 다음과 같습니다. 1.<서비스 제공> <서비스 제공>와 관련한 개인정보는 수집.이용에 관한 동의일로부터<사용자의 설정시간>까지 위 이용목적을 위하여 보유.이용됩니다. 보유근거 : 앱의 기본기능 활용에 필요한 위치정보 제3조(개인정보의 제3자 제공) ① billcoreaTech은(는) 개인정보를 제1조(개인정보의 처리 목적)에서 명시한 범위 내에서만 처리하며, 정보주체의 동의, 법률의 특별한 규정 등 「개인정보 보호법」 제17조 및 제18조에 해당하는 경우에만 개인정보를 제3자에게 제공합니다. ② billcoreaTech

안드로이드 앱 만들기 : jetpack compose URL 에서 image 받아와서 보여 주기 (feat coil)

원본출처: 티스토리 바로가기 샘플 이미지 오늘은 앱을 만들면서 이미지를 보여 주어야 하는 경우 중에  URL에서 이미지를 가져와 보는 것에 대해서 기술해 보겠습니다.  URL에서 image를 가져온다는 것은 서버에 저장된 image 일수도 있고, SNS profile의 image 정보일수도 있을 것입니다.    구글에서 찾아보면 다른 것들도 많이 있기는 합니다.  그 중에서 coil 라이브러리를 이용해서 한번 만들어 보도록 하겠습니다.    gradle 설정 그래들 설정은 아래 한 줄입니다. 현재 시점에서 최신 버전인 것으로 보입니다.  이 글을 보시는 시점에 최신 버전이 아니라면 아마도 android studio는 추천을 해 줍니다. // image load from url implementation("io.coil-kt:coil-compose:2.2.2")   manaifest  설정 당연한 이야기 겠지만, url에서 이미지를 받아 와야 하기 때문에 권한 설정을 해야 합니다. 또한 인터넷에서 자료를 받아 오는 것은 지연이 발생할 수 있기 때문에 application에서도 다음 문구의 추가가 필요합니다.  <uses-permission android:name="android.permission.INTERNET" /> <application ... android:networkSecurityConfig="@xml/network_config" ... /> 이 설정을 하게 되면 xml 파일을 하나 추가해 주면 됩니다.  network_config.xml 이라는 이름으로 말입니다.  <?xml version="1.0" encoding="utf-8"?> <network-security-config> <domain-config cleartextT