Skip to main content

Presentation recipes

These recipes use only the public screen contracts and presenters. Adapt widget names and navigation to the game; keep the lifecycle and state rules.

Observe and clean upโ€‹

Every page keeps one observation (screen contract) or subscription (presenter) for its active lifetime. See the full Lobby example. The guarantees:

  1. one active Observe or SubscribeAsync per page, created when it opens;
  2. the exact IDisposable disposed when it closes;
  3. presenter calls that take a token cancelled when the page closes. Screen commands take no token: check that the page is still alive before acting on their result.

ProjectV's own views use UniTask (.AsUniTask()); the rules are the same.

Start, restore and show Loginโ€‹

private async Task RunStartupAsync(bool contentReady, CancellationToken token)
{
var result = await startup.StartAsync(contentReady, token);
var state = result.Snapshot;

if (state.IsReady)
{
NavigateToLobby();
return;
}

if (state.RequiresLogin)
{
ShowLoginChoices();
return;
}

if (state.IsBlocking)
{
ShowBlocking(state.Status, state.Error?.LocalizationKey, state.Error?.RequestId);
}
}

Password login, then continue startup:

var result = await authentication.LoginPasswordAsync(username, password, token);
if (!result.IsSuccess)
{
ShowLocalized(result.Snapshot.Error?.LocalizationKey);
return;
}

await RunStartupAsync(contentReady: true, token);

Call GetNativeAvailability before showing Google or Apple buttons. Guest sign-in is on the Authentication page.

Render the headerโ€‹

Subscribe to IProjectVHeaderPresenter; render Currencies by view key and read StaminaRecovery for the countdown. See Header. Never add purchase or reward amounts to a balance yourself.

Load a screenโ€‹

With a screen contract, observing is enough: the SDK loads what the cache lacks, and returning to a screen usually costs no request. Show a retry prompt from LoadError while IsLoading.

With a presenter, call RefreshAsync once on entry, or on an explicit Retry; it answers from the cache when the data is fresh. Do not refresh every frame, refresh from several widgets in parallel, or refresh after a successful command: the command already wrote the server's new state into the cache.

Purchase a Shop offerโ€‹

private async Task BuyAsync(string offerId, int quantity)
{
var result = await shop.PurchaseAsync(offerId, quantity); // IProjectVShopScreen
if (this != null && !result.IsSuccess)
{
ShowMessage(result.Message);
}
}

A repeated tap joins the running purchase. The listing and the Header update through their own renders once the server confirms; the purchase costs one request.

Begin a missionโ€‹

Call IProjectVCampaignScreen.BeginMissionAsync, then load the gameplay scene once; on any load failure call CancelSceneLoadAsync. See Screen contracts and Gameplay data.

Level up a characterโ€‹

Call IProjectVCharacterScreen.LevelUpAsync(characterId, items) (or the Alliance equivalent) once per tap. On success Message is empty: the view redraws with the new level, so compare Level before and after to show "+N levels". On failure, show Message. See Character.

Render recovery stateโ€‹

When a snapshot has Recovery:

  • keep data visible when HasUsableData is true;
  • show an offline/partial indicator from Phase;
  • show Retry only when CanRetry, Sync only when CanSync;
  • respect RetryAfterSeconds.

Logoutโ€‹

Call IProjectVAccountPresenter.LogoutAsync. When the snapshot reports RequiresLoginNavigation, load the Login scene. The SDK clears the session and every account snapshot; do not delete PlayerPrefs or tokens yourself.