Skip to content

[HEL-4907] | Flutter SDK log levels#88

Merged
salami merged 3 commits into
mainfrom
feature/HEL-4907-sdkLogLevels
May 5, 2026
Merged

[HEL-4907] | Flutter SDK log levels#88
salami merged 3 commits into
mainfrom
feature/HEL-4907-sdkLogLevels

Conversation

@koyinusa

@koyinusa koyinusa commented May 4, 2026

Copy link
Copy Markdown
Collaborator

Note

Medium Risk
Touches platform-channel plumbing and native initialization/log forwarding behavior on both Android and iOS; mistakes could cause missing logs or runtime method-call errors but no auth/data-path logic changes.

Overview
Adds a new Flutter API setLogLevel(HeliumLogLevel) (with shared enum + method-channel constant) and wires it through the platform interface and method-channel implementation.

Implements native handlers for setLogLevel on Android and iOS to update Helium.config.logLevel, and updates Android’s BridgingLogger to filter forwarded log events by the configured level while preserving the current level when swapping in the bridging logger during setupCore/re-init.

Updates tests/mocks in helium_flutter and helium_stripe to include the new platform method.

Reviewed by Cursor Bugbot for commit a1cce9c. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • New Features

    • Added a public API to set the native SDK log level (off, error, warn, info, debug, trace) and platform-channel support to propagate it to native code.
    • Log forwarding now respects the configured log level so only messages at or above the selected severity are emitted.
  • Tests

    • Added tests and updated mocks to cover the new setLogLevel behavior.

@koyinusa koyinusa requested a review from salami May 4, 2026 13:35
@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a runtime setLogLevel API that forwards a Dart HeliumLogLevel to native (Android/iOS) plugins, updates Helium.config.logLevel, and makes the Android BridgingLogger conditionally forward Flutter log events based on the configured level.

Changes

Set Log Level Flow

Layer / File(s) Summary
Type Definition
packages/helium_flutter/lib/types/helium_log_level.dart
Introduces HeliumLogLevel enum with off(0), error(1), warn(2), info(3), debug(4), trace(5) and rawValue.
Platform Abstraction
packages/helium_flutter/lib/core/helium_flutter_platform.dart
Adds abstract method setLogLevel(HeliumLogLevel level) to the platform interface.
Public API
packages/helium_flutter/lib/helium_flutter.dart
Exports HeliumLogLevel and adds HeliumFlutter.setLogLevel(HeliumLogLevel level) that delegates to the platform instance.
Method-Channel Constant & Client Wiring
packages/helium_flutter/lib/core/const/contants.dart, packages/helium_flutter/lib/core/helium_flutter_method_channel.dart
Adds setLogLevelMethodName constant and HeliumFlutterMethodChannel.setLogLevel which invokes the native method with level.rawValue and logs invocation errors.
Native Handlers
packages/helium_flutter/android/src/main/kotlin/.../HeliumFlutterPlugin.kt, packages/helium_flutter/ios/helium_flutter/.../HeliumFlutterPlugin.swift
Adds "setLogLevel" method-channel case: parses integer arg, maps to native HeliumLogLevel by rawValue, returns "BAD_ARGS"/FlutterError on invalid input, and sets Helium.config.logLevel on success.
Bridging Logger Filtering & Setup
packages/helium_flutter/android/src/main/kotlin/.../BridgingLogger.kt, packages/helium_flutter/android/src/main/kotlin/.../HeliumFlutterPlugin.kt
BridgingLogger log methods (e,w,i,d,v) now guard sendLogEvent(...) by comparing logLevel.rawValue to severity thresholds. Plugin setupCore copies existing Helium.config.logLevel into the bridging logger before installing it.
Tests & Mocks
packages/helium_flutter/test/helium_flutter_test.dart, packages/helium_stripe/test/helium_stripe_test.dart
Mocks implement setLogLevel; test added that iterates HeliumLogLevel.values and calls setLogLevel for each value to ensure calls complete.

Sequence Diagram(s)

