Compare commits

...

9 Commits

Author SHA1 Message Date
gem
c69b1d4f8a fix(android): box channel history count as long 2026-05-12 10:41:41 +08:00
gem
74aa08a4b5 chore: bump version to 0.0.42
- Android: sdkcore 1.0.23 → 1.0.24
- iOS: pod version 0.0.17 → 0.0.18, openim_sdk_core_ios 0.17.0 → 0.18.0

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-07 11:37:04 +08:00
gem
39fb9b5cb3 feat(channel): add getChannelHistoryMessages binding
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-07 11:19:10 +08:00
gem
6bcf3de467 no message 2025-12-27 10:43:59 +08:00
gem
c341cb5a7c no message 2025-12-12 16:38:31 +08:00
gem
9b219b92b8 no message 2025-12-11 10:54:08 +08:00
gem
2bdf5a4507 no message 2025-12-10 20:12:42 +08:00
gem
752da45b25 no message 2025-12-10 20:05:45 +08:00
gem
f8afe2c8a5 no message 2025-12-10 19:48:04 +08:00
16 changed files with 776 additions and 724 deletions

View File

@@ -0,0 +1,3 @@
{"file": ".trellis/spec/guides/cross-layer-thinking-guide.md", "reason": "Verify cross-layer numeric type alignment between Flutter Android bridge and SDK core bindings."}
{"file": ".trellis/spec/guides/code-reuse-thinking-guide.md", "reason": "Check whether repetitive numeric forwarding patterns were fixed consistently and safely."}
{"file": ".trellis/tasks/05-12-audit-android-int-long-native-parameter-mismatches-against-sdk-core/prd.md", "reason": "Validation target for confirmed mismatch fixes and audit coverage."}

View File

@@ -0,0 +1,3 @@
{"file": ".trellis/spec/guides/cross-layer-thinking-guide.md", "reason": "Audit Android Flutter bridge numeric boxing against SDK core signatures across Dart -> MethodChannel -> Java -> gomobile bindings."}
{"file": ".trellis/spec/guides/code-reuse-thinking-guide.md", "reason": "Look for repeated Android bridge numeric forwarding patterns that should use typed helpers consistently."}
{"file": ".trellis/tasks/05-12-audit-android-int-long-native-parameter-mismatches-against-sdk-core/prd.md", "reason": "Task scope, confirmed crash, signature findings, and acceptance criteria for the Android int/long mismatch audit."}

View File

@@ -0,0 +1,74 @@
# audit android int-long native parameter mismatches against sdk core
## Goal
Audit the Flutter Android bridge managers against the bound SDK core signatures, find places where Flutter `int` values are forwarded with the wrong boxed Java type (`Integer` vs `Long` / `int32`-style expectations), and fix confirmed mismatch risks to prevent more runtime `ClassCastException` failures.
## What I already know
- A real crash was reproduced in `getChannelHistoryMessages` on Android: `java.lang.Integer cannot be cast to java.lang.Long`.
- The fixed plugin call site was `android/src/main/java/io/openim/flutter_openim_sdk/manager/ChannelManager.java`, where `count` was passed via `value(...)` instead of numeric conversion.
- Flutter side sends Dart `int` values through `MethodChannel`, which arrive on Android as boxed Java numbers.
- This repos Android bridge currently mixes raw `value(methodCall, key)` and `int2long(methodCall, key)` for numeric parameters.
- iOS already uses typed accessors like `methodCall[int:]`, `methodCall[int32:]`, `methodCall[int64:]`.
- SDK core source is available at `/mnt/d/workspace/go/im_dev/openim-sdk-core` for signature comparison.
- Core signature examples already checked:
- `GetChannelHistoryMessages(..., count int, sinceSeq int64)`
- `GetChannelMemberList(..., filter int32, offset int32, count int32)`
- `GetGroupMemberList(..., filter int32, offset int32, count int32)`
- `GetJoinedGroupListPage(..., offset, count int32)`
- `GetGroupMemberListByJoinTimeFilter(..., offset int32, count int32, joinTimeBegin int64, joinTimeEnd int64)`
- `GetFriendListPage(..., offset int32, count int32, filterBlack bool)`
- `GetConversationListSplit(..., offset int, count int)`
- `gomobile`/`gobind` Java bindings map Go `int` and `int64` to Java `long`, while `int32` maps to Java `int`; this is a common source of cross-platform bridge confusion.
## Assumptions (temporary)
- The Android binding layer should match the generated Java APIs expected boxed types exactly enough to avoid reflection/runtime cast failures.
- Some existing `value(...)` calls on numeric args are safe when the generated Java signature expects Java `int`/boxed `Integer`, but unsafe when it expects Java `long`/boxed `Long`.
- The task likely includes both audit and code fixes for confirmed risks, not just reporting.
## Open Questions
- Should this task only fix confirmed Android mismatches found by comparing to SDK core/bound signatures, or also refactor the bridge toward explicit typed helpers (`int32` / `int64`) even for currently-safe call sites?
## Requirements (evolving)
- Compare Android manager numeric forwarding against SDK core signatures and binding behavior.
- Identify confirmed mismatch sites and distinguish them from safe `int32` call sites.
- Fix confirmed mismatch sites with minimal behavior change.
- Summarize findings clearly, including why each changed call needed conversion.
- If a broader pattern is found, capture it in spec/guides to prevent recurrence.
## Acceptance Criteria (evolving)
- [ ] All Android manager numeric parameters that should be passed as Java `Long` are identified and fixed.
- [ ] No confirmed `Integer``Long` mismatch remains in audited Android manager methods.
- [ ] Findings are backed by SDK core signature comparison.
- [ ] Any preventive guidance learned from the audit is written back into `.trellis/spec/`.
## Definition of Done (team quality bar)
- Audit completed across Android managers
- Code changes applied only where signature comparison justifies them
- Relevant docs/specs updated if a reusable lesson is found
- Build/test verification run as feasible for the touched layer
## Out of Scope (explicit)
- Changing SDK core public APIs
- Large redesign of the plugin dispatch/reflection mechanism unless separately required
- iOS behavior changes unless needed for parity documentation
## Technical Notes
- Touched/focus paths:
- `android/src/main/java/io/openim/flutter_openim_sdk/manager/*.java`
- `ios/Classes/Module/*.swift`
- `lib/src/manager/*.dart`
- `/mnt/d/workspace/go/im_dev/openim-sdk-core/open_im_sdk/*.go`
- Initial likely-risk buckets:
- Go `int` / `int64` parameters exposed to Android as Java `long`
- Pagination / cursor params inconsistently forwarded via `value(...)`
- Confirmed fixed bug:
- `ChannelManager.getChannelHistoryMessages`: `count` now uses `int2long(...)`

View File

@@ -0,0 +1,26 @@
{
"id": "audit-android-int-long-native-parameter-mismatches-against-sdk-core",
"name": "audit-android-int-long-native-parameter-mismatches-against-sdk-core",
"title": "audit android int-long native parameter mismatches against sdk core",
"description": "",
"status": "in_progress",
"dev_type": null,
"scope": null,
"package": null,
"priority": "P2",
"creator": "gem",
"assignee": "gem",
"createdAt": "2026-05-12",
"completedAt": null,
"branch": null,
"base_branch": "main",
"worktree_path": null,
"commit": null,
"pr_url": null,
"subtasks": [],
"children": [],
"parent": null,
"relatedFiles": [],
"notes": "",
"meta": {}
}

View File

@@ -1,16 +1,32 @@
group = "io.openim.flutter_openim_sdk"
version = "1.0"
group 'io.openim.flutter_openim_sdk'
version '1.0'
def dir = getCurrentProjectDir()
def getCurrentProjectDir() {
String result = ""
rootProject.allprojects { project ->
if (project.properties.get("name").toString() == "flutter_openim_sdk") {
result = project.properties.get("projectDir").toString()
}
}
return result
}
buildscript {
ext.kotlin_version = '2.0.20'
repositories {
maven { url 'https://maven.aliyun.com/repository/public' }
maven { url 'https://maven.aliyun.com/repository/central' }
maven { url 'https://maven.aliyun.com/nexus/content/repositories/google' }
maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.7.3'
classpath 'com.android.tools.build:gradle:7.3.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@@ -42,21 +58,9 @@ android {
abiFilters "arm64-v8a","x86" // 根据需要添加其他 ABI
}
}
dependencies {
implementation 'io.openim:core-sdk:3.8.3-patch10@aar'
testImplementation("junit:junit:4.13.2")
testImplementation("org.mockito:mockito-core:5.0.0")
}
testOptions {
unitTests.all {
testLogging {
events "passed", "skipped", "failed", "standardOut", "standardError"
outputs.upToDateWhen {false}
showStandardStreams = true
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
@@ -65,5 +69,5 @@ android {
dependencies {
//implementation 'com.openim:sdkcore:1.0.15-local'
implementation 'com.openim:sdkcore:1.0.19'
implementation 'com.openim:sdkcore:1.0.24'
}

View File

@@ -100,4 +100,14 @@ public class ChannelManager extends BaseManager {
jsonValue(methodCall, "userIDs")
);
}
public void getChannelHistoryMessages(MethodCall methodCall, MethodChannel.Result result) {
Open_im_sdk.getChannelHistoryMessages(
new OnBaseListener(result, methodCall),
value(methodCall, "operationID"),
value(methodCall, "channelID"),
int2long(methodCall, "count"),
int2long(methodCall, "sinceSeq")
);
}
}

View File

@@ -45,6 +45,10 @@ android {
}
buildTypes {
debug {
minifyEnabled false
shrinkResources false
}
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.

View File

@@ -1,44 +0,0 @@
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "io.openim.flutter_openim_sdk_example"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "io.openim.flutter_openim_sdk_example"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug")
}
}
}
flutter {
source = "../.."
}

