Skip to content

Commit

Permalink
Cover interop back press with test
Browse files Browse the repository at this point in the history
  • Loading branch information
CherryPerry committed Nov 21, 2022
1 parent bc017f1 commit 20afd0c
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 0 deletions.
1 change: 1 addition & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ mvicore-android = { module = "com.github.badoo.mvicore:mvicore-android", version
mvicore-binder = { module = "com.github.badoo.mvicore:binder", version.ref = "mvicore" }
ribs-base = { module = "com.github.badoo.RIBs:rib-base", version.ref = "ribs" }
ribs-base-test = { module = "com.github.badoo.RIBs:rib-base-test", version.ref = "ribs" }
ribs-base-test-activity = { module = "com.github.badoo.RIBs:rib-base-test-activity", version.ref = "ribs" }
ribs-base-test-rx2 = { module = "com.github.badoo.RIBs:rib-base-test-rx2", version.ref = "ribs" }
ribs-compose = { module = "com.github.badoo.RIBs:rib-compose", version.ref = "ribs" }
ribs-rx = { module = "com.github.badoo.RIBs:rib-rx2", version.ref = "ribs" }
Expand Down
8 changes: 8 additions & 0 deletions libraries/interop-ribs/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
id("com.android.library")
id("kotlin-android")
id("kotlin-parcelize")
id("appyx-publish-android")
id("appyx-lint")
id("appyx-detekt")
Expand Down Expand Up @@ -33,4 +34,11 @@ dependencies {
implementation(libs.androidx.activity.compose)
implementation(libs.compose.ui.ui)
implementation(libs.ribs.compose)

androidTestImplementation(libs.androidx.activity.compose)
androidTestImplementation(libs.androidx.test.espresso.core)
androidTestImplementation(libs.androidx.test.junit)
androidTestImplementation(libs.compose.foundation.layout)
androidTestImplementation(libs.compose.ui.test.junit4)
androidTestImplementation(libs.ribs.base.test.activity)
}
12 changes: 12 additions & 0 deletions libraries/interop-ribs/src/androidTest/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application>

<activity
android:name="com.bumble.appyx.interop.ribs.AppyxRibsInteropActivity"
android:theme="@style/Theme.AppCompat.Light.NoActionBar" />

</application>

</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.bumble.appyx.interop.ribs

import android.os.Bundle
import android.view.ViewGroup
import com.badoo.ribs.core.Rib
import com.badoo.ribs.core.modality.BuildContext

class AppyxRibsInteropActivity : InteropActivity() {

lateinit var ribsNode: RibsNode

override val rootViewGroup: ViewGroup
get() = findViewById(android.R.id.content)

override fun createRib(savedInstanceState: Bundle?): Rib =
RibsNodeBuilder()
.build(BuildContext.root(savedInstanceState), appyxIntegrationPoint)
.also { ribsNode = it }

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.bumble.appyx.interop.ribs

import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.test.espresso.Espresso
import org.junit.Rule
import org.junit.Test

class InteropNodeImplTest {

@get:Rule
val rule = createAndroidComposeRule(AppyxRibsInteropActivity::class.java)

@Test
fun appyx_back_press_is_handled_before_rib_parent() {
// push a new child into the backstack
var newChild = ""
var oldChild = ""
rule.activityRule.scenario.onActivity {
oldChild = it.ribsNode.current()
newChild = it.ribsNode.push()
}

Espresso.pressBack()

// the back press is swollen by the child node, newChild is still displayed
rule.onNodeWithTag(newChild).assertExists()

Espresso.pressBack()

// the back press is handled by RIBs now and the old child is displayed
rule.onNodeWithTag(oldChild).assertExists()
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package com.bumble.appyx.interop.ribs

import android.os.Parcelable
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.lifecycle.Lifecycle
import com.badoo.ribs.builder.Builder
import com.badoo.ribs.core.Node
import com.badoo.ribs.core.modality.BuildParams
import com.badoo.ribs.core.plugin.Plugin
import com.badoo.ribs.core.view.AndroidRibView2
import com.badoo.ribs.core.view.ViewFactory
import com.badoo.ribs.routing.Routing
import com.badoo.ribs.routing.resolution.ChildResolution
import com.badoo.ribs.routing.resolution.Resolution
import com.badoo.ribs.routing.router.Router
import com.badoo.ribs.routing.source.backstack.BackStack
import com.badoo.ribs.routing.source.backstack.operation.push
import com.bumble.appyx.core.integrationpoint.IntegrationPoint
import com.bumble.appyx.core.modality.BuildContext
import kotlinx.parcelize.Parcelize
import java.util.UUID

class RibsNode(
buildParams: BuildParams<*>,
private val backStack: BackStack<RibsNodeRouter.Configuration>,
plugins: List<Plugin>,
) : Node<RibsNode.View>(
buildParams = buildParams,
viewFactory = View.Factory(),
plugins = plugins,
) {

fun current(): String =
backStack.activeConfiguration.id

fun push(): String {
val id = UUID.randomUUID().toString()
backStack.push(RibsNodeRouter.Configuration(id))
return id
}

class View(
androidView: ViewGroup,
lifecycle: Lifecycle,
) : AndroidRibView2(
androidView,
lifecycle
) {
class Factory : ViewFactory<View> {
override fun invoke(context: ViewFactory.Context): View =
View(FrameLayout(context.parent.context), context.lifecycle)
}
}
}

class RibsNodeBuilder : Builder<IntegrationPoint, RibsNode>() {
override fun build(buildParams: BuildParams<IntegrationPoint>): RibsNode {
val backStack = BackStack(
RibsNodeRouter.Configuration(UUID.randomUUID().toString()),
buildParams
)
val router = RibsNodeRouter(buildParams, buildParams.payload, backStack)
return RibsNode(buildParams, backStack, listOf(router))
}
}

class RibsNodeRouter(
buildParams: BuildParams<*>,
private val integrationPoint: IntegrationPoint,
backStack: BackStack<Configuration>,
) : Router<RibsNodeRouter.Configuration>(
buildParams = buildParams,
routingSource = backStack
) {
@Parcelize
data class Configuration(val id: String) : Parcelable

override fun resolve(routing: Routing<Configuration>): Resolution =
ChildResolution.child {
InteropBuilder(
nodeFactory = { buildContext -> AppyxNode(buildContext, routing.configuration.id) },
integrationPoint = integrationPoint,
).build(it)
}
}

class AppyxNode(
buildContext: BuildContext,
private val s: String,
) : com.bumble.appyx.core.node.Node(
buildContext,
) {
var shouldInterceptBackPress by mutableStateOf(true)

@Composable
override fun View(modifier: Modifier) {
Box(modifier = modifier.testTag(s)) {
BackHandler(shouldInterceptBackPress) {
shouldInterceptBackPress = false
}
}
}
}

0 comments on commit 20afd0c

Please sign in to comment.