Skip to content

Conversation

@wpinho-branch
Copy link
Collaborator

@wpinho-branch wpinho-branch commented Jun 23, 2025

Reference

SDK-EMT-2135 -- Implement StateFlow-based session state management in Branch SDK.

Description

This PR introduces a comprehensive refactoring of the Branch SDK's session state management system, replacing the legacy SESSION_STATE enum with a modern StateFlow-based architecture. The changes improve thread safety, performance, and provide better state observation capabilities while maintaining full backward compatibility.

Key Changes:

  • StateFlow Implementation: Replaced legacy SESSION_STATE enum with BranchSessionState and BranchSessionStateManager for thread-safe state management
  • Enhanced Architecture: Introduced BranchSessionStateProvider interface for better abstraction and BranchSessionStateListener for deterministic state observation
  • Improved Performance: Simplified BranchSessionManager to provide reactive interfaces, reducing memory overhead and improving responsiveness
  • Comprehensive Testing: Added extensive unit tests covering all session states (Uninitialized, Initializing, Initialized, Failed, Resetting) and state transitions
  • Documentation: Included detailed documentation with Kotlin and Java usage examples, migration guides, and implementation details

Benefits:

  • Thread-safe session state management with improved memory efficiency
  • Deterministic state observation for SDK clients
  • Better testability with comprehensive unit test coverage
  • Maintained backward compatibility with existing API contracts
  • Enhanced developer experience with clear documentation and examples

Testing Instructions

  1. Unit Tests: Run the comprehensive test suite covering session state management:
    ./gradlew test
  2. Integration Testing: Verify session state transitions work correctly in the test bed app
  3. Backward Compatibility: Ensure existing SDK integrations continue to work without modifications
  4. State Flow Validation: Test state transitions through the complete lifecycle (Uninitialized → Initializing → Initialized/Failed)
  5. Listener Functionality: Verify BranchSessionStateListener correctly receives state change notifications
  6. Performance Testing: Confirm improved memory usage and responsiveness compared to legacy system

Risk Assessment MEDIUM

This refactoring involves core session state management changes that affect the SDK's fundamental behavior. While backward compatibility is maintained, the changes are significant enough to warrant thorough testing across different Android versions and device configurations.

Additional Notes:

  • All existing API contracts remain unchanged to ensure seamless integration

  • Comprehensive unit tests provide confidence in the new implementation

  • Documentation includes migration guides for developers transitioning to the new system

  • Performance improvements should be validated across various device specifications

  • I, the PR creator, have tested — integration, unit, or otherwise — this code.

Reviewer Checklist (To be checked off by the reviewer only)

  • JIRA Ticket is referenced in PR title.
  • Correctness & Style
    • Conforms to AOSP Style Guides
    • Mission critical pieces are documented in code and out of code as needed.
  • Unit Tests reviewed and test issue sufficiently.
  • Functionality was reviewed in QA independently by another engineer on the team.

cc @BranchMetrics/saas-sdk-devs for visibility.

- Replaced the legacy SESSION_STATE enum with a new StateFlow-based system for improved session state management.
- Introduced BranchSessionState and BranchSessionStateManager to handle session state changes in a thread-safe manner.
- Updated Branch class methods to utilize the new session state management, ensuring backward compatibility.
- Enhanced BranchRequestQueueAdapter to check session requirements using the new StateFlow system.
- Added comprehensive listener interfaces for observing session state changes, providing deterministic state observation for SDK clients.
- Ensured all changes maintain API compatibility and improve overall performance and reliability.
- Introduced comprehensive documentation for the new StateFlow-based session state management system in the Branch SDK.
- Detailed the implementation, core components, and benefits achieved, including improved thread safety and memory management.
- Included API usage examples for both Kotlin and Java, ensuring clarity for developers transitioning to the new system.
- Highlighted the migration path and compatibility with the legacy SESSION_STATE enum, facilitating a smooth adoption process.
- Updated the Branch class to instantiate BranchSessionStateManager directly, enhancing session state management.
- Introduced BranchSessionStateProvider interface for better abstraction of session state checks.
- Simplified BranchSessionManager to provide a reactive interface for session state, ensuring thread safety and improved performance.
- Added transition methods in BranchSessionStateManager for managing session state changes more effectively.
- Ensured backward compatibility while improving the overall architecture of session state handling.
- Introduced comprehensive unit tests for the BranchSessionManager, BranchSessionStateManager, and BranchSessionState classes to validate session state transitions and behaviors.
- Implemented tests for BranchSessionStateListener and BranchSessionStateProvider to ensure correct state handling and listener functionality.
- Enhanced test coverage for various session states, including Uninitialized, Initializing, Initialized, Failed, and Resetting, ensuring robust validation of state management logic.
- Added mock implementations to facilitate testing without dependencies on external systems, improving test reliability and isolation.
- Ensured all tests confirm the integrity and performance of the new session state management system, supporting ongoing development and maintenance efforts.
@wpinho-branch wpinho-branch marked this pull request as ready for review June 23, 2025 16:21
@matter-code-review
Copy link
Contributor

Code Quality new feature refactoring clean-code state-management testability concurrency

Reference

EMT-2135 -- Replace SESSION_STATE with StateFlow.

Description

🔄 What Changed

This pull request introduces a significant refactoring of the Branch SDK's session state management. The previous enum-based SESSION_STATE is being replaced with a more robust, thread-safe, and reactive StateFlow-based system using Kotlin's kotlinx.coroutines.flow.StateFlow.

Key changes include:

  • New State Management Classes: Introduction of BranchSessionState.kt (sealed class for states like Uninitialized, Initializing, Initialized, Failed, Resetting), BranchSessionStateListener.kt (interfaces for state observation), and BranchSessionStateManager.kt (the core logic managing the MutableStateFlow and state transitions).
  • Branch.java Updates: The Branch class now holds an instance of BranchSessionStateManager. Its internal methods (setInitState, executeClose, getLatestReferringParamsSync, initializeSession) are updated to interact with the new state manager. New public APIs (addSessionStateObserver, removeSessionStateObserver, getCurrentSessionState, canPerformOperations, hasActiveSession, getSessionStateFlow) are exposed for clients to observe and query the session state.
  • BranchRequestQueueAdapter.kt Update: The request queue now uses Branch.getInstance().canPerformOperations() (which delegates to the new state system) instead of directly checking the legacy initState_.
  • Facade/Extension Classes: BranchSessionManager.kt provides a facade over BranchSessionStateManager, and BranchSessionStateProvider.kt offers an extension function to make Branch conform to a state provider interface.

🔍 Impact of the Change

This change significantly improves the SDK's state management by:

  • Enhanced Thread Safety: StateFlow and CopyOnWriteArrayList (for listeners) provide thread-safe state updates and listener management.
  • Deterministic State Observation: Clients can now react to state changes deterministically using StateFlow or the new listener interfaces, leading to more reliable integration.
  • Improved Testability: The new modular state management classes are highly testable, as evidenced by the comprehensive unit tests added.
  • Modern Kotlin Idioms: Adopts modern Kotlin features like sealed classes and StateFlow for cleaner and more expressive code.
  • Reduced Boilerplate: Centralizes state logic, reducing the need for manual synchronization and checks across the codebase.

📁 Total Files Changed

7 files changed.

🧪 Test Added

Yes, comprehensive unit tests have been added for the new state management system:

  • BranchSessionManagerTest.kt: Tests the BranchSessionManager facade, including initial state, listener addition/removal, synchronization with legacy Branch state, and debug information.
  • BranchSessionStateListenerTest.kt: Verifies the functionality of BranchSessionStateListener and SimpleBranchSessionStateListener interfaces, including state transitions, error handling, and conversion between listener types.
  • BranchSessionStateManagerTest.kt: Extensive tests for the core BranchSessionStateManager, covering initial state, valid and invalid state transitions, convenience methods (initialize, reset, initializeComplete, initializeFailed), force updates, debug info, and listener management.
  • BranchSessionStateProviderTest.kt: Tests the BranchSessionStateProvider extension function, ensuring it correctly maps Branch's hasActiveSession() and canPerformOperations() to the new BranchSessionState concepts (Initialized, Initializing, Uninitialized).
  • BranchSessionStateTest.kt: Unit tests for the BranchSessionState sealed class, verifying the behavior of each state (Uninitialized, Initializing, Initialized, Failed, Resetting) regarding canPerformOperations(), hasActiveSession(), isErrorState(), toString(), and equality.

🔒Security Vulnerabilities

No new security vulnerabilities were detected in this pull request. The changes improve the robustness of the SDK's internal state management, which can indirectly contribute to overall stability and reliability.

Testing Instructions

  1. Run all new unit tests: Execute BranchSessionManagerTest, BranchSessionStateListenerTest, BranchSessionStateManagerTest, BranchSessionStateProviderTest, and BranchSessionStateTest to ensure the new state management system functions as expected.
  2. Integrate with a sample application: Build the SDK with these changes and integrate it into a sample application.
  3. Observe session state: Use the new addSessionStateObserver or getSessionStateFlow APIs in the sample app to monitor session state changes during various SDK operations (initialization, deep link handling, app close/open, error scenarios).
  4. Verify core SDK functionality: Ensure that existing SDK functionalities (e.g., deep linking, event tracking, user identity) continue to work correctly, as they now rely on the new state management.
  5. Test re-initialization scenarios: Verify that the SDK correctly handles re-initialization after a reset or a failed initialization attempt.

Risk Assessment [MEDIUM]

This is a MEDIUM risk change due to the fundamental refactoring of the SDK's core state management. While extensive unit tests are provided, changes to such a critical component always carry a risk of introducing subtle bugs or race conditions if not thoroughly tested in various real-world scenarios. The coexistence of old and new state management in Branch.java during the migration period adds complexity.

  • I, the PR creator, have tested — integration, unit, or otherwise — this code.

Reviewer Checklist (To be checked off by the reviewer only)

  • JIRA Ticket is referenced in PR title.
  • Correctness & Style
    • Conforms to AOSP Style Guides
    • Mission critical pieces are documented in code and out of code as needed.
  • Unit Tests reviewed and test issue sufficiently.
  • Functionality was reviewed in QA independently by another engineer on the team.

cc @BranchMetrics/saas-sdk-devs for visibility.

Tip

Quality Recommendations

  1. The coexistence of the legacy SESSION_STATE initState_ and the new BranchSessionStateManager in Branch.java creates a dual source of truth. While setInitState attempts to synchronize them, a clear deprecation strategy and eventual removal of initState_ should be planned to fully leverage the new StateFlow system and avoid potential inconsistencies.

  2. The reset() method in BranchSessionStateManager uses mainHandler.postDelayed({ forceUpdateState(BranchSessionState.Uninitialized) }, 10) for a delayed state transition. While small, a fixed delay like 10ms can be unreliable across different environments or under varying load. Consider if this delay is strictly necessary or if the state machine can be designed to transition atomically without a heuristic delay.

  3. The BranchSessionManager.kt class acts as a facade over BranchSessionStateManager. Evaluate if this extra layer of abstraction is strictly necessary or if BranchSessionStateManager could be directly used where BranchSessionManager is currently intended, simplifying the overall architecture.

  4. The import java.util.concurrent.CopyOnWriteArrayList; is added in Branch.java but does not appear to be used in the provided patch. It should be removed if not intended for immediate use to maintain code cleanliness.

Sequence Diagram

sequenceDiagram
    participant SDK_Client
    participant Branch
    participant BranchSessionManager
    participant BranchSessionStateManager
    participant BranchRequestQueueAdapter
    participant BranchSessionStateListener

    %% Initialization
    Branch->>BranchSessionStateManager: new BranchSessionStateManager()
    Branch->>Branch: private final BranchSessionStateManager sessionStateManager

    %% SDK Client observes state via Branch
    SDK_Client->>Branch: addSessionStateObserver(@NonNull listener: BranchSessionStateListener)
    Branch->>BranchSessionStateManager: addListener(listener, true)
    BranchSessionStateManager-->>BranchSessionStateListener: onSessionStateChanged(null, BranchSessionState.Uninitialized) (initial notification)

    SDK_Client->>Branch: getSessionStateFlow(): StateFlow<BranchSessionState>
    Branch->>BranchSessionStateManager: getSessionState(): StateFlow<BranchSessionState>
    BranchSessionStateManager-->>SDK_Client: Returns StateFlow for reactive observation

    %% Internal state changes in Branch (legacy setInitState now updates new system)
    Branch->>Branch: setInitState(SESSION_STATE.INITIALISING)
    Branch->>BranchSessionStateManager: initialize()
    BranchSessionStateManager->>BranchSessionStateManager: updateState(BranchSessionState.Initializing)
    BranchSessionStateManager-->>BranchSessionStateListener: onSessionStateChanged(Uninitialized, Initializing)

    Branch->>Branch: initializeSession(initRequest: ServerRequestInitSession, delay: int)
    alt Branch Key Invalid
        Branch->>BranchSessionStateManager: initializeFailed(keyError: BranchError)
        BranchSessionStateManager->>BranchSessionStateManager: updateState(BranchSessionState.Failed(keyError))
        BranchSessionStateManager-->>BranchSessionStateListener: onSessionStateChanged(Initializing, Failed(keyError))
        Branch->>initRequest.callback_: onInitFinished(null, keyError)
    else Initialization Success
        Branch->>BranchSessionStateManager: initializeComplete()
        BranchSessionStateManager->>BranchSessionStateManager: updateState(BranchSessionState.Initialized)
        BranchSessionStateManager-->>BranchSessionStateListener: onSessionStateChanged(Initializing, Initialized)
    end

    Branch->>Branch: executeClose()
    Branch->>BranchSessionStateManager: reset()
    BranchSessionStateManager->>BranchSessionStateManager: updateState(BranchSessionState.Resetting)
    BranchSessionStateManager-->>BranchSessionStateListener: onSessionStateChanged(Initialized, Resetting)
    BranchSessionStateManager->>BranchSessionStateManager: forceUpdateState(BranchSessionState.Uninitialized) (after 10ms delay)
    BranchSessionStateManager-->>BranchSessionStateListener: onSessionStateChanged(Resetting, Uninitialized)

    %% Consumers checking state via Branch
    BranchRequestQueueAdapter->>Branch: canPerformOperations()
    Branch->>BranchSessionStateManager: canPerformOperations()
    BranchSessionStateManager-->>Branch: Returns current state.canPerformOperations()
    Branch-->>BranchRequestQueueAdapter: Returns boolean

    Branch->>Branch: getLatestReferringParamsSync()
    Branch->>BranchSessionStateManager: getCurrentState()
    BranchSessionStateManager-->>Branch: Returns BranchSessionState
    Branch->>Branch: Checks if state is BranchSessionState.Initialized
    alt Not Initialized
        Branch->>Branch: getLatestReferringParamsLatch.await(LATCH_WAIT_UNTIL, TimeUnit.MILLISECONDS)
    end

    %% BranchSessionManager facade (separate utility, not directly in core flow shown above)
    BranchSessionManager->>BranchSessionStateManager: new BranchSessionStateManager()
    BranchSessionManager->>Branch: updateFromBranchState(branch: Branch)
    Branch->>Branch: getInitState(): SESSION_STATE (legacy)
    BranchSessionManager->>BranchSessionStateManager: transitionToInitialized()/Initializing()/Uninitialized() (based on legacy state)
    BranchSessionStateManager-->>BranchSessionStateListener: onSessionStateChanged(...)
Loading

@matter-code-review
Copy link
Contributor

Important

PR Review Skipped

PR review skipped as per the configuration setting. Run a manually review by commenting /matter review

💡Tips to use Matter AI

Command List

  • /matter summary: Generate AI Summary for the PR
  • /matter review: Generate AI Reviews for the latest commit in the PR
  • /matter review-full: Generate AI Reviews for the complete PR
  • /matter release-notes: Generate AI release-notes for the PR
  • /matter : Chat with your PR with Matter AI Agent
  • /matter remember : Generate AI memories for the PR
  • /matter explain: Get an explanation of the PR
  • /matter help: Show the list of available commands and documentation
  • Need help? Join our Discord server: https://discord.gg/fJU5DvanU3

@wpinho-branch wpinho-branch self-assigned this Jul 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants