Skip to content

Add SafeBoxGlobalStateObserver to Support Runtime Lifecycle Monitoring #12

@harrytmthy

Description

@harrytmthy

Background

In Issue #3, we introduced strategies to prevent multiple SafeBox instances from accessing the same blob file. This effort culminated in Issue #10, where SafeBoxBlobFileRegistry was implemented to enforce singleton-per-file constraint.

However, the solution was enforcement-only. It prevents conflicts but gives no insight into SafeBox's runtime state.


Problem

In non-singleton scenarios (e.g. ViewModel-scoped SafeBox), consumers have no way to know:

  • Is SafeBox writing right now?
  • Has the SafeBox already been closed?
  • Can I safely re-create a new instance for the same file name?

This makes it difficult to support real-world async lifecycles (e.g. heavy writes + rapid screen transitions), making it easy to trigger:

IllegalStateException: SafeBox with filename 'xyz' is already in use


Proposal

1. Introduce SafeBoxGlobalStateObserver

This public API is useful for file-level state tracking:

val listener = object : SafeBoxStateListener {
    override fun onStateChanged(state: SafeBoxState) {
        // Observe IDLE, WRITING, or CLOSED states
    }
}
SafeBoxGlobalStateObserver.addListener("my_prefs", listener)

and remove it when it's no longer in use:

SafeBoxGlobalStateObserver.removeListener("my_prefs", listener)

Listeners that were added to SafeBoxGlobalStateObserver will outlive its instance, since they observe states by file name, not by their SafeBox instance.

2. Add SafeBoxListener to SafeBox.create(...)

For per-instance scoping, the SafeBox.create(...) API will support an optional SafeBoxStateListener that is automatically disposed during safeBox.close().

SafeBox.create(
    context = ctx,
    fileName = "my_prefs",
    listener = SafeBoxStateListener { ... } // instance-scoped
)

This listener follows the lifetime of the SafeBox instance and does not persist after close().


Implementation Notes

  • Global state observer: SafeBoxGlobalStateObserver backed by ConcurrentHashMap<String, SafeBoxState> and listener sets.
  • File registry: SafeBoxBlobFileRegistry remains internal and responsible for enforcing singleton.
  • State transitions are triggered via registry and SafeBox lifecycle.
  • Per-instance listeners do not need manual removal; they follow SafeBox's lifecycle.
  • Global listeners must be removed manually to prevent leaks.
  • Additionally, a new closeWhenIdle() will be introduced to close only when the ongoing write completes (if any).

Sub-issues

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

Status

Done

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions