2026/02/15

오늘의 이야기










 View rootView = LayoutInflater.from(context).inflate(R.layout.row_timeline, null, false);
RecyclerView.LayoutParams lp = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
rootView.setLayoutParams(lp);
return new TimeLineViewHolder(rootView, viewType);

앱을 구현하다 보면 리스트를 목록 형태로 보여 주어야 하는 경우가 왕왕 발생 하게 된다.  이런 경우 주로 사용하는것이 예전에는 listview 을 많이 사용했는데, 이제는 recycleview 을 권장하는 시기가 되어 가는 것 같다.   나만의 생각일까 ?


뭐 암튼 recycleview 을 이용한 앱을 구현하는 동안 혼돈이 되었던 부분에 대한 기술을 해 두고자 한다.  먼저 recycleview 을 화면에 넣고 실행 했더니만...  layout 의 match_parent 가 적용 되지 않는다.  왜 ???


https://stackoverflow.com/questions/30691150/match-parent-width-does-not-work-in-recyclerview



 


match_parent width does not work in RecyclerView


My RecyclerView and item has match_parent width but the result is :


stackoverflow.com




잘 알 수는 없는 노릇이기는 하나,  아래 코드를 유의해서 보시길...


 View rootView = LayoutInflater.from(context).inflate(R.layout.row_timeline, null, false);
RecyclerView.LayoutParams lp = new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
rootView.setLayoutParams(lp);
return new TimeLineViewHolder(rootView, viewType);

recycleview 에 setLayoutParams 을 이용해서 파라미터를 전달하는 방식으로 witdh 을 match_parent 을 적용하였다.  그러면 끝.   


다음은, adapter 까지 잘 구현했나 싶은데...  화면에 아무것도 나오지 않는다.


https://recipes4dev.tistory.com/154



 


안드로이드 리사이클러뷰 기본 사용법. (Android RecyclerView)


1. 안드로이드 리사이클러뷰(RecyclerView) 리사이클러뷰(RecyclerView)는, "많은 수의 데이터 집합을, 제한된 영역 내에서 유연하게(flexible) 표시할 수 있도록 만들어주는 위젯"입니다. [안드로이드 개발


recipes4dev.tistory.com




 


listview 을 사용할 때는 고민하지 않았던 문제인데,  


        RecyclerView recyclerView = findViewById(R.id.recycler1) ;
recyclerView.setLayoutManager(new LinearLayoutManager(this)) ;

layoutManager 는 왜 지정해야 하는 가? 하는 의문이 들기는 하지만, layoutManager 설정이 되어 있지 않으면,  화면에 표시가 되지 않는다.  java 에서 지정할 때는 위의 코드처럼 구현 하면 되고, layout 에서 지정할 수도 있으니 참고하시길.


listview 에서는 이름처럼 list 모양으로만 view 을 구현할 수 있지만, recycleview 의 경우는 layoutmanager 을 이용해서 다양한 모양의 view 을 구성해 줄 수 있기 때문에 layoutmanager 을 설정해 주어야 했다. 


 


https://developer.android.com/guide/topics/ui/layout/recyclerview-custom



 


RecyclerView 맞춤설정  |  Android 개발자  |  Android Developers


RecyclerView의 고급 맞춤설정 옵션


developer.android.com




 


또한 아래 예제에서 보여 주고 있는 것 처럼 adapter 을 구현하는 소스의 모양도 조금 다르니  구조를 보고 이해를 해 두는 것이 좋을 것 같다.  listview 을 활용하는 경우의 adapter 구현을 모양과 비교를 해 보면 거의 비슷해 보이기는 하나,  ViewHolder 을 기본으로 구현해 주고 있으니 잘 활용할 수 있으면 좋을 것 같다.  ViewHolder 을 구현하는 이유는 자원 재활용에 도움이 된다고 했던 것으로 본 기억이 있다.  list 가 스크롤 되면서 화면 밖으로 넘어가면 없어지고 그 자리를 다른 데이터로 채우는 모양으로 자원을 재활용 한다는 설명을 어딘가에서 보았던 기억이 난다.


public class SimpleTextAdapter extends RecyclerView.Adapter<SimpleTextAdapter.ViewHolder> {

private ArrayList<String> mData = null ;

// 아이템 뷰를 저장하는 뷰홀더 클래스.
public class ViewHolder extends RecyclerView.ViewHolder {
TextView textView1 ;

ViewHolder(View itemView) {
super(itemView) ;

// 뷰 객체에 대한 참조. (hold strong reference)
textView1 = itemView.findViewById(R.id.text1) ;
}
}

// 생성자에서 데이터 리스트 객체를 전달받음.
public SimpleTextAdapter(ArrayList<String> list) {
mData = list ;
}

// onCreateViewHolder() - 아이템 뷰를 위한 뷰홀더 객체 생성하여 리턴.
@Override
public SimpleTextAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext() ;
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) ;

View view = inflater.inflate(R.layout.recyclerview_item, parent, false) ;
SimpleTextAdapter.ViewHolder vh = new SimpleTextAdapter.ViewHolder(view) ;

return vh ;
}

