mini_program_ui 0.2.3 copy "mini_program_ui: ^0.2.3" to clipboard
mini_program_ui: ^0.2.3 copied to clipboard

Pure-Dart authoring helpers for versioned Mp JSON mini-program screens.

mini_program_ui #

Pure-Dart authoring helpers for the Mp JSON mini-program engine.

This package intentionally has no Flutter, Stac, Material, Cupertino, analyzer, or build_runner dependency. Mini-program authors write Mp.* source, then mini_program_tooling runs tool/build_mp.dart and writes versioned JSON for the SDK renderer.

Supported Import #

Mini-program source should use only the package barrel:

import 'package:mini_program_ui/mini_program_ui.dart';

Files below lib/src/ are implementation details. The implementation is organized into dependency-free core values, program assembly, and feature-owned node/action builders. The temporary legacy src compatibility re-exports were removed in 0.2.0; direct internal imports are unsupported.

Program Shape #

import 'package:mini_program_ui/mini_program_ui.dart';

final miniProgram = MpProgram(
  screens: <String, MpScreenBuilder>{
    'coupon_home': buildCouponHome,
    'coupon_details': buildCouponDetails,
  },
);

The build script is small and deterministic:

import 'package:mini_program_ui/mini_program_ui.dart';

import '../mp/program.dart';

Future<void> main(List<String> arguments) {
  return writeMpBuildOutput(miniProgram, arguments: arguments);
}

Basic UI #

MpNode buildCouponHome() {
  return Mp.column(
    children: <MpNode>[
      Mp.heading('Coupon Center'),
      Mp.text('Portable rewards for host apps.'),
      Mp.image(src: 'https://example.com/reward.png'),
      Mp.card(child: Mp.text('SDK-owned component styling')),
      Mp.primaryButton(
        label: 'Open details',
        action: Mp.navigation.openScreen('coupon_details'),
      ),
    ],
  );
}

Auth #

Mp.authBuilder(
  loading: Mp.text('Checking session...'),
  signedOut: Mp.card(
    child: Mp.column(
      children: <MpNode>[
        Mp.text('Sign in to continue.'),
        Mp.primaryButton(
          label: 'Sign in with email',
          action: Mp.auth.showEmailAuth(),
        ),
      ],
    ),
  ),
  signedIn: Mp.card(
    child: Mp.column(
      children: <MpNode>[
        Mp.text('Signed in as {{auth.user.email}}'),
        Mp.secondaryButton(label: 'Sign out', action: Mp.auth.signOut()),
      ],
    ),
  ),
  error: Mp.text('{{auth.message}}'),
);

Auth bindings never expose idToken, refreshToken, passwords, or backend secrets.

Backend Data #

Mp.backendBuilder(
  requestId: 'home',
  endpoint: 'home/bootstrap',
  loading: Mp.text('Loading...'),
  error: Mp.text('{{backend.home.message}}'),
  child: Mp.card(
    child: Mp.column(
      children: <MpNode>[
        Mp.heading('{{backend.home.data.title}}'),
        Mp.text('{{backend.home.data.message}}'),
      ],
    ),
  ),
);

Artifact-Local Data And Visualization #

Large immutable lookup data can stay under the mini-program's assets/ directory. The runtime validates it, persists it only in the host-approved data cache, and keeps search indexes outside live state.

Mp.initialize(
  actions: <MpAction>[
    Mp.data.loadJsonAsset(
      id: 'locations',
      asset: 'data/locations.json',
      statusState: 'location.resource_status',
      errorState: 'location.resource_error',
    ),
  ],
  child: Mp.column(
    children: <MpNode>[
      Mp.searchField(
        stateKey: 'location.query',
        hint: 'Dhaka',
        onChanged: Mp.data.search(
          resourceId: 'locations',
          query: '{{state.location.query}}',
          fields: const <String>['name', 'district'],
          itemsPath: 'locations',
          targetState: 'location.results',
        ),
      ),
      Mp.lineChart(
        source: '{{state.forecast.hourly}}',
        valueField: 'temperature',
        labelField: 'timeLabel',
      ),
    ],
  ),
);

JSON data paths are artifact-relative and cannot be remote URLs. Use direction: 'horizontal' with an explicit height on Mp.listView or Mp.repeat for horizontal collections. Mp.refreshIndicator is supported only as a screen root.

Local Text Editing #

Use a state-bound field for local drafts and editors that are not submitted as a backend form. The SDK keeps its controller synchronized with live state, and the host's live-state limits still apply.

Mp.stateTextField(
  stateKey: 'note.body',
  hint: 'Enter text...',
  maxLength: 4096,
  minLines: 8,
  maxLines: 30,
  keyboardType: 'multiline',
  textInputAction: 'newline',
);

Wrap custom visual content with Mp.tap when it needs one semantic action without adopting button presentation.

Paged Lists #

Use Mp.lazy.chunk when repeated data is large, dynamic, comes from a Publisher API, and needs pagination or manual Load more:

Mp.lazy.chunk(
  id: 'rewards_chunk',
  itemsState: 'rewards.items',
  cursorState: 'rewards.next_cursor',
  hasMoreState: 'rewards.has_more',
  statusState: 'rewards.status',
  cacheKeyPrefix: 'rewards_chunk',
  placeholder: Mp.text('Loading rewards...'),
  loadingMore: Mp.text('Loading more...'),
  empty: Mp.text('No rewards yet.'),
  end: Mp.text('No more rewards.'),
  error: Mp.text('Rewards failed to load.'),
  itemTemplate: Mp.card(child: Mp.text('{{item.title}}')),
  initialActions: <MpAction>[
    Mp.backend.loadMore(
      requestId: 'rewards',
      endpoint: 'coupons/page',
      limit: 20,
    ),
  ],
  loadMoreActions: <MpAction>[
    Mp.backend.loadMore(
      requestId: 'rewards',
      endpoint: 'coupons/page',
      limit: 20,
    ),
  ],
  loadMore: Mp.secondaryButton(
    label: 'Load more',
    action: Mp.lazy.loadMore(id: 'rewards_chunk'),
  ),
);

Do not use Mp.lazy.chunk for login pages, small settings pages, static about pages, single detail pages, payment forms, fixed menus, or small local JSON lists.

Mp.pagedBackendBuilder remains available for direct backend-bound paged lists:

Mp.pagedBackendBuilder(
  requestId: 'rewards',
  endpoint: 'coupons/page',
  limit: 20,
  loading: Mp.text('Loading rewards...'),
  loadingMore: Mp.text('Loading more...'),
  empty: Mp.text('No rewards yet.'),
  end: Mp.text('No more rewards.'),
  error: Mp.text('{{backend.rewards.message}}'),
  itemTemplate: Mp.card(
    child: Mp.column(
      children: <MpNode>[
        Mp.heading('{{item.title}}'),
        Mp.text('{{item.description}}'),
      ],
    ),
  ),
  loadMore: Mp.secondaryButton(
    label: 'Load more',
    action: Mp.backend.loadMore(requestId: 'rewards'),
  ),
);

Default provider-neutral response shape:

{
  "items": [],
  "nextCursor": null,
  "hasMore": false
}
Mp.primaryButton(
  label: 'Open details',
  action: Mp.navigation.openScreen('coupon_details'),
);

Mp.secondaryButton(
  label: 'Back',
  action: Mp.navigation.popScreen(),
);

Control Flow And Timers #

Conditions are strict booleans or full bindings. Countdown state contains the remaining whole seconds, rounded up.