sequenceDiagram
    participant Dart as Flutter App (Dart)
    participant Channel as MethodChannel
    participant Plugin as Platform Plugin (Kotlin/Swift)
    participant Core as Native Helium SDK (Helium.config / BridgingLogger)

    Dart->>Channel: invoke setLogLevel(level.rawValue)
    Channel->>Plugin: onMethodCall("setLogLevel", value)
    Plugin->>Plugin: map value -> HeliumLogLevel (validate)
    alt valid
        Plugin->>Core: Helium.config.logLevel = mappedLevel
        Plugin-->>Channel: success (null)
    else invalid
        Plugin-->>Channel: error("BAD_ARGS", "Invalid log level")
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hop from Dart to native land,
Whispering levels with a gentle hand.
From trace to none the logs obey,
Bridging silence or letting play.
A tiny rabbit, logging bright and grand.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[HEL-4907] | Flutter SDK log levels' clearly and specifically describes the main change: adding log level configuration support to the Flutter SDK.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/HEL-4907-sdkLogLevels

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@packages/helium_flutter/android/src/main/kotlin/com/helium/helium_flutter/HeliumFlutterPlugin.kt`:
- Line 443: Replace the deprecated HeliumLogLevel.values() call with
HeliumLogLevel.entries and keep the same lookup logic; specifically update the
expression in HeliumFlutterPlugin.kt that uses
HeliumLogLevel.values().firstOrNull { it.rawValue == value } to use
HeliumLogLevel.entries.firstOrNull { it.rawValue == value } so it compiles
cleanly on Kotlin 1.9+.
- Around line 440-451: The setLogLevel handler updates Helium.config.logLevel
but does not update the already-constructed BridgingLogger instance, so the
bridging layer ignores runtime changes; to fix, make the BridgingLogger a
mutable instance field on the plugin (create a private var
bridgingLoggerInstance: BridgingLogger? set in setupCore where you currently do
bridgingLogger.logLevel = Helium.config.logLevel), and in the "setLogLevel"
handler after assigning Helium.config.logLevel = level also set
bridgingLoggerInstance?.logLevel = level; additionally, when you currently reset
Helium.config.logger = HeliumLogger.Stdout in onDetachedFromEngine and the
resetHelium handler, set bridgingLoggerInstance = null (and/or reassign
Helium.config.logger to bridgingLoggerInstance when reinitializing in setupCore)
so the instance remains the single source of truth for log filtering.

In `@packages/helium_flutter/lib/core/helium_flutter_method_channel.dart`:
- Around line 683-689: The setLogLevel method calls methodChannel.invokeMethod
which returns a Future but currently has no error handling; wrap the invoke call
in defensive async error handling (either await inside a try/catch or attach
.catchError) inside the setLogLevel implementation to handle PlatformException
(e.g., BAD_ARGS) and log/ignore it safely; locate the setLogLevel method in the
HeliumFlutterMethodChannel class and update the invocation of
setLogLevelMethodName with HeliumLogLevel.rawValue to catch and handle any
exceptions rather than letting them propagate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 061e3775-0ca9-4f61-9883-63c038824729

📥 Commits

Reviewing files that changed from the base of the PR and between 285de3b and 024db87.

📒 Files selected for processing (10)
  • packages/helium_flutter/android/src/main/kotlin/com/helium/helium_flutter/BridgingLogger.kt
  • packages/helium_flutter/android/src/main/kotlin/com/helium/helium_flutter/HeliumFlutterPlugin.kt
  • packages/helium_flutter/ios/helium_flutter/Sources/helium_flutter/HeliumFlutterPlugin.swift
  • packages/helium_flutter/lib/core/const/contants.dart
  • packages/helium_flutter/lib/core/helium_flutter_method_channel.dart
  • packages/helium_flutter/lib/core/helium_flutter_platform.dart
  • packages/helium_flutter/lib/helium_flutter.dart
  • packages/helium_flutter/lib/types/helium_log_level.dart
  • packages/helium_flutter/test/helium_flutter_test.dart
  • packages/helium_stripe/test/helium_stripe_test.dart

Comment on lines +440 to +451
"setLogLevel" -> {
val rawValue = (call.arguments as? Number)?.toInt()
val level = rawValue?.let { value ->
HeliumLogLevel.values().firstOrNull { it.rawValue == value }
}
if (level == null) {
result.error("BAD_ARGS", "Invalid log level", null)
} else {
Helium.config.logLevel = level
result.success(null)
}
}

@coderabbitai coderabbitai Bot May 4, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

setLogLevel after initialization doesn't update the BridgingLogger's filtering level on Android.

Helium.config.logLevel = level (line 448) updates the config, but BridgingLogger.logLevel — which controls the >= guards in e/w/i/d/v — is a copied field set once in setupCore (line 587: bridgingLogger.logLevel = Helium.config.logLevel). Subsequent changes to Helium.config.logLevel don't propagate to the already-constructed BridgingLogger, so any setLogLevel call after initialization is silently ignored by the bridging layer on Android. This contradicts the public docstring: "Safe to call before or after initialize; the level is preserved across re-initializations."

The fix below adds an instance variable for the logger so it can be updated at any time:

🐛 Proposed fix
 private var nativeViewFactory: HeliumNativeViewFactory? = null
+private var bridgingLogger: BridgingLogger? = null

In setupCore (around line 586):

-val bridgingLogger = BridgingLogger(channel)
-bridgingLogger.logLevel = Helium.config.logLevel
-Helium.config.logger = bridgingLogger
+val logger = BridgingLogger(channel)
+logger.logLevel = Helium.config.logLevel
+Helium.config.logger = logger
+bridgingLogger = logger

In the setLogLevel handler (line 448):

 Helium.config.logLevel = level
+bridgingLogger?.logLevel = level
 result.success(null)

In both onDetachedFromEngine and the resetHelium handler where Helium.config.logger = HeliumLogger.Stdout is set:

 Helium.config.logger = HeliumLogger.Stdout
+bridgingLogger = null
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"setLogLevel" -> {
val rawValue = (call.arguments as? Number)?.toInt()
val level = rawValue?.let { value ->
HeliumLogLevel.values().firstOrNull { it.rawValue == value }
}
if (level == null) {
result.error("BAD_ARGS", "Invalid log level", null)
} else {
Helium.config.logLevel = level
result.success(null)
}
}
"setLogLevel" -> {
val rawValue = (call.arguments as? Number)?.toInt()
val level = rawValue?.let { value ->
HeliumLogLevel.values().firstOrNull { it.rawValue == value }
}
if (level == null) {
result.error("BAD_ARGS", "Invalid log level", null)
} else {
Helium.config.logLevel = level
bridgingLogger?.logLevel = level
result.success(null)
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@packages/helium_flutter/android/src/main/kotlin/com/helium/helium_flutter/HeliumFlutterPlugin.kt`
around lines 440 - 451, The setLogLevel handler updates Helium.config.logLevel
but does not update the already-constructed BridgingLogger instance, so the
bridging layer ignores runtime changes; to fix, make the BridgingLogger a
mutable instance field on the plugin (create a private var
bridgingLoggerInstance: BridgingLogger? set in setupCore where you currently do
bridgingLogger.logLevel = Helium.config.logLevel), and in the "setLogLevel"
handler after assigning Helium.config.logLevel = level also set
bridgingLoggerInstance?.logLevel = level; additionally, when you currently reset
Helium.config.logger = HeliumLogger.Stdout in onDetachedFromEngine and the
resetHelium handler, set bridgingLoggerInstance = null (and/or reassign
Helium.config.logger to bridgingLoggerInstance when reinitializing in setupCore)
so the instance remains the single source of truth for log filtering.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure?
The native HeliumSdkConfig.logLevel setter writes through to logger.logLevel, which is the live BridgingLogger after setupCore. The line-587 copy is just initial sync; runtime updates flow through the setter.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