// onBindViewHolder() - position에 해당하는 데이터를 뷰홀더의 아이템뷰에 표시.
@Override
public void onBindViewHolder(SimpleTextAdapter.ViewHolder holder, int position) {
String text = mData.get(position) ;
holder.textView1.setText(text) ;
}

// getItemCount() - 전체 데이터 갯수 리턴.
@Override
public int getItemCount() {
return mData.size() ;
}
}

 


이번에는 listview 을 지원하는 adapter 의 모양은 아래와 같다.


import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import org.techtown.tab.databinding.ItemChattingListBinding;
import org.techtown.tab.model.ChatRooms;

import java.util.ArrayList;

public class ChatRoomAdapter extends BaseAdapter {

ItemChattingListBinding binding ;
ArrayList<ChatRooms> chattingListArrayList ;
Context context ;
LayoutInflater inflater;
int nListCnt ;

public ChatRoomAdapter(ArrayList<ChatRooms> oData, Context context) {
this.chattingListArrayList = oData ;
this.context = context ;
this.inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

@Override
public int getCount() {
return chattingListArrayList.size();
}

@Override
public ChatRooms getItem(int position) {
return chattingListArrayList.get(position);
}

@Override
public long getItemId(int position) {
return position;
}

public void updateReceiptsList(ArrayList<ChatRooms> _oData) {
chattingListArrayList = _oData;
nListCnt = chattingListArrayList.size(); // 배열 사이즈 다시 확인
this.notifyDataSetChanged(); // 그냥 여기서 하자
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
binding = ItemChattingListBinding.inflate(inflater);
CustomViewHolder holder ;
if (convertView == null) {
convertView = binding.getRoot();
holder = new CustomViewHolder();
holder.board = binding.boardingLocation2 ;
holder.dest = binding.destination2;
holder.chatMember = binding.boardingPeopleNum2;
convertView.setTag(holder);
} else {
holder = (CustomViewHolder) convertView.getTag();
}
String strDest = chattingListArrayList.get(position).getDestination2() ;
holder.board.setText(chattingListArrayList.get(position).getBoardingLocation2());
holder.dest.setText(strDest);
holder.chatMember.setText("" + chattingListArrayList.get(position).getBoardingPeopleNum2());
return convertView;
}

private class CustomViewHolder {
TextView dest ;
TextView board ;
TextView chatMember ;
}
}

listview 을 이용하는 경우에도 viewHolder 을 사용하여 데이터를 저장하는 것이 listview 을 상하 스크롤를 하더라도 데이터가 없어지는 현상을 방지할 수 있었다.


오늘도 즐~





반응형























오늘의 이야기



#스치니1000프로젝트 #재미 #행운기원 #Compose #Firebase

🎯 야 너 토요일마다 로또 확인하냐?
나도 맨날 "혹시나~" 하면서 봤거든 ㅋㅋ

근데 이제는 그냥 안 해
AI한테 맡겼어 🤖✨

그것도 구글 Gemini로다가!

그래서 앱 하나 만들었지
👉 "로또 예상번호 by Gemini" 🎱

AI가 분석해서 번호 딱! 뽑아줌
그냥 보고 참고만 하면 됨

재미로 해도 좋고…
혹시 모르는 거잖아? 😏


https://play.google.com/store/apps/details?id=com.billcorea.gptlotto1127




오늘의 이야기




Button crashButton = new Button(this);
crashButton.setText("Test Crash");
crashButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
throw new RuntimeException("Test Crash"); // Force a crash
}
});

addContentView(crashButton, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));

이건 뭐할 떄 쓰면 좋은가 하는 생각이 든다.  앱을 만들고 나서 누군가에게 전달을 했는데, 그 앱에서 에러가 난다고 한다. 흐미... 내가 테스트 할 때는 문제가 없었던 것 같은데 ...


그래서 이런 저런 방법을 찾아보던 중에 오호라... 이런 것도 있네... 각설하고... 일단 개발자 가이드를 읽어보자. 불행하게도 지금(2021.08.07일)까지는 한글로 된 문서가 안드로이드에 적합하게 되어 있지 않은 것 같다. 어쩔 수 없어 영문 사이트를 보면서 따라하기...(크롬의 자동번역기능을 이용해서)


https://firebase.google.com/docs/crashlytics/get-started?hl=en&platform=android 



 


Firebase Crashlytics 시작하기


iOS Android Unity 이 빠른 시작에서는 Firebase Crashlytics SDK를 사용해 앱에 Firebase Crashlytics를 설정하여 Firebase Console에서 포괄적인 비정상 종료 보고서를 확인하는 방법을 설명합니다. 시작하기 전에 아


firebase.google.com




https://firebase.google.com/docs/crashlytics/get-started?platform=Android 



 


Firebase Crashlytics 시작하기


iOS Android Unity 이 빠른 시작에서는 Firebase Crashlytics SDK를 사용해 앱에 Firebase Crashlytics를 설정하여 Firebase Console에서 포괄적인 비정상 종료 보고서를 확인하는 방법을 설명합니다. 시작하기 전에 아


firebase.google.com




내용은 같은 내용이지만, 아래 링크는 안드로이드에 대한 설며이 없고, 위에 링크는 설명은 있지만, 영문 페이지이고, 한글은 지원 하지 않는다. 아직 한글 사용자가 많지 않아서 인지... 흠흠흠...


뭐 하여간 크롬이 지원하는 자동번역기능을 이용해서 살펴보면...




위 그림 처럼 firebase console 에서 Crashlytics 을 들어가 보면 모래시계가 나오고 계속해서 기다린다고 되어 있다.  그래서 설명을 읽고 Crashlytics 을 활성화 하고  그 다음 할일은 gradle 을 수정하는 것이다...


먼저 project 의 gradle 파일에 아래 classpath 을 추가한다.


    dependencies {
...

// Add the Crashlytics Gradle plugin
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.7.1'
}
}

 다음은 apps 의 gradle 파일에 추가한다.


apply plugin: 'com.google.firebase.crashlytics'

...


dependencies {
// Import the BoM for the Firebase platform
implementation platform('com.google.firebase:firebase-bom:28.3.0')

// Declare the dependencies for the Crashlytics and Analytics libraries
// When using the BoM, you don't specify versions in Firebase library dependencies
implementation 'com.google.firebase:firebase-crashlytics'
implementation 'com.google.firebase:firebase-analytics'
}

 


그리고 나서 gradle 파일에서 sync 을 눌러서 필요한 파일들을 준비하고 나면 그다음은 마지막으로 MainActivity 에 아래 코드를 넣고 실행을 한번 하는 것이다. 


Button crashButton = new Button(this);
crashButton.setText("Test Crash");
crashButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
throw new RuntimeException("Test Crash"); // Force a crash
}
});

addContentView(crashButton, new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));

이 코드는 고민할 것도 없다.  MainActivity 의 onCreate 에 넣고 실행하면 화면에 내가 design 하지 않은 버튼이 하나 나온다. Test Crash 라고 ... 그럼 그냥 한번 눌러주면 Firebase 의 console 화면이 변한다.. 아래 그림 처럼




 


그러면 이제 준비가 끝난 것이다.  그럼 이제 MainActivity 에 넣어던 위에 Test Crase 버튼은 필요가 없다. comment 처리를 하고 내가 만든 앱을 실행해 보는 것이다. 그러면 혹시나 모르는 오류가 발생했을 떄, console 에서 리스트를 확인해 볼 수 있다.  원격지에 있는 사람이 사용하다가 오류를 발생시키더라도 그의 폰에서 로그를 받아올 필요가 없어지는 것이다.


오호~ 이제 debug 는 끝났다. 





반응형





























오늘의 이야기










 


https://pixabay.com/ko/photos/%EA%BD%83-%EB%93%A4%EA%BD%83-%EA%B0%80%EC%9D%84-%EC%8B%9D%EB%AC%BC-%EC%95%BC%EC%83%9D%ED%99%94-3729845/



인터넷 펌.


코스모스가 피는 시기는 가을인가 ?


날이 더워 어서 왔으면 하는 바램은 나만인가 ?


시간이 가는 것도 그닥 기쁜(?)일은 아니기는 하지만.


코시대 이제 그만 물러나길 바래봄...





반응형























오늘의 이야기


#스하리1000명프로젝트

스치니들!
내가 만든 이 앱은, 내 폰에 오는 알림 중에서 중요한 키워드가 있는 경우
등록해둔 친구에게 자동으로 전달해주는 앱이야 📲

예를 들어, 카드 결제 알림을 와이프나 자녀에게 보내주거나
이번 달 지출을 달력처럼 확인할 수도 있어!

앱을 함께 쓰려면 친구도 설치 & 로그인해줘야 해.
그래야 친구 목록에서 서로 선택할 수 있으니까~
서로 써보고 불편한 점 있으면 알려줘 🙏

👉 https://play.google.com/store/apps/details?id=com.nari.notify2kakao





오늘의 이야기






귀요미...
어부바...
가득 가득 채워주기 바래





반응형





























오늘의 이야기




앞에 이야기를 보고 준비를 잘 했다면 이제 하나씩 만들어 보자.


카카오 지도 준비는 되었으니, 이제 카카오 개발자 페이지에서 주변 정보를 수집할 방법에 대하여 생각해 보자, 구글에서도 place API 을 지원하고 있으나, 사용에 부담이 되는 것은 아무래도 간혹 나오는 영문 데이터 떄문이다, 아직은 한국적인 느낌이 들지 않는다.


    <uses-permission android:name="android.permission.INTERNET" />

먼저 manifasts 파일에 인터넷 사용을 위한 권한등록을 한다. 데이터는 retrofit API을 이용해서 받아올 꺼니까


    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'

다음은 gradle 파일에 retrofit 사용을 위해서 implementation 을 선언한다. 나는 데이터를 json 방식으로 받아서 처리를 할 것이라서 simplexml 을 없어도 되나. 데이터를 혹시나 xml 구조로 받아야 하는 경우가 있을 때는 기술할 필요가 있다.




이제 카카오개발자 페이지에서 새로 앱을 만들기 등록을 하고, API 키를 받는다...(저 그림의 키는 일부이니 붙여넣기를 해도 소용이 없을 듯...)




그리고 플랫폼에 android 앱에 대한 정보를 하나 등록을 해야 하고, 키 해시는 앱을 실행해서 나오는 키 해시 값을 등록해 주어야 카카오 지도를 활용할 수 있다.


    public PackageInfo getPackageInfo(final Context context, int flag) {
try {
return context.getPackageManager().getPackageInfo(context.getPackageName(), flag);
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, "Unable to get PackageInfo", e);
}
return null;
}

public String getKeyHash(Context context) {
PackageInfo packageInfo = getPackageInfo(context, PackageManager.GET_SIGNATURES);
if (packageInfo == null)
return null;

for (Signature signature : packageInfo.signatures) {
try {
MessageDigest md = MessageDigest.getInstance("SHA");
md.update(signature.toByteArray());
return Base64.encodeToString(md.digest(), Base64.NO_WRAP);
} catch (NoSuchAlgorithmException e) {
Log.e("getKeyHash", "Unable to get MessageDigest. signature=" + signature, e);
}
}
return null;
}

구글링을 해보면 다 나오겠지만, 위와 같이 코드를 넣어 실행 결과를 받아보면 해시값을 알 수 있다.


이제 주변 정보를 얻기 위해서. 카카오의 로컬 서비스를 호출할 준비를 해 보자.


https://developers.kakao.com/docs/latest/ko/local/dev-guide



 


Kakao Developers


카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.


developers.kakao.com




아직 까지는 (2021.08.현재) rest 호출만 지원하고 있다 하니, 앞에서 준비할 것 처럼 http 통신을 해서 결과를 수집해야 할 것 같다.


내가 사용할 방법은 키워드를 통해서 주변을 찾아 보는 것이다. 




 


이런 내용이 있는 위치 부터 읽어 본다.  그리고 응답을 받아서 관리하기 위해서 아래와 같이 응답 구조체 class 을 만들었다.


package ...

import com.google.gson.annotations.SerializedName;

import java.util.ArrayList;

public class ResponseBean {
@SerializedName("meta")
Meta meta ;
@SerializedName("documents")
ArrayList<Documents> documents ;

public Meta getMeta() {
return meta;
}

public ArrayList<Documents> getDocuments() {
return documents;
}

public void setMeta(Meta meta) {
this.meta = meta;
}

public void setDocuments(ArrayList<Documents> documents) {
this.documents = documents;
}

public class Meta {
@SerializedName("same_name")
SameName sameName ;
@SerializedName("pageable_count")
int pageableCount ;
@SerializedName("total_count")
int totalCount ;
@SerializedName("is_end")
boolean isEnd ;

public SameName getSameName() {
return sameName;
}

public int getPageableCount() {
return pageableCount;
}

public int getTotalCount() {
return totalCount;
}

public boolean isEnd() {
return isEnd;
}

public void setSameName(SameName sameName) {
this.sameName = sameName;
}

public void setPageableCount(int pageableCount) {
this.pageableCount = pageableCount;
}

public void setTotalCount(int totalCount) {
this.totalCount = totalCount;
}

public void setEnd(boolean end) {
isEnd = end;
}
}

public class Documents {
@SerializedName("place_name")
String placeName ;
@SerializedName("distance")
String distance ;
@SerializedName("place_url")
String placeUrl ;
@SerializedName("category_name")
String categoryName ;
@SerializedName("address_name")
String addressName ;
@SerializedName("road_address_name")
String roadAddressName ;
@SerializedName("id")
String id ;
@SerializedName("phone")
String phone;
@SerializedName("category_group_code")
String categoryGroupCode ;
@SerializedName("category_group_name")
String categoryGroupName ;
@SerializedName("x")
String posX ;
@SerializedName("y")
String posY ;

public String getAddressName() {
return addressName;
}

public String getCategoryGroupCode() {
return categoryGroupCode;
}

public String getCategoryGroupName() {
return categoryGroupName;
}

public String getCategoryName() {
return categoryName;
}

public String getDistance() {
return distance;
}

public String getId() {
return id;
}

public String getPhone() {
return phone;
}

public String getPlaceName() {
return placeName;
}

public String getPlaceUrl() {
return placeUrl;
}

public double getPosX() {
return Double.parseDouble(posX);
}

public double getPosY() {
return Double.parseDouble(posY);
}

public String getRoadAddressName() {
return roadAddressName;
}

public void setAddressName(String addressName) {
this.addressName = addressName;
}

public void setCategoryGroupCode(String categoryGroupCode) {
this.categoryGroupCode = categoryGroupCode;
}

public void setCategoryGroupName(String categoryGroupName) {
this.categoryGroupName = categoryGroupName;
}

public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}

public void setDistance(String distance) {
this.distance = distance;
}

public void setId(String id) {
this.id = id;
}

public void setPhone(String phone) {
this.phone = phone;
}

public void setPlaceName(String placeName) {
this.placeName = placeName;
}

public void setPlaceUrl(String placeUrl) {
this.placeUrl = placeUrl;
}

public void setPosX(String posX) {
this.posX = posX;
}

public void setPosY(String posY) {
this.posY = posY;
}

public void setRoadAddressName(String roadAddressName) {
this.roadAddressName = roadAddressName;
}
}

public class SameName {
@SerializedName("region")
String[] region ;
@SerializedName("keyword")
String keyWord ;
@SerializedName("selected_region")
String selectedRegion;

public String getSelectedRegion() {
return selectedRegion;
}

public String getKeyWord() {
return keyWord;
}

public String[] getRegion() {
return region;
}

public void setRegion(String[] region) {
this.region = region;
}

public void setKeyWord(String keyWord) {
this.keyWord = keyWord;
}

public void setSelectedRegion(String selectedRegion) {
this.selectedRegion = selectedRegion;
}
}
}

부분 쪼개서 class 로 나누는 것도 좋기는 하나, 뭐 그러나 저러나 비슷한 것 같아서... 그냥 파일 하나에 다 담아서 정리를 했다. 그 다음은 이제 저 class 을 이용해서 호출하는 api 을 하나 만들어 보자 (2021.08.21 아래 소스 일부 수정)


package ...

import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Headers;
import retrofit2.http.Query;

/**
* http 호출을 위한 API
* 앱 등록 및 사용자 설정 필수 category
*
* query String 검색을 원하는 질의어 O
* category_group_code String 카테고리 그룹 코드 * 결과를 카테고리로 필터링을 원하는 경우 사용 X
* x String 중심 좌표의 X값 혹은 longitude * 특정 지역을 중심으로 검색하려고 할 경우 radius와 함께 사용 가능 X
* y String 중심 좌표의 Y값 혹은 latitude * 특정 지역을 중심으로 검색하려고 할 경우 radius와 함께 사용 가능 X
* radius Integer 중심 좌표부터의 반경거리. 특정 지역을 중심으로 검색하려고 할 경우 중심좌표로 쓰일 x,y와 함께 사용 * 단위 meter, 0~20000 사이의 값 X
* rect String 사각형 범위내에서 제한 검색을 위한 좌표. 지도 화면 내 검색시 등 제한 검색에서 사용 가능 * 좌측 X 좌표,좌측 Y 좌표, 우측 X 좌표, 우측 Y 좌표 형식 X
* page Integer 결과 페이지 번호 * 1~45 사이의 값 (기본값: 1) X
* size Integer 한 페이지에 보여질 문서의 개수 * 1~15 사이의 값 (기본값: 15) X
* sort String 결과 정렬 순서, distance 정렬을 원할 때는 기준 좌표로 쓰일 x, y와 함께 사용 * distance 또는 accuracy (기본값: accuracy) X
*
* category_group_code String 카테고리 코드 O
* x String 중심 좌표의 X값 혹은 longitude * 특정 지역을 중심으로 검색하려고 할 경우 radius와 함께 사용 가능. (x,y,radius) 또는 rect 필수
* y String 중심 좌표의 Y값 혹은 latitude * 특정 지역을 중심으로 검색하려고 할 경우 radius와 함께 사용 가능. (x,y,radius) 또는 rect 필수
* radius Integer 중심 좌표부터의 반경거리. 특정 지역을 중심으로 검색하려고 할 경우 중심좌표로 쓰일 x,y와 함께 사용. 단위 meter, 0~20000 사이의 값 (x,y,radius) 또는 rect 필수
* rect String 사각형 범위내에서 제한 검색을 위한 좌표 * 지도 화면 내 검색시 등 제한 검색에서 사용 가능 * 좌측 X 좌표, 좌측 Y 좌표, 우측 X 좌표, 우측 Y 좌표 형식 * x, y, radius 또는 rect 필수 X
* page Integer 결과 페이지 번호 * 1~45 사이의 값 (기본값: 1) X
* size Integer 한 페이지에 보여질 문서의 개수 * 1~15 사이의 값 (기본값: 15) X
* sort String 결과 정렬 순서, distance 정렬을 원할 때는 기준좌표로 쓰일 x, y 파라미터 필요 * distance 또는 accuracy (기본값: accuracy) X
*/
public interface RetrofitApi {
@Headers("Authorization:KakaoAK 647a2bd........0b9d8")
@GET("/v2/local/search/keyword.json")
Call<ResponseBean> getKeywordData (@Query(value="query", encoded = true) String strAddr,
@Query("x") double x,
@Query("y") double y,
@Query("radius") int radius,
@Query("page") int page
);

@Headers("Authorization:KakaoAK 647a2........970b9d8")
@GET("/v2/local/search/category.json")
@GET("/v2/local/search/category.json")
Call<ResponseBean> getCategoryData (@Query("category_group_code") String categoryCode,
@Query("x") double x,
@Query("y") double y,
@Query("radius") int radius,
@Query("rect") String rect,
@Query("page") int page);
);
}

