fi_mapxus_positioning_flutter 1.0.9 copy "fi_mapxus_positioning_flutter: ^1.0.9" to clipboard
fi_mapxus_positioning_flutter: ^1.0.9 copied to clipboard

PlatformAndroid

A Flutter plugin for Mapxus indoor positioning services. Provides real-time location tracking and positioning state management for indoor navigation applications.

Mapxus Positioning Flutter #

pub package License

A Flutter plugin for Mapxus indoor positioning services. This plugin provides real-time location tracking and positioning state management for indoor navigation applications.

Latest Updates (v1.0.0): Enhanced with typed event system, JSON format support, orientation tracking, and multiple filtered stream options while maintaining backward compatibility.

Features #

  • 🏢 Indoor Positioning: Accurate indoor location tracking using Mapxus positioning technology
  • 📡 Real-time Updates: Stream-based position updates with location data and positioning states
  • 🎛️ Lifecycle Management: Full control over positioning with start, pause, resume, and stop operations
  • 🔧 Easy Integration: Simple API for quick integration into Flutter applications
  • 📱 Android Support: Native Android implementation with Mapxus SDK 2.3.1
  • 🎯 Typed Events: Structured event system with dedicated objects for different event types
  • 📊 JSON Format: Modern JSON-based event structure for better data handling
  • 🧭 Orientation Support: Device orientation changes with accuracy information
  • 🔄 Backward Compatible: Legacy string format still supported alongside new typed events
  • 🔔 Foreground Service: Optional Android foreground service that keeps positioning alive after the app is closed

Installation #

Add this plugin to your pubspec.yaml:

dependencies:
  mapxus_positioning_flutter: ^1.0.6

Then run:

flutter pub get

Prerequisites #

Mapxus Account Setup #

  1. Create an account at Mapxus Developer Portal
  2. Create a new application to get your App ID and Secret
  3. Configure your app's bundle identifier/package name in the Mapxus dashboard

Android Configuration #

Add the following permissions to your android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

For Android 6.0+, make sure to request location permissions at runtime.

Foreground Service (optional — for background positioning)

If you intend to use the foreground service, add these additional permissions:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

These permissions are already declared in the plugin's own manifest and will be merged automatically. You only need to add them to your app manifest if you want to make them visible to Lint or if your project requires explicit declaration.

ProGuard/R8 Note: This plugin automatically includes the necessary ProGuard rules for release builds. No additional configuration is required!

Usage #

Basic Implementation #

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

import 'package:flutter/services.dart';
import 'package:mapxus_positioning_flutter/mapxus_positioning_flutter.dart';
import 'package:mapxus_positioning_flutter/models/mapxus_event_model.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  final mapxus = MapxusPositioningFlutter.instance;
  MapxusLocationEvent? latestLocation;
  String? lastError;
  String serviceState = "UNKNOWN";
  double orientation = 0.0;
  int sensorAccuracy = 0;

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

  void startListener() {
    mapxus.events.listen((MapxusEvent event) {
      switch(event.type) {
        case 'location':
          MapxusLocationEvent locationEvent = event as MapxusLocationEvent;
          debugPrint("Location updated > Longitude: ${locationEvent.longitude} | Latitude: ${locationEvent.latitude}");
          setState(() {
            latestLocation = locationEvent;
          });
          break;
        case 'state':
          MapxusStateEvent stateEvent = event as MapxusStateEvent;
          setState(() {
            serviceState = stateEvent.state.toUpperCase();
          });
          break;
        case 'error':
          MapxusErrorEvent stateEvent = event as MapxusErrorEvent;
          setState(() {
            lastError = stateEvent.message.toUpperCase();
          });
          break;
        case 'orientation':
          PositioningOrientationEvent stateEvent = event as PositioningOrientationEvent;
          setState(() {
            orientation = event.orientation!;
            sensorAccuracy = event.accuracy!;
          });
          break;
      }
    });
  }

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

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.resumed:
        debugPrint("App came to foreground");
        resume();
        break;
      case AppLifecycleState.paused:
        pause();
        debugPrint("App went to background");
        break;
      default:
        break;
    }
  }

  void pause() async {
    var result = await mapxus.pause();
    debugPrint("Pause result > ${result.message}");
  }

  void resume() async {
    await mapxus.resume();
  }

  void init() async {
    await mapxus.init(
        "6f772bc659464f988cbe8ecb7faa4a5b",
        "47ae5790d3024f83a81c12e78966427a"
    );
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text(
                  serviceState,
                  style: TextStyle(
                      color: Colors.black,
                      fontSize: 30
                  ),
                ),
                SizedBox(height: 30),
                Text(
                  "Orientation: ${orientation.toString()}  |  Accuracy: ${sensorAccuracy.toString()}",
                  style: TextStyle(color: Colors.black, fontSize: 13),
                ),
                SizedBox(height: 15),
                Text(
                  "Latitude: ${getCoordinates(latestLocation, "lat")}",
                  style: TextStyle(color: Colors.black, fontSize: 20),
                ),
                SizedBox(height: 5),
                Text(
                  "Longitude: ${getCoordinates(latestLocation, "lan")}",
                  style: TextStyle(color: Colors.black, fontSize: 20),
                ),
                SizedBox(height: 40),
                OutlinedButton(
                    child: Text('Initialize'),
                    style: OutlinedButton.styleFrom(
                      foregroundColor: Colors.green,
                    ),
                    onPressed: () async {
                      var result = await mapxus.init(
                          "6f772bc659464f988cbe8ecb7faa4a5b",
                          "47ae5790d3024f83a81c12e78966427a"
                      );
                      if(result.success) {
                        setState(() {
                          serviceState = "INITIALIZED";
                        });
                      } else {
                        setState(() {
                          lastError = result.message;
                        });
                      }
                    }
                ),
                SizedBox(height: 20,),
                OutlinedButton(
                    child: Text('Start'),
                    style: OutlinedButton.styleFrom(
                      foregroundColor: Colors.green,
                    ),
                    onPressed: () async {
                      await mapxus.start();
                    }
                ),
                SizedBox(height: 20,),
                OutlinedButton(
                    child: Text('Pause'),
                    style: OutlinedButton.styleFrom(
                      foregroundColor: Colors.green,
                    ),
                    onPressed: () async {
                      pause();
                    }
                ),
                SizedBox(height: 20,),
                OutlinedButton(
                    child: Text('Resume'),
                    style: OutlinedButton.styleFrom(
                      foregroundColor: Colors.green,
                    ),
                    onPressed: () async {
                      await mapxus.resume();
                    }
                ),
                SizedBox(height: 20,),
                OutlinedButton(
                    child: Text('Check Initialized'),
                    style: OutlinedButton.styleFrom(
                      foregroundColor: Colors.green,
                    ),
                    onPressed: () async {
                      await mapxus.isInitialized();
                    }
                ),
                SizedBox(height: 20,),
                OutlinedButton(
                    child: Text('Stop'),
                    style: OutlinedButton.styleFrom(
                      foregroundColor: Colors.green,
                    ),
                    onPressed: () async {
                      await mapxus.stop();
                    }
                ),
                SizedBox(height: 20),
                Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 15),
                  child: Text(
                    "Last Error: ${lastError ?? "No error"}",
                    style: TextStyle(color: Colors.red),
                  ),
                ),
              ],
            )
        ),
      ),
    );
  }

  String getCoordinates(MapxusLocationEvent? location, String dataType) {
    if(location != null) {
      if(dataType == "lat") {
        return location.latitude.toString();
      } else {
        return location.longitude.toString();
      }
    } else {
      return "0";
    }
  }
}

