REALM: java.lang.IllegalStateException: Async query cannot be created on current thread. Realm cannot be automatically updated on a thread without a looper.

There is very simple example:

realm.executeTransaction(realm1 -> {
    Foo foo = realm1.createObject(Foo.class);
    foo.value = 100;
});
RealmResults<Foo> realmResults = realm.where(Foo.class).findAllAsync();

I stored a value to DB and read it asynchronously. Even though it seems no problems, if I run it on android instrumentation test I can see this error message:

java.lang.IllegalStateException: Async query cannot be created on current thread. Realm cannot be automatically updated on a thread without a looper.

Actually this situation is written on the document Realm (https://realm.io/docs/java/latest/#queries). I need Looper to use async query and unfortunately main thread in android instrumentation test does not have Looper. I found out a solution like below, which is not so clean:

HandlerThread thread = new HandlerThread("test");
thread.start();

Handler handler = new Handler(thread.getLooper());
CountDownLatch latch = new CountDownLatch(1);
handler.post(() -> {
    Realm realm = Realm.getInstance(realmConfiguration);
    realm.executeTransaction(realm1 -> {
        Foo foo = realm1.createObject(Foo.class);
        foo.value = 100;
    });
    RealmResults<Foo> realmResults = realm.where(Foo.class).findAllAsync();
    //assertThat(realmResults)...;
    latch.countDown();
});
latch.await();

Additionally, It is worth to see RunInLooperThread (https://github.com/realm/realm-java/blob/master/realm/realm-library/src/androidTest/java/io/realm/rule/RunInLooperThread.java). It seems same functionality with above code, and easy to use as a rule (https://github.com/realm/realm-java/blob/master/realm/realm-library/src/androidTest/java/io/realm/RxJavaTests.java).

REALM: java.lang.IllegalStateException: Async query cannot be created on current thread. Realm cannot be automatically updated on a thread without a looper.

Realm: javasisst.CannotCompileException: [source error]

realm을 사용해 보려고 디펜던시 추가하고 모델 추가해서 DB에 넣는 간단한 테스트를 돌리는데, 난데없이 이런 에러가 발생하면서 빌드가 안 된다.

javasisst.CannotCompileException: realmSet$AAA(TYPE) not found in BBB

관련 링크는 아래와 같다.

https://github.com/realm/realm-java/issues/2936

https://github.com/realm/realm-java/pull/3030

요약하면 RealmObject를 상속받은 모델의 필드에 직접 접근하면 문제가 생긴다. 구글의 빌드툴 버그이며, 수정을 요청해 놓은 상태이다. 지금은 getter/setter를 사용하면 해결된다.

Realm: javasisst.CannotCompileException: [source error]

Lightweight-Stream-API added Comparator backport

LSA has not provided Comparator backport so I could not use reversed(), thenComparing(), etc. As of 1.1.6, it started to provide ComparatorCompat.

v1.1.6

aNNiMON released this 21 days ago · 15 commits to master since this release

  • Added Stream.withoutNulls()Stream.nullsOnly() and Predicate.Util.notNull(). Thanks to @IlyaGulya
  • Added Java 8 Comparator backport. Thanks to @BattleShipPark

Even though I don’t like the name ComparatorCompat, Enjoy it!

Lightweight-Stream-API added Comparator backport

꼬리 재귀 호출 최적화

꼬리 재귀 호출 최적화에 대해 이 링크(http://www.drdobbs.com/jvm/tail-call-optimization-and-java/240167044)를 읽고 도움을 받았다. 최적화가 된다는 사실은 알고 있었는데 어떻게 처리되는지에 대해서는 이번에 알게 되었다.

링크를 보면 아래와 같은 꼬리 재귀 호출 코드가

int func_a(int data) {
    data = do_this(data);
    return do_that(data);
}

아래와 같은 어셈블리어를 생성한다는 것을 알 수 있다

...         ! executing inside func_a()
push EIP    ! push current instruction pointer on stack
push data   ! push variable 'data' on the stack
jmp do_this ! call do_this() by jumping to its address
...         ! executing inside do_this()
push EIP    ! push current instruction pointer on stack
push data   ! push variable 'data' on the stack
jmp do_that ! call do_that() by jumping to its address
...         ! executing inside do_that()
pop data    ! prepare to return value of 'data'
pop EIP     ! return to do_this()
pop data    ! prepare to return value of 'data'
pop EIP     ! return to func_a()
pop data    ! prepare to return value of 'data'
pop EIP     ! return to func_a() caller
...

여기서 최적화를 하게 되면 아래처럼 몇 가지 명령어가 제거된다

...         ! executing inside func_a()
push EIP    ! push current instruction pointer on stack
push data   ! push variable ‘data’ on the stack
jmp do_this ! call do_this() by jumping to its address
...         ! executing inside do_this()
push EIP    ! push current instruction pointer on stack
push data   ! push variable ‘data’ on the stack
jmp do_that ! call do_that() by jumping to its address
...         ! executing inside do_that()
pop data    ! prepare to return value of ‘data’
pop EIP     ! return to do_this()
pop data    ! prepare to return value of ‘data’
pop EIP     ! return to func_a() caller
pop data    ! prepare to return value of ‘data’
pop EIP     ! return to func_a() caller
...

함수 호출로 처리하기 위해 IP와 인자를 스택에 넣고 빼는 작업이 필요한데, 이게 빠지면서 jmp do_that만 실행하는 것이다. 즉 스택 프레임을 계속 만들지 않고 현재 스택 프레임을 그대로 사용하게 된다.

그럼 우리에게 익숙한 팩토리얼 예제로 확인해 보면,

int Factorial(int n)
{
    if (n == 1) return 1;
    return n * Factorial(n-1);
}

Factorial()을 다시 호출하지 않고 jmp만 해버리면 루프와 다를게 없어진다. 즉 아래와 같이 처리되는 것이다.

int FactorialTail(int n)
{
    int acc = 1;
    do
    {
        if (n == 1) return;
        acc = acc * n;
        n = n - 1;
    } while (true);
    return acc;
}

그런데 충격적인 것은, 자바는 이런 꼬리 호출 최적화를 지원하지 않는다는 것이다!!

꼬리 재귀 호출 최적화

OutOfMemoryError in GridLayoutManager

I was making sample app using GridLayoutManager, but I encountered exception below:

java.lang.OutOfMemoryError: Failed to allocate a 8525185040 byte allocation with 4194304 free bytes and 252MB until OOM
        at android.support.v7.widget.GridLayoutManager.calculateItemBorders(GridLayoutManager.java:322)

What did I do? I was confused.. and I found out code below:

recyclerView.setLayoutManager(new GridLayoutManager(this, R.integer.span_count));

I used integer resource because I wanted to use different span_count in case of landscape mode. However constructor of GridLayoutManager needs integer value, not integer resource id. I put very large integer of resource id as span count, which made OOM. Proper code is like:

recyclerView.setLayoutManager(new GridLayoutManager(this, getResources().getInteger(R.integer.span_count)));
OutOfMemoryError in GridLayoutManager

Added another resource directory in Gradle

Adding new features to the existing project required extra drawable and layout resources, which made me interested in creating another resource directory. I knew that I could separate the resource directory with Gradle, but I didn’t try it before.

What I want to do is to add another directory for new resources like this:

app
    src
        main
            res
                drawable
                layout
                ...
            res2
                drawable
                layout
                ...

I added below to build.gradle like in developer site (https://developer.android.com/studio/write/add-resources.html),

sourceSets {
    main {
        res.srcDirs = ['res', 'res2']
    }
}

However, NOT WORKS! Resource files are not processed!

I guessed what the problem was, I changed like below. IT WORKS.

sourceSets {
    main {
        res.srcDirs = ['src/main/res', 'src/main/res2']
    }
}
Added another resource directory in Gradle

그레이들에서 리소스 디렉토리를 추가해 보았다

기존 프로젝트에 새로운 기능을 추가하면서 drawable과 layout 리소스를 추가해야 했는데, 갑자기 호기심이 생겨서 리소스 디렉토리를 하나 더 만들어서 추가해 보았다. 그레이들을 사용하면 리소스 디렉토리를 분리할 수 있다는 사실은 알았는데 적용해 보지 못하다가 처음 시도해 봤다.

만들려는 구조는 이런 식으로 리소스를 위한 하나의 큰 디렉토리를 추가하려는건데,

app
    src
        main
            res
                drawable
                layout
                ...
            res2
                drawable
                layout
                ...

개발자 페이지(https://developer.android.com/studio/write/add-resources.html)에 있는 것처럼 아래와 같이 build.gradle에 추가하니,

sourceSets {
    main {
        res.srcDirs = ['res', 'res2']
    }
}

안된다! 리소스 파일이 제대로 인식되지 않는다!

그래서 뭐가 문제인지 생각해보다 혹시나 하고 아래와 같이 바꿔보니, 동작한다

sourceSets {
    main {
        res.srcDirs = ['src/main/res', 'src/main/res2']
    }
}
그레이들에서 리소스 디렉토리를 추가해 보았다