Repository: bumble-tech/appyx
Issue: Sandbox IntegrationPointExampleNode throws exception when Activity recreated 

The Sandbox IntegrationPointExampleNode class throws an exception when Activity recreated due to the integrationPoint not being attached yet.

We should change the node to not access the integrationPoint immediately, and also update the error message for clarity.

Demontration
Using the dark mode button to cause the Activity to be recreated.

[appyx-sandbox-lifecycle-crash.webm](https://user-images.githubusercontent.com/1333155/178144662-d73cdf0e-a42a-4687-bf1b-57cb1f213163.webm)


**Stacktrace**
```
java.lang.IllegalStateException: You're accessing an IntegrationPointStub. This means you're using a Node without ever integrating it to a proper IntegrationPoint. This is fine during tests with limited scope, but it looks like the code that leads here requires interfacing with a valid implementation.
	at com.bumble.appyx.core.integrationpoint.IntegrationPointStub.getPermissionRequester(IntegrationPointStub.kt:18)
	at com.bumble.appyx.sandbox.client.integrationpoint.IntegrationPointExampleNode.<init>(IntegrationPointExampleNode.kt:37)
	at com.bumble.appyx.sandbox.client.container.ContainerNode.resolve(ContainerNode.kt:130)
	at com.bumble.appyx.sandbox.client.container.ContainerNode.resolve(ContainerNode.kt:63)
	at com.bumble.appyx.core.children.ChildEntry$Companion.create(ChildEntry.kt:58)
	at com.bumble.appyx.core.node.ParentNode.restoreChildren(ParentNode.kt:121)
	at com.bumble.appyx.core.node.ParentNode.onBuilt(ParentNode.kt:82)
	at com.bumble.appyx.core.node.NodeExtKt.build(NodeExt.kt:3)
	at com.bumble.appyx.core.integration.NodeHostKt$rememberNode$2.invoke(NodeHost.kt:72)
	at com.bumble.appyx.core.integration.NodeHostKt$rememberNode$2.invoke(NodeHost.kt:64)
	at androidx.compose.runtime.saveable.MapSaverKt$mapSaver$2.invoke(MapSaver.kt:52)
	at androidx.compose.runtime.saveable.MapSaverKt$mapSaver$2.invoke(MapSaver.kt:33)
	at androidx.compose.runtime.saveable.SaverKt$Saver$1.restore(Saver.kt:68)
	at androidx.compose.runtime.saveable.RememberSaveableKt$mutableStateSaver$1$2.invoke(RememberSaveable.kt:162)
	at androidx.compose.runtime.saveable.RememberSaveableKt$mutableStateSaver$1$2.invoke(RememberSaveable.kt:151)
	at androidx.compose.runtime.saveable.SaverKt$Saver$1.restore(Saver.kt:68)
	at androidx.compose.runtime.saveable.RememberSaveableKt.rememberSaveable(RememberSaveable.kt:86)
	at androidx.compose.runtime.saveable.RememberSaveableKt.rememberSaveable(RememberSaveable.kt:142)
	at com.bumble.appyx.core.integration.NodeHostKt.rememberNode(NodeHost.kt:62)
	at com.bumble.appyx.core.integration.NodeHostKt.NodeHost(NodeHost.kt:38)
	at com.bumble.appyx.sandbox.MainActivity$onCreate$1$1$1.invoke(MainActivity.kt:25)
	at com.bumble.appyx.sandbox.MainActivity$onCreate$1$1$1.invoke(MainActivity.kt:23)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
	at androidx.compose.material.SurfaceKt$Surface$1.invoke(Surface.kt:134)
	at androidx.compose.material.SurfaceKt$Surface$1.invoke(Surface.kt:117)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
	at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
	at androidx.compose.material.SurfaceKt.Surface-F-jzlyU(Surface.kt:114)
	at com.bumble.appyx.sandbox.MainActivity$onCreate$1$1.invoke(MainActivity.kt:23)
	at com.bumble.appyx.sandbox.MainActivity$onCreate$1$1.invoke(MainActivity.kt:21)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
	at androidx.compose.material.MaterialTheme_androidKt.PlatformMaterialTheme(MaterialTheme.android.kt:23)
	at androidx.compose.material.MaterialThemeKt$MaterialTheme$1$1.invoke(MaterialTheme.kt:82)
	at androidx.compose.material.MaterialThemeKt$MaterialTheme$1$1.invoke(MaterialTheme.kt:81)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
	at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
	at androidx.compose.material.TextKt.ProvideTextStyle(Text.kt:265)
	at androidx.compose.material.MaterialThemeKt$MaterialTheme$1.invoke(MaterialTheme.kt:81)
	at androidx.compose.material.MaterialThemeKt$MaterialTheme$1.invoke(MaterialTheme.kt:80)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
	at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
	at androidx.compose.material.MaterialThemeKt.MaterialTheme(MaterialTheme.kt:72)
	at com.bumble.appyx.sandbox.ui.ThemeKt.AppyxSandboxTheme(Theme.kt:39)
	at com.bumble.appyx.sandbox.MainActivity$onCreate$1.invoke(MainActivity.kt:21)
	at com.bumble.appyx.sandbox.MainActivity$onCreate$1.invoke(MainActivity.kt:20)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
	at androidx.compose.ui.platform.ComposeView.Content(ComposeView.android.kt:402)
	at androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(ComposeView.android.kt:248)
	at androidx.compose.ui.platform.AbstractComposeView$ensureCompositionCreated$1.invoke(ComposeView.android.kt:247)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
	at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
	at androidx.compose.ui.platform.CompositionLocalsKt.ProvideCommonCompositionLocals(CompositionLocals.kt:177)
	at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$ProvideAndroidCompositionLocals$3.invoke(AndroidCompositionLocals.android.kt:123)
	at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt$ProvideAndroidCompositionLocals$3.invoke(AndroidCompositionLocals.android.kt:122)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
	at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
	at androidx.compose.ui.platform.AndroidCompositionLocals_androidKt.ProvideAndroidCompositionLocals(AndroidCompositionLocals.android.kt:114)
	at androidx.compose.ui.platform.WrappedComposition$setContent$1$1$3.invoke(Wrapper.android.kt:157)
	at androidx.compose.ui.platform.WrappedComposition$setContent$1$1$3.invoke(Wrapper.android.kt:156)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
	at androidx.compose.runtime.CompositionLocalKt.CompositionLocalProvider(CompositionLocal.kt:228)
	at androidx.compose.ui.platform.WrappedComposition$setContent$1$1.invoke(Wrapper.android.kt:156)
	at androidx.compose.ui.platform.WrappedComposition$setContent$1$1.invoke(Wrapper.android.kt:140)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:107)
	at androidx.compose.runtime.internal.ComposableLambdaImpl.invoke(ComposableLambda.jvm.kt:34)
	at androidx.compose.runtime.ActualJvm_jvmKt.invokeComposable(ActualJvm.jvm.kt:74)
	at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3193)
	at androidx.compose.runtime.ComposerImpl$doCompose$2$5.invoke(Composer.kt:3183)
	at androidx.compose.runtime.SnapshotStateKt__DerivedStateKt.observeDerivedStateRecalculations(DerivedState.kt:252)
	at androidx.compose.runtime.SnapshotStateKt.observeDerivedStateRecalculations(Unknown Source:1)
	at androidx.compose.runtime.ComposerImpl.doCompose(Composer.kt:3183)
	at androidx.compose.runtime.ComposerImpl.composeContent$runtime_release(Composer.kt:3119)
	at androidx.compose.runtime.CompositionImpl.composeContent(Composition.kt:578)
	at androidx.compose.runtime.Recomposer.composeInitial$runtime_release(Recomposer.kt:811)
	at androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:513)
	at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:140)
	at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:131)
	at androidx.compose.ui.platform.AndroidComposeView.setOnViewTreeOwnersAvailable(AndroidComposeView.android.kt:1015)
	at androidx.compose.ui.platform.WrappedComposition.setContent(Wrapper.android.kt:131)
	at androidx.compose.ui.platform.WrappedComposition.onStateChanged(Wrapper.android.kt:182)
	at androidx.lifecycle.LifecycleRegistry$ObserverWithState.dispatchEvent(LifecycleRegistry.java:354)
	at androidx.lifecycle.LifecycleRegistry.addObserver(LifecycleRegistry.java:196)
	at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:138)
	at androidx.compose.ui.platform.WrappedComposition$setContent$1.invoke(Wrapper.android.kt:131)
	at androidx.compose.ui.platform.AndroidComposeView.onAttachedToWindow(AndroidComposeView.android.kt:1102)
	at android.view.View.dispatchAttachedToWindow(View.java:20479)
	at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3489)
	at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3496)
	at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3496)
	at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3496)
	at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3496)
	at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3496)
	at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3496)
	at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2417)
	at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1952)
	at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8171)
	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:972)
	at android.view.Choreographer.doCallbacks(Choreographer.java:796)
	at android.view.Choreographer.doFrame(Choreographer.java:731)
	at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:957)
	at android.os.Handler.handleCallback(Handler.java:938)
	at android.os.Handler.dispatchMessage(Handler.java:99)
	at android.os.Looper.loop(Looper.java:223)
	at android.app.ActivityThread.main(ActivityThread.java:7656)
	at java.lang.reflect.Method.invoke(Native Method)
	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
```


Changed files:
sandbox/src/main/java/com/bumble/appyx/sandbox/client/integrationpoint/IntegrationPointExampleNode.kt
core/src/main/java/com/bumble/appyx/core/integrationpoint/IntegrationPointStub.kt

diff --git a/core/src/main/java/com/bumble/appyx/core/integrationpoint/IntegrationPointStub.kt b/core/src/main/java/com/bumble/appyx/core/integrationpoint/IntegrationPointStub.kt
index 3c76e1b6..a0e78a2a 100644
--- a/core/src/main/java/com/bumble/appyx/core/integrationpoint/IntegrationPointStub.kt
+++ b/core/src/main/java/com/bumble/appyx/core/integrationpoint/IntegrationPointStub.kt
@@ -8,7 +8,8 @@ class IntegrationPointStub : IntegrationPoint(savedInstanceState = null) {
         private const val ERROR = "You're accessing an IntegrationPointStub. " +
             "This means you're using a Node without ever integrating it to a proper IntegrationPoint. " +
             "This is fine during tests with limited scope, but it looks like the code that leads here " +
-            "requires interfacing with a valid implementation."
+            "requires interfacing with a valid implementation. " +
+            "You may be attempting to access the IntegrationPoint before it is attached to the Node."
     }
 
     override val activityStarter: ActivityStarter
diff --git a/sandbox/src/main/java/com/bumble/appyx/sandbox/client/integrationpoint/IntegrationPointExampleNode.kt b/sandbox/src/main/java/com/bumble/appyx/sandbox/client/integrationpoint/IntegrationPointExampleNode.kt
index 695b2d41..e598b4db 100644
--- a/sandbox/src/main/java/com/bumble/appyx/sandbox/client/integrationpoint/IntegrationPointExampleNode.kt
+++ b/sandbox/src/main/java/com/bumble/appyx/sandbox/client/integrationpoint/IntegrationPointExampleNode.kt
@@ -23,6 +23,7 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
+import com.bumble.appyx.core.integrationpoint.activitystarter.ActivityStarter
 import com.bumble.appyx.core.integrationpoint.permissionrequester.PermissionRequester
 import com.bumble.appyx.core.integrationpoint.requestcode.RequestCodeClient
 import com.bumble.appyx.core.minimal.reactive.Cancellable
@@ -34,8 +35,8 @@ import java.util.*
 class IntegrationPointExampleNode(buildContext: BuildContext) : Node(buildContext = buildContext),
     RequestCodeClient {
 
-    private val permissionRequester = integrationPoint.permissionRequester
-    private val activityStarter = integrationPoint.activityStarter
+    private val permissionRequester: PermissionRequester get() = integrationPoint.permissionRequester
+    private val activityStarter: ActivityStarter get() = integrationPoint.activityStarter
     private var permissionsResultCancellable: Cancellable? = null
     private var activityResultsCancellable: Cancellable? = null
     private var permissionsResultState by mutableStateOf("Press request permissions to check permissions state")
@@ -91,13 +92,13 @@ class IntegrationPointExampleNode(buildContext: BuildContext) : Node(buildContex
     }
 
     private fun launchActivity() {
-        integrationPoint.activityStarter.startActivity {
+        activityStarter.startActivity {
             Intent(this, StartActivityExample::class.java)
         }
     }
 
     private fun launchActivityForResult() {
-        integrationPoint.activityStarter.startActivityForResult(this, StartActivityForResultCode) {
+        activityStarter.startActivityForResult(this, StartActivityForResultCode) {
             Intent(this, StartActivityExample::class.java)
         }
     }