-
Notifications
You must be signed in to change notification settings - Fork 6
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
Migrate ParselyTracker
from Java to Kotlin
#99
Conversation
To fix functional tests execution
The `sharedInstance` that creates a new instance of `ParselyTracker` should not have nullable return type
To improve API usage for both Kotlin and Java SDK consumers
…ely_tracker_kotlin_migration
Due to Java->Kotlin conversion, this condition was lost during merge.
…viewUuid` I think there's no a lot of value in providing `null` in `extraData` items
Fix missing visibility modifiers
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## main #99 +/- ##
==========================================
- Coverage 71.81% 68.75% -3.07%
==========================================
Files 18 18
Lines 369 400 +31
Branches 52 49 -3
==========================================
+ Hits 265 275 +10
- Misses 92 113 +21
Partials 12 12 ☔ View full report in Codecov by Sentry. |
ParselyTracker
from Java to KotlinParselyTracker
from Java to Kotlin
* Accessed as a singleton. Maintains a queue of pageview events in memory and periodically | ||
* flushes the queue to the Parse.ly pixel proxy server. | ||
*/ | ||
public open class ParselyTracker protected constructor(siteId: String, flushInterval: Int, c: Context) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This shouldn't be public
and open
but I haven't found any other way for now. If I introduced some interface, let's say Tracker
to use it in EngagementManagerTest
to create a fake implementation, I'd have to expose enqueueEvent
to public
- apparently, interface methods cannot be internal
.
But I believe this shouldn't be a blocker - ParselyTracker
, as previously written in Java, was "open
" by default.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I introduced some interface, let's say
Tracker
to use it inEngagementManagerTest
to create a fake implementation, I'd have to exposeenqueueEvent
topublic
- apparently, interface methods cannot beinternal
.
Assuming that this is what we wanted to do, wouldn't it be more appropriate to use multiple interfaces in this case? We'd have a Tracker
interface that we expose to public
and another internal
interface that has enqueueEvent
function. ParselyTracker
then will implement both and everything should work? Let me know if I am missing anything in this scenario.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's very likely that I am missing something, but I was thinking something like this would work? 74a9f18
Note that this ^ is not production quality code, I just put it together to make it easier to discuss. Let me know what you think!
Also, if we did go with such a design, I think we can make ParselyTracker
internal as well and expose the shared instance some other way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you! I really like your idea, haven't seen in this way myself. I also appreciate it clarifies the public contract better.
I've cherry-picked your comment and added some changes. Most notably, I've renamed Tracker
interface to ParselyTracker
to minimize breaking changes for the API consumers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am glad you found it useful! 🥳
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@wzieba I finished my first pass of this, but I might have more to add in my second pass as I want to think about it a bit. Hope you find the comments useful!
parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.kt
Outdated
Show resolved
Hide resolved
parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.kt
Outdated
Show resolved
Hide resolved
parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.kt
Outdated
Show resolved
Hide resolved
parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.kt
Outdated
Show resolved
Hide resolved
parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.kt
Outdated
Show resolved
Hide resolved
* Accessed as a singleton. Maintains a queue of pageview events in memory and periodically | ||
* flushes the queue to the Parse.ly pixel proxy server. | ||
*/ | ||
public open class ParselyTracker protected constructor(siteId: String, flushInterval: Int, c: Context) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I introduced some interface, let's say
Tracker
to use it inEngagementManagerTest
to create a fake implementation, I'd have to exposeenqueueEvent
topublic
- apparently, interface methods cannot beinternal
.
Assuming that this is what we wanted to do, wouldn't it be more appropriate to use multiple interfaces in this case? We'd have a Tracker
interface that we expose to public
and another internal
interface that has enqueueEvent
function. ParselyTracker
then will implement both and everything should work? Let me know if I am missing anything in this scenario.
BREAKING CHANGE: This commit removes `setDebug` method which is a part of public contract. Instead, API consumers should set debug parameter when invoking `sharedInstance` method.
I believe it better communicates the intention than `debug` flag.
We either way were setting empty string in case of nullable `urlRef`. Now it's more explicit. BREAKING CHANGE: As `urlRef` values are no longer nullable, this breaks the public contract.
For better Java co-op.
Thank you @oguzkocer for the review! Ready for the second round. |
Reason: to minimize breaking changes for the clients
Thank you @oguzkocer for the suggestions! Applied them, ready for another round. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@wzieba Thank you for addressing my previous feedback. I am glad that it was useful.
I had a one last look and wanted to bring up a couple points about the singleton. I am approving the changes just so this PR doesn't block you anymore, but I am curious to hear your thoughts on those.
In general, it's looking really good and the SDK is looking significantly better 🙇♂️
/** | ||
* Singleton instance accessor. Note: This must be called after [.sharedInstance] | ||
* | ||
* @return The singleton instance | ||
*/ | ||
@JvmStatic | ||
public fun sharedInstance(): ParselyTracker? { | ||
return instance | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure if the additional sharedInstance
method is helpful or not. I think the following is the intended usage:
- First call
sharedInstance({configuration})
- Subsequently call
sharedInstance()
Although this works - the better approach in my opinion is to:
- First call
sharedInstance({configuration})
and assign it to a property (preferably somewhere where you can inject it) - Then only access the tracker through your local (to the client binary) property and not use the
sharedInstance()
This approach is better in my opinion, because it doesn't deal with the nullability and somewhat forces the instantiation of the property to be done in a more proper place. It's also how a client would handle the tracker if it wasn't using a singleton pattern.
So, I wonder if we should not give them the option to use the first approach at all. What do you think?
@JvmStatic | ||
@JvmOverloads | ||
public fun sharedInstance( | ||
siteId: String, | ||
flushInterval: Int = DEFAULT_FLUSH_INTERVAL_SECS, | ||
context: Context, | ||
dryRun: Boolean = false, | ||
): ParselyTracker { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A singleton that requires a configuration presents an interesting problem. What happens if the client tries to pass a different configuration after the instance is already initialized?
Currently, the secondary configuration is silently ignored. And, I could see a case where this can result in developers trying to figure out why their configuration change is not applied. Assuming that we don't want to allow updating the configuration after the initialization, I think we can give them a heads up about this in the documentation and by printing a log.
How do you feel about:
- Adding a note to the documentation of this method, saying that this method should only be called once and subsequent calls will ignore its arguments and return the existing instance
- Either add a generic warning about this ^ as a log every time we return the existing instance. Or - add a specific warning if this method is called with a different configuration as compared to the existing instance.
Note that an alternative approach that would avoid this whole issue would be to use a build time configuration instead of function parameters, but I don't think that's appropriate for this use case. Even if it was, that approach has its own issues - but I wanted to mention it for completeness-sake.
Thank you @oguzkocer for the comments about |
Best review after #100
Description
This PR migrates
ParselyTracker
, from Java to Kotlin. This class is the starting point for any user of this SDK.Aside from the migration, this PR introduces also default arguments for tracking methods, to make calling this SDK from Kotlin more convenient, some code simplifications. This PR also introduces enforcing visibility modifiers for the whole library by setting
explicitApi
toSTRICT
.Testing
This PR does not require testing. Functional tests should assert the validity of basic flows.