Nosmai / docs
Nosmai Effects Nosmai Moderation Nosmai Try-ons coming soon
Docs menu Flutter
docs / nosmai effects / platform guides / flutter

Flutter

Add Nosmai Effects to a Flutter app: camera preview, beauty, makeup, color, filters, capture and recording.

Overview

The Flutter package provides one Dart API for the Nosmai Android and iOS SDKs.

It supports:

  • native processed camera preview
  • front and back camera switching
  • built-in beauty and makeup
  • local and cloud .nosmai filters
  • active-effect state
  • background color, image, and video replacement
  • photo capture
  • processed video recording
  • gallery save
  • native error and license events

The official Flutter repository contains the package, example application, releases, and issue tracker:

github.com/nosmai/nosmai_camera_sdk_flutter

Requirements

RequirementValue
Flutter3.22.0 or later
Dart3.0.0 or later
AndroidAPI 21 or later, arm64-v8a device
iOSiOS 15.0 or later, arm64 device
Test environmentPhysical Android or iOS device

Use a physical device for camera preview, face tracking, recording, and performance testing.

Install

Add the current package:

dependencies:
  flutter:
    sdk: flutter
  nosmai_camera_sdk: ^3.0.6

Run:

flutter pub get

The Flutter package resolves its native iOS dependency through CocoaPods. The proprietary Android AAR is distributed separately and is intentionally not included in the pub.dev package.

Configure Android

Add the native Android SDK

  1. Download nosmai-sdk-3.0.1.aar and SHA256SUMS from the Android SDK v3.0.1 release.
  2. Verify the artifact with shasum -a 256 -c SHA256SUMS.
  3. Rename the AAR to nosmai-release.aar.
  4. Place it at android/app/libs/nosmai-release.aar.

Add the local repository to Groovy projects:

allprojects {
    repositories {
        google()
        mavenCentral()
        flatDir { dirs "${rootProject.projectDir}/app/libs" }
    }
}

Kotlin DSL projects use:

allprojects {
    repositories {
        google()
        mavenCentral()
        flatDir { dirs("${rootProject.projectDir}/app/libs") }
    }
}

Add the AAR to the Flutter application module:

dependencies {
    implementation files('libs/nosmai-release.aar')
}

Kotlin DSL application modules use:

dependencies {
    implementation(files("libs/nosmai-release.aar"))
}

Do not place the AAR inside the Flutter package cache or plugin source. The consuming application owns this commercial binary.

Minimum version

Confirm the application uses API 21 or later:

android {
    defaultConfig {
        minSdk = 21

        ndk {
            abiFilters += "arm64-v8a"
        }
    }
}

Permissions

Add the permissions used by the application:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.RECORD_AUDIO" />

    <application
        android:label="Camera App"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">
        ...
    </application>
</manifest>
PermissionRequired when
CAMERAShowing the camera preview
INTERNETLicense verification and cloud filters
RECORD_AUDIORecording or streaming with microphone audio

Request camera and microphone permission at runtime. Do not request microphone permission if the application does not record or stream audio.

Configure iOS

The plugin installs NosmaiCameraSDK through CocoaPods. Do not copy nosmai.framework into the Flutter application manually.

The plugin and native framework supply their SDK privacy manifests. The consuming application remains responsible for declaring its own data collection and required-reason API usage accurately in App Store Connect and any app-level privacy manifest.

Minimum version

Set iOS 15.0 or later:

platform :ios, '15.0'

target 'Runner' do
  use_frameworks! :linkage => :static
  use_modular_headers!

  flutter_install_all_ios_pods(
    File.dirname(File.realpath(__FILE__))
  )
end

After changing native dependencies, run:

cd ios
pod install --repo-update

Permissions

Add the permission descriptions used by the application:

<key>NSCameraUsageDescription</key>
<string>This app uses the camera for real-time filters and effects.</string>

<key>NSMicrophoneUsageDescription</key>
<string>This app uses the microphone when recording video or streaming.</string>

<key>NSPhotoLibraryUsageDescription</key>
<string>This app accesses the photo library when selecting or saving media.</string>

<key>NSPhotoLibraryAddUsageDescription</key>
<string>This app saves captured photos and videos to the photo library.</string>

Camera permission is required for preview. Microphone and photo library permissions are only required when the related features are used.

Initialize

Initialize once before showing NosmaiCameraPreview:

import 'package:flutter/material.dart';
import 'package:nosmai_camera_sdk/nosmai_camera_sdk.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final initialized = await NosmaiFlutter.initialize(
    'NOSMAI-YOUR-LICENSE-KEY',
  );

  if (!initialized) {
    throw StateError('Nosmai initialization failed');
  }

  runApp(const CameraApp());
}

Do not initialize in a widget build method. Use one application-level initialization and reuse NosmaiFlutter.instance.

For production applications, read the license key from application configuration instead of committing it in source code.

Display the camera preview

NosmaiCameraPreview creates the native camera view for the current platform:

import 'package:flutter/material.dart';
import 'package:nosmai_camera_sdk/nosmai_camera_sdk.dart';

class CameraScreen extends StatefulWidget {
  const CameraScreen({super.key});

  @override
  State<CameraScreen> createState() => _CameraScreenState();
}

class _CameraScreenState extends State<CameraScreen> {
  bool _cameraReady = false;
  String? _cameraError;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Stack(
        fit: StackFit.expand,
        children: [
          NosmaiCameraPreview(
            onInitialized: () {
              if (!mounted) return;
              setState(() {
                _cameraReady = true;
                _cameraError = null;
              });
            },
            onError: (error) {
              if (!mounted) return;
              setState(() {
                _cameraReady = false;
                _cameraError = error;
              });
            },
          ),
          if (!_cameraReady && _cameraError == null)
            const Center(child: CircularProgressIndicator()),
          if (_cameraError != null)
            Center(child: Text(_cameraError!)),
        ],
      ),
    );
  }
}

The widget starts native processing when the preview is ready. Do not call startProcessing for the same preview unless building a custom camera flow.

Keep one NosmaiCameraPreview active for a camera screen. Multiple active native preview widgets can compete for the same camera.

Camera controls

Switch the camera

final nosmai = NosmaiFlutter.instance;
final switched = await nosmai.switchCamera();

The method ignores rapid repeated taps while a camera switch is already running. Disable the switch button until the returned future completes.

Configure the starting camera

For a custom camera flow:

await NosmaiFlutter.instance.configureCamera(
  position: NosmaiCameraPosition.front,
);

The standard preview already starts with the native default configuration.

Flash and torch

Check support before showing the control:

final nosmai = NosmaiFlutter.instance;

if (await nosmai.hasTorch()) {
  await nosmai.setTorchMode(NosmaiTorchMode.on);
}

The front camera normally does not have a hardware torch.

Built-in beauty

Apply built-in beauty after the preview reports that it is ready:

final nosmai = NosmaiFlutter.instance;

await nosmai.applySkinSmoothing(0.35);
await nosmai.applySkinWhitening(0.10);
await nosmai.applySharpening(0.15);
await nosmai.applyTeethWhitening(0.20);

Use subtle defaults. Strong values can look unnatural and can make small tracking movement more visible.

Remove built-in color and skin controls:

await nosmai.removeBuiltInFilters();

Makeup

Lipstick

await NosmaiFlutter.instance.applyLipstick(
  style: NosmaiLipstickStyle.matte,
  intensity: 0.65,
);

Update or remove it:

await NosmaiFlutter.instance.setLipstickIntensity(0.45);
await NosmaiFlutter.instance.removeLipstick();

Eyeshadow, blusher, eyelashes, and eyebrows

final nosmai = NosmaiFlutter.instance;

await nosmai.applyEyeshadow(
  style: NosmaiEyeshadowStyle.natural,
  intensity: 0.30,
);

await nosmai.applyBlusher(
  style: NosmaiBlusherStyle.natural,
  intensity: 0.25,
);

await nosmai.applyEyelash(
  style: NosmaiEyelashStyle.natural,
  intensity: 0.40,
);

await nosmai.applyEyebrow(
  style: NosmaiEyebrowStyle.natural,
  intensity: 0.30,
);

Remove all makeup:

await nosmai.removeAllMakeup();

Face shaping

final nosmai = NosmaiFlutter.instance;

await nosmai.setFaceSlimLevel(0.20);
await nosmai.setEyeSizeLevel(0.10);
await nosmai.setNoseSlimLevel(0.10);

Remove face shaping:

await nosmai.removeAllMorphing();

Remove all makeup, face shaping, and eye coloring:

await nosmai.removeAllBeautyEffects();

Color controls

Examples:

final nosmai = NosmaiFlutter.instance;

await nosmai.applyBrightnessFilter(0.05);
await nosmai.applyContrastFilter(1.05);
await nosmai.applyRGBFilter(
  red: 1.02,
  green: 1.00,
  blue: 0.98,
);
await nosmai.applyWhiteBalance(
  temperature: 6500,
  tint: 0,
);

Use the documented range of each method. The same value does not represent the same strength for every control.

Add local filters

Production filters use one folder per filter:

assets/
  nosmai_filters/
    glam_lips/
      glam_lips.nosmai
      glam_lips_manifest.json
      glam_lips_preview.png

Declare each folder:

flutter:
  assets:
    - assets/nosmai_filters/glam_lips/
    - assets/nosmai_filters/soft_blush/

Each production entry must contain:

  1. one .nosmai package
  2. one external manifest JSON file
  3. one preview image

The external files allow the application to show names, types, and previews without opening the protected package.

List local filters

Get all production filters:

final filters =
    await NosmaiFlutter.instance.getLocalFilters();

Get them grouped by package type:

final grouped =
    await NosmaiFlutter.instance.getAllLocalFilters();

final regularFilters = grouped['filter'] ?? [];
final effects = grouped['effect'] ?? [];
final beautyEffects = grouped['beauty_effect'] ?? [];
final backgrounds = grouped['background'] ?? [];

Or request one type directly:

final nosmai = NosmaiFlutter.instance;

final effects = await nosmai.getLocalEffects();
final beauty = await nosmai.getLocalBeautyEffects();
final backgrounds = await nosmai.getLocalBackgrounds();

Force a refresh after adding or downloading files:

final filters = await NosmaiFlutter.instance.getLocalFilters(
  forceRefresh: true,
);

Development-only filter discovery

For development, loose .nosmai files can be placed in one asset directory:

assets/
  filters/
    effect_one.nosmai
    effect_two.nosmai

Declare the directory:

flutter:
  assets:
    - assets/filters/

Get every development filter:

final filters =
    await NosmaiFlutter.instance.getDebugFilters();

Get one type:

final effects = await NosmaiFlutter.instance.getDebugFilters(
  type: NosmaiLocalFilterType.effect,
);

Pass no type to get every supported package type. Use the manifest-based local methods for a production filter catalog.

Apply and remove filters

Use applyEffect for all .nosmai package types:

final NosmaiFilter filter = filters.first;
final applied =
    await NosmaiFlutter.instance.applyEffect(filter.path);

The SDK reads the package type and applies it in the correct active category.

Remove the selected package without clearing unrelated active features:

await NosmaiFlutter.instance.removeEffect(filter);

Use the narrow clearing methods when the application has separate controls:

final nosmai = NosmaiFlutter.instance;

await nosmai.clearFilter();
await nosmai.clearAREffect();
await nosmai.clearBackgroundSegmentation();
await nosmai.removeAllBeautyEffects();

Clear every active visual feature:

await nosmai.clearAll();

Adjust effect parameters

Some .nosmai packages expose adjustable values such as intensity, animation speed, or display text.

Read the parameters provided by the active package:

final nosmai = NosmaiFlutter.instance;
final parameters = await nosmai.getEffectParameters();

