for-each문을 돌리면서 조건에 맞으면 리스트의 요소를 삭제해주는 부분에서 java.util.ConcurrentModificationException이라며 런타임 에러가 났다.

 

오류난 부분의 코드는 다음과 같다.

이 에러는 딱 보자마자 원인이 짐작이 갔다.

 

예를 들어 처음에 list라는 리스트의 크기가 10이었다고 하자.

그러면 for-each문은 인덱스 0부터 9까지 반복하면서, 즉 list.get(0)부터 list.get(9)까지 반복하면서 일을 처리할 것이다.

그런데 인덱스 4에서 조건이 맞아서 list.get(4)를 삭제하면 이제 리스트의 크기는 9가 되고 마지막 원소는 list.get(8)이 된다. 그런데 list.get(9)를 하려고 하니까 인덱스의 범위를 넘어서면서 문제가 발생하는 것이다.

 

이 문제는 다음과 같은 방법으로 해결이 가능하다.

 

int size = list.size();
for(int i = 0; i < size(); i++)

for-each문이 아닌 for문을 직접 돌리는데 i < list.size()까지가 아니라 size라는 변수를 선언하고 i < size까지 돌린다.

if문에 해당되어 원소를 지운 경우는 i와 size를 하나씩 줄여준다.

int size = list.size();
for(int i = 0; i < size; i++) {
	리스트의 타입 element = list.get(i);
	if(조건) {
    		list.remove(element);
        	size--;
        	i--;
    	}
}

이렇게 말이다.

그러면 에러가 해결된다.

SearchView를 만들어서 OnClickListener를 등록했는데 서치뷰를 클릭해도 onClick 메소드는 호출되지 않았다. 로그캣으로 찍어봐도 아예 호출이 되지 않았다.

 

알고보니 SearchView는 setOnClickListener가 아니라 setSearchClickListener라는 것이 있었다. 이것으로 리스너를 등록했더니 해결됐다.

Retrofit2로 서버와 통신을 하는 부분이 실행이 안 되길래 onFailure 메소드에서 로그캣으로 메시지를 찍어보았다.

그랬더니 JSON forbids NaN and infinities: NaN at line 1 column 6 path $ 이런 에러가 났다.

 

그래서 저 API가 정의되어 있는 서버 부분에서 뭘 리턴하는지 찍어보았다.

그랬더니 sum / count를 리턴하는 메소드인데 sum / count가 NaN이었다.

 

그래서 서버 부분을 다음과 같이 수정해주었다.

이렇게 수정하고 나니 이제 더 이상 NaN을 리턴하지 않고 0.0을 리턴하므로 안드로이드 Retrofit을 사용하는 부분에서 onFailure 메소드로 들어가지 않고 onResponse 메소드로 들어간다.

 

서버 개발과 앱 개발을 모두 내가 하니 이렇게 오류가 생겼을 때 바로 바로 해결할 수 있어서 좋은 것 같다.

오늘도 안드로이드 개발을 하다가

java.lang.IndexOutOfBoundsException:Inconsistency detected. Invalid view holder adapter positionMyViewHolder{152d0dc position=5 id=-1, oldPos=-1, androidx.recyclerview.widget.RecyclerView

 

이런 에러가 났다.

 

SwipeRefreshLayout을 썼는데 당겨서 새로고침을 하고나면 앱이 죽으면서 저 에러가 났다.

 

구글링을 하다가 https://stackoverrun.com/ko/q/12042505

 

RecyclerView 및 java.lang.IndexOutOfBoundsException 잘못된 뷰 홀더 어댑터 positionViewHolder

식료품 품목을 바인딩하는 리사이클 러 뷰가 있습니다. 항목을 추가하면 완벽하게 작동합니다. 그러나 항목을 삭제하려고하면 응용 프로그램이 충돌하고 IndexOutOfBoundsException 오류가 발생합니다

stackoverrun.com

이 글을 발견했다.

 

이렇게 인자로 넘어온 position을 그대로 썼었는데

int safePosition = holder.getAdapterPosition();

이렇게 선언하고

이렇게 safePosition을 쓰니까 해결됐다.

 

분명히 스크롤뷰로 씌우기 전에는 멀쩡했던 뷰가 ScrollView로 감싸고 나니 이렇게 화면이 찌그러지고

이렇게 뷰들이 뒤로 숨어서 보이지 않는 현상이 발생했다. 당연히 ScrollView 안에 자식은 1개였는데 말이다.

 

검색해도 잘 나오지 않았다. 그리고 이 상황을 뭐라고 검색해야 할지도 잘 몰랐다.

 

그러다가 이 글을 발견했다.

https://mrgamza.tistory.com/110

 

[Android]ScrollView가 화면에 가득차지 않을 경우

ScrollView의 height를 match_parent로 지정을 하여도 전부 차지하지 않는 문제가 발생한다. 이런 부분은 본질적으로 보면 ScrollView가 가지고 있는 자식 View의 높이가 그정도로 보이기 때문인데, 다시 말��

mrgamza.tistory.com

 

android:fillViewport="true"

이 속성만 ScrollView의 속성으로 추가해주면

이렇게 다시 멀쩡한 화면으로 되는 것을 볼 수 있다.

build.gradle 파일에 라이브러리를 추가했는데  

AndroidX로 바꾸라는 에러메시지

version 28 (intended for Android Pie and below) is the last version of the legacy support library, so we recommend that you migrate to AndroidX libraries when using Android Q and moving forward. The IDE can help with this: Refactor > Migrate to AndroidX... more... (Ctrl+F1)

 

이런 에러가 뜬다.

 

애초에 프로젝트를 만들 때 androidX로 만들었어야 하는데... 프로젝트를 새로 만들기는 귀찮다면 간단히 androidX로 바꿀 수 있는 방법이 있다.

 

저기 에러 메시지에 나와 있듯이 Refactor>Migrate to androidX...를 누르면 간단히 androidX로 바꿀 수 있다.

Migrate to AndroidX

이걸 누르고 백업하고 싶다면 백업해놓을 위치를 선택하고 Migrate 한다. 그러면 아래에 Find에 뭐라고 뜰 것이다. 그 때 Do Refactor를 누르면 된다. 그러면 몇 초 안에 프로젝트가 androidX로 바뀐다.

java.lang.BootstrapMethodError:Exception from call site #4 bootstrap method okhttp

이 에러때문에 3일 정도를 삽질(?)했다.

구글에 검색해보니 다 모듈 수준의 build.gradle 파일에

compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
}

이 코드를 추가하라는 말 뿐이었다.

그래도 되지 않았다. 똑같이 저 오류가 났다.

정말 스택오버플로우를 비롯해서 중국 사이트까지 가봐도 다 이 얘기 뿐이었다.

어떤 사람은 androidX로 바꿨더니 해결됐다고 해서 다시 프로젝트를 androidx로 만들었다.

그래도 저 오류가 똑같이 났다.

 

그런데 Build>Clean Project를 눌렀더니 이 에러가 나지 않고 해결됐다.

3일동안 난 뭐한걸까... 이번 기회로 교훈을 얻었다. Gradle 파일을 수정한 뒤에는 Clean Project를 해주자.

람다식 안 됨

안드로이드 스튜디오에서 앱을 만드는 도중 이렇게 람다식이 지원이 안 된다고 뜬다면

 

다음과 같이 모듈 수준 gradle 파일에 들어가서

모듈 수준 gradle 파일

다음 코드를 추가한다.

compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}

그러면 이제 오류 없이 람다식을 사용할 수 있다.

안드로이드 스튜디오로 개발을 할 때 로그캣은 디버깅을 하기 매우 유용하다.

하지만 종종 이렇게 LogCat에 필터를 적용해서 검색할 수 있는 창이 안 보일 때가 있다.

다른 사람들도 그런지는 모르겠지만 나는 이런 일이 자주 발생한다. 왜 그런지 이유는 모르겠다. 안드로이드 스튜디오의 버그인 것 같다.

지난 학기 안드로이드 수업 시간에 onStart, onCreate, onResume, onPause 등 생명주기 메소드가 언제 불리는지 알기 위해서 로그캣을 이용해서 실습하는 도중 로그캣 필터가 없어졌던 기억이 난다. 옆 친구들은 다 실습하는데 나만 로그캣 필터가 없어져서 매우 당황스러웠다.

 

그 이후에도 이런 일이 발생해서 혼자 이것저것 눌러보다가 발견한 해결법이다.

오른쪽 아래 EventLog 버튼을 누르면 이렇게 검색창이 다시 멀쩡히 보인다. 그리고 다시 EventLog를 클릭해서 이벤트 로그 창은 없애주면 된다.

InputMethodManager imm;
imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);

이 3줄이면 소프트 키보드를 숨길 수 있다.

 

나는 주로 InputMethodManager 변수를

InputMethodManager imm;

이렇게 전역변수(클래스의 멤버변수)로 선언해놓고

 

onCreate() 메소드 안에서

imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); 

이렇게 초기화 해주고

 

필요한 부분에

imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);

이 코드를 넣어준다.

 

필요한 부분이란 대부분 onClick()메소드 안이다.

EditText에 뭔가를 입력하고 입력을 완료했을 때 누르는 그 버튼의 OnClickListener에 넣어주는 것이 일반적이다.

입력을 다 끝냈으니 이제 키보드가 필요없기 때문이다.

 

그런데 오늘은 onClick()메소드가 아닌 곳에서 키보드를 숨길 필요가 있었다.

 

EditText에 뭔가를 입력하다가 네비게이션 드로어(Navigation Drawer)를 열어야 하는 일이 있을 수도 있다.

그 때 키보드가 사라지지 않는다면

이렇게 드로어가 가려져서 사용하기에도 불편하고 보기에도 좋지 않은 상황이 발생한다.

 

네비게이션 드로어가 있는 액티비티를 만들었다면 MainActivity.java 파일에 이 코드가 있을 것이다.

ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.addDrawerListener(toggle);

drawer.addDrawerListener(toggle); 이라고 한 것을 보니 toggle은 리스너 객체인데 아무리 봐도 이벤트 핸들러 메소드를 추가할 수 있는 곳이 없어 보인다.

 

그냥 이렇게 해주면 된다.

ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close){
            @Override
            public void onDrawerOpened(View drawerView) {
                imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);
            }
        };
drawer.addDrawerListener(toggle);

괄호 뒤에 중괄호를 열어주면 된다. 그러고 나서 alt+insert로 필요한 메소드를 오버라이딩하면 된다.

나는 드로어가 열렸을 때 키보드를 숨기고 싶어서 onDrawerOpened() 메소드 안에

imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);

이 코드 한 줄만 추가해줬다.

그랬더니 드로어를 열면 키보드가 자동으로 숨겨져서 훨씬 보기 좋아졌다.

 

반대로 키보드를 보이게 하고 싶으면

imm.showSoftInput(editText, 0);

이 코드 한 줄을 추가해주면 된다.

+ Recent posts