fi_mapxus_positioning_flutter 1.0.9
fi_mapxus_positioning_flutter: ^1.0.9 copied to clipboard
A Flutter plugin for Mapxus indoor positioning services. Provides real-time location tracking and positioning state management for indoor navigation applications.
Mapxus Positioning Flutter #
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 #
- Create an account at Mapxus Developer Portal
- Create a new application to get your App ID and Secret
- 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
onBackgroundLocationfunction in that engine, so full async Dart code (HTTP calls, database writes, etc.) works normally.
Android permissions required at runtime:
ACCESS_FINE_LOCATION(andPOST_NOTIFICATIONSon Android 13+). Request these before callingstartForegroundService.
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 IDsecret: Your Mapxus application secret
- Returns:
Future<bool?>-trueif initialization succeeded
MapxusPositioning.start()
Starts the positioning service.
- Returns:
Future<bool?>-trueif positioning started successfully
MapxusPositioning.pause()
Pauses the positioning service without stopping it completely.
- Returns:
Future<bool?>-trueif positioning paused successfully
MapxusPositioning.resume()
Resumes a paused positioning service.
- Returns:
Future<bool?>-trueif positioning resumed successfully
MapxusPositioning.stop()
Stops the positioning service completely.
- Returns:
Future<bool?>-trueif 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 IDsecret: Your Mapxus application secretnotificationTitle(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 infoPositioningStateEvent- Positioning state changesPositioningErrorEvent- Error events with code and messagePositioningOrientationEvent- 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 stoppedRUNNING- Positioning service is actively runningPAUSED- 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 #
-
"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
-
No location updates
- Verify location permissions are granted
- Check that you're in a Mapxus-supported venue
- Ensure Bluetooth and WiFi are enabled
-
Initialization fails
- Check internet connectivity
- Verify Mapxus service status
- Review Android logs for detailed error messages
-
Event parsing issues
- Ensure you're using the latest plugin version
- Use typed
eventStreaminstead of legacypositionStreamfor 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