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
24 changes: 5 additions & 19 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ version: 2.1
jobs:
test:
docker:
- image: cirrusci/flutter
- image: ghcr.io/cirruslabs/flutter:stable
steps:
- checkout
- run: flutter doctor
Expand Down Expand Up @@ -41,6 +41,9 @@ jobs:
fi
echo 'export PATH="$HOME/flutter/bin:$PATH"' >> "$BASH_ENV"
- run: flutter --version
# castle_flutter is Swift Package Manager only, so SPM has to be enabled
# before `flutter pub get` — it fails to resolve the plugin otherwise.
- run: flutter config --enable-swift-package-manager
- run: flutter precache --ios
- save_cache:
name: Save Flutter SDK cache
Expand All @@ -63,21 +66,6 @@ jobs:
- ~/.pub-cache
key: pub-cache-v1-{{ checksum "example/pubspec.lock" }}

- run:
name: Build iOS with CocoaPods
working_directory: example
command: |
flutter config --no-enable-swift-package-manager
flutter build ios --no-codesign --debug

- run:
name: Reset example for SPM build
working_directory: example
command: |
flutter clean
rm -rf ios/Pods ios/.symlinks ios/Podfile.lock
flutter pub get

- restore_cache:
name: Restore SPM cache
keys:
Expand All @@ -86,9 +74,7 @@ jobs:
- run:
name: Build iOS with Swift Package Manager
working_directory: example
command: |
flutter config --enable-swift-package-manager
flutter build ios --no-codesign --debug
command: flutter build ios --no-codesign --debug
- save_cache:
name: Save SPM cache
paths:
Expand Down
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
# Changelog

## 4.0.0 (2026-08-11)

This release follows 2.2.1. There is no 3.x release — the version number was moved straight to
4.0.0 so that it matches the major version of the native SDKs the plugin wraps.

- Updated Castle iOS SDK to 4.2.0
- Updated Castle Android SDK to 4.0.2

**Breaking changes**

- Minimum Android API level raised to 26 (Android 8.0), up from 21.
- iOS integration is now **Swift Package Manager only**. The `castle_flutter.podspec` has
been removed. Enable SPM in your app with `flutter config --enable-swift-package-manager`
(on by default from Flutter 3.44).
- Minimum iOS version is 13.0.
- Minimum Flutter version is 3.29.0, minimum Dart SDK 3.4.0.
- Removed methods that no longer exist in the native SDKs.
- Removed `flushIfNeeded(url)`
- Removed `userAgent`
- Removed `queueSize`

**Fixes and enhancements**

- `advertisingIdentifier(id)` is now applied on Android as well, via the new `adIdProvider`
configuration option. It was previously a no-op on Android.
- Fixed `baseURLAllowList` never being applied on iOS. The values are sent from Dart as
strings but were cast to `URL`, so the cast always failed and the option was dropped.