Foreground Service (Background Positioning) #

The foreground service keeps positioning alive even after the user fully closes the app (swipes from recents). To also run Dart code while the app is closed (e.g. upload to an API), register a background handler.

Step 1 — Define a top-level background handler

// Must be a top-level function (not inside a class).
// @pragma prevents tree-shaking in release builds.
@pragma('vm:entry-point')
Future<void> onBackgroundLocation(MapxusEvent event) async {
  if (event is MapxusLocationEvent) {
    debugPrint('BG: lat=${event.latitude}, lng=${event.longitude}');
    // Upload to your API, save to local DB, etc.
    await MyApi.uploadLocation(event.latitude, event.longitude);
  }
}

Step 2 — Register the handler and start the service

final mapxus = MapxusPositioningFlutter.instance;

// Register background handler BEFORE starting the service.
await mapxus.setBackgroundHandler(onBackgroundLocation);

// Start the foreground service (shows a persistent notification).
await mapxus.startForegroundService(
  appId: 'YOUR_APP_ID',
  secret: 'YOUR_SECRET',
  notificationTitle: 'Indoor Positioning',
  notificationContent: 'Tracking your location in the background',
);

Step 3 — Listen while the app is open

// While the app is open, events also arrive here as usual.
mapxus.events.listen((MapxusEvent event) {
  if (event is MapxusLocationEvent) {
    setState(() => latestLocation = event);
  }
});

Step 4 — Stop when done

await mapxus.stopForegroundService();

How it works when the app is closed: A headless Flutter engine is started inside the foreground service. Each location event invokes your onBackgroundLocation function in that engine, so full async Dart code (HTTP calls, database writes, etc.) works normally.

Android permissions required at runtime: ACCESS_FINE_LOCATION (and POST_NOTIFICATIONS on Android 13+). Request these before calling startForegroundService.

API Reference #

Methods #

All methods return Future<bool?> where true indicates success.

MapxusPositioning.init(String appId, String secret)

Initializes the positioning client with your Mapxus credentials.

  • Parameters:
    • appId: Your Mapxus application ID
    • secret: Your Mapxus application secret
  • Returns: Future<bool?> - true if initialization succeeded

MapxusPositioning.start()

Starts the positioning service.

  • Returns: Future<bool?> - true if positioning started successfully

MapxusPositioning.pause()

Pauses the positioning service without stopping it completely.

  • Returns: Future<bool?> - true if positioning paused successfully

MapxusPositioning.resume()

Resumes a paused positioning service.

  • Returns: Future<bool?> - true if positioning resumed successfully

