ViewPager를 처음 본 후 코드량과 아웃풋에 반해서 가지고 있던 앱들의 메인 네비게이션을 모두 Viewpager로 변경을 했는데, 크고 작은 문제들이 계속 나오고 있다.


그 중에서 오늘 발견한 문제는 Fragment 안에 있는 getAcitivity()가 null이 되면서 null exception error를 뱉고 죽는 현상이었다.


Fragment 안에서 아래와 같이 토스트 메시지를 뛰우고 인텐트를 통해 다른 페이지로 이동하는 코드에서 발생했는데 처음보는 현상이라 원인이 Viewpager일거라고 상상도 못했다. 

Toast.makeText(getActivity(), "메시지", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(getActivity(), PhoneNumCheckActivity.class);
startActivity(intent);

getActivity().finish();

구글을 뒤적뒤적하다보니 Fragment가 전환되는 과정에서 activity가 detach 된 것 같다는 의견이 다수였던 것 같다.


Fragment생명주기 상 OnAttach()에서 activity를 연결하는 과정을 통하면 해결된다는 것 같았다. 그런데 어떻게????


아래와 같이 하라는데.....뭘 어떻게 하라는 건지 몰라 살짝 패닉이 왔다.



출처: 

- https://codedump.io/share/ffbRcoGlaWq/1/getactivity-returns-null-in-fragment-function 

- http://stackoverflow.com/questions/6215239/getactivity-returns-null-in-fragment-function

onAttach(Activity activity)


그래서 다시 "viewpager getactivity null"이란 키워드로 집중적으로 찾다보니 아래 페이지를 찾게 됐다.

http://stackoverflow.com/questions/38891906/why-does-getactivity-returns-null-in-fragment-in-viewpager


정확하게 내가 가진 문제를 해결하는 방법은 아니었지만 그래도 코드 중에 힌트가 많이 있어 해결하는데 도움이 많이 됐다.

private Activity activity;


@Override
public void onAttach(Context context) {
super.onAttach(context);

if (context instanceof Activity) {
activity = (Activity) context;
}

}

위에서 볼 수 있는 것처럼 일단 onAttach()에서 activity를 context와 연결해주고, 연결된 acitivity를 아래와 같이 getActivity()처럼 활용하면 잘 작동한다.

Toast.makeText(activity, "전화번호 인증이 되지 않았습니다. 인증페이지로 이동합니다", Toast.LENGTH_SHORT).show();
Intent intent = new Intent(activity, PhoneNumCheckActivity.class);
activity.startActivity(intent);

activity.finish();

제대로 된 해결책인지는 모르지만 암튼 잘된다.

특정 사이트를 긁어오는데 유용한  Beautifulsoup을 써서 사이트를 긁어오는 작업을 하는 중이었다.


해당 사이트의 순위 정보를 긁어와서 리스트에 [{1:키워드1}, {2:키워드2}] 이런 식으로 담아 넣는 작업을 하게 되었다.

그런데 Python 인터프리터는 Dictionary 안에 한글값이 있을 때 유니코드값을 쏟아내다보니 순위 정보가 제대로 담겨있는지 확인하는데 어려움이 있었다.


Dictionary의 값을 확인하는 방법은 ditionary[key값]으로 확인을 해야 하는데 각dictionary별로 키값이 어떻게 되는지 확인하는 방법이 뭐가 있는지 확인하다보니 dictionary.keys() 함수가 있는 것을 발견했다.


list = [{1:키워드1}, {2:키워드2}, {3:키워드3}]

for i in list:

keyList = i.keys()

print i[keyList[0]]


위와 같은 코드를 돌리면 아래와 같은 값이 출력된다.


키워드1

키워드2

키워드3


솔직히 너무 쉬운 코드긴 한데 암튼 잊지 말자는 의미에서...ㅋ

최근 구글의 firebase 실시간 데이터베이스에 흥미가 생겼기 때문에 이것 저것 해보는 중인데, 그 중에서도 Rest API를 python에서 다루는 것을 실험해보기 위해 python firebase 라이브러리를 설치하게 되었다. 


firebase 공식 사이트에서는 아래 두 python 라이브러리를 소개하고 있다.

1. https://github.com/mikexstudios/python-firebase

2. http://ozgur.github.io/python-firebase/


pip를 지원하기 때문에 아래 명령어를 통해 설치를 하고 IDE인 pycharm을 켜고 프로젝트를 만들었다. 


pip install -e git://github.com/mikexstudios/python-firebase.git#egg=python-firebase


그런데 "from firebase import firebase"가 인식이 안되는 현상이 발생했다.

왜그런가 확인을 하다보니 compile하는 python의 버전이 아까 터미널에서 확인한 버전과 다른 것이 확인이 되었다.


데이터 분석을 공부해보기 위해 Canopy 를 설치하다보니 python버전이 2.6/2,7 두 개가 설치 되어 있었다보니 pip로 설치를 하면 default로 설정되어 있는

Canopy 설치 시에 깔려 있던 2.7.11버전에 라이브러리가 설치되어 있었는데, pycharm에는 2.6.9버전이 컴파일하다보니 라이브러리가 없는 것으로 인식된 것이다.


그래서 버전이 다른 python에  pip로 설치하는 방법을 찾아봤는데 제대로 된 답을 얻을 수가 없었다.

결국 github에 있는 소스를 다운 받은 다음 python setup.py install를 통해 직접 설치하여 해결 할 수가 있었다.


혹시라도 삽질하는 사람 있으면 그냥 직접 설치하시는 방법을 권장한다.

GET 방식으로 Request 할 때 브라우져에 그냥 Request주소를 치면 결과값이 잘 리턴되는데, 코드로 Request하면 안되는 경우를 발견하고 고생했던 적이 있다.

몇일 간 사투 끝에 알아낸 것이 한글로 된 Query 를 직접 주소에 넣어보내면 제대로 된 URI로 인식하지 못하기 때문에 한글을 UTF-8로 변경해줘야 했던 것이었는데 브라우져는 그 부분을 그냥 자동으로 알아서 인코딩해서 Request하기 때문에 문제 없이 결과값을 받아냈던 것이었다.


어찌보면 너무나 당연한 것인데 문제를 겪는 당시에는 전혀 예상을 못한 문제라 크게 낭패를 봤던 기억이 난다.


암튼 네이버 오픈 API를 사용하기 위해서는 GET방식의 Request를 해야 한다.


네이버 오픈 API 지역 검색 : https://developers.naver.com/docs/search/local 


예를 들어 네이버 오픈 API의 경우 "https://openapi.naver.com/v1/search/local.xml"주소를 호출하는데 특정 Query를 호출하게 되면 다음과 같은 주소를 요청하게 된다. 물론 인증을 위해 헤더에 클라이언트 키값과 시크릿코드를 넣긴해야 작동하기 때문에 아래 주소를 바로 넣으면 인증되지 않은 요청이라는 결과값이 뜰것이다.


https://openapi.naver.com/v1/search/local.xml?query=서울


그럼에도 불구하고 "서울"이란 단어를 바로 사용할 경우 제대로 된 응답을 얻을 수 없게 된다.


이 경우에는 "서울"을 UTF-8로 인코딩을 해야 한다.


안드로이드에서는 URLEncoder라는 기능을 제공한다. 이전 포스트에서 [네이버 오픈 API] 안드로이드에서 Get Request 하기 (Feat. Asynchronous Http Client for Android)  사용했던 라이브러리를 쓰면 다음과 같이 방식으로 검색결과를 호출할 수 있다.


search_start.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

String query = query_input.getText().toString();

try {

convertQuery = URLEncoder.encode(query, "utf-8");

} catch (UnsupportedEncodingException e) {

e.printStackTrace();

}


final String defaultUrl = "https://openapi.naver.com/v1/search/local.xml?query=";

AsyncHttpClient client = new AsyncHttpClient();

client.addHeader("Content-Type", "application/xml");
client.addHeader("X-Naver-Client-Id", "{클라이언트키}");
client.addHeader("X-Naver-Client-Secret","{시크릿키}");
client.get(defaultUrl + convertQuery , new AsyncHttpResponseHandler() {
@Override
public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {


}

@Override
public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {


}
});




}
});



네이버 오픈 API를 통해서 지역 검색 결과를 불러오는 기능을 구현하려고 했다.


우선 생각해 볼 것이 API를 Call하는 것인데 안드로이드에 있는 기능을 순수하게 사용해서 불러오는 방법보다는 쉽게 쓸수 있는 라이브러리를 쓰기로 결심하고이것저것 찾아봤다. 


그 중에서 이번에 활용한 라이브러리는 Asynchronous Http Client for Android


깃허브: https://github.com/loopj/android-async-http

홈페이지: http://loopj.com/android-async-http/


무엇보다 간단한 사용법 때문에 사용하게 되었는데 다음과 같다.

AsyncHttpClient client = new AsyncHttpClient();

client.get("{URL}" , new AsyncHttpResponseHandler() {
@Override
public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {



}

@Override
public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {


}
});

기존 안드로이드에서 제공하는 방법을 사용하면 코드만 한무더기인데 매우 깔끔하게 끝났다.


그 동안 앱을 만들 때 메인 메뉴 네비게이션은 각각 버튼을 만들고, 해당 버튼을 클릭하면 Fragment가 호출되는 방식으로 앱 화면을 구현했는데, 최근 Firebase sample app 예제 소스를 보던 중 메인메뉴 네비게이션을 Viewpager로 구현하는 예제를 보고 간단한 소스와 Adapter를 쓰는 영리함(?)에 감흥을 받아 기존에 만지고 있던 앱들의 메인 메뉴 네비게이션을 모두 Viewpager로 구현했다.


