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

Zero-config friction capture, voice-AI feedback, and auto-ticketing for Flutter apps. The AI knows what broke because it watched it break.

example/lib/main.dart

import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:friction_sdk/friction_sdk.dart';

/// Configure this with **your** project's publishable key from the Friction
/// dashboard (Project → Keys). It looks like `pk_live_...` and is safe to ship
/// in client code — it only identifies the project for telemetry ingestion.
const _appId = 'pk_live_YOUR_PROJECT_KEY';

/// Resolves the right host for the local backend depending on platform.
/// Android emulator routes the host's `localhost` to `10.0.2.2`; everywhere else
/// `localhost` (or `127.0.0.1`) works.
String _backendBaseUrl() {
  if (kIsWeb) return 'http://localhost:5050';
  if (Platform.isAndroid) return 'http://10.0.2.2:5050';
  return 'http://localhost:5050';
}

Future<void> main() async {
  // Friction.init must run before runApp so error handlers + HttpOverrides
  // are installed before the app starts making requests.
  await Friction.init(
    appId: _appId,
    baseUrl: _backendBaseUrl(),
  );

  runApp(
    FrictionScope(
      child: MaterialApp(
        title: 'Friction example',
        debugShowCheckedModeBanner: false,
        navigatorObservers: [FrictionRouteObserver()],
        theme: ThemeData(
          colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF5B4FFF)),
          useMaterial3: true,
        ),
        initialRoute: '/',
        routes: {
          '/': (_) => const HomePage(),
          '/checkout/payment': (_) => const PaymentPage(),
        },
      ),
    ),
  );
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Friction example')),
      body: ListView(
        padding: const EdgeInsets.all(20),
        children: [
          Container(
            margin: const EdgeInsets.only(bottom: 16),
            padding: const EdgeInsets.all(12),
            decoration: BoxDecoration(
              color: const Color(0xFFEEECFF),
              borderRadius: BorderRadius.circular(8),
              border: Border.all(color: const Color(0xFFD9D6FF)),
            ),
            child: Text(
              'Backend → ${_backendBaseUrl()}\nAppId → ${_appId.substring(0, 16)}…',
              style: const TextStyle(fontSize: 11.5, fontFamily: 'monospace'),
            ),
          ),
          const Text(
            'Tap a tile to trigger a friction signal. The widget will appear from the bottom.',
            style: TextStyle(fontSize: 14, color: Colors.black54),
          ),
          const SizedBox(height: 16),
          _Tile(
            label: '402 from a payment API',
            subtitle: 'Triggers `api_error`',
            onTap: () => _triggerHttp402(context),
          ),
          _Tile(
            label: 'Uncaught exception',
            subtitle: 'Triggers `uncaught_exception`',
            onTap: () => _triggerException(),
          ),
          _Tile(
            label: 'Open the broken payment screen',
            subtitle: 'Use the Pay button — it will rage-click',
            onTap: () => Navigator.of(context).pushNamed('/checkout/payment'),
          ),
          _Tile(
            label: 'Manual report',
            subtitle: 'A user-initiated "report a problem"',
            onTap: () => Friction.report(
              reason: 'manual',
              userTranscript: 'I tapped Submit but nothing happened.',
            ),
          ),
        ],
      ),
    );
  }

  Future<void> _triggerHttp402(BuildContext context) async {
    // Hit a URL that returns a non-2xx. httpstat.us is handy for demos.
    final client = HttpClient();
    try {
      final req = await client.getUrl(Uri.parse('https://httpstat.us/402'));
      final res = await req.close();
      await res.drain<void>();
    } catch (_) {
      // Even socket errors are friction.
    } finally {
      client.close();
    }
  }

  void _triggerException() {
    Future<void>.microtask(() => throw StateError('demo exception'));
  }
}

class PaymentPage extends StatelessWidget {
  const PaymentPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Checkout')),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            const Text('Total \$128.40',
                style: TextStyle(fontSize: 28, fontWeight: FontWeight.w600)),
            const SizedBox(height: 24),
            ElevatedButton(
              // Intentionally a no-op so users rage-click it.
              onPressed: () {},
              child: const Text('Pay now'),
            ),
            const SizedBox(height: 12),
            const Text(
              'Tap "Pay now" rapidly to fire a rage_click trigger.',
              style: TextStyle(color: Colors.black54),
            ),
          ],
        ),
      ),
    );
  }
}

class _Tile extends StatelessWidget {
  const _Tile({required this.label, required this.subtitle, required this.onTap});
  final String label;
  final String subtitle;
  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.symmetric(vertical: 6),
      child: ListTile(
        title: Text(label),
        subtitle: Text(subtitle),
        trailing: const Icon(Icons.chevron_right),
        onTap: onTap,
      ),
    );
  }
}
0
likes
140
points
127
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Zero-config friction capture, voice-AI feedback, and auto-ticketing for Flutter apps. The AI knows what broke because it watched it break.

Repository (GitHub)
View/report issues

Topics

#error-reporting #observability #telemetry #feedback #voice

License

MIT (license)

Dependencies

audioplayers, flutter, http, http_parser, path_provider, permission_handler, record

More

Packages that depend on friction_sdk