Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/sht 5366 dynamically initialize posthog #1

Merged
merged 6 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ 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
Expand All @@ -15,127 +14,89 @@ 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
private lateinit var applicationContext: Context

override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "posthog_flutter")

initPlugin(flutterPluginBinding.applicationContext)

applicationContext = flutterPluginBinding.applicationContext
initPlugin(applicationContext)
channel.setMethodCallHandler(this)
}

private fun initPlugin(applicationContext: Context) {
private fun initPlugin(context: Context) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@shuttlers-ifiok here.

On start, the plugin tries to initialize using the values in the manifest. Where apiKey is empty, initialization process is aborted.

Leaving the developer to manually call configure.

try {
// TODO: replace deprecated method API 33
val ai = applicationContext.packageManager.getApplicationInfo(applicationContext.packageName, PackageManager.GET_META_DATA)
val ai = context.packageManager.getApplicationInfo(context.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.DEFAULT_HOST)
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
sdkName = "posthog-flutter"
sdkVersion = postHogVersion
sdkVersion = PostHog.VERSION
}
PostHogAndroid.setup(applicationContext, config)

} catch (e: Throwable) {
e.localizedMessage?.let { Log.e("PostHog", "initPlugin error: $it") }
} catch (e: Exception) {
Log.e("PostHog", "initPlugin error: ${e.localizedMessage}")
}
}

override fun onMethodCall(call: MethodCall, result: Result) {

when (call.method) {
"configure" -> configure(call, result)
"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)
"debug" -> debug(call, result)
"register" -> register(call, result)
"unregister" -> unregister(call, result)
"flush" -> flush(result)
"isFeatureEnabled" -> isFeatureEnabled(call, result)
"reloadFeatureFlags" -> reloadFeatureFlags(result)
"group" -> group(call, result)
"getFeatureFlag" -> getFeatureFlag(call, result)
"getFeatureFlagPayload" -> getFeatureFlagPayload(call, result)
else -> result.notImplemented()
}
}

