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();

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

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를 사용하더라도 데이터를 갱신 할 수 있게 된다.



+ Recent posts