Skip to content

Conversation

@devnatan
Copy link
Owner

@devnatan devnatan commented Apr 27, 2025

Generic asynchronous state. TBD.

Today we have a limbo in the flow of opening an view that is at the moment BEFORE opening and AFTER opening, where the information between those steps goes. People often come to me with questions about how to deal with asynchronous data due to the nature of the Inventory Framework views. Async state come to help.

Without an async state we needed to write a UserProfileView that loads a UserProfile from the database we...

  1. Create a initialState that holds a UserProfile type
  2. Opens Inventory Framework view with it as initial state.
class UserProfileView extends View {
    
    private final State<UserProfile> profileState = initialState();
    
    @Override
    public void onFirstRender(RenderContext render) {
        final UserProfile profile = profileState.get(render);
    }
}

This makes the view dependent on the value coming from wherever, given that the view can be opened in multiple ways and makes it incompatible with the idea of ​​creating Flows with inventories, so, if we use for example a context.back() or context.openForPlayer(UserProfileView.class) from another view, we would need to somehow obtain this value (UserProfile) that is being pulled from the database asynchronously and assign it as a parameter of the view but... the problem here is that this view that is calling the UserProfileView view is not asynchronous and it does not have the capacity to process this information.

Now with asynchronous state we...

  1. Create a initialAsyncState that holds a UserProfile type that expects a CompletableFuture<UserProfile> parameter
  2. Assign that state value with values injected from constructor like UserProfileManager or UserProfileDatabase;
class UserProfileView extends View {
    
    private final AsyncState<UserProfile> profileState;

    public UserProfileView(UserProfileDatabase userProfileDatabase) {
        // Creates a new Initial Async State that loads user profile once the view is open
        profileState = initialAsyncState(ctx -> userProfileDatabase.loadProfile(ctx.getPlayer().getUniqueId()));
    }

    @Override
    public void onOpen(OpenContext open) {
        // Waits until profile is loaded before opening the inventory (optional)
        open.waitFor(profileState);
    }
    
    @Override
    public void onFirstRender(RenderContext render) {
        UserProfile profile = profileState.get(render);

        // If we DO NOT use `open.waitFor(profileState)`, inventory will not wait
        // user profile to load to open so `profileState.get(render)` will return `null`
        // in that case, we can show a loading state inside the inventory.
        AsyncValue profileAsyncValue = profileState.access(render);
        
        // Shows a PAPER if profile stills loading, hides automatically once profile is loaded
        render.middleSlot(new ItemStack(Material.PAPER))
            .displayIf(profileAsyncValue::isLoading)
            .updateOnStateChange(profileState);
    }
}

Error handling can also be done in the view itself, showing a item for example!

@Override
public void onFirstRender(RenderContext render) {
    UserProfile profile = profileState.get(render);
    AsyncValue profileAsyncValue = profileState.access(render);

    // Shows a BARRIER if profile load fails
    render.middleSlot(new ItemStack(Material.BARRIER))
        .displayIf(profileAsyncValue.getResult()::isError)
        .updateOnStateChange(profileState);
}

I will rewrite State Management on Wiki entirely before merging this PR.

@devnatan devnatan self-assigned this Apr 27, 2025
@devnatan devnatan added the feature New feature or enchancement label Nov 28, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New feature or enchancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants