pikd_flutter_ui 0.8.0-beta.2 copy "pikd_flutter_ui: ^0.8.0-beta.2" to clipboard
pikd_flutter_ui: ^0.8.0-beta.2 copied to clipboard

Themeable Flutter widgets for PIKD leaderboards, challenges, profiles, feeds, collections, and geospatial discovery experiences.

pikd_flutter_ui — PIKD prebuilt UI modules #

Themeable, drop-in Flutter widgets for the PIKD experience: Leaderboard, Explore map, Profile/Inventory, Feed/Updates, Challenges/Quests. Take the modules you want, theme them to your brand, and back them with either the live /sdk/v1 client or built-in sample data, so you can see a module before you have a key.

Each module is the same shape:

view  (PikdXView)  ←  repository (XRepository)  ←  data
                         ├── SampleXRepository     (built-in sample data)
                         └── PikdSdkXRepository     (live /sdk/v1)

The view never talks to the network itself — it always reads through a repository. That one seam is what lets you start with sample data, switch to the live client, or plug in your own backend, without changing the view.

Release candidate: pikd_flutter_ui version 0.8.0-beta.2.


Install #

dependencies:
  pikd_flutter_ui: ^0.8.0-beta.2
  pikd_flutter_api: ^0.8.0-beta.2 # Import directly when configuring live repositories.

Then run flutter pub get. No PIKD repository access is required.

Runtime deps pulled in transitively: google_maps_flutter, lottie, cached_network_image, palette_generator, flutter_widget_from_html_core.


1. Wrap your app in the theme #

Provide a PikdTheme above your Navigator (e.g. via MaterialApp.builder) so pushed detail routes inherit it too. Omit theme to get the PIKD default dark.

MaterialApp(
  builder: (context, child) => PikdThemeProvider(
    theme: PikdTheme.pikdDefault(),          // or your rebrand — see Theming
    child: child!,
  ),
  home: const MyHome(),
);

2. Sample data (no backend — great for a first run) #

Every module takes a repository, so you can render one before you have an SDK key. The Sample* repositories return fixed in-memory data: no network calls, no artificial delays.

const PikdLeaderboardView(repository: SampleLeaderboardRepository());
const PikdProfileView(repository: SampleProfileRepository());
const PikdFeedView(repository: SampleFeedRepository());
const PikdChallengesView(repository: SampleChallengeRepository());

Don't ship these in an app you hand to users, and don't use them as a fallback when credentials are missing — fabricated rows that look real are worse than an honest error. The repository demo deliberately has no such fallback for that reason.

3. Live mode (/sdk/v1) #

Build one ApiClient (base URL + SDK key), hand each module its *Api + your userRef (the host-side user reference; the server maps it to the PIKD user). User-scoped modules need it; anonymous (Tier 0) can omit it where allowed.

import 'package:pikd_flutter_api/api.dart';

ApiClient client() {
  final c = ApiClient(basePath: 'https://api.pikd.app/sdk/v1');
  c.addDefaultHeader('x-pikd-sdk-key', 'pk_live_xxx');
  return c;
}

const userRef = 'your-host-user-id';

// Leaderboard — own-rank card needs userRef, list works without it.
PikdLeaderboardView(
  repository: PikdSdkLeaderboardRepository(LeaderboardApi(client()), userRef: userRef),
);

// Profile / Inventory — user-scoped, userRef required.
PikdProfileView(
  repository: PikdSdkProfileRepository(ProfileApi(client()), userRef: userRef),
);

// Feed / Updates.
PikdFeedView(
  repository: PikdSdkFeedRepository(FeedApi(client()), userRef: userRef),
  onOpenUpdate: (item) { /* push your detail route */ },
  onOpenComments: (item) { /* push your comments route */ },
);

// Challenges / Quests.
PikdChallengesView(
  repository: PikdSdkChallengeRepository(ChallengesApi(client()), userRef: userRef),
  onOpenChallenge: (challenge) { /* push detail */ },
);

4. Explore map (extra setup) #

The Explore module renders a Google map, so it needs a Google Maps API key in your host app (iOS Info.plist GMSApiKey + AppDelegate, Android manifest com.google.android.geo.API_KEY) and iOS deployment target ≥ 14.0. It also takes a location provider and callbacks for opening AR:

PikdExploreMapView(
  repository: PikdSdkExploreRepository(
    CollectiblesApi(client()),
    languageRef: tenantLanguageRef,
    xPikdUser: userRef,
  ),
  locationProvider: myLocationProvider,      // implement ExploreLocationProvider
  onCollect: (collectible) { /* launch AR collect — see the AR binding */ },
  onNavigateAr: (collectible) { /* launch AR wayfinding */ },
  onOpenExternalMap: (lat, lng) { /* open Apple/Google Maps */ },
);

The two nearby operations have different jobs and must not be interchanged:

  • PikdExploreMapView uses /collectibles/nearby-within for the map's paginated Nearby 100, challenge, and expiry-filtered discovery.
  • An AR camera uses PikdSdkExploreRepository.fetchArNearby() and therefore /collectibles/nearby, passing the collection radius and current user.

/collectibles/nearby caps the radius at 5 km, and /sdk/v1 does not yet offer the bounding-box or distinct-nearby endpoints the React Native app uses — so both the viewport search and the nearby sheet filter that radius result for now.

3D / AR is a separate package. pikd_flutter_ui Explore is the map and discovery only; it does not render 3D. Each ExploreCollectible carries assetModelUrlIos (USDZ) / assetModelUrlAndroid (GLB) from the nearby payload — take those into pikd_flutter_ar (PikdArView) to place and render the asset in AR. See that package's README for native integration details.

Repeatable (multi-collect) assets #

Some collectibles are repeatable: they stay pinned on the map permanently and a user can collect a duplicate until the asset's instances run out. They arrive flagged as ExploreCollectible.isMultipleInstance (from the nearby payload) and the map gives them a distinct, themed marker so they read differently from normal one-off drops.

Three things to handle if you build your own collect surface:

  • Don't remove them from the map after a collect — the backend keeps returning them, and they're meant to stay visible.
  • collect returns status: instance_maxed_out once the asset has no collections left. It comes back HTTP 201, not an error — treat it as its own "nothing left to collect" state. If you branch on status == 'collected' and lump everything else into a failure message, users will see "collect failed" when they've simply hit the cap. There is no remaining-count or reset-at field, so the limit is only discoverable this way. (The API docs example shows threshold_exceeded for the same case; the deployed API sends instance_maxed_out, so accept both.)
  • The cap is global per asset, and permanent. Backend-confirmed 2026-07-31: it is not per user and it never resets — once an asset's instances are exhausted it is finished for everybody. We previously documented it as a per-user daily limit and told users to "check back tomorrow"; both halves were wrong. Word your copy as a permanent state, and note there's no remaining-count field, so you can't warn a user that they're taking the last one.
  • Inventory returns one row per collect — repeated collects of the same asset appear as separate instances (distinct id + collectedAt), not a single row.

The PIKD demo application wires onCollect end-to-end into an AR collect screen with instant placement, tap-to-collect, a loading state, collection success, and the repeatable-asset limit state.

Don't call startArSession() at all when you mount PikdArView — the view owns the session. It auto-starts on attach on both platforms (and handles the not-yet-initialised case itself). Starting it yourself too is a real double-start: both land in one pending-completion slot, so the view's (completion-less) start replaces yours and your await never resumes — the session runs fine, your screen waits forever — and the duplicate start can leave native session state inconsistent across a close/reopen. Instead subscribe to PikdAr.events first and wait for ArSessionStarted, with a timeout that proceeds anyway (an already-running session emits no new event). The reference screen does exactly this — copy that shape.


Theming (rebranding) #

Every brand/surface/accent/glass colour and the fonts route through PikdTheme. Pass a custom theme to fully rebrand — no view code changes:

final myBrand = PikdTheme.pikdDefault().copyWith(
  colors: PikdColors.dark.copyWith(
    primary: const Color(0xFF7C4DFF),
    activeAccent: const Color(0xFFFF6D00),
    // surfaceBrand, glass, primaryGradient, rank1/2/3, ...
  ),
);

A handful of values are intentionally fixed (shadows, scrims, medal gold/silver/bronze, neutral control greys) — see the theming notes. Provide the theme above the Navigator so pushed routes inherit it.

Notifications: use PikdToast, not SnackBar #

Transient messages go through PikdToast — a top-anchored card with a title, an optional description, and a severity accent taken from the success / error / warning / info roles. It auto-hides after 3 s and dismisses on tap or swipe-up.

PikdToast.show(
  context,
  title: 'Missing iOS 3D file',
  description: 'iOS requires USDZ format.',
  type: PikdToastType.warning,
);

Reach for this instead of Flutter's SnackBar: a SnackBar paints Material's own colours, so it is the one surface in a screen that would not follow your rebrand. PikdToast needs an Overlay in scope (any MaterialApp has one) and no-ops rather than throwing if there isn't.


Bring your own data #

Any module works against your own backend — implement the repository interface (LeaderboardRepository, ProfileRepository, FeedRepository, ChallengeRepository, ExploreRepository) and pass it to the view. The PikdSdk* and Sample* classes are just two implementations.

Try it #

Run the package example to preview the leaderboard module without credentials:

flutter run example/pikd_flutter_ui_example.dart

The full PIKD demo application runs all modules against /sdk/v1 and therefore requires issued credentials:

flutter run \
  --dart-define=PIKD_BASE=https://api.pikd.app/sdk/v1 \
  --dart-define=PIKD_SDK_KEY=pk_live_xxx \
  --dart-define=PIKD_USER=your-host-user-id \
  --dart-define=PIKD_LANGUAGE_REF=tenant-language

The full demo additionally requires a Google Maps key and platform-specific AR configuration.

License #

This package is proprietary software. Use requires a written SDK agreement with ELEOS WORLD LTD. See LICENSE and the PIKD SDK license page.

0
likes
130
points
90
downloads

Documentation

API reference

Publisher

verified publisherpikd.app

Weekly Downloads

Themeable Flutter widgets for PIKD leaderboards, challenges, profiles, feeds, collections, and geospatial discovery experiences.

Topics

#augmented-reality #geospatial #ui #widget #sdk

License

unknown (license)

Dependencies

cached_network_image, flutter, flutter_widget_from_html_core, google_maps_flutter, lottie, palette_generator, pikd_flutter_api

More

Packages that depend on pikd_flutter_ui