square / flow

Name UI states, navigate between them, remember where you've been.
Apache License 2.0
2.79k stars 241 forks source link

java.lang.NullPointerException: Expected to find a PathContext but did not. #90

Closed Lakedaemon closed 8 years ago

Lakedaemon commented 9 years ago

I am in the process to convert on of my project to dagger2 + mortar + flow + kotlin. I mostly copied code from the mortar HelloDagger2 samples, flow samples and kotlin+dagger samples

I manage to display a "Search" screen through flowSupport = FlowDelegate.onCreate(nonConfig, getIntent(), savedInstanceState, parceler, History.single(Search()), this)

Then I click on a button to display a "Build" screen through Flow.get(this@FlowActivity).set(Build()) and I get the exception : java.lang.NullPointerException: Expected to find a PathContext but did not.

I guess that the offending view got inflated from a Context that has not been wrapped in a PathContext though it should have been.

To understand why the exception happened I added a logging line to see if both views where instanciated with a PathContext (they are)

The weird thing is that : without the logging line on the Search view, the exception happens, with the logging line, it doesn't happen... (timing/thread issue ? o.O)

public class SearchView(context: Context, attrs: AttributeSet) : DrawerLayout(context, attrs) { var searchPresenter: SearchPresenter? = null [Inject] set

init {
    DaggerService.getDaggerComponent<Search.Component>(context).inject(this)
   // if (context.getSystemService("PATH_CONTEXT") == null) throw Exception("not a path context $context ${(context as? ContextWrapper)?.getBaseContext()}") 
}

....

}

The complete stack trace (first line helps me track which view is the offender--> the first View)

05-17 15:10:43.120 14120-14707/org.lakedaemon.japanese.dictionary.pro E/AndroidRuntime﹕ FATAL EXCEPTION: main Process: org.lakedaemon.japanese.dictionary.pro, PID: 14120 java.lang.RuntimeException: org.lakedaemon.android.flow.pathview.FramePathContainerView{4287e228 V.E..... ........ 0,0-720,1118 #7f0e00c4 app:id/container} org.lakedaemon.android.flow.view.BuildView{42dbb758 VFE..... ........ 0,0-720,1118 #1020012 android:id/tabhost} java.lang.NullPointerException: Expected to find a PathContext but did not. at org.lakedaemon.android.flow.pathview.SimplePathContainer.performTraversal(SimplePathContainer.kt:55) at flow.path.PathContainer.executeTraversal(PathContainer.java:89) at org.lakedaemon.android.flow.pathview.FramePathContainerView.dispatch(FramePathContainerView.kt:51) at org.lakedaemon.android.flow.FlowActivity.dispatch(FlowActivity.kt:163) at flow.Flow$PendingTraversal.dispatch(Flow.java:315) at flow.Flow$2.doExecute(Flow.java:197) at flow.Flow$PendingTraversal.execute(Flow.java:323) at flow.Flow.move(Flow.java:232) at flow.Flow.set(Flow.java:162) at org.lakedaemon.android.flow.FlowActivity$onCreateOptionsMenu$1.onMenuItemClick(FlowActivity.kt:132) at android.support.v7.internal.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:149) at android.support.v7.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:949) at android.support.v7.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:939) at android.support.v7.widget.ActionMenuView.invokeItem(ActionMenuView.java:598) at android.support.v7.internal.view.menu.ActionMenuItemView.onClick(ActionMenuItemView.java:139) at android.view.View.performClick(View.java:4741) at android.view.View$PerformClick.run(View.java:19384) at android.os.Handler.handleCallback(Handler.java:733) at android.os.Handler.dispatchMessage(Handler.java:95) at android.os.Looper.loop(Looper.java:146) at android.app.ActivityThread.main(ActivityThread.java:5679) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:515) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1291) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1107) at dalvik.system.NativeStart.main(Native Method) Caused by: java.lang.NullPointerException: Expected to find a PathContext but did not. at flow.path.Preconditions.checkNotNull(Preconditions.java:30) at flow.path.PathContext.get(PathContext.java:100) at org.lakedaemon.android.flow.pathview.SimplePathContainer.performTraversal(SimplePathContainer.kt:50)             at flow.path.PathContainer.executeTraversal(PathContainer.java:89)             at org.lakedaemon.android.flow.pathview.FramePathContainerView.dispatch(FramePathContainerView.kt:51)             at org.lakedaemon.android.flow.FlowActivity.dispatch(FlowActivity.kt:163)             at flow.Flow$PendingTraversal.dispatch(Flow.java:315)             at flow.Flow$2.doExecute(Flow.java:197)             at flow.Flow$PendingTraversal.execute(Flow.java:323)             at flow.Flow.move(Flow.java:232)             at flow.Flow.set(Flow.java:162)             at org.lakedaemon.android.flow.FlowActivity$onCreateOptionsMenu$1.onMenuItemClick(FlowActivity.kt:132)             at android.support.v7.internal.view.menu.MenuItemImpl.invoke(MenuItemImpl.java:149)             at android.support.v7.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:949)             at android.support.v7.internal.view.menu.MenuBuilder.performItemAction(MenuBuilder.java:939)             at android.support.v7.widget.ActionMenuView.invokeItem(ActionMenuView.java:598)             at android.support.v7.internal.view.menu.ActionMenuItemView.onClick(ActionMenuItemView.java:139)             at android.view.View.performClick(View.java:4741)             at android.view.View$PerformClick.run(View.java:19384)             at android.os.Handler.handleCallback(Handler.java:733)             at android.os.Handler.dispatchMessage(Handler.java:95)             at android.os.Looper.loop(Looper.java:146)             at android.app.ActivityThread.main(ActivityThread.java:5679)             at java.lang.reflect.Method.invokeNative(Native Method)             at java.lang.reflect.Method.invoke(Method.java:515)             at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1291)             at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1107)             at dalvik.system.NativeStart.main(Native Method)

Lakedaemon commented 9 years ago

I did another experiment : I added a loging command to the commented line : if (context.getSystemService("PATH_CONTEXT") == null) { L.info("not a path context $context ${(context as? ContextWrapper)?.getBaseContext()}") throw Exception("not a path context $context ${(context as? ContextWrapper)?.getBaseContext()}") }

And I got interesting results : 1) getContext() of both views are actualy NOT wrapped with a PathContext (the 2 lines appeared in logcat) 2) adding an exception with the commented line must have trigger a catch block somewhere that swallows my exception and that makes the code work (bad luck, I was hoping to find where the problem was coming from that way)

Lakedaemon commented 9 years ago

Another experiment with print debug statements. It looks like the error comes from this part of the code : val context = PathContext.create(oldPath, to, contextFactory) val layout = getLayout(to) val newView = LayoutInflater.from(context).cloneInContext(context).inflate(layout, containerView, false)

My print statements tell me that context is able to deliver a PathContext but not newView.getContext() Which is weird as I guess that the cloneInContext part was all about using a layoutInflater set with the good PathContexted context... Let's now grep LayoutInflater

Lakedaemon commented 9 years ago

I looked into the chains of ContextWrapper. newView.getContext() has : org.lakedaemon.android.flow.MyFlowActivity@427b02c8 android.app.ContextImpl@42807fe0

context has : flow.path.PathContext@428fc308 flow.path.Path$LocalPathWrapper@428fc2c8 org.lakedaemon.android.flow.MyFlowActivity@427b02c8 ...

Also I checked LayoutInflater.from(context).cloneInContext(context) the Context it holds is identityEquals to context So the issue certainly comes from inflate(layout, containerView, false)

When I grepped the LayoutInflater code, it seemed to me that there might be a possibility the context could be changed (when a view uses a theme...)

almozavr commented 9 years ago

@Lakedaemon Do you use AppCompatActivity from 22.1.+? If so, there is a know issues with misusing context during inflation (see workaround inside): #88 (dig into mortar issue)

Lakedaemon commented 9 years ago

Yes I'm using AppCompatActivity (to get support for the api range 9 to 22) and I believe that I suffer from this exact issue. Thanks for the pointer.

Zhuinden commented 9 years ago

To fix this, just use LayoutInflater.from(context.getApplication()).cloneInContext(context).inflate(...)

Zhuinden commented 9 years ago

And you also need to initialize your container view group like this

PathContext pathContext = PathContext.root(this);
    framePathContainerView = (MortarScreenSwitcherFrame) LayoutInflater.from(this)
            .cloneInContext(pathContext)
            .inflate(R.layout.activity_main, null);
setContentView(framePathContainerView);
Lakedaemon commented 9 years ago

Thanks for the tip. It really helped.

loganj commented 8 years ago

See also #91