core_haptics 0.0.4 copy "core_haptics: ^0.0.4" to clipboard
core_haptics: ^0.0.4 copied to clipboard

PlatformiOSmacOS

Type-safe Core Haptics FFI plugin (iOS & macOS) via SwiftPM.

alt Banner of the core_haptics project

A type-safe, FFI-based Flutter plugin that gives you full access to Apple's Core Haptics framework. Create custom vibration patterns, play AHAP files, and deliver tactile experiences that feel native.

โœจ What's inside #

  • ๐ŸŽฏ Complete Core Haptics wrapper โ€” engines, patterns, players, and dynamic parameters
  • โšก One-liner haptics โ€” HapticEngine.success(), HapticEngine.mediumImpact() with zero setup
  • ๐Ÿ“„ AHAP everywhere โ€” load from JSON strings, files, or Flutter assets
  • ๐ŸŽจ Programmatic patterns โ€” build haptic sequences with HapticEvent (no JSON needed!)
  • ๐Ÿ›ก๏ธ Memory-safe FFI โ€” automatic cleanup with finalizers, strongly-typed enums
  • ๐ŸŽ›๏ธ Live parameter control โ€” adjust intensity and sharpness during playback
  • ๐Ÿ”„ Interruption handling โ€” callbacks for audio session changes and resets
  • ๐Ÿšซ Zero CocoaPods โ€” Swift Package Manager only, clean and modern

๐Ÿ“ฆ Installation #

Add to your pubspec.yaml:

dependencies:
  core_haptics: ^latest_version

Then run:

flutter pub get

๐Ÿ”ง Platform Setup #

iOS / macOS (Swift Package Manager) #

Since this plugin uses SwiftPM instead of CocoaPods, you need to manually link the native module:

Step 1: Open your app in Xcode
example/ios/Runner.xcworkspace (or macos/Runner.xcworkspace)

Step 2: Add the local Swift Package

  • File โ†’ Add Package Dependencies
  • Click "Add Local..." and navigate to the plugin's ios/ folder
  • Select Package.swift and add it

Step 3: Link to your target

  • In your app target's Frameworks and Libraries, add CoreHapticsFFI
  • Set to Embed & Sign (iOS) or Do Not Embed (macOS)

Requirements:

  • iOS 13.0+ or macOS 11.0+
  • Physical device with haptic engine (iPhone 8+ or supported Mac)

Note

SwiftPM gives cleaner builds, better dependency management, and is Apple's recommended approach for modern Swift libraries.

๐Ÿš€ Quick Start #

The easiest way to use the plugin is to use the static methods. Instead if you want to use the full API, you can create an engine instance.

One-liner haptics #

All static methods automatically check device support and silently do nothing on unsupported devices โ€” no need to wrap calls in isSupported checks!

import 'package:core_haptics/core_haptics.dart';

// Impact feedback
await HapticEngine.lightImpact();
await HapticEngine.mediumImpact();
await HapticEngine.heavyImpact();

// Notification feedback (not available in Flutter's HapticFeedback!)
await HapticEngine.success();
await HapticEngine.warning();
await HapticEngine.error();

// Selection feedback
await HapticEngine.selection();

Use isSupported when you need to make UI decisions based on haptics availability:

// Show/hide haptics settings based on device capability
final showHapticsToggle = await HapticEngine.isSupported;

For custom patterns with precise timing:

await HapticEngine.play([
  HapticEvent(type: HapticEventType.transient, intensity: 0.8, sharpness: 0.5),
  HapticEvent(
    type: HapticEventType.continuous,
    time: Duration(milliseconds: 100),
    duration: Duration(seconds: 1),
    intensity: 0.5,
  ),
]);

Advanced usage #

For full control over engines, patterns, players, looping, and dynamic parameters, use the HapticEngine API directly.

Basic haptic tap

import 'package:core_haptics/core_haptics.dart';

Future<void> playSimpleTap() async {
  // Create and start the engine
  final engine = await HapticEngine.create();
  await engine.start();

  // Load an AHAP pattern (JSON string)
  final pattern = await engine.loadPatternFromAhap('''
  {
    "Version": 1,
    "Pattern": [{
      "Event": {
        "EventType": "HapticTransient",
        "Time": 0,
        "EventParameters": [
          {"ParameterID": "HapticIntensity", "ParameterValue": 0.8},
          {"ParameterID": "HapticSharpness", "ParameterValue": 0.5}
        ]
      }
    }]
  }
  ''');

  // Play it
  final player = await engine.createPlayer(pattern);
  await player.play();
  
  // Cleanup
  await player.dispose();
  await pattern.dispose();
  await engine.dispose();
}

Programmatic patterns

final pattern = await engine.loadPatternFromEvents([
  // Sharp tap at start
  const HapticEvent(
    type: HapticEventType.transient,
    time: Duration.zero,
    intensity: 1.0,
    sharpness: 0.8,
  ),
  // Continuous rumble after 300ms
  const HapticEvent(
    type: HapticEventType.continuous,
    time: Duration(milliseconds: 300),
    duration: Duration(seconds: 2),
    intensity: 0.6,
    sharpness: 0.3,
  ),
]);

Load from Flutter assets

// 1. Add AHAP file to pubspec.yaml assets
// 2. Load it:
final pattern = await engine.loadPatternFromAsset('assets/haptics/my_pattern.ahap');

Handle interruptions #

final engine = await HapticEngine.create(
  onEvent: (event, message) {
    switch (event) {
      HapticEngineEvent.interrupted => print('โš ๏ธ Haptics interrupted: $message');
      HapticEngineEvent.restarted => print('โœ… Haptics resumed');
    }
  },
);

๐ŸŽฏ API Reference #

HapticEngine #

Your main entry point. Provides both static one-liner methods and full engine control.

Static methods (uses native UIFeedbackGenerator, auto-checks device support):

// Impact feedback (silently no-ops on unsupported devices)
await HapticEngine.lightImpact();
await HapticEngine.mediumImpact();
await HapticEngine.heavyImpact();
await HapticEngine.softImpact();
await HapticEngine.rigidImpact();

// Notification feedback
await HapticEngine.success();
await HapticEngine.warning();
await HapticEngine.error();

// Selection feedback
await HapticEngine.selection();

// Custom patterns
await HapticEngine.play(eventList);

// Check device support (for UI decisions)
if (await HapticEngine.isSupported) { ... }

Instance API (for full control):

// Create
final engine = await HapticEngine.create(onEvent: callback);

// Start/stop
await engine.start();
await engine.stop();

// Load patterns
final p1 = await engine.loadPatternFromAhap(jsonString);
final p2 = await engine.loadPatternFromFile(path);
final p3 = await engine.loadPatternFromAsset('assets/pattern.ahap');
final p4 = await engine.loadPatternFromEvents(eventList);

// Create players
final player = await engine.createPlayer(pattern);

// Cleanup
await engine.dispose();

HapticPlayer #

Controls playback of a haptic pattern.

await player.play(atTime: 0);
await player.stop(atTime: 0);

// Looping (not available on all platforms)
await player.setLoop(enabled: true, loopStart: 0, loopEnd: 2.0);

// Dynamic parameter updates (during playback)
await player.setParameter(HapticParameterId.hapticIntensity, 0.9, atTime: 0);

await player.dispose();

HapticEvent #

Programmatically define haptic events.

const HapticEvent({
  required HapticEventType type,      // transient or continuous
  Duration time = Duration.zero,      // when to fire (relative to pattern start)
  Duration? duration,                 // required for continuous events
  double? intensity,                  // 0.0 to 1.0
  double? sharpness,                  // 0.0 to 1.0
});

Error Handling #

All errors throw HapticsException:

try {
  await engine.start();
} on HapticsException catch (e) {
  print('Error: ${e.code} - ${e.message}');
  // e.code is a HapticsErrorCode enum
}

Error codes:

Code Description
notSupported Device doesn't support haptics
engine Engine failed to start
invalidArgument Bad pattern or parameter
decode Invalid AHAP JSON
io File not found
runtime Playback issue

๐Ÿงช Testing #

Dart unit tests:

flutter test

Uses mocked FFI bridge for ~90% API coverage.

Swift native tests:

cd ios && swift test

Tests the Core Haptics bridge (skips on devices without haptics).

๐Ÿ› Troubleshooting #

"Cannot find symbol 'chffi_engine_create'" #

The SwiftPM package isn't linked. Go back to Platform Setup and ensure CoreHapticsFFI is added to your app target's frameworks.

"Device does not support haptics" #

Core Haptics requires an iPhone 8+ or newer Mac with Taptic Engine. Simulators don't support haptics.

"HapticsException: runtime (-4820)" #

Tried to send a parameter update to a player that isn't actively playing. Ensure the pattern is playing before calling setParameter.

๐Ÿ“š Learn More #

๐Ÿ“„ License #

See LICENSE for details.

๐Ÿ™Œ Contributing #

Issues and PRs welcome! This plugin maintains a 1:1 mapping with Core Haptics APIs, so contributions should align with Apple's framework design.

10
likes
160
points
355
downloads

Publisher

verified publisherfrancescocoppola.me

Weekly Downloads

Type-safe Core Haptics FFI plugin (iOS & macOS) via SwiftPM.

Repository (GitHub)
View/report issues

Topics

#haptics #vibration #ahap #core-haptics #ffi

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

ffi, flutter

More

Packages that depend on core_haptics

Packages that implement core_haptics