Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions Sources/SkipAndroidBridgeSamples/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"sourceLanguage" : "en",
"strings" : {
"literal" : {

},
"localized" : {
"comment" : "localized string",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Localized into English"
}
}
}
}
},
"version" : "1.0"
}
40 changes: 40 additions & 0 deletions Sources/SkipAndroidBridgeSamples/SkipAndroidBridgeSamples.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import SwiftJNI
import AndroidNative
#endif

let logger: Logger = Logger(subsystem: "SkipAndroidBridgeSamples", category: "Samples")

public let swiftStringConstant = "s"

public func getStringValue(_ string: String?) -> String? {
Expand Down Expand Up @@ -50,6 +52,10 @@ public func localizedStringResourceInterpolatedKey() -> String {
return interpolation.key
}

public func localizedStringValueNS() -> String {
NSLocalizedString("localized", bundle: Bundle.module, comment: "localized string")
}

public func mainActorAsyncValue() async -> String {
await Task.detached {
await MainActorClass().mainActorValue()
Expand All @@ -72,3 +78,37 @@ public func nativeAndroidContextPackageName() throws -> String? {
"MainActor!"
}
}

public typealias MainActorCallback = @MainActor () async -> ()

public struct MainActorCallbacks: @unchecked Sendable {
let callbackMainActor: MainActorCallback

public init(callbackMainActor: @escaping MainActorCallback) {
self.callbackMainActor = callbackMainActor
}
}

// disabling causes a hang when running tests
/*@MainActor*/ public class MainActorCallbackModel {
public static let shared = MainActorCallbackModel()
var callbacks: MainActorCallbacks?

public init(callbacks: MainActorCallbacks? = nil) {
self.callbacks = callbacks
}

public func setCallbacks(_ callbacks: MainActorCallbacks) {
logger.log("setting callbacks on thread: \(Thread.current)")
self.callbacks = callbacks
logger.log("done setting callbacks: \(String(describing: callbacks.callbackMainActor))")
}

public func doSomething() async {
logger.log("calling callbacks on thread: \(Thread.current)")
//try? await Task.sleep(for: .seconds(2))
//logger.log("calling callbacks: done sleeping")
await callbacks?.callbackMainActor()
logger.log("calling callbacks: done")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,41 @@ import SkipBridge
import SkipAndroidBridge
import SkipAndroidBridgeSamples
import XCTest
#if SKIP
import androidx.test.platform.app.InstrumentationRegistry
#endif

let logger: Logger = Logger(subsystem: "SkipAndroidBridgeSamplesTests", category: "Tests")

final class SkipAndroidBridgeSamplesTests: XCTestCase {
static var wasSetupOnce = false
static var wasSetupOnMainThread: Bool? = nil

override func setUp() {
super.setUp()

#if SKIP
if Self.wasSetupOnce {
return // already set up once
}
Self.wasSetupOnce = true

loadPeerLibrary(packageName: "skip-android-bridge", moduleName: "SkipAndroidBridgeSamples")

//try AndroidBridge.initBridge("SkipAndroidBridgeSamples") // doesn't work on Robolectric

let context = ProcessInfo.processInfo.androidContext
try AndroidBridgeBootstrap.initAndroidBridge(filesDir: context.getFilesDir().getAbsolutePath(), cacheDir: context.getCacheDir().getAbsolutePath())
// we need to run this synchronously on the main thread in order for initAndroidBridge to setup the main looper properly
// androidx.test.runner.AndroidJUnitRunner,5,main]
InstrumentationRegistry.getInstrumentation().runOnMainSync {
logger.info("setting up tests on thread=\(java.lang.Thread.currentThread()) vs. mainLooper thread=\(android.os.Looper.getMainLooper().getThread())")
Self.wasSetupOnMainThread = java.lang.Thread.currentThread() == android.os.Looper.getMainLooper().getThread()

let context = ProcessInfo.processInfo.androidContext
try AndroidBridgeBootstrap.initAndroidBridge(filesDir: context.getFilesDir().getAbsolutePath(), cacheDir: context.getCacheDir().getAbsolutePath())
}
#endif

logger.info("setup complete")
}

func testSimpleConstants() {
Expand All @@ -39,7 +62,7 @@ final class SkipAndroidBridgeSamplesTests: XCTestCase {
func testResourceURL() throws {
if isRobolectric {
// unwrap fails on Robolectric
throw XCTSkip("unknown error on Robolectric")
throw XCTSkip("bridged assets not working on Robolectric")
}

let url = try XCTUnwrap(getAssetURL(named: "sample.json"))
Expand Down Expand Up @@ -87,6 +110,10 @@ final class SkipAndroidBridgeSamplesTests: XCTestCase {

func testAndroidContext() throws {
if !isAndroid {
throw XCTSkip("test only runs on Android")
}

if isRobolectric {
throw XCTSkip("no package name on Robolectric")
}

Expand All @@ -99,9 +126,49 @@ final class SkipAndroidBridgeSamplesTests: XCTestCase {
XCTAssertEqual(localizedStringResourceInterpolatedKey(), "interpolated %lld!")
}

// not working yet…
// func testMainActorAsync() async throws {
// let value = await mainActorAsyncValue()
// XCTAssertEqual("MainActor!", value)
// }
func testLocalizedStringNS() throws {
if isRobolectric {
throw XCTSkip("bridged localized strings not working on Robolectric")
}

if !isJava && ProcessInfo.processInfo.environment["XCODE_SCHEME_NAME"] == nil {
// we guard for !isJava because on CI we are running using the OSS Swift toolchain, which doesn't process .xcstrings files
// this _will_ pass when running using Xcode's swift version, but AFAIK there isn't any way to check for that at runtime other than checking for an environment variable that is usually set by Xcode
throw XCTSkip("xcstrings not working on Swift OSS toolchain")
}

XCTAssertEqual(localizedStringValueNS(), "Localized into English")
}

func testMainActorAsync() async throws {
#if SKIP
XCTAssertEqual(true, Self.wasSetupOnMainThread, "test case was not initialized on the main thread")

if isAndroid {
throw XCTSkip("test hangs on Android")
}
#endif

// test hangs on Android emulator for some reason
let value = await mainActorAsyncValue()
XCTAssertEqual("MainActor!", value)
}

func testMainActorCallback() async throws {
var counter = 0
logger.log("setting callbacks")
let callbacks = MainActorCallbacks(callbackMainActor: {
try? await Task.sleep(nanoseconds: 1000)
counter += 1
})
MainActorCallbackModel.shared.setCallbacks(callbacks)
logger.log("setting callbacks: done")

XCTAssertEqual(0, counter)
logger.log("calling callbacks")
await MainActorCallbackModel.shared.doSomething()
logger.log("calling callbacks: done")
_ = callbacks
XCTAssertEqual(1, counter)
}
}