Skip to content

Commit

Permalink
feat: bring back ParselyTracker interface
Browse files Browse the repository at this point in the history
And `ParselyTracker#sharedInstance` method. Allows consumer clients to
mock the `ParselyTracker` interface in their tests.
  • Loading branch information
wzieba committed Jan 24, 2024
1 parent 9f6c637 commit fa7a23e
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 104 deletions.
24 changes: 12 additions & 12 deletions example/src/main/java/com/example/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ protected void onCreate(Bundle savedInstanceState) {
public void handleMessage(Message msg) {
TextView[] v = (TextView[]) msg.obj;
TextView iView = v[0];
if (ParselyTracker.flushTimerIsActive()) {
iView.setText(String.format("Flush Interval: %d", ParselyTracker.getFlushInterval()));
if (ParselyTracker.sharedInstance().flushTimerIsActive()) {
iView.setText(String.format("Flush Interval: %d", ParselyTracker.sharedInstance().getFlushInterval()));
} else {
iView.setText("Flush timer inactive");
}
Expand All @@ -62,23 +62,23 @@ public void run() {

private void updateEngagementStrings() {
StringBuilder eMsg = new StringBuilder("Engagement is ");
if (ParselyTracker.engagementIsActive()) {
if (ParselyTracker.sharedInstance().engagementIsActive()) {
eMsg.append("active.");
} else {
eMsg.append("inactive.");
}
eMsg.append(String.format(" (interval: %.01fms)", ParselyTracker.getEngagementInterval()));
eMsg.append(String.format(" (interval: %.01fms)", ParselyTracker.sharedInstance().getEngagementInterval()));

TextView eView = findViewById(R.id.et_interval);
eView.setText(eMsg.toString());

StringBuilder vMsg = new StringBuilder("Video is ");
if (ParselyTracker.videoIsActive()) {
if (ParselyTracker.sharedInstance().videoIsActive()) {
vMsg.append("active.");
} else {
vMsg.append("inactive.");
}
vMsg.append(String.format(" (interval: %.01fms)", ParselyTracker.getVideoEngagementInterval()));
vMsg.append(String.format(" (interval: %.01fms)", ParselyTracker.sharedInstance().getVideoEngagementInterval()));

TextView vView = findViewById(R.id.video_interval);
vView.setText(vMsg.toString());
Expand All @@ -88,20 +88,20 @@ public void trackPageview(View view) {
// NOTE: urlMetadata is only used when "url" has no version accessible outside the app. If
// the post has an internet-accessible URL, we will crawl it. urlMetadata is only used
// in the case of app-only content that we can't crawl.
ParselyTracker.trackPageview(
ParselyTracker.sharedInstance().trackPageview(
"http://example.com/article1.html", "http://example.com/", null, null
);
}

public void startEngagement(View view) {
final Map<String, Object> extraData = new HashMap<>();
extraData.put("product-id", "12345");
ParselyTracker.startEngagement("http://example.com/article1.html", "http://example.com/", extraData);
ParselyTracker.sharedInstance().startEngagement("http://example.com/article1.html", "http://example.com/", extraData);
updateEngagementStrings();
}

public void stopEngagement(View view) {
ParselyTracker.stopEngagement();
ParselyTracker.sharedInstance().stopEngagement();
updateEngagementStrings();
}

Expand All @@ -117,13 +117,13 @@ public void trackPlay(View view) {
90
);
// NOTE: For videos embedded in an article, "url" should be the URL for that article.
ParselyTracker.trackPlay("http://example.com/app-videos", null, metadata, null);
ParselyTracker.sharedInstance().trackPlay("http://example.com/app-videos", null, metadata, null);

}

public void trackPause(View view) {
ParselyTracker.trackPause();
ParselyTracker.sharedInstance().trackPause();
}

public void trackReset(View view) {ParselyTracker.resetVideo(); }
public void trackReset(View view) {ParselyTracker.sharedInstance().resetVideo(); }
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class FunctionalTests {

private lateinit var parselyTracker: ParselyTracker
private val server = MockWebServer()
private val url = server.url("/").toString()
private lateinit var appsFiles: Path
Expand All @@ -62,7 +63,7 @@ class FunctionalTests {
initializeTracker(activity)

repeat(51) {
ParselyTracker.trackPageview("url")
parselyTracker.trackPageview("url")
}
}

Expand Down Expand Up @@ -92,13 +93,13 @@ class FunctionalTests {
server.enqueue(MockResponse().setResponseCode(200))
initializeTracker(activity)

ParselyTracker.trackPageview("url")
parselyTracker.trackPageview("url")
}

Thread.sleep((defaultFlushInterval / 2).inWholeMilliseconds)

scenario.onActivity {
ParselyTracker.trackPageview("url")
parselyTracker.trackPageview("url")
}

Thread.sleep((defaultFlushInterval / 2).inWholeMilliseconds)
Expand All @@ -107,7 +108,7 @@ class FunctionalTests {
assertThat(firstRequestPayload!!["events"]).hasSize(2)

scenario.onActivity {
ParselyTracker.trackPageview("url")
parselyTracker.trackPageview("url")
}

Thread.sleep(defaultFlushInterval.inWholeMilliseconds)
Expand Down Expand Up @@ -137,7 +138,7 @@ class FunctionalTests {
initializeTracker(activity, flushInterval = 1.hours)

repeat(20) {
ParselyTracker.trackPageview("url")
parselyTracker.trackPageview("url")
}
}

Expand Down Expand Up @@ -171,7 +172,7 @@ class FunctionalTests {
initializeTracker(activity)

repeat(eventsToSend) {
ParselyTracker.trackPageview("url")
parselyTracker.trackPageview("url")
}
}

Expand Down Expand Up @@ -220,12 +221,12 @@ class FunctionalTests {

// when
startTimestamp = System.currentTimeMillis().milliseconds
ParselyTracker.trackPageview("url")
ParselyTracker.startEngagement(engagementUrl)
parselyTracker.trackPageview("url")
parselyTracker.startEngagement(engagementUrl)
}

Thread.sleep((firstInterval + secondInterval + pauseInterval).inWholeMilliseconds)
ParselyTracker.stopEngagement()
parselyTracker.stopEngagement()

// then
val request = server.takeRequest(35, TimeUnit.SECONDS)!!.toMap()["events"]!!
Expand Down Expand Up @@ -320,6 +321,7 @@ class FunctionalTests {
ParselyTracker.init(
siteId, flushInterval.inWholeSeconds.toInt(), activity.application
)
parselyTracker = ParselyTracker.sharedInstance()
}

private companion object {
Expand Down
118 changes: 52 additions & 66 deletions parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,102 +21,88 @@ import org.jetbrains.annotations.TestOnly
/**
* Tracks Parse.ly app views in Android apps
*
*
* 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 object ParselyTracker {

private const val DEFAULT_FLUSH_INTERVAL_SECS = 60
private var instance: ParselyTrackerInternal? = null

private fun ensureInitialized(): ParselyTrackerInternal {
return instance ?: run {
throw ParselyNotInitializedException();
}
}
public interface ParselyTracker {

public val engagementInterval: Double?
@JvmStatic
get() = ensureInitialized().engagementInterval

public val videoEngagementInterval: Double?
@JvmStatic
get() = ensureInitialized().videoEngagementInterval

public val flushInterval: Long
@JvmStatic
get() = ensureInitialized().flushInterval

@JvmStatic
public fun engagementIsActive(): Boolean = ensureInitialized().engagementIsActive()
public fun engagementIsActive(): Boolean

@JvmStatic
public fun videoIsActive(): Boolean = ensureInitialized().videoIsActive()
public fun videoIsActive(): Boolean

@JvmStatic
@JvmOverloads
public fun trackPageview(
url: String,
urlRef: String = "",
urlMetadata: ParselyMetadata? = null,
extraData: Map<String, Any>? = null,
): Unit = ensureInitialized().trackPageview(url, urlRef, urlMetadata, extraData)
)

@JvmStatic
@JvmOverloads
public fun startEngagement(
url: String,
urlRef: String = "",
extraData: Map<String, Any>? = null
): Unit = ensureInitialized().startEngagement(url, urlRef, extraData)
)

@JvmStatic
public fun stopEngagement(): Unit = ensureInitialized().stopEngagement()
public fun stopEngagement()

@JvmStatic
@JvmOverloads
public fun trackPlay(
url: String,
urlRef: String = "",
videoMetadata: ParselyVideoMetadata,
extraData: Map<String, Any>? = null,
): Unit = ensureInitialized().trackPlay(url, urlRef, videoMetadata, extraData)

@JvmStatic
public fun trackPause(): Unit = ensureInitialized().trackPause()

@JvmStatic
public fun resetVideo(): Unit = ensureInitialized().resetVideo()

@JvmStatic
public fun flushTimerIsActive(): Boolean = ensureInitialized().flushTimerIsActive()

/**
* Singleton instance factory Note: this must be called before [.sharedInstance]
*
* @param siteId The Parsely public site id (eg "example.com")
* @param flushInterval The interval at which the events queue should flush, in seconds
* @param context The current Android application context
* @param dryRun If set to `true`, events **won't** be sent to Parse.ly server
* @return The singleton instance
*/
@JvmStatic
@JvmOverloads
public fun init(
siteId: String,
flushInterval: Int = DEFAULT_FLUSH_INTERVAL_SECS,
context: Context,
dryRun: Boolean = false,
) {
if (instance != null) {
throw ParselyAlreadyInitializedException()
)

public fun trackPause()

public fun resetVideo()

public fun flushTimerIsActive(): Boolean

public companion object {
private const val DEFAULT_FLUSH_INTERVAL_SECS = 60
private var instance: ParselyTrackerInternal? = null

private fun ensureInitialized(): ParselyTrackerInternal {
return instance ?: run {
throw ParselyNotInitializedException()
}
}

/**
* Singleton instance factory Note: this must be called before [.sharedInstance]
*
* @param siteId The Parsely public site id (eg "example.com")
* @param flushInterval The interval at which the events queue should flush, in seconds
* @param context The current Android application context
* @param dryRun If set to `true`, events **won't** be sent to Parse.ly server
* @return The singleton instance
*/
@JvmStatic
@JvmOverloads
public fun init(
siteId: String,
flushInterval: Int = DEFAULT_FLUSH_INTERVAL_SECS,
context: Context,
dryRun: Boolean = false,
) {
if (instance != null) {
throw ParselyAlreadyInitializedException()
}
instance = ParselyTrackerInternal(siteId, flushInterval, context, dryRun)
}
instance = ParselyTrackerInternal(siteId, flushInterval, context, dryRun)
}

@TestOnly
internal fun tearDown() {
instance = null
@JvmStatic
public fun sharedInstance(): ParselyTrackerInternal = ensureInitialized()

@TestOnly
internal fun tearDown() {
instance = null
}
}
}
Loading

0 comments on commit fa7a23e

Please sign in to comment.