여기서 ResponseBean 은 위에서 말한 class 이름이고, @Headers 에 들어 있는 문장에 키값은 위에서 받은 rest api 키 이니 참고 하시길...  자 이제 호출해 볼까 ?  아래와 같이 화면의 버튼 event 등에서 getData을 호출하면 된다. 


넘어가는 파라미터는 검색할 때 사용할 strKeyword 그리고 중심이 되는 x, y 좌표(Longitude, Latitude)값, 그리고 반경(radius), 그리고 받아올 페이지의 시작값(iPage) 


페이지의 시작값은 난 무조건 1페이지 분량만 받아서 할 거라서 1로 설정해서 받아왔지만, 주변 검색을 더 하고 싶으면 페이지 번호를 계속 변경해서 받아오면 된다.  그 페이지가 마지막 인지 여부를 응답구조체 중에서 meta is_end 이 값을 보고 알 수 있다.


    public void getData(String strKeyword, double x, double y, int radius, int iPage) {
Log.e(TAG, "(" + x + "," + y + ")" + strKeyword);
service.getKeywordData(strKeyword, x, y, radius, iPage).enqueue(new Callback<ResponseBean>() {
@Override
public void onResponse(Call<ResponseBean> call, Response<ResponseBean> response) {
Log.i(TAG, "code=" + response.code() + "" ) ;
try {
for (ResponseBean.Documents documents : response.body().getDocuments()) {
Log.e(TAG, documents.getPlaceName());
MapPoint mapPoint = MapPoint.mapPointWithGeoCoord(documents.getPosY(), documents.getPosX());
addMarker(mapPoint, documents.getPlaceName());
}
} catch (Exception e) {
e.printStackTrace();
}
}

@Override
public void onFailure(Call<ResponseBean> call, Throwable t) {

Log.e(TAG, t.toString()) ;
t.printStackTrace();

}
});
}

그리고 결과를 받아 왔다면 addMarker 함수를 호출해서 지도에 마커들을 출력하면 끝...


 


다른 것들은 이제 다음에...





반응형





























오늘의 이야기


#스하리1000명프로젝트,
บางครั้งการพูดคุยกับแรงงานต่างด้าวก็ยากใช่ไหม?
ฉันสร้างแอปง่ายๆ ที่ช่วยได้! คุณเขียนเป็นภาษาของคุณ และคนอื่นๆ ก็เห็นเป็นภาษาของพวกเขา
มันแปลอัตโนมัติตามการตั้งค่า
มีประโยชน์มากสำหรับการแชทที่ง่ายดาย ควรดูเมื่อมีโอกาส!
https://play.google.com/store/apps/details?id=com.billcoreatech.multichat416




오늘의 이야기










오늘 부터 앱을 하나 만들꺼다... 생각만 하고 있던... 그래서 하나씩 도전을 해 볼껀데,  잘 할 수 있을까 ???


구현 목적 : 집에 가는 길에 약국에 들러서 약을 하나 사 가야겠다는 생각을 하고 퇴근을 했는데,  땀 삐질 거리며 집으로 와서 보니 으헉~ 집에 오는 길에 있는 약국앞을 그냥 지나쳐 온 것이다.   그래서 준비를 하기 시작 했다.


할려는 방안 : 카카오 개발자 페이지를 활용해서 앱에 지도를 넣고, 주변 검색 기능도 넣는다. 그리고 그걸 이용해서 집에 가는 길에 있는 약국 위치에 기록을 해 두는 것이지 , 여기 근처에 가면 해열제 하나를 꼭 사야 한다고...


생각은 되었으니, 이제 구현을 하나씩 해 보자.


준비물 : 카카오 개발자 페이지 등록, API 키 받기, 통신을 위한 retrofit 에 대한 이해. 그리고 개발툴 (android studio Arctic Fox 2020.3.1 버전 기준 : 2021.07.29쯤 업데이트가 적용 되었다)


먼저 카카오 지도 연동에 대한 이야기는 이제 내용을 참조한다.


https://billcorea.tistory.com/23



 


Kakao 지도 연동...


몇해전에는 카카오 지도를 연동하는 데, 애로 사항을 많이 느꼈다. 카카오의 기술지원은 어디에 있는 것인지 찾을 수 도 없고... 이번에 다시금 도전~ apis.map.kakao.com/android/ 이 페이지는 예나 지금


