Skip to main content
Firstlight

Firstlight / Docs

Recipes for the screens a forms app needs, setup for your coding agent, Laravel on native screens, and accessibility.

View source
Show connectivity and refresh on reconnect

Show Connectivity and Refresh on Reconnect

Firstlight tells users when the device is offline with one line in the nav bar subtitle, and refreshes stale screens when the device comes back online:

When Subtitle
Online, nothing waiting none
Offline Offline
Offline with writes your app queued Offline · 3 waiting to sync
Back online, your queue is sending Syncing 3 changes…
Some writes failed 1 failed to sync

Firstlight never fetches and doesn't queue writes. It reads the OS, shows the counts your queue reports, and calls your screen's revalidate() hook.

Install the Network plugin

Firstlight reads Network::status(), so install NativePHP's Network plugin:

composer require nativephp/mobile-network

Without it Firstlight can't tell, treats the device as online, and shows nothing. In debug builds it logs one warning saying so.

Put the line in the nav bar

Mobile UI owns the bar. Set its subtitle from your layout:

use FirstlightUI\Facades\Connectivity;
use Native\Mobile\Edge\Layouts\Builders\NavBar;
use Native\Mobile\Edge\NativeComponent;

public function navBar(NativeComponent $screen): ?NavBar
{
    return NavBar::make()
        ->title($screen->navTitle())
        ->subtitle(Connectivity::statusLine());
}

statusLine() returns null when there's nothing to say, which leaves no subtitle. On iOS, NativePHP Mobile 4.7 doesn't draw the subtitle on a tab root yet; mobile-air#673 fixes it.

Anywhere else, Connectivity::isOffline() is true only when the OS says the device is offline.

Refresh when the user is back

With TracksFreshness, override revalidate(). Firstlight calls it on the screen on display when the device comes back online, and when the app returns from the background after longer than the screen's shortest freshFor window:

protected function revalidate(): void
{
    if ($this->freshness('servers')->isStale()) {
        $this->reload();
    }
}

The screen Show data freshness builds needs only this method. Data loaded a few seconds ago is still fresh, so returning from the camera, which counts as leaving the app on Android, costs nothing.

Firstlight hears the app return from NativePHP's AppForegrounded event, or from App Guard's Foregrounded on NativePHP Mobile versions without it. Without either, it still notices a reconnect when a screen opens or is shown again after Back.

Show your write queue

If your app queues writes while offline, implement PendingChanges on the queue and register it in a service provider:

use FirstlightUI\Connectivity\PendingChanges;

class Outbox implements PendingChanges
{
    public function waitingCount(): int { /* not yet sent, including sending */ }

    public function failedCount(): int { /* refused by the server */ }

    public function isSyncing(): bool { /* a drain is running */ }

    public function sync(): void
    {
        // Start one drain, such as an AsyncTask, and return.
    }
}
// AppServiceProvider::boot()
Connectivity::pendingUsing(Outbox::class);

When the device comes back online, Firstlight calls sync() once, then revalidate(). When your drain finishes, report it:

Connectivity::syncFinished($synced, $failed);

That shows Synced 3 changes., and 1 change failed to sync. with a Review action. Open your own pending screen from it:

use FirstlightUI\Connectivity\ConnectivityManager;
use FirstlightUI\Events\FeedbackActionPressed;
use Native\Mobile\Attributes\On;

#[On(FeedbackActionPressed::class)]
public function feedbackAction(string $id, string $actionKey): void
{
    if ($actionKey === ConnectivityManager::REVIEW_ACTION) {
        $this->navigate('/pending');
    }
}

For rows changed while offline, use a Status Label with Firstlight's strings:

<firstlight:status-label :label="__('firstlight::chrome.sync_waiting_label')" tone="neutral" />
<firstlight:status-label :label="__('firstlight::chrome.sync_failed_label')" tone="danger" />

Change the wording

Override connectivity_offline, connectivity_offline_waiting, connectivity_syncing, connectivity_failed, sync_succeeded, sync_failed, sync_review, sync_waiting_label, and sync_failed_label in lang/vendor/firstlight/{locale}/chrome.php, as described in Localize chrome. Counted strings use Laravel plural forms, such as Synced :count change.|Synced :count changes..

Test it

Connectivity::fake() starts online and drives the transitions:

use App\NativeComponents\ServersScreen;
use FirstlightUI\Facades\Connectivity;
use Illuminate\Support\Facades\Http;
use Native\Mobile\Testing\Native;

it('loads the servers when the device comes back online', function () {
    $online = false;
    Http::fake(function () use (&$online) {
        return $online
            ? Http::response(['data' => [['id' => 1, 'name' => 'web-1']]])
            : Http::failedConnection();
    });

    $connectivity = Connectivity::fake()->goOffline();
    $screen = Native::test(ServersScreen::class)->assertSet('servers', []);

    $online = true;
    $connectivity->goOnline();

    $screen->assertSet('servers', [['id' => 1, 'name' => 'web-1']]);
});

goOffline(), goOnline(), goUnknown(), and foreground($secondsAway) mirror what the device sends. The fake keeps your pendingUsing() queue.

Served from firstlightui/nativephp on GitHub.