MapxusPositioning.stop()

Stops the positioning service completely.

  • Returns: Future<bool?> - true if positioning stopped successfully

MapxusPositioning.startForegroundService({required appId, required secret, notificationTitle, notificationContent})

Starts an Android foreground service that keeps indoor positioning active even after the user closes the app. Events are delivered through the same events stream so no extra listener setup is needed.

  • Parameters:
    • appId: Your Mapxus application ID
    • secret: Your Mapxus application secret
    • notificationTitle (optional): Title shown in the persistent notification (default: "Mapxus Positioning")
    • notificationContent (optional): Body text of the persistent notification (default: "Location tracking is active")
  • Returns: Future<MapxusMethodResponse>

MapxusPositioning.stopForegroundService()

Stops the foreground service and removes the persistent notification.

  • Returns: Future<MapxusMethodResponse>

Streams #

The plugin provides multiple stream options for receiving positioning updates, from raw data to filtered typed events.

MapxusPositioning.positionStream

A stream that provides real-time positioning updates in raw format (for backward compatibility).

MapxusPositioning.eventStream

A stream that provides typed positioning events as objects. This is the recommended approach for new implementations.

  • Event types:
    • PositioningLocationEvent - Location updates with lat/lng, accuracy, venue info
    • PositioningStateEvent - Positioning state changes
    • PositioningErrorEvent - Error events with code and message
    • PositioningOrientationEvent - Device orientation changes

MapxusPositioning.locationStream

A filtered stream that only emits PositioningLocationEvent objects.

MapxusPositioning.stateStream

A filtered stream that only emits PositioningStateEvent objects.

MapxusPositioning.errorStream

A filtered stream that only emits PositioningErrorEvent objects.

MapxusPositioning.orientationStream

A filtered stream that only emits PositioningOrientationEvent objects.

Event Objects #

PositioningLocationEvent

class PositioningLocationEvent {
  final double latitude;
  final double longitude;
  final double accuracy;      // Accuracy in meters
  final String? venueId;      // Mapxus venue identifier
  final String? buildingId;   // Mapxus building identifier
  final String? floor;        // Floor level
  final int timestamp;        // Event timestamp
}

PositioningStateEvent

class PositioningStateEvent {
  final String state;         // STOPPED, RUNNING, PAUSED
}

PositioningErrorEvent

class PositioningErrorEvent {
  final int code;            // Error code
  final String message;      // Error description
}

PositioningOrientationEvent

class PositioningOrientationEvent {
  final double orientation;   // Orientation in degrees
  final int accuracy;        // Orientation accuracy level
}

Positioning States:

  • STOPPED - Positioning service is stopped
  • RUNNING - Positioning service is actively running
  • PAUSED - Positioning service is paused

Error Handling #

The plugin provides error information through the position stream and method return values. Common issues:

Authentication Errors #

E/UserRemoteDataSource: mapxus validate appid fail
  • Cause: Invalid App ID or Secret
  • Solution: Verify your credentials in the Mapxus developer console

Permission Errors #

  • Cause: Missing location permissions
  • Solution: Request location permissions before initializing positioning

Network Errors #

  • Cause: No internet connection during initialization
  • Solution: Ensure device has internet connectivity for credential validation

Example #

Check out the example app for a complete implementation showing:

  • Initialization with credentials
  • Real-time position tracking with both legacy and typed events
  • State management and lifecycle handling
  • Error handling and user feedback
  • UI integration with live event display
  • Comparison between legacy string format and new typed events

Troubleshooting #

Common Issues #

  1. "mapxus validate appid fail"

    • Verify your App ID and Secret are correct
    • Check that your app's package name matches the one registered in Mapxus dashboard
    • Ensure your Mapxus account has positioning service enabled
  2. No location updates

    • Verify location permissions are granted
    • Check that you're in a Mapxus-supported venue
    • Ensure Bluetooth and WiFi are enabled
  3. Initialization fails

    • Check internet connectivity
    • Verify Mapxus service status
    • Review Android logs for detailed error messages
  4. Event parsing issues

    • Ensure you're using the latest plugin version
    • Use typed eventStream instead of legacy positionStream for better error handling
    • Check that JSON events are properly formatted

Platform Support #

Platform Support
Android
iOS ❌ (Coming soon)

Requirements #

  • Flutter >= 3.3.0
  • Dart >= 3.8.1
  • Android SDK >= 24

Contributing #

Contributions are welcome! Please read our contributing guidelines and submit pull requests to help improve this plugin.

License #

This project is licensed under the BSD 3-Clause License - see the LICENSE file for details.

Changelog #

See CHANGELOG.md for version history and updates.


Made with ❤️ by Fidenz Technologies

0
likes
160
points
245
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for Mapxus indoor positioning services. Provides real-time location tracking and positioning state management for indoor navigation applications.

Repository (GitHub)
View/report issues

License

BSD-3-Clause (license)

Dependencies

flutter, plugin_platform_interface, sensors_plus

More

Packages that depend on fi_mapxus_positioning_flutter

Packages that implement fi_mapxus_positioning_flutter