-
Notifications
You must be signed in to change notification settings - Fork 30
State Management
States are a data management feature used to store and access context-specific data within InventoryFramework. In practical terms, a state behaves like a type-safe key-value map that exists within the lifecycle of a context.
States in InventoryFramework are tied to the lifecycle of a context. They act as a scoped and type-safe key-value storage system, designed to make working with dynamic or viewer-specific data easier and safer.
There are three main scopes for state:
- Global State: shared across all viewers of a context.
- Viewer State: isolated per viewer (usually per player), allowing personalized data storage.
- Component State: isolated to a component, allowing personalized item type, display name, lore, etc.
Each state is identified by a explicit key or a implicit key (automatically generated), and its value can be of any type. The state system ensures type safety, so you can store and retrieve values without manual casting.

States are automatically created and destroyed along with the context.
That means:
- A new StateValue map is initialized when a inventory of a context is opened.
- Global and viewer-specific data live as long as the inventory is open.
- Once the inventory is closed for all viewers, all associated state data is discarded.
This design ensures a clean separation of data per context instance.
Let's imagine a usage scenario
For a counter, we will have an "increment" button, a "decrement" button and an item that will represent the current value of the counter. The counter value is an integer so we have mutableIntState(initialValue)
with a initial value of 0
for that.
1 class CounterView extends View {
2 private final MutableIntState counterState = mutableIntState(0);
3
4 }
States are defined during the initialization of your view so put them at the top-level of the class or in the constructor.
Now we are going to create the "increase" and "decrease" button, here there is no secret, just as it is explained in the Basic Usage guide. They will be both arrow.
1 class CounterView extends View {
2 private final MutableIntState counterState = mutableIntState(0);
3
4 @Override
5 public void onFirstRender(RenderContext render) {
6 // Decrement button
7 render.slot(2, new ItemStack(Material.ARROW))
8 .cancelOnClick()
9 .onClick(counterState::increment);
10
11 // Increment button
12 render.slot(6, new ItemStack(Material.ARROW))
13 .cancelOnClick()
14 .onClick(counterState::decrement);
15 }
16 }
States are used to store data for a context.
MutableState<String> textState = mutableState("");
textState.get(context); // ""
textState.set("abc", context);
textState.get(context); // "abc"
Real-world example
A counter whose value increases each time the player clicks on the item.
-
watch
Will trigger a update (re-render) on item every timecounterState
value changes. -
rendered
Dynamic renderization so the item is always up to date with the new state value. -
counterState::increment
Increments state value by 1 everyone the player clicks on it
private final MutableIntState counterState = mutableState(0);
@Override
public void onFirstRender(RenderContext render) {
render.firstSlot()
.watch(counterState)
.renderWith(() -> new ItemStack(
/* type = */ Material.DIAMOND,
/* amount = */ counterState.get(render)
))
.onClick(counterState::increment);
}
A computed state, is a state whose value that is returned when there is an attempt to obtain the value of that state is computed from its initial value.
The declaration is similar to the mutable state one except it asks for a Supplier
instead of a static initial value.
State<Integer> randomNumberState = computedState(ThreadLocalRandom.current()::nextInt);
randomNumberState.get(host); // some random number
randomNumberState.get(host); // another random number
Real-world example
An item that sends a random number to the player with each click.
As it is a computed state each time the value is obtained a new value will be computed based on the factory provided when creating the state, in our case ThreadLocalRandom.current()::nextInt
.

private final State<Integer> randomNumberState =
computedState(() -> ThreadLocalRandom.current().nextInt(1, 64));
@Override
public void onFirstRender(RenderContext render) {
render.firstSlot()
.onRender(slotRender -> slotRender(new ItemStack(
/* type */ Material.DIAMOND,
/* amount */ randomNumberState.get(slotRender)
))
.onClick(IFContext::updateSlot);
}
A lazy state is one whose value is only set the first time some object tries to get the value of that state.
In short, you define what the value will be, try to get the value, and the value obtained from there will be the value that will be obtained in subsequent calls to get the value of the state.
State<Integer> intState = lazyState(ThreadLocalRandom.current()::nextInt);
randomIntState.get(...); // 54 - from initial computation of random integer ^^
randomIntState.get(...); // 54 - previously defined by the initial computation
The initial state is a immutable lazy state whose value is defined during the creation of the object that holds it.
For example: if the object that holds it is a ViewContext, the initial value of that state will be the initial data defined during the creation of the context, that is, when the view that the context originated from is opened for a player.
<T> State<T> initialState(String! key);
Example of using the initial state to store the id of an object initially defined when opening a context container
State<String> someId = initialState("some-id");
How someId
is defined: before context creation, on open.
class MyAwesomeView extends View {
private static final String SOME_ID = "some-id";
final State<String> someId = initialState(SOME_ID);
}
Set it on open through initial data map.
viewFrame.open(
MyAwesomeView.class,
player,
ImmutableMap.of(SOME_ID, "github")
);
- Installation
- Introduction
- Advanced Usage
- Built-In Features
- Extra Features
- Anvil Input
- Proxy Inventory