-
Notifications
You must be signed in to change notification settings - Fork 33
MOB-11639 Background Initialization #946
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
Changes from 8 commits
35c5798
0d5267c
b1111ad
1bc2507
f56eb7f
577ac00
956046b
8d7f6b1
1558f1a
528cf13
3cca277
5d8b26e
51e00f4
82a6246
1d76339
05bc53e
26303d5
d9ff956
4533d2c
c69d901
b149237
32d3f8d
44f8730
4db9ffd
d57929e
b02d594
98b45f5
2f1c646
66d3e0c
3bacaac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| package com.iterable.iterableapi; | ||
|
|
||
| import androidx.annotation.NonNull; | ||
|
|
||
| /** | ||
| * Callback interface for background initialization completion. | ||
| * All callbacks are executed on the main thread. | ||
| */ | ||
| public interface AsyncInitializationCallback { | ||
| /** | ||
| * Called on the main thread when initialization completes successfully. | ||
| * At this point, all queued operations have been processed and the SDK is ready for use. | ||
| */ | ||
| void onInitializationComplete(); | ||
|
|
||
| /** | ||
| * Called on the main thread if initialization fails. | ||
| * Any queued operations will be cleared when initialization fails. | ||
| * | ||
| * @param exception The exception that caused initialization failure | ||
| */ | ||
| void onInitializationFailed(@NonNull Exception exception); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,9 +31,9 @@ public class IterableApi { | |
| static volatile IterableApi sharedInstance = new IterableApi(); | ||
|
|
||
| private static final String TAG = "IterableApi"; | ||
| private Context _applicationContext; | ||
| Context _applicationContext; // Package-private for background initializer access | ||
| IterableConfig config; | ||
| private String _apiKey; | ||
| String _apiKey; // Package-private for background initializer access | ||
| private String _email; | ||
| private String _userId; | ||
| String _userIdUnknown; | ||
|
|
@@ -56,6 +56,16 @@ public class IterableApi { | |
| private HashMap<String, String> deviceAttributes = new HashMap<>(); | ||
| private IterableKeychain keychain; | ||
|
|
||
| //region Background Initialization - Delegated to IterableBackgroundInitializer | ||
| //--------------------------------------------------------------------------------------- | ||
|
|
||
| /** | ||
| * Helper method to queue operations if initialization is in progress | ||
| */ | ||
| private void queueOrExecute(Runnable operation, String description) { | ||
| IterableBackgroundInitializer.queueOrExecute(operation, description); | ||
| } | ||
|
|
||
| void fetchRemoteConfiguration() { | ||
| apiClient.getRemoteConfiguration(new IterableHelper.IterableActionHandler() { | ||
| @Override | ||
|
|
@@ -347,11 +357,20 @@ private void logoutPreviousUser() { | |
| disablePush(); | ||
| } | ||
|
|
||
| getInAppManager().reset(); | ||
| getEmbeddedManager().reset(); | ||
| getAuthManager().reset(); | ||
| // Only reset managers if they're initialized | ||
| if (inAppManager != null) { | ||
| inAppManager.reset(); | ||
| } | ||
| if (embeddedManager != null) { | ||
| embeddedManager.reset(); | ||
| } | ||
| if (authManager != null) { | ||
| authManager.reset(); | ||
| } | ||
|
|
||
| apiClient.onLogout(); | ||
| if (apiClient != null) { | ||
| apiClient.onLogout(); | ||
| } | ||
| } | ||
|
|
||
| private void onLogin( | ||
|
|
@@ -726,6 +745,80 @@ public static void initialize(@NonNull Context context, @NonNull String apiKey, | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Initialize the Iterable SDK in the background to avoid ANRs. | ||
| * This method returns immediately and performs all initialization work on a background thread. | ||
| * Any API calls made before initialization completes will be queued and executed after initialization. | ||
| * | ||
| * @param context Application context | ||
| * @param apiKey Iterable API key | ||
| * @param callback Optional callback for initialization completion (can be null) | ||
| */ | ||
| public static void initializeInBackground(@NonNull Context context, | ||
| @NonNull String apiKey, | ||
| @Nullable AsyncInitializationCallback callback) { | ||
| IterableBackgroundInitializer.initializeInBackground(context, apiKey, null, callback); | ||
| } | ||
|
|
||
| /** | ||
| * Initialize the Iterable SDK in the background to avoid ANRs. | ||
| * This method returns immediately and performs all initialization work on a background thread. | ||
| * Any API calls made before initialization completes will be queued and executed after initialization. | ||
| * | ||
| * @param context Application context | ||
| * @param apiKey Iterable API key | ||
| * @param config Optional configuration (can be null) | ||
| * @param callback Optional callback for initialization completion (can be null) | ||
| */ | ||
| public static void initializeInBackground(@NonNull Context context, | ||
| @NonNull String apiKey, | ||
| @Nullable IterableConfig config, | ||
| @Nullable AsyncInitializationCallback callback) { | ||
| IterableBackgroundInitializer.initializeInBackground(context, apiKey, config, callback); | ||
| } | ||
|
|
||
| /** | ||
| * Check if background initialization is in progress | ||
| * @return true if initialization is currently running in background | ||
| */ | ||
| public static boolean isInitializingInBackground() { | ||
| return IterableBackgroundInitializer.isInitializingInBackground(); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What different states do we want to support? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah if both of them return false then its not initalized |
||
| } | ||
|
|
||
| /** | ||
| * Check if background initialization has completed | ||
| * @return true if background initialization completed successfully | ||
| */ | ||
| public static boolean isBackgroundInitializationComplete() { | ||
| return IterableBackgroundInitializer.isBackgroundInitializationComplete(); | ||
| } | ||
|
|
||
| /** | ||
| * Get the number of operations currently queued | ||
| * @return number of queued operations | ||
| */ | ||
| @VisibleForTesting | ||
| static int getQueuedOperationCount() { | ||
| return IterableBackgroundInitializer.getQueuedOperationCount(); | ||
| } | ||
|
|
||
| /** | ||
| * Shutdown the background executor for proper cleanup | ||
| * Should be called during application shutdown or for testing | ||
| */ | ||
| @VisibleForTesting | ||
| static void shutdownBackgroundExecutor() { | ||
| IterableBackgroundInitializer.shutdownBackgroundExecutor(); | ||
| } | ||
|
|
||
| /** | ||
| * Reset background initialization state - for testing only | ||
| */ | ||
| @VisibleForTesting | ||
| static void resetBackgroundInitializationState() { | ||
| IterableBackgroundInitializer.resetBackgroundInitializationState(); | ||
| } | ||
|
|
||
| public static void setContext(Context context) { | ||
| IterableActivityMonitor.getInstance().registerLifecycleCallbacks(context); | ||
| } | ||
|
|
@@ -807,7 +900,7 @@ public void pauseAuthRetries(boolean pauseRetry) { | |
| } | ||
|
|
||
| public void setEmail(@Nullable String email) { | ||
| setEmail(email, null, null, null, null); | ||
| queueOrExecute(() -> setEmail(email, null, null, null, null), "setEmail(" + email + ")"); | ||
|
||
| } | ||
|
|
||
| public void setEmail(@Nullable String email, IterableIdentityResolution identityResolution) { | ||
|
|
@@ -876,7 +969,7 @@ public void setUnknownUser(@Nullable String userId) { | |
| } | ||
|
|
||
| public void setUserId(@Nullable String userId) { | ||
| setUserId(userId, null, null, null, null, false); | ||
| queueOrExecute(() -> setUserId(userId, null, null, null, null, false), "setUserId(" + userId + ")"); | ||
| } | ||
|
|
||
| public void setUserId(@Nullable String userId, IterableIdentityResolution identityResolution) { | ||
|
|
@@ -1026,11 +1119,11 @@ public void removeDeviceAttribute(String key) { | |
| * @param deviceToken Push token obtained from GCM or FCM | ||
| */ | ||
| public void registerDeviceToken(@NonNull String deviceToken) { | ||
| registerDeviceToken(_email, _userId, _authToken, getPushIntegrationName(), deviceToken, deviceAttributes); | ||
| queueOrExecute(() -> registerDeviceToken(_email, _userId, _authToken, getPushIntegrationName(), deviceToken, deviceAttributes), "registerDeviceToken"); | ||
| } | ||
|
|
||
| public void trackPushOpen(int campaignId, int templateId, @NonNull String messageId) { | ||
| trackPushOpen(campaignId, templateId, messageId, null); | ||
| queueOrExecute(() -> trackPushOpen(campaignId, templateId, messageId, null), "trackPushOpen(" + campaignId + ", " + templateId + ", " + messageId + ")"); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -1202,7 +1295,7 @@ public boolean isIterableIntent(@Nullable Intent intent) { | |
| * @param eventName | ||
| */ | ||
| public void track(@NonNull String eventName) { | ||
| track(eventName, 0, 0, null); | ||
| queueOrExecute(() -> track(eventName, 0, 0, null), "track(" + eventName + ")"); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -1211,7 +1304,7 @@ public void track(@NonNull String eventName) { | |
| * @param dataFields | ||
| */ | ||
| public void track(@NonNull String eventName, @Nullable JSONObject dataFields) { | ||
| track(eventName, 0, 0, dataFields); | ||
| queueOrExecute(() -> track(eventName, 0, 0, dataFields), "track(" + eventName + ", dataFields)"); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -1221,7 +1314,7 @@ public void track(@NonNull String eventName, @Nullable JSONObject dataFields) { | |
| * @param templateId | ||
| */ | ||
| public void track(@NonNull String eventName, int campaignId, int templateId) { | ||
| track(eventName, campaignId, templateId, null); | ||
| queueOrExecute(() -> track(eventName, campaignId, templateId, null), "track(" + eventName + ", " + campaignId + ", " + templateId + ")"); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -1248,14 +1341,16 @@ public void track(@NonNull String eventName, int campaignId, int templateId, @Nu | |
| * @param items | ||
| */ | ||
| public void updateCart(@NonNull List<CommerceItem> items) { | ||
| if (!checkSDKInitialization() && _userIdUnknown == null) { | ||
| if (sharedInstance.config.enableUnknownUserActivation) { | ||
| unknownUserManager.trackUnknownUpdateCart(items); | ||
| queueOrExecute(() -> { | ||
| if (!checkSDKInitialization() && _userIdUnknown == null) { | ||
| if (sharedInstance.config.enableUnknownUserActivation) { | ||
|
Comment on lines
+1369
to
+1370
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do configuration check need to get into queue as well? If so, we will have to make it consistent accross other method calls as well
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the whole method goes into the queue so we make sure everything is done after initialization |
||
| unknownUserManager.trackUnknownUpdateCart(items); | ||
| } | ||
| return; | ||
| } | ||
| return; | ||
| } | ||
|
|
||
| apiClient.updateCart(items); | ||
| apiClient.updateCart(items); | ||
| }, "updateCart(" + items.size() + " items)"); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -1264,7 +1359,7 @@ public void updateCart(@NonNull List<CommerceItem> items) { | |
| * @param items list of purchased items | ||
| */ | ||
| public void trackPurchase(double total, @NonNull List<CommerceItem> items) { | ||
| trackPurchase(total, items, null, null); | ||
| queueOrExecute(() -> trackPurchase(total, items, null, null), "trackPurchase(" + total + ", " + items.size() + " items)"); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -1274,7 +1369,7 @@ public void trackPurchase(double total, @NonNull List<CommerceItem> items) { | |
| * @param dataFields a `JSONObject` containing any additional information to save along with the event | ||
| */ | ||
| public void trackPurchase(double total, @NonNull List<CommerceItem> items, @Nullable JSONObject dataFields) { | ||
| trackPurchase(total, items, dataFields, null); | ||
| queueOrExecute(() -> trackPurchase(total, items, dataFields, null), "trackPurchase(" + total + ", " + items.size() + " items, dataFields)"); | ||
| } | ||
|
|
||
|
|
||
|
|
@@ -1302,7 +1397,7 @@ public void trackPurchase(double total, @NonNull List<CommerceItem> items, @Null | |
| * @param newEmail New email | ||
| */ | ||
| public void updateEmail(final @NonNull String newEmail) { | ||
| updateEmail(newEmail, null, null, null); | ||
| queueOrExecute(() -> updateEmail(newEmail, null, null, null), "updateEmail(" + newEmail + ")"); | ||
| } | ||
|
|
||
| public void updateEmail(final @NonNull String newEmail, final @NonNull String authToken) { | ||
|
|
||
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.
How can this method be useful for developer?
Is it necessary to implement something here? Like implement a flag to understand SDK's state?
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.
yeah if they want to do something after initialization is done
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.
the sdk is deemed to be initialized at this point