Skip to content

Commit

Permalink
Merge branch 'main' into update-adapter
Browse files Browse the repository at this point in the history
* main: (34 commits)
  Bump the gradle plugin to 8.1.0
  Update .idea git-ignored files
  Open up the component's didReceive(), didStart(), and didStop() for use with app component testing
  Update Android Gradle plugin to 8.0.2
  Include the stack trace in logged errors
  Improve logging and expose warnings to apps that don't have debug logging enabled
  Don't access the bridge directly from the component, so testing from apps is possible through the delegate directly
  Move the debugLoggingEnabled flag to the Strada.config namespace
  Move userAgent substring generation from Bridge to Strada namespace
  Add tests for message data encoding/decoding
  Provide an available KotlinXJsonConverter, since the kotlinx.serialization is multiplatform and requires inline reified functions, which aren't compatible with a generic interface or abstract class
  Rename StradaJsonEncoder -> StradaJsonConverter
  Cleanup
  Initial work to encode/decode data by providing Strada a custom app encoder
  Rename messageReceivedFor(event) -> receivedMessageFor(event)
  Make the component's onStart() and onStop() have protected access, so it's only available to component subclasses
  Update BridgeComponent tests with the boolean flag when replying
  Allow replying to an event without modifying its jsonData. Return a boolean flag from replies to indicate whether the reply was successful.
  Consolidate test data to a new TestData object
  Store the last received message for each event type so it can be easily retrieved and add a new replyTo() convenience way to reply to messages
  ...

# Conflicts:
#	strada/src/main/assets/js/strada.js
  • Loading branch information
jayohms committed Sep 7, 2023
2 parents df7efdd + 45a36ea commit 79e6e55
Show file tree
Hide file tree
Showing 30 changed files with 744 additions and 156 deletions.
6 changes: 6 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ jobs:
publish-release:
runs-on: ubuntu-latest
steps:
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'

- name: Checkout code
uses: actions/checkout@v2

Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'

- name: Checkout code
uses: actions/checkout@v2

Expand Down
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ buildscript {
}

dependencies {
classpath 'com.android.tools.build:gradle:7.3.1'
classpath 'com.android.tools.build:gradle:8.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
}
Expand Down
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
4 changes: 2 additions & 2 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#Mon Feb 13 14:19:36 EST 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
18 changes: 14 additions & 4 deletions gradlew
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
Expand All @@ -80,10 +80,10 @@ do
esac
done

APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit

APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
Expand Down Expand Up @@ -143,12 +143,16 @@ fi
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
Expand Down Expand Up @@ -205,6 +209,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \
"$@"

# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi

# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
Expand Down
15 changes: 9 additions & 6 deletions gradlew.bat
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
@rem limitations under the License.
@rem

@if "%DEBUG%" == "" @echo off
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
Expand All @@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal

set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

Expand All @@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
if %ERRORLEVEL% equ 0 goto execute

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Expand Down Expand Up @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
if %ERRORLEVEL% equ 0 goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%

:mainEnd
if "%OS%"=="Windows_NT" endlocal
Expand Down
18 changes: 14 additions & 4 deletions strada/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,12 @@ android {
}

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}

kotlinOptions {
jvmTarget = "1.8"
jvmTarget = "17"
}

buildTypes {
Expand All @@ -64,19 +64,29 @@ android {
testOptions {
unitTests.includeAndroidResources = true
unitTests.returnDefaultValues = true

kotlinOptions {
freeCompilerArgs += [
'-Xopt-in=kotlinx.coroutines.ExperimentalCoroutinesApi'
]
}
}

namespace 'dev.hotwire.strada'
}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])

implementation 'androidx.core:core-ktx:1.9.0'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1'
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0'
implementation 'androidx.lifecycle:lifecycle-common:2.6.1'

testImplementation 'junit:junit:4.13.2'
testImplementation 'androidx.test:core:1.5.0'
testImplementation 'org.assertj:assertj-core:3.24.2'
testImplementation 'androidx.lifecycle:lifecycle-runtime-testing:2.6.1'
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4"
testImplementation 'org.robolectric:robolectric:4.9.2'
testImplementation 'org.mockito:mockito-core:5.2.0'
testImplementation 'com.nhaarman:mockito-kotlin:1.6.0'
Expand Down
4 changes: 2 additions & 2 deletions strada/src/main/assets/js/strada.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@
return this.supportedComponents.includes(component)
}