Viewpager로 네비게이션을 구현하고 나니 다음과 같은 장점이 있었다.


- 네비게이션 버튼 스타일링을 할 필요가 없어졌고, 메뉴 버튼 선택에 따른 UI변경 사항을 구현할 필요가 없어졌다.

- 좌우 슬라이딩을 통해 메뉴 이동이 가능해져 보다 훌륭한 UX를 제공한다.

- 핵심적으로 소스코드가 간결하다.






그런데, Viewpager를 통해 구현할 경우 Fragment를 호출했을 때 내용이 갱신이 되지 않는 현상이 발생했다.

어찌보면 간단한 내용인데 이것 때문에 이틀째 해결책을 고민하고 있었다.


일반으로 구글링을 하다보면 가장 많이 제안해주는 방법은 FragmentPagerAdapter를 사용하지 않고 FragmentStatePagerAdpater를 사용하는 방법이다.


참고 링크:

http://gogorchg.tistory.com/entry/Android-FragmentPagerAdapter-%EA%B0%B1%EC%8B%A0


내가 원했던 것은 페이지가 바뀔 때마다 Fragment 안에 있는 특정 데이터들이 갱신되는 것이었는데 해결이 되지 않았다.

Fragment의 View를 재활용하지 않고 새로 그리는 것 같은데, 데이터베이스에서 데이터가 업데이트 됐는지 한번 더 불러오는 작업은 하지 않았다.


그러던 중 Adapter쪽에 왠지 현재 Fragment가 선택될 때 Event를 감지 하는 부분이 있지 않을까 싶어서 확인해보니 역시 선택된 페이지를 확인하는 기능이 존재 했다. 직접 소스를 보면 다음과 같다.


mViewPager.setAdapter(pageAdapter);
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

}

@Override
public void onPageSelected(int position) {


}

@Override
public void onPageScrollStateChanged(int state) {

}
});

위 소스에서 확인할 수 있는 것과 같이 페이지를 선택하면 OnPageSelected 부분이 현재 페이지가 몇 번째 페이지인지 확인해준다.

이 부분에 Refresh하고 싶은 데이터를 갱신하는 코드를 입력하면 ViewPager를 사용하더라도 데이터를 갱신 할 수 있게 된다.




코스를 바꿨다 경치는 더 좋으나 힘은 두배가 듬 ㅋㅋ



8일차 등반이 점점 빨라지고 있음

오늘은 안개가 많은 날....많이 흐리네


..

오늘은 날씨가 꾸리꾸리


+ Recent posts