Mp.timer.countdown(
  duration: const Duration(seconds: 10),
  running: '{{state.screen.running}}',
  restartToken: '{{state.screen.content_id}}',
  remainingState: 'screen.remaining_seconds',
  onComplete: Mp.action.ifElse(
    condition: '{{state.screen.can_advance}}',
    thenAction: Mp.state.set('screen.status', 'advanced'),
    elseAction: Mp.state.set('screen.status', 'expired'),
  ),
  child: Mp.condition(
    condition: '{{state.screen.ready}}',
    whenTrue: Mp.text('{{state.screen.remaining_seconds}} seconds'),
    whenFalse: Mp.text('Waiting'),
  ),
);

Setting running to false pauses the countdown. Changing restartToken resets it to the configured duration. Timers are cancelled when their node is disposed.

Current Location #

Mini-programs can request one foreground, approximate location through a host provider. The host must separately accept the request and install the native provider.

Mp.location.getCurrent(
  accuracy: 'approximate',
  timeout: const Duration(seconds: 10),
  targetState: 'location.current',
  statusState: 'location.status',
  errorState: 'location.error',
  requestId: 'current-location',
);

This API does not support background tracking, continuous updates, or precise location.

Camera And Flashlight #

Camera capture delegates to the Android system camera. Bounds are optional; when omitted, the camera's native output size is retained. The host returns an opaque media reference and metadata rather than a path, URI, or image bytes.

Mp.camera.capturePhoto(
  quality: 95,
  maxWidth: 1920,
  targetState: 'camera.photo',
  statusState: 'camera.status',
  errorState: 'camera.error',
);

Mp.camera.cancel(statusState: 'camera.status');

Mp.flashlight.toggle(
  targetState: 'flashlight.status',
  errorState: 'flashlight.error',
);

Camera and flashlight are separate host permissions. Camera does not provide a live camera feed, video recording, or arbitrary camera access. A captured photo can be rendered through the trusted host media provider without exposing its path or bytes to mini-program state:

Mp.image(
  src: '{{state.camera.photo.mediaRef}}',
  source: MpImageSource.hostMedia,
  alt: 'Captured photo',
);

Publisher File Transfers #

File actions use relative routes on the artifact-declared Publisher API. The host must accept file policy and install a platform transfer provider. Files are streamed by the host; mini-program state receives only progress and sanitized result metadata.

Mp.file.upload(
  endpoint: 'files/upload',
  mimeTypes: const <String>['image/*', 'application/pdf'],
  mediaRefs: const <String>['{{state.camera.photo.mediaRef}}'],
  multiple: true,
  metadata: const <String, Object?>{'folderId': 'inbox'},
  progressState: 'files.progress',
  targetState: 'files.uploadResult',
  statusState: 'files.status',
  errorState: 'files.error',
);

Mp.file.download(
  endpoint: 'files/download',
  request: const <String, Object?>{'fileId': 'file-1'},
  destination: 'downloads',
  suggestedName: 'report.pdf',
  expectedMimeType: 'application/pdf',
  progressState: 'files.progress',
  targetState: 'files.downloadResult',
);

Mp.file.cancel(
  transferId: '{{state.files.progress.transferId}}',
  statusState: 'files.status',
);

Mp.media.release(
  mediaRef: '{{state.camera.photo.mediaRef}}',
  statusState: 'camera.status',
);

Upload and download are network operations, not aliases for local pick/save. The publisher server owns file IDs, folders, ACLs, and business metadata. When mediaRefs is empty, upload opens the platform document picker. When it is present, the host streams already-owned temporary media. Release media only after upload succeeds or when the user discards it; host lifecycle cleanup is the final fallback.

Security Model #

mini_program_ui only serializes declarative JSON. It does not execute host code and it does not contain renderer logic. Runtime validation, auth sessions, runtime API headers, and bridge dispatch are owned by mini_program_sdk.

0
likes
0
points
499
downloads

Publisher

unverified uploader

Weekly Downloads

Pure-Dart authoring helpers for versioned Mp JSON mini-program screens.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

mini_program_contracts

More

Packages that depend on mini_program_ui