// Send message to web
send(message) {
// Reply to web with message
replyWith(message) {
if (this.isStradaAvailable) {
this.webBridge.receive(JSON.parse(message))
}
Expand Down
6 changes: 3 additions & 3 deletions strada/src/main/kotlin/dev/hotwire/strada/Bridge.kt
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ class Bridge internal constructor(webView: WebView) {
evaluate(javascript)
}

internal fun send(message: Message) {
logMessage("bridgeWillSendMessage", message)
internal fun replyWith(message: Message) {
logEvent("bridgeWillReplyWithMessage", message.toString())
val internalMessage = InternalMessage.fromMessage(message)
val javascript = generateJavaScript("send", internalMessage.toJson().toJsonElement())
val javascript = generateJavaScript("replyWith", internalMessage.toJson().toJsonElement())
evaluate(javascript)
}

Expand Down
116 changes: 111 additions & 5 deletions strada/src/main/kotlin/dev/hotwire/strada/BridgeComponent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,120 @@ abstract class BridgeComponent<in D : BridgeDestination>(
val name: String,
private val delegate: BridgeDelegate<D>
) {
abstract fun handle(message: Message)
private val receivedMessages = hashMapOf<String, Message>()

fun send(message: Message) {
delegate.bridge?.send(message) ?: run {
logEvent("bridgeMessageFailedToSend", "bridge is not available")
}
/**
* Returns the last received message for a given `event`, if available.
*/
protected fun receivedMessageFor(event: String): Message? {
return receivedMessages[event]
}

/**
* Called when a message is received from the web bridge. Handle the
* message for its `event` type for the custom component's behavior.
*/
abstract fun onReceive(message: Message)

/**
* This passes a received message to onReceive(message), caching it
* for use with replyTo(event) and receivedMessageFor(event).
*
* NOTE: This should not be called directly from within a component,
* but is available to use for testing.
*/
fun didReceive(message: Message) {
receivedMessages[message.event] = message
onReceive(message)
}

/**
* This passes the start lifecycle event to onStart().
*
* NOTE: This should not be called directly from within a component,
* but is available to use for testing.
*/
fun didStart() {
onStart()
}

/**
* This passes the stop lifecycle event to onStop().
*
* NOTE: This should not be called directly from within a component,
* but is available to use for testing.
*/
fun didStop() {
onStop()
}

/**
* Called when the component's destination starts (and is active)
* based on its lifecycle events. You can use this as an opportunity
* to update the component's state/view.
*/
open fun onStart() {}

/**
* Called when the component's destination stops (and is inactive)
* based on its lifecycle events. You can use this as an opportunity
* to update the component's state/view.
*/
open fun onStop() {}

/**
* Reply to the web with a received message, optionally replacing its
* `event` or `jsonData`.
*/
fun replyWith(message: Message): Boolean {
return reply(message)
}

/**
* Reply to the web with the last received message for a given `event`
* with its original `jsonData`.
*
* NOTE: If a message has not been received for the given `event`, the
* reply will be ignored.
*/
fun replyTo(event: String): Boolean {
val message = receivedMessageFor(event) ?: run {
logWarning("bridgeMessageFailedToReply", "message for event '$event' was not received")
return false
}

return reply(message)
}

/**
* Reply to the web with the last received message for a given `event`,
* replacing its `jsonData`.
*
* NOTE: If a message has not been received for the given `event`, the
* reply will be ignored.
*/
fun replyTo(event: String, jsonData: String): Boolean {
val message = receivedMessageFor(event) ?: run {
logWarning("bridgeMessageFailedToReply", "message for event '$event' was not received")
return false
}

return reply(message.replacing(jsonData = jsonData))
}

/**
* Reply to the web with the last received message for a given `event`,
* replacing its `jsonData` with encoded json from the provided `data`
* object.
*
* NOTE: If a message has not been received for the given `event`, the
* reply will be ignored.
*/
inline fun <reified T> replyTo(event: String, data: T): Boolean {
return replyTo(event, jsonData = StradaJsonConverter.toJson(data))
}

private fun reply(message: Message): Boolean {
return delegate.replyWith(message)
}
}
Loading

0 comments on commit 79e6e55

Please sign in to comment.