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:
- one active
ObserveorSubscribeAsyncper page, created when it opens; - the exact
IDisposabledisposed when it closes; - 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
HasUsableDatais true; - show an offline/partial indicator from
Phase; - show Retry only when
CanRetry, Sync only whenCanSync; - 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.