See the [Android](https://docs.castle.io/docs/android-sdk-migrating-from-v3-to-v4) and
[iOS](https://docs.castle.io/docs/ios-sdk-migrating-from-v3-to-v4) native migration guides
for details on the underlying SDK changes.

## 2.2.1 (2026-05-27)
- Fix Android issues ([#60](https://github.com/castle/castle-flutter/issues/57))

Expand Down
31 changes: 26 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,41 @@
[![Pub Version](https://img.shields.io/pub/v/castle_flutter)](https://pub.dev/packages/castle_flutter)
[![CircleCI](https://img.shields.io/circleci/build/github/castle/castle-flutter/master?token=1ac87baf6b68cf1b425e8156ffc0c7497432026f)](https://circleci.com/gh/castle/castle-flutter)

## Usage
## Installation
To use this plugin, add `castle_flutter` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/).

This plugin requires **Swift Package Manager**. CocoaPods is not supported, because the Castle
iOS SDK is not published to CocoaPods from version 4.0.0 onwards. SPM is enabled by default from
Flutter 3.44; on earlier versions enable it before running `flutter pub get`:

```sh
flutter config --enable-swift-package-manager
```

Note that `flutter pub get` fails for every platform, not just iOS, while SPM is disabled.

## Requirements

### Flutter
- Flutter 1.20.0+
- Flutter 3.29.0+ (3.44+ recommended)
- Dart 3.4.0+

### iOS
- iOS 12.0+
- Xcode 10.0+
- iOS 13.0+
- Swift Package Manager

### Android
- Android 5.0
- Android 8.0 (API 26)

## Upgrading from 2.x to 4.0.0

There is no 3.x release. The version number was moved straight to 4.0.0 so that it matches the
major version of the native SDKs the plugin wraps.

Version 4.0.0 updates to Castle Android SDK 4.x and Castle iOS SDK 4.x, which contain breaking
changes of their own. See the [CHANGELOG](CHANGELOG.md), and the
[Android](https://docs.castle.io/docs/android-sdk-migrating-from-v3-to-v4) and
[iOS](https://docs.castle.io/docs/ios-sdk-migrating-from-v3-to-v4) migration guides.

## Usage

Expand Down
12 changes: 5 additions & 7 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
group 'io.castle.flutter.castle_flutter'
version '2.0.14'
version '4.0.0'

buildscript {
ext.kotlin_version = '2.1.20'
ext.kotlin_version = '2.2.20'
repositories {
google()
jcenter()
mavenCentral()
}

Expand All @@ -18,7 +17,6 @@ buildscript {
rootProject.allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}
Expand All @@ -29,13 +27,13 @@ apply plugin: 'kotlin-android'
android {
namespace 'io.castle.flutter.castle_flutter'

compileSdkVersion 35
compileSdkVersion 36

sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
minSdkVersion 21
minSdkVersion 26
}
lintOptions {
disable 'InvalidPackage'
Expand All @@ -59,5 +57,5 @@ android {

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
api 'io.castle.android:castle:3.1.8'
api 'io.castle.android:castle:4.0.2'
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package io.castle.flutter.castle_flutter

import android.app.Application
import android.content.Context
import androidx.annotation.NonNull
import io.castle.android.Castle
import io.castle.android.CastleConfiguration
import io.castle.Castle
import io.castle.Configuration
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
Expand All @@ -21,6 +20,11 @@ class CastleFlutterPlugin: FlutterPlugin, MethodCallHandler {

private lateinit var application: Application

/// Advertising identifier supplied from Dart. Read lazily by the adIdProvider
/// installed at configure time, so it can be set before or after configure.
@Volatile
private var advertisingIdentifier: String? = null

override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "castle")
channel.setMethodCallHandler(this)
Expand All @@ -41,15 +45,9 @@ class CastleFlutterPlugin: FlutterPlugin, MethodCallHandler {
call.method.equals("flush") -> {
this.flush(call, result)
}
call.method.equals("flushIfNeeded") -> {
this.flushIfNeeded(call, result)
}
call.method.equals("reset") -> {
this.reset(result)
}
call.method.equals("baseUrl") -> {
this.baseUrl(call, result)
}
call.method.equals("createRequestToken") -> {
this.createRequestToken(call, result)
}
Expand All @@ -59,12 +57,6 @@ class CastleFlutterPlugin: FlutterPlugin, MethodCallHandler {
call.method.equals("userJwt") -> {
this.userJwt(call, result)
}
call.method.equals("userAgent") -> {
this.userAgent(call, result)
}
call.method.equals("queueSize") -> {
this.queueSize(call, result)
}
call.method.equals("advertisingIdentifier") -> {
this.advertisingIdentifier(call, result)
}
Expand All @@ -76,7 +68,7 @@ class CastleFlutterPlugin: FlutterPlugin, MethodCallHandler {

private fun configure(call: MethodCall, result: Result) {
try {
val builder = CastleConfiguration.Builder()
val builder = Configuration.Builder()

call.argument<String>("publishableKey")?.let {
builder.publishableKey(it)
Expand All @@ -96,6 +88,8 @@ class CastleFlutterPlugin: FlutterPlugin, MethodCallHandler {
call.argument<List<String>>("baseURLAllowList")?.let {
builder.baseURLAllowList(it)
}
builder.adIdProvider { advertisingIdentifier ?: "" }

val configuration = builder.build()
Castle.configure(application, configuration)

Expand All @@ -107,7 +101,7 @@ class CastleFlutterPlugin: FlutterPlugin, MethodCallHandler {

private fun screen(call: MethodCall, result: Result) {
try {
val name = call.argument<String>("name")
val name = call.argument<String>("name") ?: ""
Castle.screen(name)
result.success(true)
} catch (e: Exception) {
Expand All @@ -117,7 +111,7 @@ class CastleFlutterPlugin: FlutterPlugin, MethodCallHandler {

private fun custom(call: MethodCall, result: Result) {
try {
val name = call.argument<String>("name")
val name = call.argument<String>("name") ?: ""
val properties = call.argument<Map<String, Any>>("properties")
if (properties == null) {
Castle.custom(name)
Expand All @@ -139,16 +133,6 @@ class CastleFlutterPlugin: FlutterPlugin, MethodCallHandler {
}
}

private fun flushIfNeeded(call: MethodCall, result: Result) {
try {
val url = call.argument<String>("url")
Castle.flushIfNeeded(url)
result.success(true)
} catch (e: Exception) {
result.error("CastleException", e.localizedMessage, null)
}
}

private fun reset(result: Result) {
try {
Castle.reset()
Expand All @@ -158,18 +142,9 @@ class CastleFlutterPlugin: FlutterPlugin, MethodCallHandler {
}
}

private fun baseUrl(call: MethodCall, result: Result) {
try {
val baseUrl: String = Castle.baseUrl()
result.success(baseUrl)
} catch (e: Exception) {
result.error("CastleException", e.localizedMessage, null)
}
}

private fun createRequestToken(call: MethodCall, result: Result) {
try {
val token: String = Castle.createRequestToken()
val token: String? = Castle.createRequestToken()
result.success(token)
} catch (e: Exception) {
result.error("CastleException", e.localizedMessage, null)
Expand All @@ -187,32 +162,16 @@ class CastleFlutterPlugin: FlutterPlugin, MethodCallHandler {

private fun userJwt(call: MethodCall, result: Result) {
try {
val userJwt = call.argument<String>("userJwt")
Castle.userJwt(userJwt)
val userJwt = call.argument<String>("userJwt") ?: ""
Castle.setUserJwt(userJwt)
result.success(true)
} catch (e: Exception) {
result.error("CastleException", e.localizedMessage, null)
}
}
private fun userAgent(call: MethodCall, result: Result) {
try {
val userAgent: String = Castle.userAgent()
result.success(userAgent)
} catch (e: Exception) {
result.error("CastleException", e.localizedMessage, null)
}
}

private fun queueSize(call: MethodCall, result: Result) {
try {
val queueSize: Int = Castle.queueSize()
result.success(queueSize)
} catch (e: Exception) {
result.error("CastleException", e.localizedMessage, null)
}
}

private fun advertisingIdentifier(call: MethodCall, result: Result) {
advertisingIdentifier = call.argument<String>("identifier")
result.success(true)
}

Expand Down
8 changes: 4 additions & 4 deletions example/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ if (flutterVersionName == null) {
flutterVersionName = '1.0'
}

ext.kotlin_version = '2.1.20'
ext.kotlin_version = '2.2.20'

android {
compileSdkVersion 35
compileSdkVersion 36

namespace 'io.castle.flutter.castle_flutter_example'

Expand All @@ -40,8 +40,8 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "io.castle.flutter.castle_flutter_example"
minSdkVersion flutter.minSdkVersion
targetSdkVersion 33
minSdkVersion 26
targetSdkVersion 36
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
Expand Down
2 changes: 1 addition & 1 deletion example/android/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.9.2" apply false
id "org.jetbrains.kotlin.android" version "2.1.20" apply false
id "org.jetbrains.kotlin.android" version "2.2.20" apply false
}

include ":app"
5 changes: 5 additions & 0 deletions example/ios/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,14 @@ Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
Flutter/ephemeral/
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*

# Swift Package Manager. The plugin pins castle-ios with `exact:`, so
# Package.resolved carries no information that Package.swift doesn't.
**/xcshareddata/swiftpm/

# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
Expand Down
2 changes: 0 additions & 2 deletions example/ios/Flutter/Debug.xcconfig
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
Loading