Skip to content

Commit

Permalink
Merge pull request #48 from superwall/develop
Browse files Browse the repository at this point in the history
1.3.5
  • Loading branch information
ianrumac authored Dec 18, 2024
2 parents 16374b5 + c94a9e6 commit 14c3bce
Show file tree
Hide file tree
Showing 26 changed files with 604 additions and 420 deletions.
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@

The changelog for `Superwall`. Also see the [releases](https://github.com/superwall/Superwall-Flutter/releases) on GitHub.

## 1.3.5

### Enhancements

- Upgrades iOS SDK to 3.12.0 [View iOS SDK release notes](https://github.com/superwall-me/Superwall-iOS/releases/tag/3.12.0)
- Updates Android SDK to 1.5.0 [View Android SDK release notes](https://github.com/superwall-me/Superwall-Android/releases/tag/1.5.0)

### Fixes
- Fixes issue with `PaywallInfoBridge` and other bridges throwing NPE when reattaching to activities from deep sleep.

## 1.3.4

### Enhancements
Expand All @@ -24,7 +34,7 @@ The changelog for `Superwall`. Also see the [releases](https://github.com/superw
### Enhancements

- Upgrades Android SDK to 1.3.0 [View Android SDK release notes](https://github.com/superwall-me/Superwall-Android/releases/tag/1.3.0)
- Upgrades Android SDK to 3.10.1 [View Android SDK release notes](https://github.com/superwall-me/Superwall-iOS/releases/tag/3.10.1)
- Upgrades iOS SDK to 3.10.1 [View iOS SDK release notes](https://github.com/superwall-me/Superwall-iOS/releases/tag/3.10.1)
- Adds `confirmAllAssignments` method to `Superwall` which confirms assignments for all placements and returns an array of all confirmed experiment assignments. Note that the assignments may be different when a placement is registered due to changes in user, placement, or device parameters used in audience filters.

## 1.3.0
Expand Down
3 changes: 2 additions & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ dependencies {
testImplementation 'org.jetbrains.kotlin:kotlin-test'
testImplementation 'org.mockito:mockito-core:5.0.0'

implementation "com.superwall.sdk:superwall-android:1.3.1"
implementation "com.superwall.sdk:superwall-android:1.5.0"
implementation 'com.android.billingclient:billing:6.1.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1'
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,57 +7,70 @@ import com.superwall.superwallkit_flutter.bridges.Communicator
import com.superwall.superwallkit_flutter.bridges.bridgeClass
import com.superwall.superwallkit_flutter.bridges.generateBridgeId
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodChannel.Result
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.filterNotNull
import java.util.concurrent.ConcurrentHashMap
import io.flutter.embedding.android.FlutterActivity
import kotlinx.coroutines.flow.first

class BridgingCreator(val flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) : MethodCallHandler {
class BridgingCreator(
val flutterPluginBinding: suspend () -> FlutterPlugin.FlutterPluginBinding,
val scope: CoroutineScope = CoroutineScope(Dispatchers.IO)
) : MethodCallHandler {
private val instances: MutableMap<String, BridgeInstance> = ConcurrentHashMap()

object Constants { }
object Constants {}

companion object {
private var _shared: BridgingCreator? = null
val shared: BridgingCreator
get() = _shared ?: throw IllegalStateException("BridgingCreator not initialized")

private var _flutterPluginBinding: FlutterPlugin.FlutterPluginBinding? = null
var flutterPluginBinding: FlutterPlugin.FlutterPluginBinding?
get() = _flutterPluginBinding
set(value) {
// Only allow binding to occur once. It appears that if binding is set multiple
// times (due to other SDK interference), we'll lose access to the
// SuperwallKitFlutterPlugin current activity
if (_flutterPluginBinding != null) {
println("WARNING: Attempting to set a flutter plugin binding again.")
return
}
private var _flutterPluginBinding: MutableStateFlow<FlutterPlugin.FlutterPluginBinding?> =
MutableStateFlow(null)

// Store for getter
suspend fun waitForPlugin() : FlutterPlugin.FlutterPluginBinding {
return _flutterPluginBinding.filterNotNull().first()
}
fun setFlutterPlugin(binding: FlutterPlugin.FlutterPluginBinding) {
// Only allow binding to occur once. It appears that if binding is set multiple
// times (due to other SDK interference), we'll lose access to the
// SuperwallKitFlutterPlugin current activity
if (_flutterPluginBinding.value != null) {
println("WARNING: Attempting to set a flutter plugin binding again.")
return
}

_flutterPluginBinding = value
// Store for getter

_flutterPluginBinding?.let {
synchronized(BridgingCreator::class.java) {
val bridge = BridgingCreator(it)
_shared = bridge
val communicator = Communicator(it.binaryMessenger, "SWK_BridgingCreator")
communicator.setMethodCallHandler(bridge)
}

binding?.let {
synchronized(BridgingCreator::class.java) {
val bridge = BridgingCreator({ waitForPlugin() })
_shared = bridge
_flutterPluginBinding.value = binding
val communicator = Communicator(binding.binaryMessenger, "SWK_BridgingCreator")
communicator.setMethodCallHandler(bridge)
}
}

}
}

fun tearDown() {
print("Did tearDown BridgingCreator")
_shared = null
_flutterPluginBinding = null
_flutterPluginBinding.value = null
}

// Generic function to retrieve a bridge instance
fun <T> bridgeInstance(bridgeId: BridgeId): T? {
suspend fun <T> bridgeInstance(bridgeId: BridgeId): T? {
BreadCrumbs.append("BridgingCreator.kt: Searching for $bridgeId among ${instances.count()}: ${instances.toFormattedString()}")
var instance = instances[bridgeId] as? T

Expand All @@ -70,44 +83,58 @@ class BridgingCreator(val flutterPluginBinding: FlutterPlugin.FlutterPluginBindi
}

override fun onMethodCall(call: MethodCall, result: Result) {
when (call.method) {
"createBridgeInstance" -> {
val bridgeId = call.argument<String>("bridgeId")
val initializationArgs = call.argument<Map<String, Any>>("args")

if (bridgeId != null) {
createBridgeInstanceFromBridgeId(bridgeId, initializationArgs)
result.success(null)
} else {
println("WARNING: Unable to create bridge")
result.badArgs(call)
scope.launch {

when (call.method) {
"createBridgeInstance" -> {
val bridgeId = call.argument<String>("bridgeId")
val initializationArgs = call.argument<Map<String, Any>>("args")

if (bridgeId != null) {
createBridgeInstanceFromBridgeId(bridgeId, initializationArgs)
result.success(null)
} else {
println("WARNING: Unable to create bridge")
result.badArgs(call)
}
}

else -> result.notImplemented()
}
else -> result.notImplemented()
}
}

// Create the bridge instance as instructed from Dart
private fun createBridgeInstanceFromBridgeId(bridgeId: BridgeId, initializationArgs: Map<String, Any>?): BridgeInstance {
private suspend fun createBridgeInstanceFromBridgeId(
bridgeId: BridgeId,
initializationArgs: Map<String, Any>?
): BridgeInstance {
// An existing bridge instance might exist if it were created natively, instead of from Dart
val existingBridgeInstance = instances[bridgeId]
existingBridgeInstance?.let {
return it
}

// Create an instance of the bridge
val bridgeInstance = bridgeInitializers[bridgeId.bridgeClass()]?.invoke(flutterPluginBinding.applicationContext, bridgeId, initializationArgs)
val bridgeInstance = bridgeInitializers[bridgeId.bridgeClass()]?.invoke(
flutterPluginBinding().applicationContext,
bridgeId,
initializationArgs
)
bridgeInstance?.let { bridgeInstance ->
instances[bridgeId] = bridgeInstance
bridgeInstance.communicator.setMethodCallHandler(bridgeInstance)
bridgeInstance.communicator().setMethodCallHandler(bridgeInstance)
return bridgeInstance
} ?: run {
throw AssertionError("Unable to find a bridge initializer for ${bridgeId}. Make sure to add to BridgingCreator+Constants.kt.}")
}
}

// Create the bridge instance as instructed from native
fun createBridgeInstanceFromBridgeClass(bridgeClass: BridgeClass, initializationArgs: Map<String, Any>? = null): BridgeInstance {
suspend fun createBridgeInstanceFromBridgeClass(
bridgeClass: BridgeClass,
initializationArgs: Map<String, Any>? = null
): BridgeInstance {
return createBridgeInstanceFromBridgeId(bridgeClass.generateBridgeId(), initializationArgs)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class SuperwallkitFlutterPlugin : FlutterPlugin, ActivityAware {
}

override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
BridgingCreator.flutterPluginBinding = flutterPluginBinding
BridgingCreator.setFlutterPlugin(flutterPluginBinding)
}

override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
Expand Down Expand Up @@ -70,14 +70,14 @@ fun <T> MethodCall.argumentForKey(key: String): T? {
}

// Make sure to provide the key for the bridge (which provides the bridgeId)
fun <T> MethodCall.bridgeInstance(key: String): T? {
suspend fun <T> MethodCall.bridgeInstance(key: String): T? {
BreadCrumbs.append("SuperwallKitFlutterPlugin.kt: Invoke bridgeInstance(key:) on $this. Key is $key")
val bridgeId = this.argument<String>(key) ?: return null
BreadCrumbs.append("SuperwallKitFlutterPlugin.kt: Invoke bridgeInstance(key:) in on $this. Found bridgeId $bridgeId")
return BridgingCreator.shared.bridgeInstance(bridgeId)
}

fun <T> BridgeId.bridgeInstance(): T? {
suspend fun <T> BridgeId.bridgeInstance(): T? {
BreadCrumbs.append("SuperwallKitFlutterPlugin.kt: Invoke bridgeInstance() in on $this")
return BridgingCreator.shared.bridgeInstance(this)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import com.superwall.superwallkit_flutter.BridgingCreator
import com.superwall.superwallkit_flutter.setBridgeId
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import java.util.UUID
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first

typealias BridgeClass = String
typealias BridgeId = String
Expand All @@ -14,19 +19,34 @@ typealias Communicator = MethodChannel
abstract class BridgeInstance(
val context: Context,
val bridgeId: BridgeId,
val initializationArgs: Map<String, Any>? = null
val initializationArgs: Map<String, Any>? = null,
) : MethodChannel.MethodCallHandler {

private val mainScope: CoroutineScope = CoroutineScope(Dispatchers.IO)

companion object {
fun bridgeClass(): BridgeClass {
throw NotImplementedError("Subclasses must implement")
}
}

val communicator: Communicator by lazy {
val communicator = MethodChannel(BridgingCreator.shared.flutterPluginBinding.binaryMessenger, bridgeId)
communicator.setBridgeId(bridgeId);
communicator
private var communicatorFlow: MutableStateFlow<Communicator?> = MutableStateFlow(null)

suspend fun communicator(): Communicator {
synchronized(this@BridgeInstance) {
if (communicatorFlow.value == null) {
mainScope.launch {

val communicator = MethodChannel(
BridgingCreator.shared.flutterPluginBinding().binaryMessenger,
bridgeId
)
communicator.setBridgeId(bridgeId);
communicatorFlow.value = communicator
}
}
}
return communicatorFlow.filterNotNull().first()
}

override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class CompletionBlockProxyBridge(
) : BridgeInstance(context, bridgeId, initializationArgs) {
companion object { fun bridgeClass(): BridgeClass = "CompletionBlockProxyBridge" }

fun callCompletionBlock(value: Any? = null) {
communicator.invokeMethodOnMain("callCompletionBlock", value)
suspend fun callCompletionBlock(value: Any? = null) {
communicator().invokeMethodOnMain("callCompletionBlock", value)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ class ConfigurationStatusConfiguredBridge(context: Context, bridgeId: BridgeId)
}


fun ConfigurationStatus.createBridgeId(): BridgeId {
suspend fun ConfigurationStatus.createBridgeId(): BridgeId {

val bridgeClass = when (this) {
is ConfigurationStatus.Pending -> ConfigurationStatusPendingBridge.bridgeClass()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class ExperimentBridge(
}
}

fun Experiment.createBridgeId(): BridgeId {
suspend fun Experiment.createBridgeId(): BridgeId {
val bridgeInstance = (BridgingCreator.shared.createBridgeInstanceFromBridgeClass(
bridgeClass = ExperimentBridge.bridgeClass(),
initializationArgs = mapOf("experiment" to this)
Expand Down
Loading

0 comments on commit 14c3bce

Please sign in to comment.