Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
1d062fa
add new `isOptOut` method (#190)
luke-belton Aug 8, 2025
0d74dd5
changelog 5.2.0
marandaneto Aug 8, 2025
6a092cf
Update version
marandaneto Aug 8, 2025
fe933e8
chore: update languageVersion and apiVersion from 1.6 to 1.8 on Andro…
marandaneto Aug 11, 2025
46a11b2
changelog 5.3.0
marandaneto Aug 11, 2025
ece79c4
Update version
marandaneto Aug 11, 2025
afa47e7
chore(deps): bump actions/checkout from 4 to 5 (#195)
dependabot[bot] Aug 11, 2025
237ba16
fix: do not render HTML content (#196)
ioannisj Aug 13, 2025
aea5b40
Update version
ioannisj Aug 13, 2025
12aae3c
Update CODEOWNERS (#197)
marandaneto Aug 19, 2025
1f0b96e
chore(deps): bump actions/setup-java from 4 to 5 (#199)
dependabot[bot] Aug 26, 2025
ad2c89a
feat: add surveys support on Android (#198)
ioannisj Aug 28, 2025
42c130d
Update version
ioannisj Aug 28, 2025
be6438d
feat: surveys iOS GA (#202)
ioannisj Aug 29, 2025
0d908b2
Update version
ioannisj Aug 29, 2025
a55f5f4
chore: mask TextField widgets automatically if obscureText is enabled
marandaneto Sep 3, 2025
2a23c84
chore: mask TextField widgets automatically if obscureText is enabled…
marandaneto Sep 3, 2025
3e18976
changelog 5.4.2
marandaneto Sep 3, 2025
bf7a0f6
Update version
marandaneto Sep 3, 2025
31fc43d
fix pr id markdown
marandaneto Sep 3, 2025
3735d47
fix: Android back button wasn't cleaning up the Survey resources (#205)
marandaneto Sep 9, 2025
45944ae
Update version
marandaneto Sep 9, 2025
b8eb0c1
chore: use flutter compile sdk version (#207)
marandaneto Sep 12, 2025
4c0800d
Update version
marandaneto Sep 12, 2025
62a5611
feat: surveys use the new response question id format (#210)
marandaneto Oct 6, 2025
eae110a
changelog 5.6.0
marandaneto Oct 6, 2025
b339e95
Update version
marandaneto Oct 6, 2025
88fddae
feat: add manual error capture (#212)
ioannisj Oct 31, 2025
f71474e
Update version
ioannisj Oct 31, 2025
29d344c
feat: surveys GA (#215)
ioannisj Nov 3, 2025
6da3f30
Update version
ioannisj Nov 3, 2025
3a73971
feat: autocapture unhandled exceptions (#214)
ioannisj Nov 5, 2025
8a6f148
Update version
ioannisj Nov 5, 2025
f8ea523
fix: code comments (#218)
ioannisj Nov 8, 2025
8b2d6b8
Merge upstream/main (5.9.0+) into fork
camillaromagnoli Nov 24, 2025
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
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1 +1 @@
* @marandaneto
* @PostHog/team-mobile
11 changes: 8 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,23 @@ jobs:
build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5

- name: 'Set up Java'
uses: actions/setup-java@v4
uses: actions/setup-java@v5
with:
java-version: 11
java-version: 17
distribution: 'temurin'

- uses: dart-lang/setup-dart@v1
- uses: subosito/flutter-action@v2
with:
channel: 'stable'

- name: Select Xcode version
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable

- name: Install dependencies
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/danger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ jobs:
name: Changelog
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
with:
fetch-depth: 0
- run: npx danger ci
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
# use dart-lang/setup-dart/.github/workflows/publish.yml@v1 when https://github.com/dart-lang/setup-dart/issues/68 is fixed
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: dart-lang/setup-dart@v1
- uses: subosito/flutter-action@v2
with:
Expand Down
63 changes: 63 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,68 @@
## Next

## 5.9.0

- feat: add autocapture exceptions ([#214](https://github.com/PostHog/posthog-flutter/pull/214))
- **Limitations**:
- No Flutter web support
- No native iOS exception capture
- No native C/C++ exception capture on Android (Java/Kotlin only)
- No stacktrace demangling for obfuscated builds ([--obfuscate](https://docs.flutter.dev/deployment/obfuscate) and [--split-debug-info](https://docs.flutter.dev/deployment/obfuscate)) for Dart code and [isMinifyEnabled](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization) for Java/Kotlin code
- No [source code context](/docs/error-tracking/stack-traces)
- No background isolate error capture

## 5.8.0

- feat: surveys GA ([#215](https://github.com/PostHog/posthog-flutter/pull/215))
> Note: Surveys are now enabled by default.

## 5.7.0

- feat: add manual error capture ([#212](https://github.com/PostHog/posthog-flutter/pull/212))
- **Note**: The following features are not yet supported:
- Automatic exception capture
- De-obfuscating stacktraces from obfuscated builds ([--obfuscate](https://docs.flutter.dev/deployment/obfuscate) and [--split-debug-info](https://docs.flutter.dev/deployment/obfuscate))
- [Source code context](/docs/error-tracking/stack-traces) associated with an exception
- Flutter web support
- **BREAKING**: Minimum Dart SDK version bumped to 3.4.0 and Flutter to 3.22.0 (required for `stack_trace` dependency compatibility)

## 5.6.0

- feat: surveys use the new response question id format ([#210](https://github.com/PostHog/posthog-flutter/pull/210))

## 5.5.0

- chore: Android plugin sets compileSdkVersion to flutter.compileSdkVersion instead of hardcoded ([#207](https://github.com/PostHog/posthog-flutter/pull/207))

## 5.4.3

- fix: Android back button wasn't cleaning up the Survey resources ([#205](https://github.com/PostHog/posthog-flutter/pull/205))

## 5.4.2

- fix: mask TextField widgets automatically if obscureText is enabled ([#204](https://github.com/PostHog/posthog-flutter/pull/204))

## 5.4.1

- chore: update posthog-ios dependency to min. 3.31.0 ([#202](https://github.com/PostHog/posthog-flutter/pull/202))

## 5.4.0

- feat: surveys for Android ([#198](https://github.com/PostHog/posthog-flutter/pull/198))
- See how to setup in [Surveys docs](https://posthog.com/docs/surveys/installation?tab=Flutter)

## 5.3.1

- fix: don't render HTML content ([#196](https://github.com/PostHog/posthog-flutter/pull/196))

## 5.3.0

- chore: update languageVersion and apiVersion from 1.6 to 1.8 on Android to be compatible with Kotlin 2.2 ([#193](https://github.com/PostHog/posthog-flutter/pull/193))

## 5.2.0

- feat: add `isOptOut` method to check if the current user is opted out of data capture. ([#190](https://github.com/PostHog/posthog-flutter/pull/190))

## 5.1.0

- feat: surveys for iOS ([#188](https://github.com/PostHog/posthog-flutter/pull/188))
Expand Down
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
.PHONY: formatKotlin formatSwift formatDart checkDart installLinters
.PHONY: format formatKotlin formatSwift formatDart checkDart installLinters test

format: formatSwift formatKotlin formatDart

installLinters:
brew install ktlint
Expand All @@ -19,3 +21,6 @@ checkFormatDart:

analyzeDart:
dart analyze .

test:
flutter test -r expanded
6 changes: 3 additions & 3 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ android {
namespace 'com.posthog.flutter'
}

compileSdkVersion 33
compileSdkVersion flutter.compileSdkVersion

compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
Expand All @@ -54,8 +54,8 @@ android {
dependencies {
testImplementation 'org.jetbrains.kotlin:kotlin-test'
testImplementation 'org.mockito:mockito-core:5.0.0'
// + Version 3.+ and the versions up to 4.0, not including 4.0 and higher
implementation 'com.posthog:posthog-android:3.+'
// + Version 3.25.0 and the versions up to 4.0.0, not including 4.0.0 and higher
implementation 'com.posthog:posthog-android:[3.25.0,4.0.0]'
}

testOptions {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package com.posthog.flutter

import com.posthog.surveys.PostHogDisplayChoiceQuestion
import com.posthog.surveys.PostHogDisplayLinkQuestion
import com.posthog.surveys.PostHogDisplayOpenQuestion
import com.posthog.surveys.PostHogDisplayRatingQuestion
import com.posthog.surveys.PostHogDisplaySurvey
import com.posthog.surveys.PostHogDisplaySurveyQuestion

// Convert the survey object to a map for communication with the Dart layer
// Native platform model -> Map -> Dart model
fun PostHogDisplaySurvey.toMap(): Map<String, Any?> {
val map =
mutableMapOf<String, Any?>(
"id" to id,
"name" to name,
"questions" to
questions.map { question: PostHogDisplaySurveyQuestion ->
val questionMap =
mutableMapOf<String, Any?>(
"question" to question.question,
"isOptional" to question.isOptional,
"id" to question.id,
)

questionMap["questionDescription"] = question.questionDescription
questionMap["questionDescriptionContentType"] = question.questionDescriptionContentType?.value
questionMap["buttonText"] = question.buttonText

// Add question type-specific properties
when (question) {
is PostHogDisplayLinkQuestion -> {
questionMap["type"] = "link"
questionMap["link"] = question.link
}
is PostHogDisplayRatingQuestion -> {
questionMap["type"] = "rating"
questionMap["ratingType"] = question.ratingType.value
questionMap["scaleLowerBound"] = question.scaleLowerBound
questionMap["scaleUpperBound"] = question.scaleUpperBound
questionMap["lowerBoundLabel"] = question.lowerBoundLabel
questionMap["upperBoundLabel"] = question.upperBoundLabel
}
is PostHogDisplayChoiceQuestion -> {
questionMap["type"] = if (question.isMultipleChoice) "multiple_choice" else "single_choice"
questionMap["choices"] = question.choices
questionMap["hasOpenChoice"] = question.hasOpenChoice
questionMap["shuffleOptions"] = question.shuffleOptions
}
else -> {
questionMap["type"] = "open"
}
}

questionMap
},
)

// Add appearance if available
appearance?.let { app ->
map["appearance"] =
mapOf(
"backgroundColor" to app.backgroundColor,
"submitButtonColor" to app.submitButtonColor,
"submitButtonText" to app.submitButtonText,
"submitButtonTextColor" to app.submitButtonTextColor,
"descriptionTextColor" to app.descriptionTextColor,
"ratingButtonColor" to app.ratingButtonColor,
"ratingButtonActiveColor" to app.ratingButtonActiveColor,
"borderColor" to app.borderColor,
"placeholder" to app.placeholder,
"displayThankYouMessage" to app.displayThankYouMessage,
"thankYouMessageHeader" to app.thankYouMessageHeader,
"thankYouMessageDescription" to app.thankYouMessageDescription,
"thankYouMessageDescriptionContentType" to app.thankYouMessageDescriptionContentType?.value,
)
}

// Add dates if available (convert to milliseconds since epoch)
startDate?.let { date ->
map["startDate"] = date.time
}

endDate?.let { date ->
map["endDate"] = date.time
}

return map
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package com.posthog.flutter

import android.os.Handler
import android.os.Looper
import com.posthog.surveys.OnPostHogSurveyClosed
import com.posthog.surveys.OnPostHogSurveyResponse
import com.posthog.surveys.OnPostHogSurveyShown
import com.posthog.surveys.PostHogDisplayChoiceQuestion
import com.posthog.surveys.PostHogDisplayLinkQuestion
import com.posthog.surveys.PostHogDisplayOpenQuestion
import com.posthog.surveys.PostHogDisplayRatingQuestion
import com.posthog.surveys.PostHogDisplaySurvey
import com.posthog.surveys.PostHogDisplaySurveyQuestion
import com.posthog.surveys.PostHogNextSurveyQuestion
import com.posthog.surveys.PostHogSurveyResponse
import com.posthog.surveys.PostHogSurveysDelegate
import io.flutter.plugin.common.MethodChannel

/**
* Separate surveys delegate to avoid class loading issues in the main plugin
*/
class PostHogFlutterSurveysDelegate(
private val channel: MethodChannel,
) : PostHogSurveysDelegate {
private var currentSurvey: PostHogDisplaySurvey? = null
private var onSurveyShownCallback: OnPostHogSurveyShown? = null
private var onSurveyResponseCallback: OnPostHogSurveyResponse? = null
private var onSurveyClosedCallback: OnPostHogSurveyClosed? = null

override fun renderSurvey(
survey: PostHogDisplaySurvey,
onSurveyShown: OnPostHogSurveyShown,
onSurveyResponse: OnPostHogSurveyResponse,
onSurveyClosed: OnPostHogSurveyClosed,
) {
currentSurvey = survey
onSurveyShownCallback = onSurveyShown
onSurveyResponseCallback = onSurveyResponse
onSurveyClosedCallback = onSurveyClosed

// Convert survey to map and send to Flutter
invokeFlutterMethod("showSurvey", survey.toMap())
}

override fun cleanupSurveys() {
currentSurvey = null
onSurveyShownCallback = null
onSurveyResponseCallback = null
onSurveyClosedCallback = null
}

fun handleSurveyAction(
action: String,
payload: Map<String, Any>?,
result: MethodChannel.Result,
) {
val survey = currentSurvey
if (survey == null) {
result.error("InvalidArguments", "No active survey", null)
return
}

when (action) {
"shown" -> {
onSurveyShownCallback?.invoke(survey)
}
"response" -> {
val index = payload?.get("index") as? Int
val responsePayload = payload?.get("response")

if (index != null && responsePayload != null && index < survey.questions.size) {
val question = survey.questions[index]

// Create PostHogSurveyResponse based on question type
val surveyResponse =
when (question) {
is PostHogDisplayLinkQuestion -> {
// For link questions
val boolValue = responsePayload as? Boolean ?: false
PostHogSurveyResponse.Link(boolValue)
}
is PostHogDisplayRatingQuestion -> {
// For rating questions
val ratingValue = responsePayload as? Int
PostHogSurveyResponse.Rating(ratingValue)
}
is PostHogDisplayChoiceQuestion -> {
// For single/multiple choice questions
if (question.isMultipleChoice) {
// Multiple choice: accept array directly from Flutter
val selectedOptions = responsePayload as? List<*>
val stringOptions = selectedOptions?.mapNotNull { it as? String }
PostHogSurveyResponse.MultipleChoice(stringOptions ?: emptyList())
} else {
// Single choice: Flutter sends as a list with one element
val selectedOptions = responsePayload as? List<*>
val firstOption = selectedOptions?.firstOrNull() as? String
PostHogSurveyResponse.SingleChoice(firstOption)
}
}
else -> {
// Default to open text question
val textValue = responsePayload as? String
PostHogSurveyResponse.Text(textValue)
}
}

// Call the callback with the constructed response
onSurveyResponseCallback?.invoke(survey, index, surveyResponse)?.let { nextQuestion ->
result.success(
mapOf(
"nextIndex" to nextQuestion.questionIndex,
"isSurveyCompleted" to nextQuestion.isSurveyCompleted,
),
)
return
}
result.success(null)
return
}
}
"closed" -> {
onSurveyClosedCallback?.invoke(survey)
// Clear the callbacks after survey is closed
currentSurvey = null
onSurveyShownCallback = null
onSurveyResponseCallback = null
onSurveyClosedCallback = null
}
}

result.success(null)
}

/**
* Invoke a Flutter method on the main/UI thread
*/
private fun invokeFlutterMethod(
method: String,
arguments: Any? = null,
) {
Handler(Looper.getMainLooper()).post {
channel.invokeMethod(method, arguments)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package com.posthog.flutter

internal val postHogVersion = "5.1.0"
internal val postHogVersion = "5.9.0"
Loading
Loading