View File

@@ -8,7 +8,7 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application
android:label="flutter_openim_sdk_example"
android:label="com.example.example"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity

View File

@@ -1,6 +0,0 @@
package io.openim.flutter_openim_sdk_example;
import io.flutter.embedding.android.FlutterActivity;
public class MainActivity extends FlutterActivity {
}

View File

@@ -1,21 +0,0 @@
allprojects {
repositories {
google()
mavenCentral()
}
}
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
rootProject.layout.buildDirectory.value(newBuildDir)
subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir)
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register<Delete>("clean") {
delete(rootProject.layout.buildDirectory)
}

View File

@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip

View File

@@ -1,25 +0,0 @@
pluginManagement {
val flutterSdkPath = run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.7.3" apply false
id("org.jetbrains.kotlin.android") version "2.1.0" apply false
}
include(":app")

View File

@@ -17,6 +17,7 @@ public class ChannelManager: BaseServiceManager {
self["getUsersInChannel"] = getUsersInChannel
self["getChannelHistoryMessages"] = getChannelHistoryMessages
self["isJoinChannel"] = isJoinChannel
self["joinChannel"] = joinChannel
self["quitChannel"] = quitChannel
@@ -50,6 +51,10 @@ public class ChannelManager: BaseServiceManager {
methodCall[jsonString: "userIDs"])
}
func getChannelHistoryMessages(methodCall: FlutterMethodCall, result: @escaping FlutterResult) {
Open_im_sdkGetChannelHistoryMessages(BaseCallback(result: result), methodCall[string: "operationID"], methodCall[string: "channelID"], methodCall[int: "count"], methodCall[int64: "sinceSeq"])
}
func isJoinChannel(methodCall: FlutterMethodCall, result: @escaping FlutterResult) {
Open_im_sdkIsJoinChannel(BaseCallback(result: result), methodCall[string: "operationID"], methodCall[string: "channelID"])
}

View File

@@ -4,7 +4,7 @@
#
Pod::Spec.new do |s|
s.name = 'flutter_openim_sdk'
s.version = '0.0.14'
s.version = '0.0.18'
s.summary = 'A new Flutter project.'
s.description = <<-DESC
A new Flutter project.
@@ -19,7 +19,7 @@ A new Flutter project.
#s.ios.vendored_frameworks = 'frameworks/*.xcframework'
#s.vendored_frameworks = 'frameworks/*.xcframework'
s.dependency 'openim_sdk_core_ios','0.14.0'
s.dependency 'openim_sdk_core_ios','0.18.0'
s.static_framework = true
s.library = 'resolv'

View File

@@ -309,6 +309,25 @@ class ChannelManager {
'operationID': Utils.checkOperationID(operationID),
}));
/// Get channel short-term history messages
/// [channelID] Channel ID
/// [count] Number of messages to retrieve
/// [sinceSeq] Internal sequence cursor for pagination (0 for first page)
Future<dynamic> getChannelHistoryMessages({
required String channelID,
int count = 20,
int sinceSeq = 0,
String? operationID,
}) =>
_channel.invokeMethod(
'getChannelHistoryMessages',
_buildParam({
'channelID': channelID,
'count': count,
'sinceSeq': sinceSeq,
'operationID': Utils.checkOperationID(operationID),
}));
static Map _buildParam(Map<String, dynamic> param) {
param["ManagerName"] = "channelManager";
param = Utils.cleanMap(param);