The web search didn't find specific Helium paywall SDK internals. Let me look at the actual BridgingLogger.kt file to check whether the logLevel property is read directly from Helium.config.logLevel at call time (live reference) or stored as a copy.


🏁 Script executed:

#!/bin/bash
# Find and display BridgingLogger.kt
fd BridgingLogger.kt --exec cat {}

Length of output: 2202


🏁 Script executed:

#!/bin/bash
# Look for Helium SDK dependency declarations (Gradle files)
fd "build.gradle" --exec grep -l "helium\|tryhelium" {} 2>/dev/null

# Also check settings.gradle / pubspec files
fd "build.gradle.kts" --exec grep -l "helium\|tryhelium" {} 2>/dev/null
fd "pubspec.yaml" --exec cat {} 2>/dev/null | grep -A2 -i helium

# Try to find any AAR or SDK source in the repo
fd -e aar 2>/dev/null | head -20

Length of output: 2000


🏁 Script executed:

#!/bin/bash
# Check the Android build.gradle for the Helium SDK dependency and version
cat ./packages/helium_flutter/android/build.gradle

# Check if there's any Gradle cache or downloaded AAR we can inspect for the config setter
find /root -name "*.kt" -path "*tryhelium*" 2>/dev/null | head -20
find /root -name "HeliumSdkConfig*" 2>/dev/null | head -10
find /root -name "*.aar" -path "*helium*" 2>/dev/null | head -10

Length of output: 1571


🏁 Script executed:

#!/bin/bash
# Try to find the SDK in Gradle cache (common locations)
find /root/.gradle -name "*.jar" -path "*tryhelium*" 2>/dev/null | head -10
find /root/.gradle -name "*.aar" -path "*tryhelium*" 2>/dev/null | head -10
find /home -name "*.jar" -path "*tryhelium*" 2>/dev/null | head -10

# Also check ~/.m2
find ~/.m2 -name "*tryhelium*" 2>/dev/null | head -10

# Try downloading the AAR and inspecting it
# The dependency is com.tryhelium.paywall:core:4.4.1
# Try to locate it via mvn or curl from maven central / jitpack
curl -s -L "https://repo1.maven.org/maven2/com/tryhelium/paywall/core/4.4.1/core-4.4.1-sources.jar" -o /tmp/helium-sources.jar 2>/dev/null && echo "Downloaded from Maven Central" || echo "Not on Maven Central"

Length of output: 30


🏁 Script executed:

#!/bin/bash
# Extract and inspect the sources jar for HeliumSdkConfig
cd /tmp
jar -xf helium-sources.jar 2>/dev/null || unzip -o helium-sources.jar -d helium-sources 2>/dev/null
echo "--- Extracted files ---"
find /tmp/helium-sources -name "*.kt" 2>/dev/null | grep -i "config\|Config" | head -20

# Look specifically for HeliumSdkConfig or HeliumConfig
find /tmp/helium-sources -name "*.kt" 2>/dev/null | xargs grep -l "logLevel" 2>/dev/null | head -10

Length of output: 19377


🏁 Script executed:

#!/bin/bash
# Read HeliumSdkConfig.kt to inspect the logLevel setter
cat /tmp/helium-sources/com/tryhelium/paywall/core/HeliumSdkConfig.kt

echo "---"
# Also check HeliumLogger interface
cat /tmp/helium-sources/com/tryhelium/paywall/core/logger/HeliumLogger.kt
echo "---"
cat /tmp/helium-sources/com/tryhelium/paywall/core/logger/HeliumLogLevel.kt

Length of output: 8772


@koyinusa, you're absolutely right — I apologize for the incorrect review comment. I've now confirmed this directly from the SDK source (com.tryhelium.paywall:core:4.4.1, HeliumSdkConfig.kt):

override var logLevel: HeliumLogLevel
    get() = _logLevelOverride ?: logger.logLevel
    set(value) {
        _logLevelOverride = value
        logger.logLevel = value   // ← writes through to the live logger
    }

The logLevel setter explicitly propagates the new value to the registered logger.logLevel. Since BridgingLogger is the registered logger after setupCore, calling Helium.config.logLevel = level in the setLogLevel handler already updates BridgingLogger.logLevel automatically. No extra instance variable or manual propagation is needed. The code is correct as-is.


✏️ Learnings added
Learnt from: koyinusa
Repo: cloudcaptainai/helium_flutter PR: 88
File: packages/helium_flutter/android/src/main/kotlin/com/helium/helium_flutter/HeliumFlutterPlugin.kt:440-451
Timestamp: 2026-05-04T16:58:28.384Z
Learning: In the Helium Android SDK (com.tryhelium.paywall:core), `HeliumSdkConfig.logLevel`'s setter writes through to `logger.logLevel` automatically (i.e., `logger.logLevel = value` is called inside the setter). This means that after `setupCore` registers a `BridgingLogger` via `Helium.config.logger = bridgingLogger`, any subsequent call to `Helium.config.logLevel = level` (e.g., in the `setLogLevel` method channel handler in `HeliumFlutterPlugin.kt`) will also update `BridgingLogger.logLevel` automatically. No separate instance variable or manual propagation to the BridgingLogger is needed.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: koyinusa
Repo: cloudcaptainai/helium_flutter PR: 80
File: packages/helium_flutter/android/src/main/kotlin/com/helium/helium_flutter/HeliumFlutterPlugin.kt:543-550
Timestamp: 2026-03-30T11:47:09.128Z
Learning: When using the Helium Android SDK in this plugin, prefer calling `Helium.activityTracker?.getCurrentActivity()` directly. Per the SDK’s contract, it already returns a valid Activity that is non-finishing because it performs internal lifecycle/finishing checks, so an additional guard like `.takeIf { !it.isFinishing }` is typically redundant. Only add extra finishing/lifecycle checks if you have evidence that you’re not going through `getCurrentActivity()` (e.g., using a raw `Activity` reference).

Learnt from: koyinusa
Repo: cloudcaptainai/helium_flutter PR: 87
File: packages/helium_flutter/android/src/main/kotlin/com/helium/helium_flutter/HeliumNativeView.kt:79-85
Timestamp: 2026-05-04T12:37:59.512Z
Learning: In the helium_flutter Android plugin (Kotlin), use the correct paywall API based on presentation type: for the embedded/inline paywall view (`HeliumPaywallView`, e.g., calls made on a `HeliumPaywallView` instance), the correct method is `loadPaywall(trigger, navigationDispatcher, customPaywallTraits)`. Do NOT treat `HeliumPaywallView.loadPaywall(...)` as incorrect. `presentPaywall(...)` is a different API on the top-level `Helium` object and is intended for modal paywall presentation only.

Comment thread packages/helium_flutter/lib/core/helium_flutter_method_channel.dart

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/helium_flutter/lib/helium_flutter.dart`:
- Around line 370-371: The public wrapper setLogLevel should not allow
synchronous exceptions to bubble up; change the one-line forwarding in
setLogLevel(HeliumLogLevel level) to call
HeliumFlutterPlatform.instance.setLogLevel(level) inside a try/catch and handle
failures defensively (catch (e, s) and log the error via a safe logger such as
debugPrint or FlutterError.reportError) so the SDK surface won’t propagate
unexpected synchronous exceptions from
HeliumFlutterPlatform.instance.setLogLevel.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d0cc2cbb-e536-4150-9af0-b4e856b5b6c6

📥 Commits

Reviewing files that changed from the base of the PR and between edaba97 and a1cce9c.

📒 Files selected for processing (2)
  • packages/helium_flutter/lib/helium_flutter.dart
  • packages/helium_flutter/lib/types/helium_log_level.dart

Comment thread packages/helium_flutter/lib/helium_flutter.dart
@salami salami merged commit 1a4ecca into main May 5, 2026
4 checks passed
@salami salami deleted the feature/HEL-4907-sdkLogLevels branch May 5, 2026 01:23
@promptless

promptless Bot commented May 5, 2026

Copy link
Copy Markdown

Promptless prepared a documentation update related to this change.

Triggered by PR #88 in helium_flutter

Documented the new setLogLevel() method and HeliumLogLevel enum for the Flutter SDK. Updated the SDK features matrix to show logging levels are now supported.

Review: Flutter SDK log level API documentation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants