flutter_radar 4.0.0-beta.1 copy "flutter_radar: ^4.0.0-beta.1" to clipboard
flutter_radar: ^4.0.0-beta.1 copied to clipboard

Flutter package for Radar, the leading geofencing and location tracking platform

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_radar/flutter_radar.dart';
import 'package:permission_handler/permission_handler.dart';

import 'api_section.dart';
import 'console.dart';

const String publishableKey =
    'prj_test_pk_0000000000000000000000000000000000000000';

const Map<String, double> _manhattan = {
  'latitude': 40.783826,
  'longitude': -73.975363,
};

const Map<String, double> _brooklyn = {
  'latitude': 40.703900,
  'longitude': -73.986700,
};

void main() => runApp(const MyApp());

void _onLocation(Map res) => console.logEvent('onLocation', res);

void _onClientLocation(Map res) => console.logEvent('onClientLocation', res);

void _onError(Map res) => console.logEvent('onError', res);

void _onLog(Map res) => console.logEvent('onLog', res);

void _onEvents(Map res) => console.logEvent('onEvents', res);

void _onToken(Map res) => console.logEvent('onToken', res);

void _onIpChanged() => console.logEvent('onIpChanged', 'IP address changed');

@pragma('vm:entry-point')
Future<void> _onBackgroundEvent(RadarBackgroundEvent event) async {
  debugPrint('radar background: ${event.type.name} ${event.payload}');
}

void _onSharingChanged(bool sharing) =>
    console.logEvent('onSharingChanged', {'sharing': sharing});

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'flutter_radar example',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true,
        colorSchemeSeed: const Color(0xFF3B5BFF),
      ),
      home: const HomePage(),
    );
  }
}

class _ListenerDef {
  final String name;
  final void Function() enable;
  final void Function() disable;

  const _ListenerDef(this.name, this.enable, this.disable);
}

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

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
  String _permissionStatus = 'UNKNOWN';
  String? _tripId;
  List<String> _legIds = [];

  final Set<String> _enabledListeners = {};

  late final List<_ListenerDef> _listenerDefs = [
    _ListenerDef('onLocation', () => Radar.onLocation(_onLocation),
        () => Radar.offLocation()),
    _ListenerDef('onClientLocation',
        () => Radar.onClientLocation(_onClientLocation), () => Radar.offClientLocation()),
    _ListenerDef('onError', () => Radar.onError(_onError), () => Radar.offError()),
    _ListenerDef('onLog', () => Radar.onLog(_onLog), () => Radar.offLog()),
    _ListenerDef('onEvents', () => Radar.onEvents(_onEvents), () => Radar.offEvents()),
    _ListenerDef('onToken', () => Radar.onToken(_onToken), () => Radar.offToken()),
    _ListenerDef('onIpChanged', () => Radar.onIpChanged(_onIpChanged),
        () => Radar.offIpChanged()),
    _ListenerDef('onSharingChanged',
        () => Radar.onSharingChanged(_onSharingChanged), () => Radar.offSharingChanged()),
  ];

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _initRadar();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.inactive) {
      Radar.logResigningActive();
    } else if (state == AppLifecycleState.paused) {
      Radar.logBackgrounding();
    }
  }

  Future<void> _initRadar() async {
    await Radar.initialize(
      publishableKey,
      options: RadarInitializeOptions(
        fraud: true,
        trackVerifiedAutoFailover: true,
        networkTimeout: const Duration(seconds: 20),
        ipChangeDebounceInterval: const Duration(seconds: 5),
      ),
    );
    await Radar.registerBackgroundHandler(_onBackgroundEvent);
    await Radar.setUserId('flutter');
    await Radar.setDescription('Flutter');
    await Radar.setMetadata({'foo': 'bar', 'bax': true, 'qux': 1});
    await Radar.setLogLevel('info');
    await Radar.setAnonymousTrackingEnabled(false);

    for (final def in _listenerDefs) {
      _setListener(def, true);
    }

    await _refreshPermissions();
    console.log('initialize', 'Radar initialized');
  }

  Future<void> _refreshPermissions() async {
    final status = await Radar.getPermissionsStatus();
    if (mounted) {
      setState(() => _permissionStatus = status ?? 'UNKNOWN');
    }
  }

  void _setListener(_ListenerDef def, bool enabled) {
    try {
      if (enabled) {
        def.enable();
      } else {
        def.disable();
      }
      setState(() {
        if (enabled) {
          _enabledListeners.add(def.name);
        } else {
          _enabledListeners.remove(def.name);
        }
      });
    } catch (e) {
      console.logError(def.name, e);
    }
  }

  Future<void> _run(ApiAction action) async {
    try {
      final result = await action.run();
      console.log(action.label, result);
    } catch (e) {
      console.logError(action.label, e);
    }
  }

  /// Leg operations need IDs minted by the server, so the multi-destination
  /// trip response is stashed for the buttons that follow it.
  void _captureTrip(Object? response) {
    if (response is! Map) return;
    final trip = response['trip'];
    if (trip is! Map) return;
    final legs = trip['legs'];
    setState(() {
      _tripId = trip['_id'] as String?;
      _legIds = legs is List
          ? legs
              .whereType<Map>()
              .map((leg) => leg['_id'])
              .whereType<String>()
              .toList()
          : <String>[];
    });
  }

  Future<String?> _prompt(String title, {String initial = ''}) async {
    final controller = TextEditingController(text: initial);
    return showDialog<String>(
      context: context,
      builder: (context) => AlertDialog(
        title: Text(title),
        content: TextField(
          controller: controller,
          autofocus: true,
          decoration: const InputDecoration(border: OutlineInputBorder()),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('Cancel'),
          ),
          FilledButton(
            onPressed: () => Navigator.pop(context, controller.text),
            child: const Text('Run'),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    final sections = _sections();

    return Scaffold(
      appBar: AppBar(
        title: const Text('flutter_radar'),
        actions: [
          IconButton(
            tooltip: 'Refresh permissions',
            icon: const Icon(Icons.refresh),
            onPressed: _refreshPermissions,
          ),
        ],
      ),
      body: Column(
        children: [
          _statusBar(context),
          Expanded(
            child: ListView(
              padding: const EdgeInsets.only(top: 8, bottom: 16),
              children: [
                for (int i = 0; i < sections.length; i++)
                  ApiSectionCard(
                    section: sections[i],
                    onRun: _run,
                    initiallyExpanded: i == 0,
                  ),
                _listenersCard(context),
              ],
            ),
          ),
          const ConsolePanel(),
        ],
      ),
    );
  }

  Widget _statusBar(BuildContext context) {
    final theme = Theme.of(context);
    final chips = <String>[
      'permissions: $_permissionStatus',
      if (_tripId != null) 'trip: ${_tripId!.substring(0, 8)}…',
      if (_legIds.isNotEmpty) 'legs: ${_legIds.length}',
    ];

    return Container(
      width: double.infinity,
      color: theme.colorScheme.surfaceContainerHighest,
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
      child: Wrap(
        spacing: 6,
        runSpacing: 6,
        children: [
          for (final chip in chips)
            Container(
              padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
              decoration: BoxDecoration(
                color: theme.colorScheme.surface,
                borderRadius: BorderRadius.circular(12),
              ),
              child: Text(chip, style: theme.textTheme.labelSmall),
            ),
        ],
      ),
    );
  }

  Widget _listenersCard(BuildContext context) {
    final theme = Theme.of(context);
    return Card(
      clipBehavior: Clip.antiAlias,
      margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
      child: ExpansionTile(
        leading: Icon(Icons.podcasts, color: theme.colorScheme.primary),
        title: Text('Listeners', style: theme.textTheme.titleSmall),
        subtitle: Text(
          '${_enabledListeners.length} of ${_listenerDefs.length} active',
          style: theme.textTheme.bodySmall,
        ),
        children: [
          for (final def in _listenerDefs)
            SwitchListTile(
              dense: true,
              title: Text(def.name, style: theme.textTheme.bodyMedium),
              value: _enabledListeners.contains(def.name),
              onChanged: (value) => _setListener(def, value),
            ),
        ],
      ),
    );
  }

  List<ApiSection> _sections() => [
        ApiSection(
          title: 'Setup & Status',
          icon: Icons.tune,
          actions: [
            ApiAction('isInitialized()', () => Radar.isInitialized()),
            ApiAction('sdkVersion()', () => Radar.sdkVersion()),
            ApiAction('getPermissionsStatus()', () => Radar.getPermissionsStatus()),
            ApiAction('requestPermissions(foreground)', () async {
              final status = await Radar.requestPermissions(false);
              await _refreshPermissions();
              return status;
            }),
            ApiAction('requestPermissions(background)', () async {
              final status = await Radar.requestPermissions(true);
              await _refreshPermissions();
              return status;
            }),
            ApiAction('activityRecognition permission', () async {
              final status = await Permission.activityRecognition.request();
              return status.toString();
            }),
            ApiAction("setLogLevel('info')", () async {
              await Radar.setLogLevel('info');
              return 'info';
            }),
            ApiAction("setLogLevel('debug')", () async {
              await Radar.setLogLevel('debug');
              return 'debug';
            }),
          ],
        ),
        ApiSection(
          title: 'User & Metadata',
          icon: Icons.person_outline,
          actions: [
            ApiAction('setUserId()', () async {
              final value = await _prompt('setUserId', initial: 'flutter');
              if (value == null) return 'cancelled';
              await Radar.setUserId(value);
              return value;
            }),
            ApiAction('getUserId()', () => Radar.getUserId()),
            ApiAction('setDescription()', () async {
              final value = await _prompt('setDescription', initial: 'Flutter');
              if (value == null) return 'cancelled';
              await Radar.setDescription(value);
              return value;
            }),
            ApiAction('getDescription()', () => Radar.getDescription()),
            ApiAction('setMetadata()', () async {
              await Radar.setMetadata({'foo': 'bar', 'bax': true, 'qux': 1});
              return {'foo': 'bar', 'bax': true, 'qux': 1};
            }),
            ApiAction('getMetadata()', () => Radar.getMetadata()),
            ApiAction("setUserLanguage('es')", () async {
              await Radar.setUserLanguage('es');
              return 'es';
            }),
            ApiAction('getUserLanguage()', () => Radar.getUserLanguage()),
            ApiAction("setProduct('flutter-qa')", () async {
              await Radar.setProduct('flutter-qa');
              return 'flutter-qa';
            }),
            ApiAction('getProduct()', () => Radar.getProduct()),
            ApiAction('setTags([qa, flutter])', () async {
              await Radar.setTags(['qa', 'flutter']);
              return ['qa', 'flutter'];
            }),
            ApiAction('getTags()', () => Radar.getTags()),
            ApiAction("addTags(['beta'])", () async {
              await Radar.addTags(['beta']);
              return Radar.getTags();
            }),
            ApiAction("removeTags(['beta'])", () async {
              await Radar.removeTags(['beta']);
              return Radar.getTags();
            }),
            ApiAction('setAnonymousTracking(true)', () async {
              await Radar.setAnonymousTrackingEnabled(true);
              return 'enabled';
            }),
            ApiAction('setAnonymousTracking(false)', () async {
              await Radar.setAnonymousTrackingEnabled(false);
              return 'disabled';
            }),
          ],
        ),
        ApiSection(
          title: 'Tracking',
          icon: Icons.my_location,
          actions: [
            ApiAction('trackOnce()', () => Radar.trackOnce()),
            ApiAction('trackOnce(location)',
                () => Radar.trackOnce(location: Map<String, dynamic>.from(_manhattan))),
            ApiAction("getLocation('high')", () => Radar.getLocation('high')),
            ApiAction("startTracking('responsive')", () async {
              await Radar.startTracking('responsive');
              return 'responsive';
            }),
            ApiAction("startTracking('continuous')", () async {
              await Radar.startTracking('continuous');
              return 'continuous';
            }),
            ApiAction("startTracking('efficient')", () async {
              await Radar.startTracking('efficient');
              return 'efficient';
            }),
            ApiAction('startTrackingCustom()', () async {
              await Radar.startTrackingCustom({
                ...Radar.presetResponsive,
                'showBlueBar': true,
                'foregroundServiceEnabled': true,
              });
              return Radar.getTrackingOptions();
            }),
            ApiAction('stopTracking()', () async {
              await Radar.stopTracking();
              return 'stopped';
            }),
            ApiAction('isTracking()', () => Radar.isTracking()),
            ApiAction('getTrackingOptions()', () => Radar.getTrackingOptions()),
            ApiAction('isUsingRemoteTrackingOptions()',
                () => Radar.isUsingRemoteTrackingOptions()),
            ApiAction('mockTracking()', () => Radar.mockTracking(
                  origin: _manhattan,
                  destination: _brooklyn,
                  mode: 'car',
                  steps: 3,
                  interval: 3,
                )),
            ApiAction('setForegroundServiceOptions()', () async {
              await Radar.setForegroundServiceOptions({
                'title': 'Tracking',
                'text': 'Flutter example is tracking',
                'iconString': 'ic_notification',
                'iconColor': '#FF6B8D',
                'importance': 2,
                'updatesOnly': false,
                'activity': 'io.radar.example.MainActivity',
              });
              return 'ok';
            }),
          ],
        ),
        ApiSection(
          title: 'Verified & Fraud',
          icon: Icons.verified_user_outlined,
          actions: [
            ApiAction('trackVerified()', () => Radar.trackVerified()),
            ApiAction('trackVerified(beacons)',
                () => Radar.trackVerified(beacons: true, reason: 'qa')),
            ApiAction('startTrackingVerified()', () async {
              await Radar.startTrackingVerified(30, false);
              return 'started';
            }),
            ApiAction('stopTrackingVerified()', () async {
              await Radar.stopTrackingVerified();
              return 'stopped';
            }),
            ApiAction('isTrackingVerified()', () => Radar.isTrackingVerified()),
            ApiAction('getVerifiedLocationToken()',
                () => Radar.getVerifiedLocationToken()),
            ApiAction('clearVerifiedLocationToken()', () async {
              await Radar.clearVerifiedLocationToken();
              return 'cleared';
            }),
            ApiAction('revealRisk()', () => Radar.revealRisk()),
            ApiAction('isSharing()', () => Radar.isSharing()),
            ApiAction('clearSharing()', () async {
              await Radar.clearSharing();
              return 'cleared';
            }),
            ApiAction('setExpectedJurisdiction(US/NY)', () async {
              await Radar.setExpectedJurisdiction(
                  countryCode: 'US', stateCode: 'NY');
              return 'US / NY';
            }),
            ApiAction('setExpectedJurisdiction(clear)', () async {
              await Radar.setExpectedJurisdiction();
              return 'cleared';
            }),
          ],
        ),
        ApiSection(
          title: 'Trips',
          icon: Icons.route_outlined,
          actions: [
            ApiAction('startTrip (single destination)', () async {
              final response = await Radar.startTrip(tripOptions: {
                'externalId': 'flutter-${DateTime.now().millisecondsSinceEpoch}',
                'destinationGeofenceTag': 'store',
                'destinationGeofenceExternalId': '123',
                'mode': 'car',
                'metadata': {'test': 123},
              });
              _captureTrip(response);
              return response;
            }),
            ApiAction('startTrip (multi-destination)', () async {
              final response = await Radar.startTrip(tripOptions: {
                'externalId': 'flutter-multi-${DateTime.now().millisecondsSinceEpoch}',
                'mode': 'car',
                'legs': [
                  {
                    'destinationGeofenceTag': 'store',
                    'destinationGeofenceExternalId': '123',
                  },
                  {'address': '841 Broadway, New York, NY'},
                  {
                    // GeoJSON order, and both SDKs index it positionally: a
                    // {latitude, longitude} map passes the length check and
                    // then crashes on element access.
                    'coordinates': [
                      _brooklyn['longitude'],
                      _brooklyn['latitude'],
                    ],
                    'arrivalRadius': 100,
                  },
                ],
              });
              _captureTrip(response);
              return response;
            }),
            ApiAction('getTrip()', () async {
              final response = await Radar.getTrip();
              _captureTrip({'trip': response});
              return response;
            }),
            ApiAction('getTripOptions()', () => Radar.getTripOptions()),
            ApiAction("updateTrip('arrived')", () => Radar.updateTrip(
                  status: 'arrived',
                  options: {
                    'externalId': 'flutter-trip',
                    'metadata': {'parkingSpot': '5'},
                  },
                )),
            ApiAction('updateTripLeg (first leg)', () async {
              if (_legIds.isEmpty) {
                return 'No leg IDs captured. Run "startTrip (multi-destination)" first.';
              }
              return Radar.updateTripLeg(
                tripId: _tripId,
                legId: _legIds.first,
                status: 'arrived',
              );
            }),
            ApiAction("updateCurrentTripLeg('arrived')",
                () => Radar.updateCurrentTripLeg(status: 'arrived')),
            ApiAction('reorderTripLegs (reversed)', () async {
              if (_legIds.length < 2) {
                return 'Need at least two legs. Run "startTrip (multi-destination)" first.';
              }
              return Radar.reorderTripLegs(
                tripId: _tripId,
                legIds: _legIds.reversed.toList(),
              );
            }),
            ApiAction('completeTrip()', () => Radar.completeTrip()),
            ApiAction('cancelTrip()', () => Radar.cancelTrip()),
          ],
        ),
        ApiSection(
          title: 'Geofences & Search',
          icon: Icons.search,
          actions: [
            ApiAction('searchGeofences()', () => Radar.searchGeofences(
                  near: Map<String, dynamic>.from(_manhattan),
                  radius: 1000,
                  limit: 10,
                  includeGeometry: true,
                  tags: List.empty(),
                  metadata: {},
                )),
            ApiAction('searchPlaces()', () => Radar.searchPlaces(
                  near: Map<String, dynamic>.from(_manhattan),
                  radius: 1000,
                  chains: ['starbucks'],
                  chainMetadata: {'customFlag': 'true'},
                  limit: 10,
                )),
            ApiAction('autocomplete()', () => Radar.autocomplete(
                  query: 'brooklyn roasting',
                  near: Map<String, dynamic>.from(_manhattan),
                  limit: 10,
                  layers: ['address', 'street'],
                  country: 'US',
                  mailable: false,
                )),
            ApiAction('geocode()', () => Radar.geocode('20 jay st brooklyn')),
            ApiAction('reverseGeocode()', () => Radar.reverseGeocode()),
            ApiAction('ipGeocode()', () => Radar.ipGeocode()),
            ApiAction('getContext()',
                () => Radar.getContext(Map<String, dynamic>.from(_manhattan))),
            ApiAction('getDistance()', () => Radar.getDistance(
                  origin: _manhattan,
                  destination: _brooklyn,
                  modes: ['car', 'foot'],
                  units: 'imperial',
                )),
            ApiAction('getMatrix()', () => Radar.getMatrix(
                  origins: [_manhattan, _brooklyn],
                  destinations: [
                    {'latitude': 40.64189, 'longitude': -73.78779},
                    {'latitude': 35.99801, 'longitude': -78.94294},
                  ],
                  mode: 'car',
                  units: 'imperial',
                )),
            ApiAction('validateAddress()', () => Radar.validateAddress({
                  'city': 'NEW YORK',
                  'stateCode': 'NY',
                  'postalCode': '10003',
                  'countryCode': 'US',
                  'street': 'BROADWAY',
                  'number': '841',
                })),
          ],
        ),
        ApiSection(
          title: 'Events & Conversions',
          icon: Icons.event_note_outlined,
          actions: [
            ApiAction('logConversion()', () => Radar.logConversion(
                  name: 'in_app_purchase',
                  revenue: 0.2,
                  metadata: {'price': '150USD'},
                )),
            ApiAction('acceptEvent()', () async {
              final eventId = await _prompt('acceptEvent — event ID');
              if (eventId == null || eventId.isEmpty) return 'cancelled';
              await Radar.acceptEvent(eventId);
              return 'accepted $eventId';
            }),
            ApiAction('rejectEvent()', () async {
              final eventId = await _prompt('rejectEvent — event ID');
              if (eventId == null || eventId.isEmpty) return 'cancelled';
              await Radar.rejectEvent(eventId);
              return 'rejected $eventId';
            }),
            ApiAction('logBackgrounding()', () async {
              await Radar.logBackgrounding();
              return 'ok';
            }),
            ApiAction('logResigningActive()', () async {
              await Radar.logResigningActive();
              return 'ok';
            }),
            ApiAction('logTermination()', () async {
              await Radar.logTermination();
              return 'ok';
            }),
          ],
        ),
        ApiSection(
          title: 'Notifications & Messaging',
          icon: Icons.notifications_none,
          actions: [
            ApiAction('setNotificationOptions()', () async {
              await Radar.setNotificationOptions({'iconString': 'icon'});
              return 'ok';
            }),
            ApiAction('setPushNotificationToken()', () async {
              final token = await _prompt('setPushNotificationToken');
              if (token == null) return 'cancelled';
              await Radar.setPushNotificationToken(token);
              return token.isEmpty ? 'cleared' : token;
            }),
            ApiAction('showInAppMessage()', () async {
              await Radar.showInAppMessage({
                'type': 'banner',
                'title': {'text': 'This is the title', 'color': '#000000'},
                'body': {'text': 'This is a demo message', 'color': '#666666'},
                'button': {
                  'text': 'Send it',
                  'color': '#FFFFFF',
                  'backgroundColor': '#EB0083',
                },
              });
              return 'shown';
            }),
          ],
        ),
      ];
}
16
likes
120
points
4.87k
downloads

Documentation

API reference

Publisher

verified publisherradar.io

Weekly Downloads

Flutter package for Radar, the leading geofencing and location tracking platform

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

flutter

More

Packages that depend on flutter_radar

Packages that implement flutter_radar