for (final parameter in parameters) {
  final name = parameter.name;
  final type = parameter.type;
  final currentValue = parameter.currentValue;

  // Build controls supported by this package.
}

Set a numeric value:

final updated = await NosmaiFlutter.instance.setEffectParameter(
  'intensity',
  0.70,
);

Read its current value:

final intensity =
    await NosmaiFlutter.instance.getEffectParameterValue(
  'intensity',
);

Set a text value:

final updated =
    await NosmaiFlutter.instance.setEffectParameterString(
  'headerText',
  'Hello',
);

Use the exact parameter names and types returned by the active package. A package can reject unknown names, unsupported types, or values outside its authored range.

Observe active filters

Read the current state when a screen opens:

final state =
    await NosmaiFlutter.instance.getActiveEffects();

final selectedFilter = state.activeFilter;
final selectedEffect = state.activeEffect;
final backgroundActive = state.hasBackground;

Listen for later changes:

import 'dart:async';

StreamSubscription<NosmaiActiveEffects>? activeEffectsSubscription;

void observeNosmaiState() {
  activeEffectsSubscription =
      NosmaiFlutter.instance.onActiveEffectsChanged.listen((state) {
    final activeFilterPath = state.activeFilterPath;
    final activeEffectPath = state.activeEffectPath;

    // Update the selected filter controls.
  });
}

Cancel the subscription when its owner is disposed:

await activeEffectsSubscription?.cancel();

Use this state instead of assuming that a button tap always leaves the requested package active. A new package in the same category can replace the previous one.

Background replacement

Solid color

await NosmaiFlutter.instance.setBackgroundSegmentation(
  NosmaiBackgroundSegmentationConfig.color(
    const Color(0xFF202124),
  ),
);

Image

Load image bytes and pass them to the SDK:

import 'dart:typed_data';
import 'package:flutter/services.dart';

final data =
    await rootBundle.load('assets/backgrounds/studio.jpg');
final bytes = Uint8List.sublistView(data);

await NosmaiFlutter.instance.setBackgroundSegmentation(
  NosmaiBackgroundSegmentationConfig.image(bytes),
);

Video

await NosmaiFlutter.instance.setBackgroundSegmentation(
  NosmaiBackgroundSegmentationConfig.video(
    '/absolute/path/background.mp4',
  ),
);

Clear it:

await NosmaiFlutter.instance.clearBackgroundSegmentation();

Background replacement requires more device work than a color filter. Test it together with recording or streaming on the lowest supported device.

Cloud filters

Get the complete catalog with the default compatibility version:

final filters =
    await NosmaiFlutter.instance.getCloudFilters();

Get one page and category:

final nosmai = NosmaiFlutter.instance;

final filters = await nosmai.getCloudFilters(
  filterType: NosmaiCloudFilterType.beautyEffect,
  page: 1,
  limit: 20,
);

final pagination = nosmai.lastPaginationInfo;

The version argument is optional. NosmaiCloudFilterVersion.v2 is used by default.

Request a specific version explicitly only when the application must control catalog compatibility:

final filters = await NosmaiFlutter.instance.getCloudFilters(
  version: NosmaiCloudFilterVersion.v2,
);

The supported category values are:

  • NosmaiCloudFilterType.effects
  • NosmaiCloudFilterType.filter
  • NosmaiCloudFilterType.background
  • NosmaiCloudFilterType.beautyEffect

Download and apply

final nosmai = NosmaiFlutter.instance;
final filter = filters.first;

final result = await nosmai.downloadCloudFilter(filter.cloudIdentifier);
final path = result['path'] as String?;

if (path != null) {
  await nosmai.applyEffect(path);
}

Remove a downloaded item:

await nosmai.removeCloudFilter(filter.cloudIdentifier);

Capture a photo

Capture a photo with the active effects:

final nosmai = NosmaiFlutter.instance;
final photo = await nosmai.capturePhoto();

if (!photo.success || photo.imageData == null) {
  throw StateError(photo.error ?? 'Photo capture failed');
}

Save it to the device gallery:

await nosmai.saveImageToGallery(
  photo.imageData!,
  name: 'nosmai_photo',
);

Request the platform photo library or media permission before saving.

Record video

Start recording:

final started =
    await NosmaiFlutter.instance.startRecording();

Stop and receive the result:

final recording =
    await NosmaiFlutter.instance.stopRecording();

if (!recording.success || recording.videoPath == null) {
  throw StateError(recording.error ?? 'Recording failed');
}

Save it:

await NosmaiFlutter.instance.saveVideoToGallery(
  recording.videoPath!,
  name: 'nosmai_video',
);

The application decides whether to save, upload, move, or delete the returned file.

Handle errors and license changes

Listen to SDK errors:

StreamSubscription<NosmaiError>? errorSubscription;

void observeNosmaiErrors() {
  errorSubscription =
      NosmaiFlutter.instance.onError.listen((error) {
    // Show an appropriate message or record the failure.
  });
}

Listen to license status:

StreamSubscription<NosmaiLicenseStatus>? licenseSubscription;

void observeLicense() {
  licenseSubscription =
      NosmaiFlutter.instance.onLicenseStatusChanged.listen((status) {
    if (status != NosmaiLicenseStatus.valid) {
      // Disable features that require a valid license.
    }
  });
}

Cancel both subscriptions when their owner is disposed.

Lifecycle and navigation

NosmaiCameraPreview handles normal application background and foreground changes.

For ordinary route navigation:

  1. remove the camera screen from the widget tree
  2. allow the native preview view to be disposed
  3. create a new NosmaiCameraPreview when returning

Do not call cleanup from every widget dispose method. A global cleanup that overlaps creation of the next camera screen can stop the new preview.

For a short tab switch where the camera widget remains mounted:

await NosmaiFlutter.instance.pauseCamera();
await NosmaiFlutter.instance.resumeCamera();

For a custom screen that must detach before navigation:

await NosmaiFlutter.instance.stopProcessing();
await NosmaiFlutter.instance.detachCameraView();

Use full cleanup only when the application is finished with Nosmai:

await NosmaiFlutter.instance.cleanup();

After full cleanup, initialize again before creating another camera preview.

Release checklist

Before distributing the application:

  1. Use license keys issued for the final Android package name and iOS bundle identifier.
  2. Test first launch with network access.
  3. Test both platform permission flows.
  4. Test front and back camera switching.
  5. Test leaving and returning to the camera screen.
  6. Test application background and foreground transitions.
  7. Test local and cloud filter failures.
  8. Test recording with beauty and background effects active.
  9. Test Android and iOS on the oldest supported physical devices.
  10. Confirm no test license key is committed in the repository.

Common issues

Initialization returns false

Confirm that:

  • the device has internet access
  • the license key is complete
  • the Android package name or iOS bundle identifier matches the Nosmai Console project
  • initialization is called once before creating the preview

The preview is blank

Confirm that:

  • camera permission was granted
  • initialization completed successfully
  • only one NosmaiCameraPreview is active
  • the preview has a non-zero size
  • no other camera package is holding the camera

The second camera screen is blank

Do not run global cleanup while the next preview is being created. Remove the old preview from the widget tree and let its native view finish disposal before creating the next camera screen.

A local filter is missing

Confirm that:

  • its folder is declared in pubspec.yaml
  • the .nosmai, manifest, and preview filenames use the expected filter name
  • the manifest contains a supported package type
  • flutter clean and flutter pub get were run after changing bundled assets

Use getDebugFilters only for loose development packages.

Performance drops

Test one feature at a time. Face-tracked makeup, background replacement, recording, and streaming each add work. Keep one camera preview active and use 30 FPS as the normal target.

Nosmai

We make advanced camera and AI technology accessible to every developer. By packaging hard problems into simple

developers
legal
newsletter

Product updates and release notes. No spam.

© 2026 nosmai, inc · all rights reserved