diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e2ff545..0b6a974 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,12 +24,22 @@ jobs: - uses: subosito/flutter-action@v2 with: channel: 'stable' + + - name: SDK format check + run: dart format --set-exit-if-changed ./ + - name: Install dependencies working-directory: ./example run: flutter pub get + - name: Build iOS working-directory: ./example run: flutter build ios --simulator --no-codesign + - name: Build Android working-directory: ./example run: flutter build apk + + - name: Build Web + working-directory: ./example + run: flutter build web diff --git a/.gitignore b/.gitignore index 0f88e40..c41c2fd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,34 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ +.vscode + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ ### Customs ### ios/Flutter/flutter_export_environment.sh .idea/** @@ -599,3 +630,6 @@ lib/example/generated_plugin_registrant.dart .fvm/ posthog_flutter.iml + +# pod files +Podfile.lock diff --git a/.metadata b/.metadata index ea562e9..8d1b6cc 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,33 @@ # This file should be version controlled and should not be manually edited. version: - revision: 20e59316b8b8474554b38493b8ca888794b0234a - channel: stable + revision: "d211f42860350d914a5ad8102f9ec32764dc6d06" + channel: "stable" project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06 + base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06 + - platform: android + create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06 + base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06 + - platform: ios + create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06 + base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06 + - platform: web + create_revision: d211f42860350d914a5ad8102f9ec32764dc6d06 + base_revision: d211f42860350d914a5ad8102f9ec32764dc6d06 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - "lib/main.dart" + - "ios/Runner.xcodeproj/project.pbxproj" diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index e0f15db..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "java.configuration.updateBuildConfiguration": "automatic" -} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 278d493..f13040c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ ## Next +- Migrate to the new SDKs and latest tooling [#70](https://github.com/PostHog/posthog-flutter/pull/70) + - Added missing features such as feature flags payloads, debug, and more + +### Breaking changes + +- Android minSdkVersion 21 +- iOS min version 13.0 +- Flutter min version 3.3.0 +- Upgraded PostHog Android SDK to [v3](https://github.com/PostHog/posthog-android/blob/main/USAGE.md) +- Upgraded PostHog iOS SDK to [v3 preview](https://github.com/PostHog/posthog-ios/blob/main/USAGE.md) +- Upgraded PostHog JS SDK to the latest version +- PostHog Flutter Plugins are written in Kotlin and Swift + +### Acknowledgements + +Thanks @nehemiekoffi for the initial PR! + ## 3.3.0 - Migrate to Java 8 and minSdkVersion 19 [#54](https://github.com/PostHog/posthog-flutter/pull/54) @@ -74,4 +91,4 @@ ## 1.9.0 -- Posthog client library for Flutter is released! +- PostHog client library for Flutter is released! diff --git a/README.md b/README.md index c4d5d23..eaabe1d 100644 --- a/README.md +++ b/README.md @@ -10,22 +10,23 @@ To use this plugin, add `posthog_flutter` as a [dependency in your pubspec.yaml ### Supported methods -| Method | Android | iOS | Web | -| ------------------ | ------- | --- | --- | -| `identify` | X | X | X | -| `capture` | X | X | X | -| `screen` | X | X | X | -| `alias` | X | X | X | -| `getAnonymousId` | X | X | X | -| `reset` | X | X | X | -| `disable` | X | X | | -| `enable` | X | X | | -| `debug` | X\* | X | X | -| `setContext` | X | X | | -| `isFeatureEnabled` | X | X | X | -| `reloadFeatureFlags`| X | X | X | - -\* Debugging must be set as a configuration parameter in `AndroidManifest.xml` (see below). The official posthog library does not offer the debug method for Android. +| Method | Android | iOS | Web | +| ------------------------- | ------- | --- | --- | +| `identify` | X | X | X | +| `capture` | X | X | X | +| `screen` | X | X | X | +| `alias` | X | X | X | +| `getDistinctId` | X | X | X | +| `reset` | X | X | X | +| `disable` | X | X | X | +| `enable` | X | X | X | +| `debug` | X | X | X | +| `register` | X | X | X | +| `register` | X | X | X | +| `reloadFeatureFlags` | X | X | X | +| `getFeatureFlag` | X | X | X | +| `group` | X | X | X | +| `getFeatureFlagPayload` | X | X | X | ### Example @@ -62,7 +63,7 @@ class MyApp extends StatelessWidget { }, ), ), - ), + ) ); } } @@ -80,7 +81,7 @@ Remember that the application lifecycle events won't have any special context se #### AndroidManifest.xml ```xml - + [...] @@ -114,7 +115,7 @@ Remember that the application lifecycle events won't have any special context se ``` -For `debug` mode on iOS, you can use the following snippet: +For `debug` mode on Android, iOS and Web, you can use the following snippet: ```dart PostHog().debug(true); @@ -125,21 +126,21 @@ PostHog().debug(true); ```html - - - example - - - - - + + ... + + + + + ... + ``` -For more informations please check: https://posthog.com/docs/integrations/js-integration +For more informations please check the [docs](https://posthog.com/docs/libraries/js). ## Issues @@ -149,15 +150,6 @@ Please file any issues, bugs, or feature requests in the [GitHub repo](https://g If you wish to contribute a change to this repo, please send a [pull request](https://github.com/posthog/posthog-flutter/pulls). -## Deploying to pub.dev - -- [Developing and publishing packages & plugins](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#publish) -- [Publishing packages](https://dart.dev/tools/pub/publishing) - ## Questions? ### [Join our Slack community.](https://join.slack.com/t/posthogusers/shared_invite/enQtOTY0MzU5NjAwMDY3LTc2MWQ0OTZlNjhkODk3ZDI3NDVjMDE1YjgxY2I4ZjI4MzJhZmVmNjJkN2NmMGJmMzc2N2U3Yjc3ZjI5NGFlZDQ) - - -[pubdev_badge]: https://img.shields.io/pub/v/posthog_flutter -[pubdev_link]: https://pub.dev/packages/posthog_flutter diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..a5744c1 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore index 3e01ddc..41a3212 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -6,5 +6,6 @@ .DS_Store /build /captures +.cxx /.settings diff --git a/android/build.gradle b/android/build.gradle index 8b8ff7b..33be9d5 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,4 +1,8 @@ +group 'com.posthog.posthog_flutter' +version '1.0-SNAPSHOT' + buildscript { + ext.kotlin_version = '1.8.10' repositories { google() mavenCentral() @@ -6,10 +10,11 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:7.4.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } -rootProject.allprojects { +allprojects { repositories { google() mavenCentral() @@ -17,34 +22,50 @@ rootProject.allprojects { } apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' android { - compileSdk 33 - - // Conditional for compatibility with AGP <4.2. if (project.android.hasProperty("namespace")) { namespace 'com.posthog.posthog_flutter' } - defaultConfig { - minSdkVersion 19 + compileSdkVersion 33 - ndk { - // Flutter does not currently support building for x86 Android (See Issue 9253). - abiFilters("armeabi-v7a", "x86_64", "arm64-v8a") - } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 } - lintOptions { - disable 'InvalidPackage' + kotlinOptions { + jvmTarget = '1.8' + languageVersion = "1.6" + apiVersion = "1.6" } - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + test.java.srcDirs += 'src/test/kotlin' + } + + defaultConfig { + minSdkVersion 21 } dependencies { - api 'com.posthog.android:posthog:2.0.3' + testImplementation 'org.jetbrains.kotlin:kotlin-test' + testImplementation 'org.mockito:mockito-core:5.0.0' + implementation 'com.posthog:posthog-android:3.0.1' + } + + testOptions { + unitTests.all { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed", "standardOut", "standardError" + outputs.upToDateWhen { false } + showStandardStreams = true + } + } } } diff --git a/android/gradle.properties b/android/gradle.properties index 2bd6f4f..95b4763 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,2 +1 @@ -org.gradle.jvmargs=-Xmx1536M - +org.gradle.jvmargs=-Xmx4G diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index b7034ea..d813a79 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -1,4 +1,3 @@ - diff --git a/android/src/main/java/com/posthog/posthog_flutter/PosthogFlutterPlugin.java b/android/src/main/java/com/posthog/posthog_flutter/PosthogFlutterPlugin.java deleted file mode 100644 index 56f83f8..0000000 --- a/android/src/main/java/com/posthog/posthog_flutter/PosthogFlutterPlugin.java +++ /dev/null @@ -1,308 +0,0 @@ -package com.posthog.posthog_flutter; - -import static com.posthog.android.PostHog.LogLevel; - -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.os.Bundle; -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.posthog.android.Middleware; -import com.posthog.android.Options; -import com.posthog.android.PostHog; -import com.posthog.android.Properties; -import com.posthog.android.payloads.BasePayload; - -import java.util.HashMap; -import java.util.Map; - -import io.flutter.embedding.engine.plugins.FlutterPlugin; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; -import io.flutter.plugin.common.PluginRegistry; - -/** PosthogFlutterPlugin */ -public class PosthogFlutterPlugin implements MethodCallHandler, FlutterPlugin { - private Context applicationContext; - private MethodChannel methodChannel; - - static HashMap appendToContextMiddleware; - - /** Plugin registration. */ - @SuppressWarnings("deprecation") - public static void registerWith(PluginRegistry.Registrar registrar) { - final PosthogFlutterPlugin instance = new PosthogFlutterPlugin(); - instance.setupChannels(registrar.context(), registrar.messenger()); - } - - @Override - public void onAttachedToEngine(@NonNull FlutterPluginBinding binding) { - setupChannels(binding.getApplicationContext(), binding.getBinaryMessenger()); - } - - private void setupChannels(Context applicationContext, BinaryMessenger messenger) { - try { - methodChannel = new MethodChannel(messenger, "posthogflutter"); - this.applicationContext = applicationContext; - - ApplicationInfo ai = applicationContext.getPackageManager() - .getApplicationInfo(applicationContext.getPackageName(), PackageManager.GET_META_DATA); - - Bundle bundle = ai.metaData; - - String writeKey = bundle.getString("com.posthog.posthog.API_KEY"); - String posthogHost = bundle.getString("com.posthog.posthog.POSTHOG_HOST"); - Boolean captureApplicationLifecycleEvents = bundle.getBoolean("com.posthog.posthog.TRACK_APPLICATION_LIFECYCLE_EVENTS"); - Boolean debug = bundle.getBoolean("com.posthog.posthog.DEBUG", false); - - PostHog.Builder posthogBuilder = new PostHog.Builder(applicationContext, writeKey, posthogHost); - if (captureApplicationLifecycleEvents) { - // Enable this to record certain application events automatically - posthogBuilder.captureApplicationLifecycleEvents(); - } - - if (debug) { - posthogBuilder.logLevel(LogLevel.DEBUG); - } - - // Here we build a middleware that just appends data to the current context. - posthogBuilder.middleware( - new Middleware() { - @Override - public void intercept(Chain chain) { - try { - if (appendToContextMiddleware == null) { - chain.proceed(chain.payload()); - return; - } - - BasePayload payload = chain.payload(); - BasePayload newPayload = payload.toBuilder() - .context(appendToContextMiddleware) - .build(); - - chain.proceed(newPayload); - } catch (Exception e) { - Log.e("PosthogFlutter", e.getMessage()); - chain.proceed(chain.payload()); - } - } - } - ); - - // Set the initialized instance as globally accessible. - // It may throw an exception if we are trying to re-register a singleton PostHog instance. - // This state may happen after the app is popped (back button until the app closes) - // and opened again from the TaskManager. - try { - PostHog.setSingletonInstance(posthogBuilder.build()); - } catch (IllegalStateException e) { - Log.w("PosthogFlutter", e.getMessage()); - } - // register the channel to receive calls - methodChannel.setMethodCallHandler(this); - } catch (Exception e) { - Log.e("PosthogFlutter", e.getMessage()); - } - } - - @Override - public void onDetachedFromEngine(FlutterPluginBinding binding) { } - - @Override - public void onMethodCall(MethodCall call, Result result) { - if(call.method.equals("identify")) { - this.identify(call, result); - } else if (call.method.equals("capture")) { - this.capture(call, result); - } else if (call.method.equals("screen")) { - this.screen(call, result); - } else if (call.method.equals("alias")) { - this.alias(call, result); - } else if (call.method.equals("getAnonymousId")) { - this.anonymousId(result); - } else if (call.method.equals("reset")) { - this.reset(result); - } else if (call.method.equals("setContext")) { - this.setContext(call, result); - } else if (call.method.equals("disable")) { - this.disable(call, result); - } else if (call.method.equals("enable")) { - this.enable(call, result); - } else if (call.method.equals("isFeatureEnabled")) { - this.isFeatureEnabled(call, result); - } else if (call.method.equals("reloadFeatureFlags")) { - this.reloadFeatureFlags(call, result); - } else if (call.method.equals("group")) { - this.group(call, result); - } else { - result.notImplemented(); - } - } - - private Properties hashMapToProperties(HashMap propertiesData) { - Properties properties = new Properties(); - - for(Map.Entry property : propertiesData.entrySet()) { - properties.putValue(property.getKey(), property.getValue()); - } - - return properties; - } - - private void identify(MethodCall call, Result result) { - try { - String userId = call.argument("userId"); - HashMap propertiesData = call.argument("properties"); - HashMap optionsData = call.argument("options"); - Properties properties = this.hashMapToProperties(propertiesData); - Options options = this.buildOptions(optionsData); - PostHog.with(this.applicationContext).identify(userId, properties, options); - - result.success(true); - } catch (Exception e) { - result.error("PosthogFlutterException", e.getLocalizedMessage(), null); - } - } - - private void capture(MethodCall call, Result result) { - try { - String eventName = call.argument("eventName"); - HashMap propertiesData = call.argument("properties"); - HashMap optionsData = call.argument("options"); - Properties properties = this.hashMapToProperties(propertiesData); - Options options = this.buildOptions(optionsData); - - PostHog.with(this.applicationContext).capture(eventName, properties, options); - result.success(true); - } catch (Exception e) { - result.error("PosthogFlutterException", e.getLocalizedMessage(), null); - } - } - - private void screen(MethodCall call, Result result) { - try { - String screenName = call.argument("screenName"); - HashMap propertiesData = call.argument("properties"); - HashMap optionsData = call.argument("options"); - Properties properties = this.hashMapToProperties(propertiesData); - Options options = this.buildOptions(optionsData); - - PostHog.with(this.applicationContext).screen(screenName, properties, options); - result.success(true); - } catch (Exception e) { - result.error("PosthogFlutterException", e.getLocalizedMessage(), null); - } - } - - private void alias(MethodCall call, Result result) { - try { - String alias = call.argument("alias"); - HashMap optionsData = call.argument("options"); - Options options = this.buildOptions(optionsData); - PostHog.with(this.applicationContext).alias(alias, options); - result.success(true); - } catch (Exception e) { - result.error("PosthogFlutterException", e.getLocalizedMessage(), null); - } - } - - private void anonymousId(Result result) { - try { - String anonymousId = PostHog.with(this.applicationContext).getAnonymousId(); - result.success(anonymousId); - } catch (Exception e) { - result.error("PosthogFlutterException", e.getLocalizedMessage(), null); - } - } - - private void reset(Result result) { - try { - PostHog.with(this.applicationContext).reset(); - result.success(true); - } catch (Exception e) { - result.error("PosthogFlutterException", e.getLocalizedMessage(), null); - } - } - - private void setContext(MethodCall call, Result result) { - try { - this.appendToContextMiddleware = call.argument("context"); - result.success(true); - } catch (Exception e) { - result.error("PosthogFlutterException", e.getLocalizedMessage(), null); - } - } - - // There is no enable method at this time for PostHog on Android. - // Instead, we use optOut as a proxy to achieve the same result. - private void enable(MethodCall call, Result result) { - try { - PostHog.with(this.applicationContext).optOut(false); - result.success(true); - } catch (Exception e) { - result.error("PosthogFlutterException", e.getLocalizedMessage(), null); - } - } - - // There is no disable method at this time for PostHog on Android. - // Instead, we use optOut as a proxy to achieve the same result. - private void disable(MethodCall call, Result result) { - try { - PostHog.with(this.applicationContext).optOut(true); - result.success(true); - } catch (Exception e) { - result.error("PosthogFlutterException", e.getLocalizedMessage(), null); - } - } - - private void isFeatureEnabled(MethodCall call, Result result) { - try { - String key = call.argument("key"); - boolean isEnabled = PostHog.with(this.applicationContext).isFeatureEnabled(key); - result.success(isEnabled); - } catch (Exception e) { - result.error("PosthogFlutterException", e.getLocalizedMessage(), null); - } - } - - private void reloadFeatureFlags(MethodCall call, Result result) { - try { - PostHog.with(this.applicationContext).reloadFeatureFlags(); - result.success(true); - } catch (Exception e) { - result.error("PosthogFlutterException", e.getLocalizedMessage(), null); - } - } - - private void group(MethodCall call, Result result) { - try { - String groupType = call.argument("groupType"); - String groupKey = call.argument("groupKey"); - HashMap propertiesData = call.argument("groupProperties"); - Properties properties = this.hashMapToProperties(propertiesData); - - PostHog.with(this.applicationContext).group(groupType, groupKey, properties, null); - result.success(true); - } catch (Exception e) { - result.error("PosthogFlutterException", e.getLocalizedMessage(), null); - } - } - /** - * Enables / disables / sets custom integration properties so Posthog can properly - * interact with 3rd parties, such as Amplitude. - * @see https://posthog.com/docs/connections/sources/catalog/libraries/mobile/android/#selecting-destinations - * @see https://github.com/posthogio/posthog-android/blob/master/posthog/src/main/java/com/posthog.android/Options.java - */ - @SuppressWarnings("unchecked") - private Options buildOptions(HashMap optionsData) { - Options options = new Options(); - return options; - } -} diff --git a/android/src/main/kotlin/com/posthog/posthog_flutter/PosthogFlutterPlugin.kt b/android/src/main/kotlin/com/posthog/posthog_flutter/PosthogFlutterPlugin.kt new file mode 100644 index 0000000..53fa137 --- /dev/null +++ b/android/src/main/kotlin/com/posthog/posthog_flutter/PosthogFlutterPlugin.kt @@ -0,0 +1,289 @@ +package com.posthog.posthog_flutter + +import android.content.Context +import android.content.pm.PackageManager +import android.util.Log +import com.posthog.PostHog +import com.posthog.PostHogConfig +import com.posthog.android.PostHogAndroid +import com.posthog.android.PostHogAndroidConfig +import io.flutter.embedding.engine.plugins.FlutterPlugin +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import io.flutter.plugin.common.MethodChannel.MethodCallHandler +import io.flutter.plugin.common.MethodChannel.Result + +/** PosthogFlutterPlugin */ +class PosthogFlutterPlugin : FlutterPlugin, MethodCallHandler { + /// The MethodChannel that will the communication between Flutter and native Android + /// + /// This local reference serves to register the plugin with the Flutter Engine and unregister it + /// when the Flutter Engine is detached from the Activity + private lateinit var channel: MethodChannel + + override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { + channel = MethodChannel(flutterPluginBinding.binaryMessenger, "posthog_flutter") + + initPlugin(flutterPluginBinding.applicationContext) + + channel.setMethodCallHandler(this) + } + + private fun initPlugin(applicationContext: Context) { + try { + // TODO: replace deprecated method API 33 + val ai = applicationContext.packageManager.getApplicationInfo(applicationContext.packageName, PackageManager.GET_META_DATA) + val bundle = ai.metaData + val apiKey = bundle.getString("com.posthog.posthog.API_KEY", null) + + if (apiKey.isNullOrEmpty()) { + Log.e("PostHog", "com.posthog.posthog.API_KEY is missing!") + return + } + + val host = bundle.getString("com.posthog.posthog.POSTHOG_HOST", PostHogConfig.defaultHost) + val trackApplicationLifecycleEvents = bundle.getBoolean("com.posthog.posthog.TRACK_APPLICATION_LIFECYCLE_EVENTS", false) + val enableDebug = bundle.getBoolean("com.posthog.posthog.DEBUG", false) + + // Init PostHog + val config = PostHogAndroidConfig(apiKey, host).apply { + captureScreenViews = false + captureDeepLinks = false + captureApplicationLifecycleEvents = trackApplicationLifecycleEvents + debug = enableDebug + } + PostHogAndroid.setup(applicationContext, config) + + } catch (e: Throwable) { + e.localizedMessage?.let { Log.e("PostHog", "initPlugin error: $it") } + } + } + + override fun onMethodCall(call: MethodCall, result: Result) { + + when (call.method) { + + "identify" -> { + identify(call, result) + } + + "capture" -> { + capture(call, result) + } + + "screen" -> { + screen(call, result) + } + + "alias" -> { + alias(call, result) + } + + "distinctId" -> { + distinctId(result) + } + + "reset" -> { + reset(result) + } + + "disable" -> { + disable(result) + } + + "enable" -> { + enable(result) + } + + "isFeatureEnabled" -> { + isFeatureEnabled(call, result) + } + + "reloadFeatureFlags" -> { + reloadFeatureFlags(result) + } + + "group" -> { + group(call, result) + } + + "getFeatureFlag" -> { + getFeatureFlag(call, result) + } + + "getFeatureFlagPayload" -> { + getFeatureFlagPayload(call, result) + } + + "register" -> { + register(call, result) + } + "debug" -> { + debug(call, result) + } + + else -> { + result.notImplemented() + } + } + + } + + override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { + channel.setMethodCallHandler(null) + } + + private fun getFeatureFlag(call: MethodCall, result: Result) { + try { + val featureFlagKey: String = call.argument("key")!! + val flag = PostHog.getFeatureFlag(featureFlagKey) + result.success(flag) + } catch (e: Throwable) { + result.error("PosthogFlutterException", e.localizedMessage, null) + } + } + + private fun getFeatureFlagPayload(call: MethodCall, result: Result) { + try { + val featureFlagKey: String = call.argument("key")!! + val flag = PostHog.getFeatureFlagPayload(featureFlagKey) + result.success(flag) + } catch (e: Throwable) { + result.error("PosthogFlutterException", e.localizedMessage, null) + } + } + + private fun identify(call: MethodCall, result: Result) { + try { + val userId: String = call.argument("userId")!! + val userProperties: Map? = call.argument("userProperties") + val userPropertiesSetOnce: Map? = call.argument("userPropertiesSetOnce") + PostHog.identify(userId, userProperties, userPropertiesSetOnce) + result.success(null) + } catch (e: Throwable) { + result.error("PosthogFlutterException", e.localizedMessage, null) + } + } + + private fun capture(call: MethodCall, result: Result) { + try { + val eventName: String = call.argument("eventName")!! + val properties: Map? = call.argument("properties") + PostHog.capture(eventName, properties = properties) + result.success(null) + } catch (e: Throwable) { + result.error("PosthogFlutterException", e.localizedMessage, null) + } + } + + private fun screen(call: MethodCall, result: Result) { + try { + val screenName: String = call.argument("screenName")!! + val properties: Map? = call.argument("properties") + PostHog.screen(screenName, properties) + result.success(null) + } catch (e: Throwable) { + result.error("PosthogFlutterException", e.localizedMessage, null) + } + } + + private fun alias(call: MethodCall, result: Result) { + try { + val alias: String = call.argument("alias")!! + PostHog.alias(alias) + result.success(null) + } catch (e: Throwable) { + result.error("PosthogFlutterException", e.localizedMessage, null) + } + } + + private fun distinctId(result: Result) { + try { + val distinctId: String = PostHog.distinctId() + result.success(distinctId) + } catch (e: Throwable) { + result.error("PosthogFlutterException", e.localizedMessage, null) + } + } + + private fun reset(result: Result) { + try { + PostHog.reset() + result.success(null) + } catch (e: Throwable) { + result.error("PosthogFlutterException", e.localizedMessage, null) + } + } + + private fun enable(result: Result) { + try { + PostHog.optIn() + result.success(null) + } catch (e: Throwable) { + result.error("PosthogFlutterException", e.localizedMessage, null) + } + } + + private fun debug(call: MethodCall, result: Result) { + try { + val debug: Boolean = call.argument("debug")!! + PostHog.debug(debug) + result.success(null) + } catch (e: Throwable) { + result.error("PosthogFlutterException", e.localizedMessage, null) + } + } + + private fun disable(result: Result) { + try { + PostHog.optOut() + result.success(null) + } catch (e: Throwable) { + result.error("PosthogFlutterException", e.localizedMessage, null) + } + } + + private fun isFeatureEnabled(call: MethodCall, result: Result) { + try { + val key: String = call.argument("key")!! + val isEnabled = PostHog.isFeatureEnabled(key) + result.success(isEnabled) + } catch (e: Throwable) { + result.error("PosthogFlutterException", e.localizedMessage, null) + } + } + + private fun reloadFeatureFlags(result: Result) { + try { + PostHog.reloadFeatureFlags() + result.success(null) + } catch (e: Throwable) { + result.error("PosthogFlutterException", e.localizedMessage, null) + } + } + + private fun group(call: MethodCall, result: Result) { + try { + val groupType: String = call.argument("groupType")!! + val groupKey: String = call.argument("groupKey")!! + val groupProperties: Map? = call.argument("groupProperties") + PostHog.group(groupType, groupKey, groupProperties) + result.success(null) + } catch (e: Throwable) { + result.error("PosthogFlutterException", e.localizedMessage, null) + } + } + + private fun register(call: MethodCall, result: Result) { + try { + val key: String = call.argument("key")!! + val value: Any = call.argument("value")!! + PostHog.register(key, value) + result.success(null) + } catch (e: Throwable) { + result.error("PosthogFlutterException", e.localizedMessage, null) + } + } + + +} diff --git a/android/src/test/kotlin/com/posthog/posthog_flutter/PosthogFlutterPluginTest.kt b/android/src/test/kotlin/com/posthog/posthog_flutter/PosthogFlutterPluginTest.kt new file mode 100644 index 0000000..b9a5d12 --- /dev/null +++ b/android/src/test/kotlin/com/posthog/posthog_flutter/PosthogFlutterPluginTest.kt @@ -0,0 +1,43 @@ +package com.posthog.posthog_flutter + +import io.flutter.plugin.common.MethodCall +import io.flutter.plugin.common.MethodChannel +import kotlin.test.Test +import org.mockito.Mockito + +/* + * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation. + * + * Once you have built the plugin's example app, you can run these tests from the command + * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or + * you can run them directly from IDEs that support JUnit such as Android Studio. + */ + +internal class PosthogFlutterPluginTest { + + @Test + fun onMethodCall_identify_returnsExpectedValue() { + val plugin = PosthogFlutterPlugin() + + var arguments = mapOf("userId" to "abc"); + + val call = MethodCall("identify", arguments) + val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java) + plugin.onMethodCall(call, mockResult) + + Mockito.verify(mockResult).success(true) + } + + @Test + fun onMethodCall_alias_returnsExpectedValue() { + val plugin = PosthogFlutterPlugin() + + var arguments = mapOf("alias" to "abc"); + + val call = MethodCall("alias", arguments) + val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java) + plugin.onMethodCall(call, mockResult) + + Mockito.verify(mockResult).success(true) + } +} diff --git a/example/.gitignore b/example/.gitignore index 5c90960..9c37774 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -1,3 +1,47 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release ### Customs ### ios/Flutter/flutter_export_environment.sh .idea/** @@ -590,7 +634,6 @@ MigrationBackup/ # End of https://www.gitignore.io/api/git,dart,flutter,intellij,webstorm,visualstudio # Web related -lib/generated_plugin_registrant.dart android/.project android/.settings diff --git a/example/README.md b/example/README.md index cf86e08..06807be 100644 --- a/example/README.md +++ b/example/README.md @@ -8,9 +8,9 @@ This project is a starting point for a Flutter application. A few resources to get you started if this is your first Flutter project: -- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, samples, guidance on mobile development, and a full API reference. diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 0000000..0d29021 --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/example/android/.gitignore b/example/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/example/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index d0c30c0..9d75ca1 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -1,3 +1,9 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { @@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) { } } -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' @@ -21,29 +22,33 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } -apply plugin: 'com.android.application' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { - compileSdk 33 + namespace "com.example.posthog_flutter_example" + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion - namespace 'com.posthog.posthog_flutter_example' + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } - lintOptions { - disable 'InvalidPackage' + sourceSets { + main.java.srcDirs += 'src/main/kotlin' } defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). - applicationId "com.posthog.posthog_flutter_example" - minSdkVersion 23 + applicationId "com.example.posthog_flutter_example" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion 21 + targetSdkVersion flutter.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName - - ndk { - // Flutter does not currently support building for x86 Android (See Issue 9253). - abiFilters("armeabi-v7a", "x86_64", "arm64-v8a") - } } buildTypes { @@ -53,19 +58,10 @@ android { signingConfig signingConfigs.debug } } - - compileOptions { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 - } - - lintOptions { - // workaround if using AGP 4.0 on release mode - // https://github.com/flutter/flutter/issues/58247 - checkReleaseBuilds false - } } flutter { source '../..' } + +dependencies {} diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..399f698 --- /dev/null +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 4b16ce4..681221c 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,44 +1,51 @@ - + - - + + android:name="io.flutter.embedding.android.NormalTheme" + android:resource="@style/NormalTheme" /> - - + + - - + + - - - - + on if you wish the library to take care of it for you. + Remember that the application lifecycle events won't have any + special context set for you by the time it is initialized. --> + + + + + + + + + + diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/example/android/app/src/main/res/values/styles.xml b/example/android/app/src/main/res/values/styles.xml index 00fa441..cb1ef88 100644 --- a/example/android/app/src/main/res/values/styles.xml +++ b/example/android/app/src/main/res/values/styles.xml @@ -1,8 +1,18 @@ - + + diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml index 9308a7e..399f698 100644 --- a/example/android/app/src/profile/AndroidManifest.xml +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/example/android/build.gradle b/example/android/build.gradle index 0822484..0ae2992 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,11 +1,12 @@ buildscript { + ext.kotlin_version = '1.8.10' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 8e4c009..da87b2c 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -1,4 +1,2 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G android.useAndroidX=true -# remove after migrating to Android SDK v3 -android.enableJetifier=true diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 068cdb2..3c472b9 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 5a2f14f..fd066b7 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -1,15 +1,29 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + } + settings.ext.flutterSdkPath = flutterSdkPath() -def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() + includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") -def plugins = new Properties() -def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') -if (pluginsFile.exists()) { - pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } + + plugins { + id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false + } } -plugins.each { name, path -> - def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() - include ":$name" - project(":$name").projectDir = pluginDirectory +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.4.2" apply false } + +include ":app" diff --git a/example/ios/.gitignore b/example/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/example/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 4f8d4d2..1dc6cf7 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 11.0 + 13.0 diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig index e8efba1..ec97fc6 100644 --- a/example/ios/Flutter/Debug.xcconfig +++ b/example/ios/Flutter/Debug.xcconfig @@ -1,2 +1,2 @@ -#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" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig index 399e934..c4855bf 100644 --- a/example/ios/Flutter/Release.xcconfig +++ b/example/ios/Flutter/Release.xcconfig @@ -1,2 +1,2 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Podfile b/example/ios/Podfile index d207307..3e44f9c 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '11.0' +platform :ios, '13.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -28,7 +28,13 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe flutter_ios_podfile_setup target 'Runner' do + use_frameworks! + use_modular_headers! + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end end post_install do |installer| diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 9677e04..73ef0ac 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -1,9 +1,9 @@ PODS: - Flutter (1.0.0) - - PostHog (2.0.0) + - PostHog (3.0.0-alpha.5) - posthog_flutter (0.0.1): - Flutter - - PostHog (~> 2.0) + - PostHog (~> 3.0.0-alpha.5) DEPENDENCIES: - Flutter (from `Flutter`) @@ -21,9 +21,9 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 - PostHog: 660ec6c9d80cec17b685e148f17f6785a88b597d - posthog_flutter: 080dc6e4638a25095095965517ca84ce85581ef0 + PostHog: bdd91417b5aa887b91c6ab296ba7b442db1fd5b2 + posthog_flutter: 1dda6896bc703ed4d3d55fc462af4beb5ced653c -PODFILE CHECKSUM: 663715e941f9adb426e33bf9376914006f9ea95b +PODFILE CHECKSUM: a57f30d18f102dd3ce366b1d62a55ecbef2158e5 COCOAPODS: 1.13.0 diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index b217473..2da4c07 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -8,16 +8,26 @@ /* Begin PBXBuildFile section */ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; - 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; - 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; + 44360FFD82CB4E1598774129 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 71A85E048F95CF8F7DD26591 /* Pods_RunnerTests.framework */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - E1DE5C5F2C5863A77B75056D /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 87291E13C894FFD338ECC13A /* libPods-Runner.a */; }; + D47ABB53A2C6FA6CCFE97A53 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 754D310CD872B23DBD35C61F /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -32,38 +42,68 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 0F8C85F2C32CED01DCD986C6 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 3E6AC6D8B0D79B1F197E002D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - 7323DBE80965D464291E84CE /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 3D2685378D59A78CEE8D8A79 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 4D79F5A21B97A3297A07B950 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 4DEE06DC491FF69DC74F835B /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 71A85E048F95CF8F7DD26591 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 754D310CD872B23DBD35C61F /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; - 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - 82ABA86F54E02CE7AB202BE4 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; - 87291E13C894FFD338ECC13A /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + DBD76F472DF24F73B87E7514 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + E9767F62DD12EC4FF59D055A /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + 6987D5771A9D4C79D8181F39 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 44360FFD82CB4E1598774129 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EB1CF9000F007C117D /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - E1DE5C5F2C5863A77B75056D /* libPods-Runner.a in Frameworks */, + D47ABB53A2C6FA6CCFE97A53 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 1238B81E29975AECDBFA1382 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 754D310CD872B23DBD35C61F /* Pods_Runner.framework */, + 71A85E048F95CF8F7DD26591 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -81,8 +121,9 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - F4D84D7E780BDF2A69A37554 /* Pods */, - BF5A4B9A6D49EE8CB1A460C4 /* Frameworks */, + 331C8082294A63A400263BE5 /* RunnerTests */, + AE737D5639978A1371024737 /* Pods */, + 1238B81E29975AECDBFA1382 /* Frameworks */, ); sourceTree = ""; }; @@ -90,48 +131,32 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); - name = Products; sourceTree = ""; - }; - 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( - 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, - 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, - 97C146F11CF9000F007C117D /* Supporting Files */, 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, ); path = Runner; sourceTree = ""; }; - 97C146F11CF9000F007C117D /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 97C146F21CF9000F007C117D /* main.m */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - BF5A4B9A6D49EE8CB1A460C4 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 87291E13C894FFD338ECC13A /* libPods-Runner.a */, - ); - name = Frameworks; - sourceTree = ""; - }; - F4D84D7E780BDF2A69A37554 /* Pods */ = { + AE737D5639978A1371024737 /* Pods */ = { isa = PBXGroup; children = ( - 3E6AC6D8B0D79B1F197E002D /* Pods-Runner.debug.xcconfig */, - 7323DBE80965D464291E84CE /* Pods-Runner.release.xcconfig */, - 82ABA86F54E02CE7AB202BE4 /* Pods-Runner.profile.xcconfig */, + E9767F62DD12EC4FF59D055A /* Pods-Runner.debug.xcconfig */, + 0F8C85F2C32CED01DCD986C6 /* Pods-Runner.release.xcconfig */, + 3D2685378D59A78CEE8D8A79 /* Pods-Runner.profile.xcconfig */, + DBD76F472DF24F73B87E7514 /* Pods-RunnerTests.debug.xcconfig */, + 4DEE06DC491FF69DC74F835B /* Pods-RunnerTests.release.xcconfig */, + 4D79F5A21B97A3297A07B950 /* Pods-RunnerTests.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -139,17 +164,37 @@ /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + EDA532F026705CDADBA609D2 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 6987D5771A9D4C79D8181F39 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - C74B05ABD691D7E1DFF02281 /* [CP] Check Pods Manifest.lock */, + 58E041B611947A968BF5FD34 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + F5AFD2FE4BB67AC06F54119D /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -166,16 +211,22 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { + BuildIndependentTargetsInParallel = YES; LastUpgradeCheck = 1430; - ORGANIZATIONNAME = "The Chromium Authors"; + ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 3.2"; + compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( @@ -188,18 +239,25 @@ projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); @@ -224,6 +282,28 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; + 58E041B611947A968BF5FD34 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -239,7 +319,7 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - C74B05ABD691D7E1DFF02281 /* [CP] Check Pods Manifest.lock */ = { + EDA532F026705CDADBA609D2 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -254,28 +334,60 @@ outputFileListPaths = ( ); outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + F5AFD2FE4BB67AC06F54119D /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, - 97C146F31CF9000F007C117D /* main.m in Sources */, + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -337,9 +449,10 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -350,25 +463,77 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 8WKF8J5LV3; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.posthog.PosthogFlutterExample; + PRODUCT_BUNDLE_IDENTIFIER = com.example.posthogFlutterExample; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DBD76F472DF24F73B87E7514 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.posthogFlutterExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4DEE06DC491FF69DC74F835B /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.posthogFlutterExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4D79F5A21B97A3297A07B950 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.posthogFlutterExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -416,7 +581,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -465,9 +630,12 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -478,21 +646,21 @@ baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 8WKF8J5LV3; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.PosthogFlutterExample; + PRODUCT_BUNDLE_IDENTIFIER = com.example.posthogFlutterExample; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; @@ -502,21 +670,20 @@ baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 8WKF8J5LV3; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; - LIBRARY_SEARCH_PATHS = ( + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", - "$(PROJECT_DIR)/Flutter", + "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = com.example.PosthogFlutterExample; + PRODUCT_BUNDLE_IDENTIFIER = com.example.posthogFlutterExample; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; @@ -524,6 +691,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index b52b2e6..87131a0 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -27,8 +27,6 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> - - - - + + + + + + - - + + + + PreviewsEnabled + + + diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png index 3d43d11..dc9ada4 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png index 28c6bf0..7353c41 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png index 2ccbfd9..797d452 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png index f091b6b..6ed2d93 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 4cde121..4cd7b00 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png index d0ef06e..fe73094 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png index dcdc230..321773c 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png index 2ccbfd9..797d452 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png index c8f9ed8..502f463 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index a6d6b86..0ec3034 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index a6d6b86..0ec3034 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index 75b2d16..e9f5fea 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index c4df70d..84ac32a 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png index 6a84f41..8953cba 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png index d0e1f58..0467bf1 100644 Binary files a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist index b59a347..6d13ee0 100644 --- a/example/ios/Runner/Info.plist +++ b/example/ios/Runner/Info.plist @@ -4,6 +4,8 @@ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Posthog Flutter CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -42,11 +44,9 @@ com.posthog.posthog.POSTHOG_HOST https://app.posthog.com com.posthog.posthog.API_KEY - YOUR_API_KEY_GOES_HERE + _6SG-F7I1vCuZ-HdJL3VZQqjBlaSb1_20hDPwqMNnGI com.posthog.posthog.CAPTURE_APPLICATION_LIFECYCLE_EVENTS - UIViewControllerBasedStatusBarAppearance - CADisableMinimumFrameDurationOnPhone UIApplicationSupportsIndirectInputEvents diff --git a/example/ios/Runner/Runner-Bridging-Header.h b/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/example/ios/RunnerTests/RunnerTests.swift b/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 0000000..f699c05 --- /dev/null +++ b/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit +import XCTest + +@testable import posthog_flutter + +// This demonstrates a simple unit test of the Swift portion of this plugin's implementation. +// +// See https://developer.apple.com/documentation/xctest for more information about using XCTest. + +class RunnerTests: XCTestCase { + +} diff --git a/example/lib/main.dart b/example/lib/main.dart index e495439..13ea349 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,124 +1,214 @@ import 'package:flutter/material.dart'; + import 'package:posthog_flutter/posthog_flutter.dart'; void main() { - /// Wait until the platform channel is properly initialized so we can call - /// `setContext` during the app initialization. - WidgetsFlutterBinding.ensureInitialized(); + runApp(const MyApp()); +} - /// The `context.device.token` is a special property. - /// When you define it, setting the context again with no token property (ex: `{}`) - /// has no effect on cleaning up the device token. - /// - /// This is used as an example to allow you to set string-based - /// device tokens, which is the use case when integrating with - /// Firebase Cloud Messaging (FCM). - /// - /// This plugin currently does not support Apple Push Notification service (APNs) - /// tokens, which are binary structures. - /// - /// Aside from this special use case, any other context property that needs - /// to be defined (or re-defined) can be done. - Posthog().setContext({ - 'device': { - 'token': 'testing', - } - }); +class MyApp extends StatefulWidget { + const MyApp({super.key}); - runApp(MyApp()); + @override + State createState() => _MyAppState(); } -class MyApp extends StatelessWidget { +class _MyAppState extends State { + final _posthogFlutterPlugin = Posthog(); + dynamic _result = ""; + + @override + void initState() { + super.initState(); + } + @override Widget build(BuildContext context) { return MaterialApp( - navigatorObservers: [ - PosthogObserver(), - ], home: Scaffold( appBar: AppBar( - title: Text('Posthog example app'), + title: const Text('PostHog Flutter App'), ), - body: Column( - children: [ - Spacer(), - Center( - child: TextButton( - child: Text('CAPTURE ACTION WITH POSTHOG'), - onPressed: () { - Posthog().capture( - eventName: 'ButtonClicked', - properties: { - 'foo': 'bar', - 'number': 1337, - 'clicked': true, + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(16), + child: Center( + child: Column( + children: [ + const Padding( + padding: EdgeInsets.all(8.0), + child: Text( + "Capture", + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton( + onPressed: () { + _posthogFlutterPlugin + .screen(screenName: "my screen", properties: { + "foo": "bar", + }); + }, + child: const Text("Capture Screen"), + ), + ElevatedButton( + onPressed: () { + _posthogFlutterPlugin + .capture(eventName: "eventName", properties: { + "foo": "bar", + }); + }, + child: const Text("Capture Event"), + ), + ], + ), + const Divider(), + const Padding( + padding: EdgeInsets.all(8.0), + child: Text( + "Activity", + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red, + ), + onPressed: () { + _posthogFlutterPlugin.disable(); + }, + child: const Text("Disable Capture"), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green, + ), + onPressed: () { + _posthogFlutterPlugin.enable(); + }, + child: const Text("Enable Capture"), + ), + ], + ), + ElevatedButton( + onPressed: () async { + await _posthogFlutterPlugin.register("foo", "bar"); }, - ); - }, - ), - ), - Spacer(), - Center( - child: TextButton( - child: Text('Update Context'), - onPressed: () { - Posthog().setContext({'custom': 123}); - }, - ), - ), - Spacer(), - Center( - child: TextButton( - child: Text('Clear Context'), - onPressed: () { - Posthog().setContext({}); - }, - ), - ), - Spacer(), - Center( - child: TextButton( - child: Text('Disable'), - onPressed: () async { - await Posthog().disable(); - Posthog().capture( - eventName: 'This event will not be logged', - properties: {}); - }, - ), - ), - Spacer(), - Center( - child: TextButton( - child: Text('Enable'), - onPressed: () async { - await Posthog().enable(); - Posthog().capture( - eventName: 'Enabled capturing events!', properties: {}); - }, - ), - ), - Spacer(), - Center( - child: TextButton( - child: Text('Is Feature enabled'), - onPressed: () async { - Posthog().isFeatureEnabled('feature').then((value) { - print('Feature enabled: $value'); - }); - }, - ), - ), - Spacer(), - Center( - child: TextButton( - child: Text('Reload feature flag'), - onPressed: () async { - Posthog().reloadFeatureFlags(); - }, + child: const Text("Register"), + ), + ElevatedButton( + onPressed: () async { + await _posthogFlutterPlugin.group( + groupType: "theType", + groupKey: "theKey", + groupProperties: { + "foo": "bar", + }); + }, + child: const Text("Group"), + ), + ElevatedButton( + onPressed: () async { + await _posthogFlutterPlugin + .identify(userId: "myId", userProperties: { + "foo": "bar", + }, userPropertiesSetOnce: { + "foo1": "bar1", + }); + }, + child: const Text("Identify"), + ), + ElevatedButton( + onPressed: () async { + await _posthogFlutterPlugin.alias(alias: "myAlias"); + }, + child: const Text("Alias"), + ), + ElevatedButton( + onPressed: () async { + await _posthogFlutterPlugin.debug(true); + }, + child: const Text("Debug"), + ), + ElevatedButton( + onPressed: () async { + await _posthogFlutterPlugin.reset(); + }, + child: const Text("Reset"), + ), + ElevatedButton( + onPressed: () async { + final result = + await _posthogFlutterPlugin.getDistinctId(); + setState(() { + _result = result; + }); + }, + child: const Text("distinctId"), + ), + const Divider(), + const Padding( + padding: EdgeInsets.all(8.0), + child: Text( + "Feature flags", + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + ElevatedButton( + onPressed: () async { + final result = await _posthogFlutterPlugin + .getFeatureFlag("feature_name"); + setState(() { + _result = result; + }); + }, + child: const Text("Get Feature Flag status"), + ), + ElevatedButton( + onPressed: () async { + final result = await _posthogFlutterPlugin + .isFeatureEnabled("feature_name"); + setState(() { + _result = result; + }); + }, + child: const Text("isFeatureEnabled"), + ), + ElevatedButton( + onPressed: () async { + final result = await _posthogFlutterPlugin + .getFeatureFlagPayload("feature_name"); + setState(() { + _result = result; + }); + }, + child: const Text("getFeatureFlagPayload"), + ), + ElevatedButton( + onPressed: () async { + await _posthogFlutterPlugin.reloadFeatureFlags(); + }, + child: const Text("reloadFeatureFlags"), + ), + const Divider(), + const Padding( + padding: EdgeInsets.all(8.0), + child: Text( + "Data result", + style: TextStyle(fontWeight: FontWeight.bold), + ), + ), + Text(_result.toString()), + ], ), ), - ], + ), ), ), ); diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 46eb5fa..603a731 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,17 +1,29 @@ name: posthog_flutter_example description: Demonstrates how to use the posthog_flutter plugin. -publish_to: "none" - -version: 0.0.1+1 +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: "none" # Remove this line if you wish to publish to pub.dev environment: - sdk: '>=2.12.0 <4.0.0' - flutter: '>=1.10.0' + sdk: '>=2.18.0 <4.0.0' + flutter: '>=3.3.0' +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. dependencies: flutter: sdk: flutter + posthog_flutter: + # When depending on this package from a real application you should use: + # posthog_flutter: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. path: ../ # The following adds the Cupertino Icons font to your application. @@ -19,26 +31,35 @@ dependencies: cupertino_icons: ^1.0.3 dev_dependencies: - flutter_test: - sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec -# The following section is specific to Flutter. +# The following section is specific to Flutter packages. flutter: # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true + # To add assets to your application, add an assets section, like this: # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. + # https://flutter.dev/assets-and-images/#resolution-aware + # For details regarding adding assets from package dependencies, see # https://flutter.dev/assets-and-images/#from-packages + # To add custom fonts to your application, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart deleted file mode 100644 index 6283f1c..0000000 --- a/example/test/widget_test.dart +++ /dev/null @@ -1,27 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:posthog_flutter_example/main.dart'; - -void main() { - testWidgets('Verify Platform version', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(MyApp()); - - // Verify that platform version is retrieved. - expect( - find.byWidgetPredicate( - (Widget widget) => - widget is Text && widget.data!.startsWith('Running on:'), - ), - findsOneWidget, - ); - }); -} diff --git a/example/web/icons/Icon-maskable-192.png b/example/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/example/web/icons/Icon-maskable-192.png differ diff --git a/example/web/icons/Icon-maskable-512.png b/example/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/example/web/icons/Icon-maskable-512.png differ diff --git a/example/web/index.html b/example/web/index.html index 24b5696..38a4209 100644 --- a/example/web/index.html +++ b/example/web/index.html @@ -1,14 +1,67 @@ + + + + - example + + + + + + + + + + + + + posthog_flutter_example + + + + + + + - - + + + \ No newline at end of file diff --git a/example/web/manifest.json b/example/web/manifest.json index a688126..38e929b 100644 --- a/example/web/manifest.json +++ b/example/web/manifest.json @@ -1,11 +1,11 @@ { - "name": "myapp", - "short_name": "myapp", + "name": "posthog_flutter_example", + "short_name": "posthog_flutter_example", "start_url": ".", "display": "standalone", "background_color": "#0175C2", "theme_color": "#0175C2", - "description": "A new Flutter project.", + "description": "Demonstrates how to use the posthog_flutter plugin.", "orientation": "portrait-primary", "prefer_related_applications": false, "icons": [ @@ -18,6 +18,18 @@ "src": "icons/Icon-512.png", "sizes": "512x512", "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" } ] -} +} \ No newline at end of file diff --git a/ios/.gitignore b/ios/.gitignore index 710ec6c..034771f 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -34,3 +34,5 @@ Icon? .tags* /Flutter/Generated.xcconfig +/Flutter/ephemeral/ +/Flutter/flutter_export_environment.sh diff --git a/ios/Classes/PosthogFlutterPlugin.h b/ios/Classes/PosthogFlutterPlugin.h deleted file mode 100644 index 80e4514..0000000 --- a/ios/Classes/PosthogFlutterPlugin.h +++ /dev/null @@ -1,4 +0,0 @@ -#import - -@interface PosthogFlutterPlugin : NSObject -@end diff --git a/ios/Classes/PosthogFlutterPlugin.m b/ios/Classes/PosthogFlutterPlugin.m deleted file mode 100644 index d8b3732..0000000 --- a/ios/Classes/PosthogFlutterPlugin.m +++ /dev/null @@ -1,309 +0,0 @@ -#import "PosthogFlutterPlugin.h" -#import -#import -#import -#import - -@implementation PosthogFlutterPlugin -// Contents to be appended to the context -static NSDictionary *_appendToContextMiddleware; - -+ (void)registerWithRegistrar:(NSObject*)registrar { - @try { - NSString *path = [[NSBundle mainBundle] pathForResource: @"Info" ofType: @"plist"]; - NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile: path]; - NSString *writeKey = [dict objectForKey: @"com.posthog.posthog.API_KEY"]; - NSString *posthogHost = [dict objectForKey: @"com.posthog.posthog.POSTHOG_HOST"]; - BOOL captureApplicationLifecycleEvents = [[dict objectForKey: @"com.posthog.posthog.CAPTURE_APPLICATION_LIFECYCLE_EVENTS"] boolValue]; - PHGPostHogConfiguration *configuration = [PHGPostHogConfiguration configurationWithApiKey:writeKey host:posthogHost]; - - // This middleware is responsible for manipulating only the context part of the request, - // leaving all other fields as is. - PHGMiddlewareBlock contextMiddleware = ^(PHGContext *_Nonnull context, PHGMiddlewareNext _Nonnull next) { - // Do not execute if there is nothing to append - if (_appendToContextMiddleware == nil) { - next(context); - return; - } - - // Avoid overriding the context if there is none to override - // (see different payload types here: https://github.com/posthogio/analytics-ios/tree/master/PostHog/Classes/Integrations) - if (![context.payload isKindOfClass:[PHGCapturePayload class]] - && ![context.payload isKindOfClass:[PHGScreenPayload class]] - && ![context.payload isKindOfClass:[PHGIdentifyPayload class]]) { - next(context); - return; - } - - next([context - modify: ^(id _Nonnull ctx) { - if (_appendToContextMiddleware == nil) { - return; - } - - // do not touch it if no payload is present - if (ctx.payload == nil) { - NSLog(@"Cannot update posthog context when the current context payload is empty."); - return; - } - - @try { - // PHGPayload does not offer copyWith* methods, so we have to - // manually test and re-create it for each of its type. - if ([ctx.payload isKindOfClass:[PHGCapturePayload class]]) { - ctx.payload = [[PHGCapturePayload alloc] - initWithEvent: ((PHGCapturePayload*)ctx.payload).event - properties: ((PHGCapturePayload*)ctx.payload).properties - ]; - } else if ([ctx.payload isKindOfClass:[PHGScreenPayload class]]) { - ctx.payload = [[PHGScreenPayload alloc] - initWithName: ((PHGScreenPayload*)ctx.payload).name - properties: ((PHGScreenPayload*)ctx.payload).properties - ]; - } else if ([ctx.payload isKindOfClass:[PHGIdentifyPayload class]]) { - ctx.payload = [[PHGIdentifyPayload alloc] - initWithDistinctId: ((PHGIdentifyPayload*)ctx.payload).distinctId - anonymousId: ((PHGIdentifyPayload*)ctx.payload).anonymousId - properties: ((PHGIdentifyPayload*)ctx.payload).properties - ]; - } - } - @catch (NSException *exception) { - NSLog(@"Could not update posthog context: %@", [exception reason]); - } - }] - ); - }; - - configuration.middlewares = @[ - [[PHGBlockMiddleware alloc] initWithBlock:contextMiddleware] - ]; - - configuration.captureApplicationLifecycleEvents = captureApplicationLifecycleEvents; - - [PHGPostHog setupWithConfiguration:configuration]; - FlutterMethodChannel* channel = [FlutterMethodChannel - methodChannelWithName:@"posthogflutter" - binaryMessenger:[registrar messenger]]; - PosthogFlutterPlugin* instance = [[PosthogFlutterPlugin alloc] init]; - [registrar addMethodCallDelegate:instance channel:channel]; - } - @catch (NSException *exception) { - NSLog(@"%@", [exception reason]); - } -} - -- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { - if ([@"identify" isEqualToString:call.method]) { - [self identify:call result:result]; - } else if ([@"capture" isEqualToString:call.method]) { - [self capture:call result:result]; - } else if ([@"screen" isEqualToString:call.method]) { - [self screen:call result:result]; - } else if ([@"alias" isEqualToString:call.method]) { - [self alias:call result:result]; - } else if ([@"getAnonymousId" isEqualToString:call.method]) { - [self anonymousId:result]; - } else if ([@"reset" isEqualToString:call.method]) { - [self reset:result]; - } else if ([@"disable" isEqualToString:call.method]) { - [self disable:result]; - } else if ([@"enable" isEqualToString:call.method]) { - [self enable:result]; - } else if ([@"debug" isEqualToString:call.method]) { - [self debug:call result:result]; - } else if ([@"setContext" isEqualToString:call.method]) { - [self setContext:call result:result]; - } else if ([@"isFeatureEnabled" isEqualToString:call.method]) { - [self isFeatureEnabled:call result:result]; - } else if ([@"reloadFeatureFlags" isEqualToString:call.method]) { - [self reloadFeatureFlags:call result:result]; - } else if ([@"group" isEqualToString:call.method]) { - [self group:call result:result]; - } else { - result(FlutterMethodNotImplemented); - } -} - -- (void)isFeatureEnabled:(FlutterMethodCall*)call result:(FlutterResult)result { - @try { - NSString *key = call.arguments[@"key"]; - - BOOL *isFeatureEnabledResult = [[PHGPostHog sharedPostHog] isFeatureEnabled: key]; - result([NSNumber numberWithBool:isFeatureEnabledResult]); - } - @catch (NSException *exception) { - result([FlutterError - errorWithCode:@"PosthogFlutterException" - message:[exception reason] - details: nil]); - } -} - -- (void)reloadFeatureFlags:(FlutterMethodCall*)call result:(FlutterResult)result { - @try { - [[PHGPostHog sharedPostHog] reloadFeatureFlags]; - result([NSNumber numberWithBool:YES]); - } - @catch (NSException *exception) { - result([FlutterError errorWithCode:@"PosthogFlutterException" message:[exception reason] details: nil]); - } -} - -- (void)setContext:(FlutterMethodCall*)call result:(FlutterResult)result { - @try { - NSDictionary *context = call.arguments[@"context"]; - _appendToContextMiddleware = context; - result([NSNumber numberWithBool:YES]); - } - @catch (NSException *exception) { - result([FlutterError - errorWithCode:@"PosthogFlutterException" - message:[exception reason] - details: nil]); - } -} - - -- (void)group:(FlutterMethodCall*)call result:(FlutterResult)result { - @try { - NSString *groupType = call.arguments[@"groupType"]; - NSString *groupKey = call.arguments[@"groupKey"]; - NSDictionary *groupProperties = call.arguments[@"groupProperties"]; - [[PHGPostHog sharedPostHog] group: groupType - groupKey: groupKey - properties: groupProperties]; - result([NSNumber numberWithBool:YES]); - } - @catch (NSException *exception) { - result([FlutterError - errorWithCode:@"PosthogFlutterException" - message:[exception reason] - details: nil]); - } -} - -- (void)identify:(FlutterMethodCall*)call result:(FlutterResult)result { - @try { - NSString *userId = call.arguments[@"userId"]; - NSDictionary *properties = call.arguments[@"properties"]; - NSDictionary *options = call.arguments[@"options"]; - [[PHGPostHog sharedPostHog] identify: userId - properties: properties - options: options]; - result([NSNumber numberWithBool:YES]); - } - @catch (NSException *exception) { - result([FlutterError - errorWithCode:@"PosthogFlutterException" - message:[exception reason] - details: nil]); - } -} - -- (void)capture:(FlutterMethodCall*)call result:(FlutterResult)result { - @try { - NSString *eventName = call.arguments[@"eventName"]; - NSDictionary *properties = call.arguments[@"properties"]; - NSDictionary *options = call.arguments[@"options"]; - [[PHGPostHog sharedPostHog] capture: eventName - properties: properties]; - result([NSNumber numberWithBool:YES]); - } - @catch (NSException *exception) { - result([FlutterError errorWithCode:@"PosthogFlutterException" message:[exception reason] details: nil]); - } -} - -- (void)screen:(FlutterMethodCall*)call result:(FlutterResult)result { - @try { - NSString *screenName = call.arguments[@"screenName"]; - NSDictionary *properties = call.arguments[@"properties"]; - NSDictionary *options = call.arguments[@"options"]; - [[PHGPostHog sharedPostHog] screen: screenName - properties: properties]; - result([NSNumber numberWithBool:YES]); - } - @catch (NSException *exception) { - result([FlutterError errorWithCode:@"PosthogFlutterException" message:[exception reason] details: nil]); - } -} - -- (void)alias:(FlutterMethodCall*)call result:(FlutterResult)result { - @try { - NSString *alias = call.arguments[@"alias"]; - NSDictionary *options = call.arguments[@"options"]; - [[PHGPostHog sharedPostHog] alias: alias]; - result([NSNumber numberWithBool:YES]); - } - @catch (NSException *exception) { - result([FlutterError errorWithCode:@"PosthogFlutterException" message:[exception reason] details: nil]); - } -} - -- (void)anonymousId:(FlutterResult)result { - @try { - NSString *anonymousId = [[PHGPostHog sharedPostHog] getAnonymousId]; - result(anonymousId); - } - @catch (NSException *exception) { - result([FlutterError errorWithCode:@"PosthogFlutterException" message:[exception reason] details: nil]); - } -} - -- (void)reset:(FlutterResult)result { - @try { - [[PHGPostHog sharedPostHog] reset]; - result([NSNumber numberWithBool:YES]); - } - @catch (NSException *exception) { - result([FlutterError errorWithCode:@"PosthogFlutterException" message:[exception reason] details: nil]); - } -} - -- (void)disable:(FlutterResult)result { - @try { - [[PHGPostHog sharedPostHog] disable]; - result([NSNumber numberWithBool:YES]); - } - @catch (NSException *exception) { - result([FlutterError errorWithCode:@"PosthogFlutterException" message:[exception reason] details: nil]); - } -} - -- (void)enable:(FlutterResult)result { - @try { - [[PHGPostHog sharedPostHog] enable]; - result([NSNumber numberWithBool:YES]); - } - @catch (NSException *exception) { - result([FlutterError errorWithCode:@"PosthogFlutterException" message:[exception reason] details: nil]); - } -} - -- (void)debug:(FlutterMethodCall*)call result:(FlutterResult)result { - @try { - BOOL enabled = call.arguments[@"debug"]; - [PHGPostHog debug: enabled]; - result([NSNumber numberWithBool:YES]); - } - @catch (NSException *exception) { - result([FlutterError errorWithCode:@"PosthogFlutterException" message:[exception reason] details: nil]); - } -} - -+ (NSDictionary *) mergeDictionary: (NSDictionary *) first with: (NSDictionary *) second { - NSMutableDictionary *result = [first mutableCopy]; - [second enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) { - id contained = [result objectForKey:key]; - if (!contained) { - [result setObject:value forKey:key]; - } else if ([value isKindOfClass:[NSDictionary class]]) { - [result setObject:[PosthogFlutterPlugin mergeDictionary:result[key] with:value] - forKey:key]; - } - }]; - return result; -} - -@end diff --git a/ios/Classes/PosthogFlutterPlugin.swift b/ios/Classes/PosthogFlutterPlugin.swift new file mode 100644 index 0000000..4ffe669 --- /dev/null +++ b/ios/Classes/PosthogFlutterPlugin.swift @@ -0,0 +1,276 @@ +import Flutter +import PostHog +import UIKit + +public class PosthogFlutterPlugin: NSObject, FlutterPlugin { + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "posthog_flutter", binaryMessenger: registrar.messenger()) + let instance = PosthogFlutterPlugin() + initPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public static func initPlugin() { + // Initialise PostHog + let apiKey = Bundle.main.object(forInfoDictionaryKey: "com.posthog.posthog.API_KEY") as? String ?? "" + + if apiKey.isEmpty { + print("[PostHog] com.posthog.posthog.API_KEY is missing!") + return + } + + let host = Bundle.main.object(forInfoDictionaryKey: "com.posthog.posthog.POSTHOG_HOST") as? String ?? PostHogConfig.defaultHost + let postHogCaptureLifecyleEvents = Bundle.main.object(forInfoDictionaryKey: "com.posthog.posthog.TRACK_APPLICATION_LIFECYCLE_EVENTS") as? Bool ?? false + + let config = PostHogConfig( + apiKey: apiKey, + host: host + ) + config.captureApplicationLifecycleEvents = postHogCaptureLifecyleEvents + config.captureScreenViews = false + PostHogSDK.shared.setup(config) + // + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "getFeatureFlag": + getFeatureFlag(call, result: result) + case "isFeatureEnabled": + isFeatureEnabled(call, result: result) + case "getFeatureFlagPayload": + getFeatureFlagPayload(call, result: result) + case "identify": + identify(call, result: result) + case "capture": + capture(call, result: result) + case "screen": + screen(call, result: result) + case "alias": + alias(call, result: result) + case "distinctId": + distinctId(call, result: result) + case "reset": + reset(call, result: result) + case "enable": + enable(call, result: result) + case "disable": + disable(call, result: result) + case "debug": + debug(call, result: result) + case "reloadFeatureFlags": + reloadFeatureFlags(call, result: result) + case "group": + group(call, result: result) + case "register": + register(call, result: result) + default: + result(FlutterMethodNotImplemented) + } + } + + private func getFeatureFlag( + _ call: FlutterMethodCall, + result: @escaping FlutterResult + ) { + if let args = call.arguments as? [String: Any], + let featureFlagKey = args["key"] as? String + { + let value = PostHogSDK.shared.getFeatureFlag(featureFlagKey) + result(value) + } else { + _badArgumentError(result: result) + } + } + + private func isFeatureEnabled( + _ call: FlutterMethodCall, + result: @escaping FlutterResult + ) { + if let args = call.arguments as? [String: Any], + let featureFlagKey = args["key"] as? String + { + let value = PostHogSDK.shared.isFeatureEnabled(featureFlagKey) + result(value) + } else { + _badArgumentError(result: result) + } + } + + private func getFeatureFlagPayload( + _ call: FlutterMethodCall, + result: @escaping FlutterResult + ) { + if let args = call.arguments as? [String: Any], + let featureFlagKey = args["key"] as? String + { + let value = PostHogSDK.shared.getFeatureFlagPayload(featureFlagKey) + result(value) + } else { + _badArgumentError(result: result) + } + } + + private func identify( + _ call: FlutterMethodCall, + result: @escaping FlutterResult + ) { + if let args = call.arguments as? [String: Any], + let userId = args["userId"] as? String + { + let userProperties = args["userProperties"] as? [String: Any] + let userPropertiesSetOnce = args["userPropertiesSetOnce"] as? [String: Any] + + PostHogSDK.shared.identify( + userId, + userProperties: userProperties, + userPropertiesSetOnce: userPropertiesSetOnce + ) + result(nil) + } else { + _badArgumentError(result: result) + } + } + + private func capture( + _ call: FlutterMethodCall, + result: @escaping FlutterResult + ) { + if let args = call.arguments as? [String: Any], + let eventName = args["eventName"] as? String + { + let properties = args["properties"] as? [String: Any] + PostHogSDK.shared.capture( + eventName, + properties: properties + ) + result(nil) + } else { + _badArgumentError(result: result) + } + } + + private func screen( + _ call: FlutterMethodCall, + result: @escaping FlutterResult + ) { + if let args = call.arguments as? [String: Any], + let screenName = args["screenName"] as? String + { + let properties = args["properties"] as? [String: Any] + PostHogSDK.shared.screen( + screenName, + properties: properties + ) + result(nil) + } else { + _badArgumentError(result: result) + } + } + + private func alias( + _ call: FlutterMethodCall, + result: @escaping FlutterResult + ) { + if let args = call.arguments as? [String: Any], + let alias = args["alias"] as? String + { + PostHogSDK.shared.alias(alias) + result(nil) + } else { + _badArgumentError(result: result) + } + } + + private func distinctId( + _: FlutterMethodCall, + result: @escaping FlutterResult + ) { + let val = PostHogSDK.shared.getDistinctId() + result(val) + } + + private func reset( + _: FlutterMethodCall, + result: @escaping FlutterResult + ) { + PostHogSDK.shared.reset() + result(nil) + } + + private func enable( + _: FlutterMethodCall, + result: @escaping FlutterResult + ) { + PostHogSDK.shared.optIn() + result(nil) + } + + private func disable( + _: FlutterMethodCall, + result: @escaping FlutterResult + ) { + PostHogSDK.shared.optOut() + result(nil) + } + + private func debug( + _ call: FlutterMethodCall, + result: @escaping FlutterResult + ) { + if let args = call.arguments as? [String: Any], + let debug = args["debug"] as? Bool + { + PostHogSDK.shared.debug(debug) + result(nil) + } else { + _badArgumentError(result: result) + } + } + + private func reloadFeatureFlags( + _: FlutterMethodCall, + result: @escaping FlutterResult + ) { + PostHogSDK.shared.reloadFeatureFlags() + result(nil) + } + + private func group( + _ call: FlutterMethodCall, + result: @escaping FlutterResult + ) { + if let args = call.arguments as? [String: Any], + let groupType = args["groupType"] as? String, + let groupKey = args["groupKey"] as? String + { + let groupProperties = args["groupProperties"] as? [String: Any] + PostHogSDK.shared.group(type: groupType, key: groupKey, groupProperties: groupProperties) + result(nil) + } else { + _badArgumentError(result: result) + } + } + + private func register( + _ call: FlutterMethodCall, + result: @escaping FlutterResult + ) { + if let args = call.arguments as? [String: Any], + let key = args["key"] as? String, + let value = args["value"] + { + PostHogSDK.shared.register([key: value]) + result(nil) + } else { + _badArgumentError(result: result) + } + } + + // Return bad Arguments error + private func _badArgumentError(result: @escaping FlutterResult) { + result(FlutterError( + code: "PosthogFlutterException", message: "Missing arguments!", details: nil + )) + } +} diff --git a/ios/posthog_flutter.podspec b/ios/posthog_flutter.podspec index 5fc95f7..60665b5 100644 --- a/ios/posthog_flutter.podspec +++ b/ios/posthog_flutter.podspec @@ -1,22 +1,24 @@ # -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint posthog_flutter.podspec` to validate before publishing. # Pod::Spec.new do |s| s.name = 'posthog_flutter' s.version = '0.0.1' s.summary = 'A new flutter plugin project.' s.description = <<-DESC -A new flutter plugin project. +Postog flutter plugin DESC - s.homepage = 'http://posthog.com' + s.homepage = 'http://example.com' s.license = { :file => '../LICENSE' } - s.author = { 'PostHog' => 'hey@posthog.com' } + s.author = { 'Your Company' => 'email@example.com' } s.source = { :path => '.' } s.source_files = 'Classes/**/*' - s.public_header_files = 'Classes/**/*.h' s.dependency 'Flutter' - s.dependency 'PostHog', '~> 2.0' - s.ios.deployment_target = '8.0' + s.dependency 'PostHog', '~> 3.0.0-alpha.5' + s.platform = :ios, '13.0' + # Flutter.framework does not contain a i386 slice. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } + s.swift_version = '5.0' end - diff --git a/lib/posthog_flutter_method_channel.dart b/lib/posthog_flutter_method_channel.dart new file mode 100644 index 0000000..15851cb --- /dev/null +++ b/lib/posthog_flutter_method_channel.dart @@ -0,0 +1,201 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +import 'posthog_flutter_platform_interface.dart'; + +/// An implementation of [PosthogFlutterPlatform] that uses method channels. +class MethodChannelPosthogFlutter extends PosthogFlutterPlatform { + /// The method channel used to interact with the native platform. + final _methodChannel = const MethodChannel('posthog_flutter'); + + @override + Future identify({ + required String userId, + Map? userProperties, + Map? userPropertiesSetOnce, + }) async { + try { + await _methodChannel.invokeMethod('identify', { + 'userId': userId, + if (userProperties != null) 'userProperties': userProperties, + if (userPropertiesSetOnce != null) + 'userPropertiesSetOnce': userPropertiesSetOnce, + }); + } on PlatformException catch (exception) { + _printIfDebug('Exeption on identify: $exception'); + } + } + + @override + Future capture({ + required String eventName, + Map? properties, + }) async { + try { + await _methodChannel.invokeMethod('capture', { + 'eventName': eventName, + if (properties != null) 'properties': properties, + }); + } on PlatformException catch (exception) { + _printIfDebug('Exeption on capture: $exception'); + } + } + + @override + Future screen({ + required String screenName, + Map? properties, + }) async { + try { + await _methodChannel.invokeMethod('screen', { + 'screenName': screenName, + if (properties != null) 'properties': properties, + }); + } on PlatformException catch (exception) { + _printIfDebug('Exeption on screen: $exception'); + } + } + + @override + Future alias({ + required String alias, + }) async { + try { + await _methodChannel.invokeMethod('alias', { + 'alias': alias, + }); + } on PlatformException catch (exception) { + _printIfDebug('Exeption on alias: $exception'); + } + } + + @override + Future getDistinctId() async { + try { + return await _methodChannel.invokeMethod('distinctId'); + } on PlatformException catch (exception) { + _printIfDebug('Exeption on getDistinctId: $exception'); + return ""; + } + } + + @override + Future reset() async { + try { + await _methodChannel.invokeMethod('reset'); + } on PlatformException catch (exception) { + _printIfDebug('Exeption on reset: $exception'); + } + } + + @override + Future disable() async { + try { + await _methodChannel.invokeMethod('disable'); + } on PlatformException catch (exception) { + _printIfDebug('Exeption on disable: $exception'); + } + } + + @override + Future enable() async { + try { + await _methodChannel.invokeMethod('enable'); + } on PlatformException catch (exception) { + _printIfDebug('Exeption on enable: $exception'); + } + } + + @override + Future debug(bool enabled) async { + try { + await _methodChannel.invokeMethod('debug', { + 'debug': enabled, + }); + } on PlatformException catch (exception) { + _printIfDebug('Exeption on debug: $exception'); + } + } + + @override + Future isFeatureEnabled(String key) async { + try { + return await _methodChannel.invokeMethod('isFeatureEnabled', { + 'key': key, + }); + } on PlatformException catch (exception) { + _printIfDebug('Exeption on isFeatureEnabled: $exception'); + return false; + } + } + + @override + Future reloadFeatureFlags() async { + try { + await _methodChannel.invokeMethod('reloadFeatureFlags'); + } on PlatformException catch (exception) { + _printIfDebug('Exeption on reloadFeatureFlags: $exception'); + } + } + + @override + Future group({ + required String groupType, + required String groupKey, + Map? groupProperties, + }) async { + try { + await _methodChannel.invokeMethod('group', { + 'groupType': groupType, + 'groupKey': groupKey, + if (groupProperties != null) 'groupProperties': groupProperties, + }); + } on PlatformException catch (exception) { + _printIfDebug('Exeption on group: $exception'); + } + } + + @override + Future getFeatureFlag({ + required String key, + }) async { + try { + return await _methodChannel.invokeMethod('getFeatureFlag', { + 'key': key, + }); + } on PlatformException catch (exception) { + _printIfDebug('Exeption on getFeatureFlag: $exception'); + return null; + } + } + + @override + Future getFeatureFlagPayload({ + required String key, + }) async { + try { + return await _methodChannel.invokeMethod('getFeatureFlagPayload', { + 'key': key, + }); + } on PlatformException catch (exception) { + _printIfDebug('Exeption on getFeatureFlagPayload: $exception'); + return null; + } + } + + @override + Future register(String key, Object value) async { + try { + return await _methodChannel + .invokeMethod('register', {'key': key, 'value': value}); + } on PlatformException catch (exception) { + _printIfDebug('Exeption on register: $exception'); + } + } + + void _printIfDebug(String message) { + if (kDebugMode) { + print(message); + } + } +} diff --git a/lib/posthog_flutter_platform_interface.dart b/lib/posthog_flutter_platform_interface.dart new file mode 100644 index 0000000..be38527 --- /dev/null +++ b/lib/posthog_flutter_platform_interface.dart @@ -0,0 +1,107 @@ +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'posthog_flutter_method_channel.dart'; + +abstract class PosthogFlutterPlatform extends PlatformInterface { + /// Constructs a PosthogFlutterPlatform. + PosthogFlutterPlatform() : super(token: _token); + + static final Object _token = Object(); + + static PosthogFlutterPlatform _instance = MethodChannelPosthogFlutter(); + + /// The default instance of [PosthogFlutterPlatform] to use. + /// + /// Defaults to [MethodChannelPosthogFlutter]. + static PosthogFlutterPlatform get instance => _instance; + + /// Platform-specific implementations should set this with their own + /// platform-specific class that extends [PosthogFlutterPlatform] when + /// they register themselves. + static set instance(PosthogFlutterPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + Future identify( + {required String userId, + Map? userProperties, + Map? userPropertiesSetOnce}) { + throw UnimplementedError('identify() has not been implemented.'); + } + + Future capture({ + required String eventName, + Map? properties, + }) { + throw UnimplementedError('capture() has not been implemented.'); + } + + Future screen({ + required String screenName, + Map? properties, + }) { + throw UnimplementedError('screen() has not been implemented.'); + } + + Future alias({ + required String alias, + }) { + throw UnimplementedError('alias() has not been implemented.'); + } + + Future getDistinctId() { + throw UnimplementedError('getDistinctId() has not been implemented.'); + } + + Future reset() { + throw UnimplementedError('reset() has not been implemented.'); + } + + Future disable() { + throw UnimplementedError('disable() has not been implemented.'); + } + + Future enable() { + throw UnimplementedError('enable() has not been implemented.'); + } + + Future debug(bool enabled) { + throw UnimplementedError('debug() has not been implemented.'); + } + + Future register(String key, Object value) { + throw UnimplementedError('register() has not been implemented.'); + } + + Future isFeatureEnabled(String key) { + throw UnimplementedError('isFeatureEnabled() has not been implemented.'); + } + + Future reloadFeatureFlags() { + throw UnimplementedError('reloadFeatureFlags() has not been implemented.'); + } + + Future group({ + required String groupType, + required String groupKey, + Map? groupProperties, + }) { + throw UnimplementedError('group() has not been implemented.'); + } + + Future getFeatureFlag({ + required String key, + }) { + throw UnimplementedError('getFeatureFlag() has not been implemented.'); + } + + Future getFeatureFlagPayload({ + required String key, + }) { + throw UnimplementedError( + 'getFeatureFlagPayload() has not been implemented.'); + } + + // TODO: missing unregister, flush, capture with more parameters, close +} diff --git a/lib/src/posthog_web.dart b/lib/posthog_flutter_web.dart similarity index 54% rename from lib/src/posthog_web.dart rename to lib/posthog_flutter_web.dart index e882de1..c8ef9a7 100644 --- a/lib/src/posthog_web.dart +++ b/lib/posthog_flutter_web.dart @@ -1,16 +1,25 @@ +// In order to *not* need this ignore, consider extracting the "web" version +// of your plugin as a separate package, instead of inlining it in the same +// package as the core of your plugin. +// ignore: avoid_web_libraries_in_flutter import 'dart:js'; - import 'package:flutter/services.dart'; -import 'package:flutter_web_plugins/flutter_web_plugins.dart' show Registrar; +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; + +import 'posthog_flutter_platform_interface.dart'; + +/// A web implementation of the PosthogFlutterPlatform of the PosthogFlutter plugin. +class PosthogFlutterWeb extends PosthogFlutterPlatform { + /// Constructs a PosthogFlutterWeb + PosthogFlutterWeb(); -class PosthogWeb { static void registerWith(Registrar registrar) { final MethodChannel channel = MethodChannel( - 'posthogflutter', + 'posthog_flutter', const StandardMethodCodec(), registrar, ); - final PosthogWeb instance = PosthogWeb(); + final PosthogFlutterWeb instance = PosthogFlutterWeb(); channel.setMethodCallHandler(instance.handleMethodCall); } @@ -18,9 +27,12 @@ class PosthogWeb { final analytics = JsObject.fromBrowserObject(context['posthog']); switch (call.method) { case 'identify': + final userProperties = call.arguments['userProperties']; + final userPropertiesSetOnce = call.arguments['userPropertiesSetOnce']; analytics.callMethod('identify', [ call.arguments['userId'], - JsObject.jsify(call.arguments['properties']), + JsObject.jsify(userProperties), + JsObject.jsify(userPropertiesSetOnce), ]); break; case 'capture': @@ -30,9 +42,14 @@ class PosthogWeb { ]); break; case 'screen': + final properties = call.arguments['properties'] ?? {}; + final screenName = call.arguments['screenName']; + if (screenName != null) { + properties['\$screen_name'] = screenName; + } analytics.callMethod('capture', [ - call.arguments['screenName'], - JsObject.jsify(call.arguments['properties']), + '\$screen', + JsObject.jsify(properties), ]); break; case 'alias': @@ -40,9 +57,9 @@ class PosthogWeb { call.arguments['alias'], ]); break; - case 'getAnonymousId': - final anonymousId = analytics.callMethod('get_distinct_id'); - return anonymousId; + case 'distinctId': + final distinctId = analytics.callMethod('get_distinct_id'); + return distinctId; case 'reset': analytics.callMethod('reset'); break; @@ -72,6 +89,22 @@ class PosthogWeb { case 'disable': analytics.callMethod('opt_out_capturing'); break; + case 'getFeatureFlag': + analytics.callMethod('getFeatureFlag', [ + call.arguments['key'], + ]); + break; + case 'getFeatureFlagPayload': + analytics.callMethod('getFeatureFlagPayload', [ + call.arguments['key'], + ]); + break; + case 'register': + final properties = {call.arguments['key']: call.arguments['value']}; + analytics.callMethod('register', [ + properties, + ]); + break; default: throw PlatformException( code: 'Unimplemented', diff --git a/lib/src/posthog.dart b/lib/src/posthog.dart index 6cd304c..a635ada 100644 --- a/lib/src/posthog.dart +++ b/lib/src/posthog.dart @@ -1,12 +1,8 @@ -import 'dart:io'; - -import 'package:posthog_flutter/src/posthog_platform_interface.dart'; - -export 'package:posthog_flutter/src/posthog_default_options.dart'; +import 'package:posthog_flutter/posthog_flutter_platform_interface.dart'; export 'package:posthog_flutter/src/posthog_observer.dart'; class Posthog { - static PosthogPlatform get _posthog => PosthogPlatform.instance; + static PosthogFlutterPlatform get _posthog => PosthogFlutterPlatform.instance; static final Posthog _instance = Posthog._internal(); @@ -14,64 +10,58 @@ class Posthog { return _instance; } - String? currentScreen; + String? _currentScreen; Future identify({ required String userId, - Map? properties, - Map? options, + Map? userProperties, + Map? userPropertiesSetOnce, }) { return _posthog.identify( - userId: userId, - properties: properties, - options: options, - ); + userId: userId, + userProperties: userProperties, + userPropertiesSetOnce: userPropertiesSetOnce); } Future capture({ required String eventName, - Map? properties, - Map? options, + Map? properties, }) { + final currentScreen = _currentScreen; if (properties != null && !properties.containsKey('\$screen_name') && - this.currentScreen != null) { - properties['\$screen_name'] = this.currentScreen; + currentScreen != null) { + properties['\$screen_name'] = currentScreen; } return _posthog.capture( eventName: eventName, properties: properties, - options: options, ); } Future screen({ required String screenName, - Map? properties, - Map? options, + Map? properties, }) { if (screenName != '/') { - this.currentScreen = screenName; + _currentScreen = screenName; } return _posthog.screen( screenName: screenName, properties: properties, - options: options, ); } Future alias({ required String alias, - Map? options, }) { return _posthog.alias( alias: alias, - options: options, ); } - Future get getAnonymousId { - return _posthog.getAnonymousId; + Future getDistinctId() { + return _posthog.getDistinctId(); } Future reset() { @@ -87,19 +77,14 @@ class Posthog { } Future debug(bool enabled) { - if (Platform.isAndroid) { - throw Exception('Debug flag cannot be dynamically set on Android.\n' - 'Add to AndroidManifest and avoid calling this method when Platform.isAndroid.'); - } - return _posthog.debug(enabled); } - Future setContext(Map context) { - return _posthog.setContext(context); + Future register(String key, Object value) { + return _posthog.register(key, value); } - Future isFeatureEnabled(String key) { + Future isFeatureEnabled(String key) { return _posthog.isFeatureEnabled(key); } @@ -110,12 +95,21 @@ class Posthog { Future group({ required String groupType, required String groupKey, - required Map groupProperties, + Map? groupProperties, }) { return _posthog.group( - groupType: groupType, - groupKey: groupKey, - groupProperties: groupProperties); + groupType: groupType, + groupKey: groupKey, + groupProperties: groupProperties, + ); + } + + Future getFeatureFlag(String key) { + return _posthog.getFeatureFlag(key: key); + } + + Future getFeatureFlagPayload(String key) { + return _posthog.getFeatureFlagPayload(key: key); } Posthog._internal(); diff --git a/lib/src/posthog_default_options.dart b/lib/src/posthog_default_options.dart deleted file mode 100644 index 2d84013..0000000 --- a/lib/src/posthog_default_options.dart +++ /dev/null @@ -1,8 +0,0 @@ -class PosthogDefaultOptions { - Map? options; - - /// Singleton of [PosthogDefaultOptions]. - static final PosthogDefaultOptions instance = PosthogDefaultOptions._(); - - PosthogDefaultOptions._(); -} diff --git a/lib/src/posthog_method_channel.dart b/lib/src/posthog_method_channel.dart deleted file mode 100644 index d3ced33..0000000 --- a/lib/src/posthog_method_channel.dart +++ /dev/null @@ -1,153 +0,0 @@ -import 'package:flutter/services.dart'; -import 'package:posthog_flutter/src/posthog_default_options.dart'; -import 'package:posthog_flutter/src/posthog_platform_interface.dart'; - -const MethodChannel _channel = MethodChannel('posthogflutter'); - -class PosthogMethodChannel extends PosthogPlatform { - Future identify({ - required String userId, - Map? properties, - Map? options, - }) async { - try { - await _channel.invokeMethod('identify', { - 'userId': userId, - 'properties': properties ?? {}, - 'options': options ?? PosthogDefaultOptions.instance.options ?? {}, - }); - } on PlatformException catch (exception) { - print(exception); - } - } - - Future capture({ - required String eventName, - Map? properties, - Map? options, - }) async { - try { - await _channel.invokeMethod('capture', { - 'eventName': eventName, - 'properties': properties ?? {}, - 'options': options ?? PosthogDefaultOptions.instance.options ?? {}, - }); - } on PlatformException catch (exception) { - print(exception); - } - } - - Future screen({ - required String screenName, - Map? properties, - Map? options, - }) async { - try { - await _channel.invokeMethod('screen', { - 'screenName': screenName, - 'properties': properties ?? {}, - 'options': options ?? PosthogDefaultOptions.instance.options ?? {}, - }); - } on PlatformException catch (exception) { - print(exception); - } - } - - Future alias({ - required String alias, - Map? options, - }) async { - try { - await _channel.invokeMethod('alias', { - 'alias': alias, - 'options': options ?? PosthogDefaultOptions.instance.options ?? {}, - }); - } on PlatformException catch (exception) { - print(exception); - } - } - - Future get getAnonymousId async { - return await _channel.invokeMethod('getAnonymousId'); - } - - Future reset() async { - try { - await _channel.invokeMethod('reset'); - } on PlatformException catch (exception) { - print(exception); - } - } - - Future disable() async { - try { - await _channel.invokeMethod('disable'); - } on PlatformException catch (exception) { - print(exception); - } - } - - Future enable() async { - try { - await _channel.invokeMethod('enable'); - } on PlatformException catch (exception) { - print(exception); - } - } - - Future debug(bool enabled) async { - try { - await _channel.invokeMethod('debug', { - 'debug': enabled, - }); - } on PlatformException catch (exception) { - print(exception); - } - } - - Future setContext(Map context) async { - try { - await _channel.invokeMethod('setContext', { - 'context': context, - }); - } on PlatformException catch (exception) { - print(exception); - } - } - - Future isFeatureEnabled(String key) async { - try { - return await _channel.invokeMethod('isFeatureEnabled', { - 'key': key, - }); - } on PlatformException catch (exception) { - print(exception); - return null; - } - } - - @override - Future reloadFeatureFlags() async { - try { - await _channel.invokeMethod('reloadFeatureFlags'); - } on PlatformException catch (exception) { - print(exception); - } - } - - Future group({ - required String groupType, - required String groupKey, - Map? groupProperties, - }) async { - try { - await _channel.invokeMethod('group', { - 'groupType': groupType, - 'groupKey': groupKey, - 'groupProperties': groupProperties ?? {}, - }); - } on PlatformException catch (exception) { - print(exception); - } - } -} diff --git a/lib/src/posthog_observer.dart b/lib/src/posthog_observer.dart index 1945105..cb76f88 100644 --- a/lib/src/posthog_observer.dart +++ b/lib/src/posthog_observer.dart @@ -1,17 +1,18 @@ import 'package:flutter/widgets.dart'; import 'package:posthog_flutter/posthog_flutter.dart'; -typedef String? ScreenNameExtractor(RouteSettings settings); +typedef ScreenNameExtractor = String? Function(RouteSettings settings); String? defaultNameExtractor(RouteSettings settings) => settings.name; class PosthogObserver extends RouteObserver> { - PosthogObserver({this.nameExtractor = defaultNameExtractor}); + PosthogObserver({ScreenNameExtractor nameExtractor = defaultNameExtractor}) + : _nameExtractor = nameExtractor; - final ScreenNameExtractor nameExtractor; + final ScreenNameExtractor _nameExtractor; void _sendScreenView(PageRoute route) { - final String? screenName = nameExtractor(route.settings); + final String? screenName = _nameExtractor(route.settings); if (screenName != null) { Posthog().screen(screenName: screenName); } diff --git a/lib/src/posthog_platform_interface.dart b/lib/src/posthog_platform_interface.dart deleted file mode 100644 index 0b515b9..0000000 --- a/lib/src/posthog_platform_interface.dart +++ /dev/null @@ -1,82 +0,0 @@ -import 'package:posthog_flutter/src/posthog_method_channel.dart'; - -abstract class PosthogPlatform { - /// The default instance of [PosthogPlatform] to use - /// - /// Platform-specific plugins should override this with their own - /// platform-specific class that extends [PosthogPlatform] when they - /// register themselves. - /// - /// Defaults to [PosthogMethodChannel] - static PosthogPlatform instance = PosthogMethodChannel(); - - Future identify({ - required String userId, - Map? properties, - Map? options, - }) { - throw UnimplementedError('identify() has not been implemented.'); - } - - Future capture({ - required String eventName, - Map? properties, - Map? options, - }) { - throw UnimplementedError('capture() has not been implemented.'); - } - - Future screen({ - required String screenName, - Map? properties, - Map? options, - }) { - throw UnimplementedError('screen() has not been implemented.'); - } - - Future alias({ - required String alias, - Map? options, - }) { - throw UnimplementedError('alias() has not been implemented.'); - } - - Future get getAnonymousId { - throw UnimplementedError('getAnonymousId() has not been implemented.'); - } - - Future reset() { - throw UnimplementedError('reset() has not been implemented.'); - } - - Future disable() { - throw UnimplementedError('disable() has not been implemented.'); - } - - Future enable() { - throw UnimplementedError('enable() has not been implemented.'); - } - - Future debug(bool enabled) { - throw UnimplementedError('debug() has not been implemented.'); - } - - Future setContext(Map context) { - throw UnimplementedError('setContext() has not been implemented.'); - } - - Future isFeatureEnabled(String key) { - throw UnimplementedError('isFeatureEnabled() has not been implemented.'); - } - - Future reloadFeatureFlags() { - throw UnimplementedError('reloadFeatureFlags() has not been implemented.'); - } - - Future group( - {required String groupType, - required String groupKey, - Map? groupProperties}) { - throw UnimplementedError('group() has not been implemented.'); - } -} diff --git a/pubspec.yaml b/pubspec.yaml index 86f9670..3b56782 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,26 +1,40 @@ name: posthog_flutter description: Flutter implementation of PostHog client for iOS, Android and Web -version: 3.3.0 +version: 3.0.0-alpha.1 homepage: https://www.posthog.com repository: https://github.com/posthog/posthog-flutter issue_tracker: https://github.com/posthog/posthog-flutter/issues documentation: https://github.com/posthog/posthog-flutter#readme environment: - sdk: '>=2.12.0 <4.0.0' - flutter: '>=1.10.0' + sdk: '>=2.18.0 <4.0.0' + flutter: '>=3.3.0' dependencies: flutter: sdk: flutter flutter_web_plugins: sdk: flutter + plugin_platform_interface: ^2.0.2 dev_dependencies: - flutter_test: - sdk: flutter + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec +# The following section is specific to Flutter packages. flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) + # which should be registered in the plugin registry. This is required for + # using method channels. + # The Android 'package' specifies package in which the registered class is. + # This is required for using method channels on Android. + # The 'ffiPlugin' specifies that native code should be built and bundled. + # This is required for using `dart:ffi`. + # All these are used by the tooling to maintain consistency when + # adding or updating assets for this project. plugin: platforms: android: @@ -29,5 +43,36 @@ flutter: ios: pluginClass: PosthogFlutterPlugin web: - pluginClass: PosthogWeb - fileName: src/posthog_web.dart + pluginClass: PosthogFlutterWeb + fileName: posthog_flutter_web.dart + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages