io.github.dinamo541.corefx.navigation

AppContext

Singleton final thread-safe

A process-wide key-value store for the shared, cross-cutting data your flows need — the current user, a selected record, feature flags — without coupling unrelated screens to one another. Backed by a ConcurrentHashMap, so reads and writes are lock-free on the common path and safe from any thread. Un almacén clave-valor a nivel de proceso para los datos compartidos y transversales que tus flujos necesitan — el usuario actual, un registro seleccionado, feature flags — sin acoplar entre sí pantallas que no se relacionan. Respaldado por un ConcurrentHashMap, así que las lecturas y escrituras son lock-free en la ruta común y seguras desde cualquier hilo.

OverviewVisión general

Where FlowController handles navigation and StageManager handles windows, AppContext holds the data both lean on. It is the singleton you reach for whenever two screens need to agree on something but shouldn't know about each other. Mientras FlowController se encarga de la navegación y StageManager de las ventanas, AppContext guarda los datos en los que ambos se apoyan. Es el singleton al que recurres cuando dos pantallas necesitan ponerse de acuerdo en algo pero no deberían conocerse entre sí.

⚠️
Strict null-safety contractContrato estricto de null-safety

Keys must never be null or blank, and values must never be null — both are rejected with descriptive exceptions (matching ConcurrentHashMap, which forbids null keys/values). Lookups for a missing key never throw; they return null or your default.Las claves nunca pueden ser null ni estar en blanco, y los valores nunca pueden ser null — ambos se rechazan con excepciones descriptivas (acorde a ConcurrentHashMap, que prohíbe claves/valores null). Buscar una clave inexistente nunca lanza excepción; devuelve null o tu valor por defecto.

Quick startInicio rápido

AppContext ctx = AppContext.getInstance();

// After login:
ctx.put("currentUser", user);

// Anywhere else — the type is inferred from the assignment:
User current = ctx.get("currentUser");
String role  = ctx.getOrDefault("role", "guest").toString();

if (ctx.contains("currentUser")) { /* ... */ }

Write operationsOperaciones de escritura

void put(String key, Object value)

Stores value under key, replacing any previous value. Throws IllegalArgumentException for a null/blank key and NullPointerException for a null value.Almacena value bajo key, reemplazando cualquier valor previo. Lanza IllegalArgumentException ante una clave null/en blanco y NullPointerException ante un valor null.

Object putIfAbsent(String key, Object value)

Stores only if the key is currently unset. Returns the existing value (or null if none, meaning the new value was stored). Same validation as put.Almacena solo si la clave no está asignada actualmente. Devuelve el valor existente (o null si no había, lo que significa que el nuevo valor se almacenó). Misma validación que put.

void remove(String key)

Removes the entry under key, if any. Validates the key.Elimina la entrada bajo key, si existe. Valida la clave.

void clear()

Removes every entry.Elimina todas las entradas.

Read operationsOperaciones de lectura

<T> T get(String key)

Returns the value, cast to the type inferred from the call site. Returns null if absent. Validates the key.Devuelve el valor, casteado al tipo inferido en el sitio de llamada. Devuelve null si no existe. Valida la clave.

Note: the cast is unchecked — a wrong target type surfaces as a ClassCastException at the use site, not here.Nota: el cast no está comprobado — un tipo destino equivocado aflora como ClassCastException en el sitio de uso, no aquí.

Object getOrDefault(String key, Object defaultValue)

The stored value, or defaultValue (which may be null) when the key is absent.El valor almacenado, o defaultValue (que puede ser null) cuando la clave no existe.

boolean contains(String key)

true if a value is stored under key.true si hay un valor almacenado bajo key.

boolean isEmpty()  ·  int size()

Whether the store holds no entries, and the entry count.Si el almacén no contiene entradas, y el número de entradas.

💡
Type-inferring getget con inferencia de tipo

Because get is generic, User u = ctx.get("user"); needs no cast. When there's no target type, T infers to Object.Como get es genérico, User u = ctx.get("user"); no necesita cast. Cuando no hay tipo destino, T se infiere como Object.

Recommendations & pitfallsRecomendaciones y trampas

Do centralize your key names as constants (public static final String CURRENT_USER = "currentUser";) to avoid typos — blank keys are rejected, but misspelled ones silently miss.Centraliza los nombres de tus claves como constantes (public static final String CURRENT_USER = "currentUser";) para evitar errores de tipeo — las claves en blanco se rechazan, pero las mal escritas fallan en silencio.

Do store domain objects, not UI nodes — this is application state, not a scene graph.Almacena objetos de dominio, no nodos de UI — esto es estado de aplicación, no un grafo de escena.

Avoid leaving stale entries on logout: call remove or clear to prevent the next user from seeing the previous session's data.Evita dejar entradas obsoletas al cerrar sesión: llama a remove o clear para impedir que el siguiente usuario vea los datos de la sesión anterior.

🔒
Thread-safe map, not atomic transactionsMapa thread-safe, no transacciones atómicas

Each operation is individually thread-safe, but a contains-then-put sequence is not atomic. Use putIfAbsent when you need check-and-set in one step.Cada operación es individualmente thread-safe, pero una secuencia contains-luego-put no es atómica. Usa putIfAbsent cuando necesites comprobar-y-asignar en un solo paso.

For diagnostics, toString() reports only the entry count — never the values — so application state can't leak into logs.Para diagnóstico, toString() reporta solo el número de entradas — nunca los valores — para que el estado de la aplicación no se filtre a los logs.