"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)
}
private fun configure(call: MethodCall, result: Result) {
try {
val apiKey: String = call.argument("apiKey") ?: return result.error("Invalid API Key", "API Key is null or empty", null)
val host: String = call.argument("host") ?: return result.error("Invalid Host", "Host is null or empty", null)

Choose a reason for hiding this comment

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

@Mastersam07 How about using the default host if the host is not passed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Earlier in the file, there is an initPlugin method where we try to initialise the plugin using the values set in the android manifest. When the apiKey is not in the manifest, the initialisation is cancelled.

This leaves the developer to manually call configure and pass in the apiKey and host as required parameters.

Copy link
Collaborator Author

@Mastersam07 Mastersam07 Apr 25, 2024

Choose a reason for hiding this comment

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

Just read your message again @shuttlers-ifiok . Will modify accordingly.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@shuttlers-ifiok this has been treated in #3

val trackLifecycleEvents: Boolean = call.argument("trackLifecycleEvents") ?: false
val enableDebug: Boolean = call.argument("enableDebug") ?: false

"register" -> {
register(call, result)
}
"unregister" -> {
unregister(call, result)
}
"debug" -> {
debug(call, result)
}
"flush" -> {
flush(result)
}
else -> {
result.notImplemented()
val config = PostHogAndroidConfig(apiKey, host).apply {
captureApplicationLifecycleEvents = trackLifecycleEvents
debug = enableDebug
sdkName = "posthog-flutter"
sdkVersion = PostHog.VERSION
}
PostHogAndroid.setup(applicationContext, config)
result.success(null)
} catch (e: Exception) {
result.error("ConfigurationError", "Failed to configure PostHog: ${e.localizedMessage}", null)
}

}


override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
Expand Down
19 changes: 19 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';

import 'package:posthog_flutter/posthog_flutter.dart';
import 'package:posthog_flutter_example/constants.dart';

void main() {
runApp(const MyApp());
Expand Down Expand Up @@ -39,6 +40,24 @@ class _MyAppState extends State<MyApp> {
child: Center(
child: Column(
children: [
const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
"Configure",
style: TextStyle(fontWeight: FontWeight.bold),
),
),
const Divider(),
ElevatedButton(
onPressed: () async {
await _posthogFlutterPlugin.configure(
apiKey: apiKey,
host: host,
debug: true,
);
},
child: const Text("Configure"),
),
const Padding(
padding: EdgeInsets.all(8.0),
child: Text(
Expand Down
72 changes: 46 additions & 26 deletions ios/Classes/PosthogFlutterPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,36 +20,33 @@ public class PosthogFlutterPlugin: NSObject, FlutterPlugin {
}

public static func initPlugin() {
// Initialise PostHog
// Initial static setup might be performed here if required
let apiKey = Bundle.main.object(forInfoDictionaryKey: "com.posthog.posthog.API_KEY") as? String ?? ""
let host = Bundle.main.object(forInfoDictionaryKey: "com.posthog.posthog.POSTHOG_HOST") as? String ?? PostHogConfig.defaultHost
let trackLifecycleEvents = Bundle.main.object(forInfoDictionaryKey: "com.posthog.posthog.CAPTURE_APPLICATION_LIFECYCLE_EVENTS") as? Bool ?? false
let debug = Bundle.main.object(forInfoDictionaryKey: "com.posthog.posthog.DEBUG") as? Bool ?? false

if apiKey.isEmpty {
print("[PostHog] com.posthog.posthog.API_KEY is missing!")
return
}
print("\nApiKey:", apiKey)
print("\nhost:", host)

let host = Bundle.main.object(forInfoDictionaryKey: "com.posthog.posthog.POSTHOG_HOST") as? String ?? PostHogConfig.defaultHost
let postHogCaptureLifecyleEvents = Bundle.main.object(forInfoDictionaryKey: "com.posthog.posthog.CAPTURE_APPLICATION_LIFECYCLE_EVENTS") as? Bool ?? false
let postHogDebug = Bundle.main.object(forInfoDictionaryKey: "com.posthog.posthog.DEBUG") as? Bool ?? false
if !apiKey.isEmpty {
let config = PostHogConfig(apiKey: apiKey, host: host)
config.captureApplicationLifecycleEvents = trackLifecycleEvents
config.debug = debug
config.captureScreenViews = false

// Update SDK name and version
postHogSdkName = "posthog-flutter"
postHogVersion = postHogFlutterVersion

let config = PostHogConfig(
apiKey: apiKey,
host: host
)
config.captureApplicationLifecycleEvents = postHogCaptureLifecyleEvents
config.debug = postHogDebug
config.captureScreenViews = false

// Update SDK name and version
postHogSdkName = "posthog-flutter"
postHogVersion = postHogFlutterVersion

PostHogSDK.shared.setup(config)
//
PostHogSDK.shared.setup(config)
}
}

public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "configure":
configure(call, result: result)
case "getFeatureFlag":
getFeatureFlag(call, result: result)
case "isFeatureEnabled":
Expand Down Expand Up @@ -89,6 +86,31 @@ public class PosthogFlutterPlugin: NSObject, FlutterPlugin {
}
}

private func configure(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
guard let args = call.arguments as? [String: Any],
let apiKey = args["apiKey"] as? String,
let host = args["host"] as? String else {
return _badArgumentError(result)
}

let trackLifecycleEvents = args["trackLifecycleEvents"] as? Bool ?? false
let debug = args["debug"] as? Bool ?? false

let config = PostHogConfig(apiKey: apiKey, host: host)
config.captureApplicationLifecycleEvents = trackLifecycleEvents
config.debug = debug
config.captureScreenViews = false

postHogSdkName = "posthog-flutter"
postHogVersion = postHogFlutterVersion

print("\nApiKey:", apiKey)
print("\nhost:", host)

PostHogSDK.shared.setup(config)
result(nil)
}

private func getFeatureFlag(
_ call: FlutterMethodCall,
result: @escaping FlutterResult
Expand Down Expand Up @@ -292,10 +314,8 @@ public class PosthogFlutterPlugin: NSObject, FlutterPlugin {
result(nil)
}

// Return bad Arguments error
// Utility method for handling errors
private func _badArgumentError(_ result: @escaping FlutterResult) {
result(FlutterError(
code: "PosthogFlutterException", message: "Missing arguments!", details: nil
))
result(FlutterError(code: "BAD_ARGS", message: "Bad or missing arguments", details: nil))
}
}
11 changes: 11 additions & 0 deletions lib/src/posthog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ class Posthog {

String? _currentScreen;

Future<void> configure({
required String apiKey,
required String host,
bool debug = false,
bool trackApplicationLifecycleEvents = false,
}) {
return _posthog.configure(apiKey, host,
debug: debug,
trackApplicationLifecycleEvents: trackApplicationLifecycleEvents);
}

Future<void> identify({
required String userId,
Map<String, Object>? userProperties,
Expand Down
16 changes: 16 additions & 0 deletions lib/src/posthog_flutter_io.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,22 @@ class PosthogFlutterIO extends PosthogFlutterPlatformInterface {
/// The method channel used to interact with the native platform.
final _methodChannel = const MethodChannel('posthog_flutter');

@override
Future<void> configure(String apiKey, String host,
{bool debug = false,
bool trackApplicationLifecycleEvents = false}) async {
try {
await _methodChannel.invokeMethod('configure', {
'apiKey': apiKey,
'host': host,
'debug': debug,
'trackApplicationLifecycleEvents': trackApplicationLifecycleEvents,
});
} on PlatformException catch (e) {
_printIfDebug('Exception on configure: $e');
}
}

@override
Future<void> identify({
required String userId,
Expand Down
5 changes: 5 additions & 0 deletions lib/src/posthog_flutter_platform_interface.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ abstract class PosthogFlutterPlatformInterface extends PlatformInterface {
_instance = instance;
}

Future<void> configure(String apiKey, String host,
{bool debug = false, bool trackApplicationLifecycleEvents = false}) {
throw UnimplementedError('configure() has not been implemented.');
}

Future<void> identify(
{required String userId,
Map<String, Object>? userProperties,
Expand Down