마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

Failure delivering result ResultInfo{who=null, request=1, result=-1, data=Intent 에러 질문

0 추천

* 앱은 메인액티비티에 A 프래그먼트가 올려져 있고, A 프래그먼트에 리사이클러뷰로 데이터베이스의 내용이 리스트 형식으로 보이게 제작하고자 합니다. 데이터베이스에 저장할 글을 작성할 때는 B 액티비티에 들어가고, B액티비티에서 글을 저장하면 B 액티비티를 빠져나가게 되면서 메인 액티비티의 A 프래그먼트가 보이고 그 안에 B액티비티에서 저장한 글이 리스트 형식으로 보이도록 만들고자 하였습니다. *

플로팅 버튼을 누르면 데이터를 입력하는 액티비티가 뜨게 하고

1. 메인 액티비티에서 플로팅 버튼을 누르면 데이터를 입력하는 B 액티비티에 진입하고

2. 데이터를 입력하는 B 액티비티에서 값을 입력하면 백프레스 버튼을 눌렀을 때 메인액티비티에 값이 전달되도록 설정하였습니다.

3. 메인액티비티에선 viewModel을 사용하여 B 액티비티에서 받은 값으로 새로이 만들어진 행을 데이터베이스에 삽입하려고 하였으나, 앱을 실행 후 B 액티비티에서 백프레스 버튼을 눌러 값을 저장하려고 하자 아래의 에러가 발생하였습니다.

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: org.jh.todomemo, PID: 18728
    java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1, result=-1, data=Intent { (has extras) }} to activity {org.jh.todomemo/org.jh.todomemo.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void org.jh.todomemo.ViewModel.writingMemoViewModel.insert(org.jh.todomemo.db.entity.writingMemo)' on a null object reference
        at android.app.ActivityThread.deliverResults(ActivityThread.java:5360)
        at android.app.ActivityThread.handleSendResult(ActivityThread.java:5401)
        at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:51)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2267)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:237)
        at android.app.ActivityThread.main(ActivityThread.java:8167)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:496)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1100)
     Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void org.jh.todomemo.ViewModel.writingMemoViewModel.insert(org.jh.todomemo.db.entity.writingMemo)' on a null object reference
        at org.jh.todomemo.MainActivity.onActivityResult(MainActivity.java:129)
        at android.app.Activity.dispatchActivityResult(Activity.java:8300)
        at android.app.ActivityThread.deliverResults(ActivityThread.java:5353)
        at android.app.ActivityThread.handleSendResult(ActivityThread.java:5401) 
        at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:51) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2267) 
        at android.os.Handler.dispatchMessage(Handler.java:107) 
        at android.os.Looper.loop(Looper.java:237) 
        at android.app.ActivityThread.main(ActivityThread.java:8167) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:496) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1100) 

4. ViewModel과 리사이클러뷰 adapter는 A 프래그먼트에 코드로 작성되어 있습니다.

 

질문: 실행이 잘 될것이라고 생각하여 코드를 구성하였으나 java.lang.RuntimeException 에러가 발생하였습니다. 코드를 어떻게 다시 구성해야 java.lang.RuntimeException 에러 없이 올바르게 작동할 수 있는지 질문드립니다.

안드11 (330 포인트) 님이 2021년 1월 2일 질문

1개의 답변

+1 추천
 
채택된 답변
님의 에러의 원인은 로그에도 나와있듯이 널인 오브젝트의 insert메소드를 호출했기 때문입니다. WritingViewModel이 널인 상태에서 insert를 호출한 모양이네요. 아마 ViewModel 이 제대로 공유가 안된 것 같습니다.  

하나의 액티비티에 프레그먼트를 여러개 사용할 때는 앱의 성능은 향상되지만, 복잡도는 증가합니다. 특히 백스택관리가 많이 복잡해집니다.

개인적으로는 왜 B의 입력값은 메인액티비티에 전달해서 저장하는지 좀 의아합니다. 코드의 복잡도가 증가하고 님의 경우처럼  발생하지 않았어야 할 에러가 발생하고 있으니까요. 저 같으면 처리하는 방식을 바꿀 것 같습니다.
실제 저장하는 동작을 B에서 처리한 후 처리결과만 A에 전달해주고, A는 성공일 경우만 화면을 다시 갱신하는 거죠. 성공이 아닐경우는 저장된 값을, ViewModel에 캐쉬됭 현재 리스트를 다시 복구해줍니다. 제 기억으로는 프레그먼트의 replace를 사용하실 경우는 다른 프레그먼트로 이동한 후 돌아오는 경우 프레그먼트가 초기화됩니다. 이걸 원하는게 아니면 백스택에 add한 후 show/hide로 처리하시면 됩니다.
B에서 저장을 하는 주된 이유는 에러 처리와 플로우의 복잡함을 줄이기 위해서입니다. 먼저, 에러처리의 경우 님의 예처럼, B에서 입력한 데이터를 A에서 처리하다 에러가 발생할 경우, 에러메세지를 보여주기 위해서는 B프레그먼트를 다시 띄워야 합니다. 그래야 사용자가 입력값을 수정하거나 한 후 다시 시도해 볼 수 있으니까요. 그리고 저장을 하는 동작은  화면의 흐름상 A프레그먼의 역할이 아닙니다. 이렇게 분리하면 굳이 ViewModel을 공유할 필요가 없습니다.
저도  MVVM를 주로 사용하고 있는 개인적은 ViewModel을 공유하는 방법은 초기에는 좀 사용했다가 다 리팩토링했습니다. 나중에 가면, 코드를 파악하기가 ViewModel을 화면당 하나씩 쓰는 것보다 훨씬 어렵더군요. 물론 코드가 아주 짧다면 전혀 상관이 없지만, 저같은 경우는 대부분의 화면이 복잡했기 때문에 이 방법은 적합하지 않았습니다. 그리고 ViewModel을 공유한다는 아이디어 자체가 서로의 의존관계를 증가시키기 때문에 분리할 수 있으면 분리하는게 더 깔끔하다고 생각합니다. 님의 경우는 TODO는 저장공간에 저장해서 이 저장소를 공유하면 되기 때문에 굳이 ViewModel 을 공유할 필요가 없다고 보여집니다.
프레그먼트 간에 통신은 여러가지 방법으로 가능합니다. 개발자 사이트에 자세히 나와있구요.
https://developer.android.com/guide/fragments/communicate

저같은 경우는 네비게이션 컴포넌트를 쓰기 때문에  백스택에 saveSate의 liveData를 통해 하고 있습니다.
spark (224,800 포인트) 님이 2021년 1월 2일 답변
안드11님이 2021년 1월 3일 채택됨
...