bumble-tech / appyx Goto Github PK
View Code? Open in Web Editor NEWModel-driven navigation + UI components with gesture control for Compose Multiplatform
Home Page: https://bumble-tech.github.io/appyx/
License: Apache License 2.0
Model-driven navigation + UI components with gesture control for Compose Multiplatform
Home Page: https://bumble-tech.github.io/appyx/
License: Apache License 2.0
Currently all branches are merged into main without requiring being up to date with main.
This could mean we merge an old branch that has a breaking change, and the main branch needs to be fixed.
Also, potentially if we introduce stricter static code analysis, potentially this older branch will increase our tech debt
We should consider using a merge queue to avoid this problem: https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue
Otherwise we could also enforce that all PRs need to be up to date with main, but I believe that will be far more annoying.
At the moment IntegrationPoint
is designed to have only one root in Appyx tree and will crash if we try to attach another one. In reality there can be more than one Appyx tree (similar to composition). For instance, clients may want to use Appyx in their codebase in disconnected from each other places creating more than one tree
There are quite a few libraries in the project that are out of date. Rather than having to remember to check these occasionally (and potentially wait months), we should investigate using a tool that does this automatically.
Unfortunately dependabot does not work with Gradle Version Catalogues (link), so we should use a different solution for now.
It looks like square is using 'Renovate' (example) which looks like it works with version catalogues. This tool potentially needs to be installed as an app, though I did see something about being able to 'self-host' and use a Github action.
This tool will automatically raise PRs for us when a new dependency is available, and will help us avoid being very out of date.
FragmentIntegrationPoint is no longer needed as we should use ActivityIntegrationPoint.getIntegrationPoint(context: Context)
method.
Create a sample project which demonstrates how someone could add Appyx to an existing Jetpack Compose Navigation project.
Same issue as #60, but in this case it happens for any NavModel
, not only nested
Repro project with more information: AppyxNavComponentBugRepro
Apologies for abusing the issues to ask a question; I was wondering whether appyx is or can be made compatible with Compose Multiplatform?
For all published modules:
org.jetbrains.kotlinx:binary-compatibility-validator
and add it as part of CI workflowexplicitApi()
mode for Kotlin Gradle pluginLooks like there is slightly more to this โ now a OnBackPressedCallback
from a custom BackPressHandler
plugin doesn't get invoked after onStop
Easiest way to repro this is in "Blocker" sample in sandbox, with same steps as before
Originally posted by @steelahhh in #102 (comment)
At the moment the InteropView error messaging (Activity where InteropNode is used must implement IntegrationPointAppyxProvider
) makes it difficult to tell which activity and which node have caused the issue.
By adding the class names to the error message, we can make investigating this problem easier.
Should we decide to support Kotlin Multiplatform in the future it would be easier for us if we supported the kotlin version of gradle build files. From what I have seen, most examples use build.gradle.kts
, and it could be difficult (or impossible) to do certain things with the Groovy API.
For example the val androidMain by getting
found here
The core
module currently depends on the Android appcompat library using implementation
. Rather than exposing it as api
it might be worthwhile to extract this into core-appcompat
, as this will give developers a choice (as they may not be allowed to use the appcompat library)
Views are difficult to subistute when ParentNodeView<> is an abstract class. Making ParentNodeView<>
an interface would fix this.
Case 1
In a Builder class you are expected to pass the view as ObservableSource<Event>, Consumer<ViewModel>
to the Interactor and as ParentNodeView<>
to the Node. If I want optionally pass in the view when created as a testable mock from a test build I can't without sharing the concrete type.
Case 2
View Customisations
The example for Customisations has the factory returning a NodeView. This would not satisfy the requirements above for a builder. Again to abstract this I would want to create a interface that has ObservableSource<Event>, Consumer<ViewModel>
and ParentNodeView<>
as an interface.
We already have attachWorkflow
and waitForChildAttached
coroutine powered public API, so I think we can migrate copy-pasted from RIBs PermissionRequestBoundary
to Flows.
Or we should commit to minimal.reactive
and change attachWorkflow
, waitForChildAttached
implementations.
Please investigate if it is an issue.
class ParentNodeView<...> {
@Composable
abstract fun ParentNode<Routing>.NodeView(modifier: Modifier)
}
abstract fun ParentNode<Routing>.NodeView(modifier: Modifier)
is equal to abstract fun NodeView(node: ParentNode<Routing>, modifier: Modifier)
.
ParentNode
is not marked as @Stable
so I suspect that we might have unnecessary recompositions in this case.
The issue seems to be happening when doing consecutive operations (push or pop), best explained with video:
Full sample code:
class RootActivity : NodeActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
MaterialTheme {
NodeHost(
integrationPoint = integrationPoint,
factory = { buildContext ->
RootNode(
buildContext = buildContext,
backstack = BackStack(
initialElement = RootNode.Routing.Child("Root"),
savedStateMap = buildContext.savedStateMap,
)
)
},
)
}
}
}
}
class RootNode(
buildContext: BuildContext,
private val backstack: BackStack<Routing>,
) : ParentNode<RootNode.Routing>(
routingSource = backstack,
buildContext = buildContext,
) {
sealed class Routing : Parcelable {
@Parcelize
data class Child(val id: String) : Routing()
}
@Composable
override fun View(modifier: Modifier) {
Surface(
modifier = modifier.fillMaxSize(),
color = Color.White
) {
Children(
routingSource = backstack,
transitionHandler = rememberBackstackSlider(
transitionSpec = { tween(2000, easing = LinearEasing) }
),
)
}
}
override fun resolve(routing: Routing, buildContext: BuildContext): Node {
return ChildNode(
name = when (routing) {
is Routing.Child -> routing.id
},
buildContext = buildContext,
pushNew = { backstack.push(Routing.Child(UUID.randomUUID().toString())) },
)
}
}
class ChildNode(
private val name: String,
private val pushNew: () -> Unit,
buildContext: BuildContext
) : Node(buildContext = buildContext) {
private val colors = listOf(
manatee,
sizzling_red,
atomic_tangerine,
silver_sand,
md_pink_500,
md_indigo_500,
md_blue_500,
md_light_blue_500,
md_cyan_500,
md_teal_500,
md_light_green_500,
md_lime_500,
md_amber_500,
md_grey_500,
md_blue_grey_500
)
private val colorIndex =
buildContext.savedStateMap?.get(KEY_COLOR_INDEX) as? Int ?: Random.nextInt(colors.size)
private val color = colors[colorIndex]
override fun onSaveInstanceState(state: MutableSavedStateMap) {
super.onSaveInstanceState(state)
state[KEY_COLOR_INDEX] = colorIndex
}
@Composable
override fun View(modifier: Modifier) {
Box(
modifier = Modifier
.fillMaxSize()
.background(
color = color,
shape = RoundedCornerShape(6.dp)
)
) {
Column(
modifier = Modifier.padding(24.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Text("Child ($name).")
Row {
// Local UI state should be saved too (both in backstack and onSaveInstanceState)
var counter by rememberSaveable { mutableStateOf(0) }
Text(text = "Counter $counter", modifier = Modifier.align(CenterVertically))
Spacer(modifier = Modifier.width(16.dp))
Button(onClick = { counter++ }, content = { Text("Increment") })
}
Row {
Button(onClick = { navigateUp() }, content = { Text("Go up") })
}
Row {
Button(onClick = pushNew, content = { Text("Push new") })
}
}
}
}
companion object {
private const val KEY_COLOR_INDEX = "ColorIndex"
}
}
When I quit my app using the back button on a phone running SDK 29, Leak Canary reports a memory leak.
If I use the fix reported here: https://issuetracker.google.com/issues/139738913, the leak is fixed.
override fun onBackPressed() {
// Fix for Memory Leak: https://issuetracker.google.com/issues/139738913
if (Build.VERSION.SDK_INT == 29 && !onBackPressedDispatcher.hasEnabledCallbacks()) {
finishAfterTransition()
} else {
super.onBackPressed()
}
}
However, if I use Appyx NodeActivity with a RootNode, I get the memory leak again.
On small screen devices (you can reproduce by using split screen if required) several of the lists within the sandbox do not scroll.
This causes the content to become truncated. We should add the vertical scrolling modifier to fix these Nodes.
Currently it is possible for someone to merge to main without running the AndroidTests within the Sandbox module (and potentially future modules). This could mean that the tests will break silently.
The 'customisations' does not need to be an Android module, and should be converted to a Java module. Should we wish to support multiplatform in the future we will need these modules to not be Android specific
Setup detekt + report issues to CodeQL like here #63 using SARIF file support in detekt
Rather than using the existing publication.gradle
class to setup our publication, we should use a plugin instead as this will give us greater type safety when switching to Kotlin gradle files (see #49)
Explore and create an API for navigating with gestures
We could introduce some simple dagger hilt integration to make it easier to share node factories across modules without exposing internal dependencies.
Assume I call backstack.pop() at initial element. Nothing happens. What should I do to exit from application, like Back gesture or button do?
Current interop integration point required activities to implement IntegrationPointAppyxProvider
.
This expose the interface in module edges, forcing parent modules to implement appyx event if they are not using it.
Example.
Module A
-> Module B
Module B
implements SomeActiviy: RibAcitivty(), IntegrationPointAppyxProvider{...}
Module A
has no appyx dependencies.
Compilation failed as Module A will try to resolve IntegrationPointAppyxProvider
At the moment navigation via deeplinks in not supported. Similarly to RIBs we need to implement workflows in order to support it
So we can instantly check changes in other repos.
NodeActivity
extends AppCompatActivity
, whereas ComponentActivity
used in Jetpack Compose template projects does not. This impedes adoption of Appyx in new projects.
Furthermore, ActivityIntegrationPoint
(the integration with which is the sole purpose of NodeActivity
) requires AppCompatActivity
as a parameter, preventing a user simply creating their own wrapper.
It looks like ActivityIntegrationPoint
could just take Activity
as its first parameter, making it more re-usable, and it should also be possible to provide a convenient boilerplate equivalent for NodeActivity
that extends ComponentActivity
, for use in Jetpack Compose projects.
Steps to reproduce:
Process: com.bumble.appyx, PID: 9242
java.lang.IllegalStateException: A root has already been attached to this integration point
at com.bumble.appyx.core.integrationpoint.IntegrationPoint.attach(IntegrationPoint.kt:27)
at com.bumble.appyx.core.integration.NodeHostKt$NodeHost$1$1.invokeSuspend(NodeHost.kt:40)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at androidx.compose.ui.platform.AndroidUiDispatcher.performTrampolineDispatch(AndroidUiDispatcher.android.kt:81)
at androidx.compose.ui.platform.AndroidUiDispatcher.access$performTrampolineDispatch(AndroidUiDispatcher.android.kt:41)
at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.doFrame(AndroidUiDispatcher.android.kt:68)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1035)
at android.view.Choreographer.doCallbacks(Choreographer.java:845)
at android.view.Choreographer.doFrame(Choreographer.java:775)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1022)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:201)
at android.os.Looper.loop(Looper.java:288)
at android.app.ActivityThread.main(ActivityThread.java:7870)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)
Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [androidx.compose.ui.platform.MotionDurationScaleImpl@766958, androidx.compose.runtime.BroadcastFrameClock@893dab1, StandaloneCoroutine{Cancelling}@97c3b96, AndroidUiDispatcher@114317]
Some operations are defined as sealed for having exhaustive when conditions when implementing transition handler. We should allow customers to define their own operations for the existing NavModels.
Discussion for the reference: #123
MviCoreExampleNode
should not provide connectors for children, but use some other mechanism to make children produce a result like we have in RIBs.
Steps:
Issue:
ActivityStarter
onActivityResult
is invoked and events are pushedActivity.setContent { }
)ActivityStarter
Discovered after converting error log into exception in #173
Add documentation for workflows
Now that we have a testing utils for both UI and unit tests, we should update the documentation to show a brief example of how to write a test.
We might want to separate them into two different pages, and the navigation would look similar to
Testing
UI testing
Unit testing
BuildContext.savedStateMap
is not cleared after restoration, effectively holding up to 1 MB of data until the next restoration cycle.
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.
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)
And provide as LocalComposition
.
See badoo/RIBs#374
By using SavedStateRegistry
per node we are scoping instance saving to a particular place instead of relying on Activity. JetPack navigation does the same.
Steps to reproduce:
next
action a few times without waiting for the transitions to finishStacktrace:
java.util.NoSuchElementException: Collection contains no element matching the predicate.
at com.bumble.appyx.routingsource.spotlight.operation.Next.invoke(Next.kt:52)
at com.bumble.appyx.routingsource.spotlight.operation.Next.invoke(Next.kt:11)
at com.bumble.appyx.core.routing.BaseRoutingSource$execute$1.invoke(BaseRoutingSource.kt:114)
at com.bumble.appyx.core.routing.BaseRoutingSource$execute$1.invoke(BaseRoutingSource.kt:112)
at com.bumble.appyx.core.routing.BaseRoutingSource.updateState(BaseRoutingSource.kt:106)
at com.bumble.appyx.core.routing.BaseRoutingSource.execute(BaseRoutingSource.kt:112)
at com.bumble.appyx.core.routing.BaseRoutingSource.access$execute(BaseRoutingSource.kt:33)
at com.bumble.appyx.core.routing.BaseRoutingSource$1.invoke(BaseRoutingSource.kt:97)
at com.bumble.appyx.core.routing.BaseRoutingSource$1.invoke(BaseRoutingSource.kt:97)
at com.bumble.appyx.core.routing.operationstrategies.ExecuteImmediately.accept(ExecuteImmediately.kt:8)
at com.bumble.appyx.core.routing.BaseRoutingSource.accept(BaseRoutingSource.kt:101)
at com.bumble.appyx.routingsource.spotlight.operation.NextKt.next(Next.kt:44)
at com.bumble.appyx.sandbox.client.spotlight.SpotlightExampleNode$LoadedState$1$2$1$1.invoke(SpotlightExampleNode.kt:160)
at com.bumble.appyx.sandbox.client.spotlight.SpotlightExampleNode$LoadedState$1$2$1$1.invoke(SpotlightExampleNode.kt:159)
After investigating existing lint/detekt issues, it was discovered that the AppyxTestRule's decorator variable is unused.
According to @CherryPerry this should be used. This issue has been created to ensure that we don't forget about it: #72 (comment)
The testing-ui
module is currently being defined as an implementation
as well as an androidTestImplementation
dependency. When I attempted to remove the implementation
usage, the tests started failing.
This is because the testing-ui
module includes an activity. This activity must be registered within the main apps manifest. This would then require developers to do something like debugImplementation testing-ui
which then leaks test code into the debug build.
To fix this, we should create a testing-ui-activity
module which only defines the activity. This allows the tests to keep working, and also avoids adding espresso code in a debug build.
This would like like:
debugImplementation testing-ui-activity
androidTestImplementation testing-ui
See Google's own documentation that shows that this is necessary: https://developer.android.com/jetpack/compose/testing#setup
I tried to get the activity working without adding it into the main apk, and it doesn't seem to work.
Working on Jetpack Compose Navigation with multi module application using Kotlin DSL.
We have dynamic feature modules DFM1, DFM2, DFM3.
How to setup navigation graph and navigate between dynamic feature modules.
We need to keep track of our composable functions. Which are skippable, restartable, etc. as well as help understand stability of the classes
Generate compiler report and see that NodeView and its implementations are used as this
parameter for composable functions. It's safe to mark it as stable improving performance
Haven't investigated the specifics, but here's the gist:
ParentNode
with BackStack
or Modals
that has a leaf ParentNode
that also has BackStack
BackStack
BackPressHandler
is calledCan be reproduced in sandbox app in "Combined routing source" or "Modal example" or "Spotlight example"
Edit: Looks like this would be fixed by #32 ๐ค
Keeping all Nodes in the NavModel alive while they're invisible can be memory consuming. We need to make it configurable. Twitter thread for the reference: https://twitter.com/Piwai/status/1563157989430661122?s=20&t=FoQaDoEPift8J2TGkqOuGQ
Update Jetpack Compose to the latest stable release - 1.2.0 and kotlin to 1.7.0
This will make Github Action jobs run faster as long as no changes have been made to the gradle wrapper, build.settings/settings.gradle or libs.versions.toml
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.