billcorea.tistory.com




다음 준비를 해야 할 부분은 아무래도 backgroud location 에 대한 준비를 해야 할 것 같다.


android 가 API29 이상으로 넘어가면서 background location 에 대한 권한에 제한이 많아지는 것 같다.


https://developer.android.com/training/location/permissions?hl=ko 



 


위치 정보 액세스 권한 요청  |  Android 개발자  |  Android Developers


사용자 개인 정보를 보호하려면 위치 서비스를 사용하는 앱에서 위치 정보 액세스 권한을 요청해야 합니다. 위치 정보 액세스 권한을 요청할 때는 다른 런타임 권한을 요청할 때와 동일한 권장


developer.android.com




 


꼭 읽어보고 가급적이면 github 에 올라와 있는 예제도 살펴볼 필요가 있을 것 같다.


https://github.com/android/location-samples/tree/main/Geofencing



 


GitHub - android/location-samples: Multiple samples showing the best practices in location APIs on Android.


Multiple samples showing the best practices in location APIs on Android. - GitHub - android/location-samples: Multiple samples showing the best practices in location APIs on Android.


github.com




 


여하튼 여기 까지가 내용들을 잘 살펴 보았다면, 준비는 끝일 것 같다.


이제 작성하는 건 다른 글에서...





반응형























오늘의 이야기




android studio 에서 gradle build 을 하는 동안 서버 인증서 때문에 오류가 발생하는 경우를 경험하게 되었다. 


이런 경우 다음과 같은 해소 방안을 찾을 수 있을 것 같다.


Caused by: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

at sun.security.validator.PKIXValidator.doBuild(Unknown Source)

at sun.security.validator.PKIXValidator.engineValidate(Unknown Source)

at sun.security.validator.Validator.validate(Unknown Source)

at sun.security.ssl.X509TrustManagerImpl.validate(Unknown Source)

at sun.security.ssl.X509TrustManagerImpl.checkTrusted(Unknown Source)

at sun.security.ssl.X509TrustManagerImpl.checkServerTrusted(Unknown Source)

... 62 common frames omitted

Caused by: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

at sun.security.provider.certpath.SunCertPathBuilder.build(Unknown Source)

at sun.security.provider.certpath.SunCertPathBuilder.engineBuild(Unknown Source)

at java.security.cert.CertPathBuilder.build(Unknown Source)

... 68 common frames omitted



출처: https://yunyun-onon.tistory.com/205 [XY 염색체의 진지한 시선 블로그]

 


이런 오류들을 만나게 되는 경우 


1. 시작 -> 명령 프롬프트(cmd)를 실행한다.



2. cd ${자바 설치 경로}\jre\bin

자바 jre를 설치한 경로 안에 bin폴더로 이동하기 위해 해당 명령어를 실행한다.

(bin폴더 안에 keytool이 있기 때문에 이동하여 실행하고자 한다.)



3. keytool -import -file "${인증서 경로}\인증서명.crt" -keystore "${자바 설치 경로}\jre\lib\security\cacerts" -storepass "changeit"



출처: https://yunyun-onon.tistory.com/205 [XY 염색체의 진지한 시선 블로그]

 


그런데 문제는 내컴퓨터에 서버 인증서 파일이 있는가 하는 것이다.


나의 경우는 회사에서 받은 파일을 이용하여 해소 하기는 했는데,  개인적인 경우라면...


음...





반응형





























오늘의 이야기


#billcorea #운동동아리관리앱
🏸 Schneedle แอปที่สโมสรแบดมินตันต้องมี!
👉 แมทช์เพลย์ – บันทึกคะแนนและค้นหาคู่ต่อสู้ 🎉
เหมาะสำหรับทุกที่ คนเดียว กับเพื่อนฝูง หรือในคลับ! 🤝
ถ้าคุณชอบแบดมินตันลองดูแน่นอน

ไปที่แอป 👉 https://play.google.com/store/apps/details?id=com.billcorea.matchplay




오늘의 이야기

View rootView = LayoutInflater.from(context).inflate(R.layout.row_timeline, null, false); RecyclerView.LayoutParams lp = new Recycl...