기본 콘텐츠로 건너뛰기

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


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

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

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...

안드로이드 앱 만들기 : onBackPressed 가 deprecated 되었다니 ?

원본출처: 티스토리 바로가기 onBackPressed 가 deprecated 되었다? 이제 우리는 구글이 제안하는 안드로이드 13에 타기팅하는 앱을 제출 해야만 하는 시기에 도달하고 있습니다.  구글이 새로운 안드로이드 버전을 배포하기 시작하면서 오래된 안드로이드에 대한 게시를 제한 합니다.    그래서 이번에 API 33 인 안드로이드 13에 타겟팅 하는 앱을 작성해 보았습니다. 그러다 만난 몇 가지 사용 제한이 되는 것들에 대한 정리를 해 두고자 합니다.    onBackPressed는 사용자가 뒤로 가기 버튼을 클릭하는 경우 제어를 하기 위해서 사용했던 함수 입니다. MainActivity 에서 최종적으로 뒤로 가기를 클릭 하는 경우 앱을 종료시키는 기능도 사용이 되는 함수였는 데...   안드로이드 13에서는 더 이상 사용할 수 없는 (?)  - 사용은 가능 하나 소스 코드에 중간 줄이 생긴 모양을 보면서 코드를 지속적으로 봐야 합니다.    onBackPressed 어떻게 해소를 하면 될까요?   CallBack을 하나 만들어 봅니다. private val callback = object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { doCloseApps() } } 다른 건 없고 이런 모양으로 callback 함수를 하나 만들어 둡니다.  그러고 onCreate 에서 이 callback 이 호출 되도록 한 줄 넣어 주는 것으로 그 코딩은 마무리 됩니다.    @RequiresApi(Build.VERSION_CODES.TIRAMISU) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(sav...