Right-to-Left (RTL) Text Support in Flutter
This tutorial shows how to properly display right-to-left scripts like Arabic, Hebrew, and Persian in your MapMetrics Flutter app — ensuring your own UI (search bars, cards, lists) renders correctly alongside the map.
Prerequisites
Before you begin, ensure you have:
- Completed the Flutter Setup Guide
- A MapMetrics API key and style URL from the MapMetrics Portal
What this SDK actually controls for RTL
MapController has no setRTLTextPlugin() method, and StyleController has no RTL-plugin-loading call either — the real interfaces expose addSource, addLayer, updateGeoJsonSource, removeLayer, removeSource, getAttributions, addImage/addImages/addSprite, removeImage, and setProjection, and nothing related to RTL text shaping. There's no app-level call in this SDK for enabling right-to-left map label rendering (the map's own text layers, such as street and place names, are shaped by the underlying MapLibre Native renderer using whatever RTL support is compiled into it — that's a native/style concern, not something you toggle from Dart).
What is fully real and worth building: RTL layout for your own Flutter UI — search bars, marker cards, side panels — using Flutter's own Directionality and TextDirection, combined with correctly-ordered Position coordinates for any markers you place. That's what the rest of this page covers.
RTL Map with City Markers
Display markers in Middle Eastern cities with Arabic labels, using WidgetLayer (a real Flutter widget marker) so the label text picks up Flutter's own RTL text shaping automatically:
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class ArabicCitiesScreen extends StatefulWidget {
@override
_ArabicCitiesScreenState createState() => _ArabicCitiesScreenState();
}
class _ArabicCitiesScreenState extends State<ArabicCitiesScreen> {
MapController? mapController;
final List<Map<String, dynamic>> cities = [
{'name': 'Dubai', 'nameAr': 'دبي', 'position': Position(55.2708, 25.2048)},
{'name': 'Abu Dhabi', 'nameAr': 'أبوظبي', 'position': Position(54.3773, 24.4539)},
{'name': 'Riyadh', 'nameAr': 'الرياض', 'position': Position(46.6753, 24.7136)},
{'name': 'Cairo', 'nameAr': 'القاهرة', 'position': Position(31.2357, 30.0444)},
{'name': 'Beirut', 'nameAr': 'بيروت', 'position': Position(35.5018, 33.8938)},
{'name': 'Amman', 'nameAr': 'عمّان', 'position': Position(35.9284, 31.9454)},
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Arabic Cities')),
body: MapMetricsView(
options: MapOptions(
initStyle:
'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY',
initCenter: Position(45.0, 27.0), // lng, lat
initZoom: 4.0,
),
onMapCreated: (MapController controller) {
mapController = controller;
},
mapChildren: [
WidgetLayer(
markers: cities.map((city) {
return Marker(
point: city['position'] as Position,
size: const Size(120, 44),
alignment: Alignment.bottomCenter,
child: Directionality(
textDirection: TextDirection.rtl,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(6),
boxShadow: const [BoxShadow(color: Colors.black26, blurRadius: 2)],
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(city['nameAr'], style: const TextStyle(fontWeight: FontWeight.bold)),
Text(city['name'], style: const TextStyle(fontSize: 10, color: Colors.grey)),
],
),
),
),
);
}).toList(),
),
],
),
);
}
}Full RTL App Layout
Build a complete RTL-aware app shell with Arabic UI. This part is pure Flutter — Directionality, TextDirection, and reverse: on scroll views work exactly as in any Flutter app, regardless of the map SDK underneath:
import 'package:flutter/material.dart';
import 'package:mapmetrics/mapmetrics.dart';
class RtlAppScreen extends StatefulWidget {
@override
_RtlAppScreenState createState() => _RtlAppScreenState();
}
class _RtlAppScreenState extends State<RtlAppScreen> {
MapController? mapController;
bool isRtl = true;
final List<Map<String, dynamic>> locations = [
{'nameAr': 'برج خليفة', 'nameEn': 'Burj Khalifa', 'position': Position(55.2744, 25.1972)},
{'nameAr': 'نخلة جميرا', 'nameEn': 'Palm Jumeirah', 'position': Position(55.1390, 25.1124)},
{'nameAr': 'دبي مول', 'nameEn': 'Dubai Mall', 'position': Position(55.2796, 25.1985)},
];
@override
Widget build(BuildContext context) {
return Directionality(
textDirection: isRtl ? TextDirection.rtl : TextDirection.ltr,
child: Scaffold(
appBar: AppBar(
title: Text(isRtl ? 'خريطة دبي' : 'Dubai Map'),
actions: [
TextButton(
onPressed: () => setState(() => isRtl = !isRtl),
child: Text(
isRtl ? 'EN' : 'عربي',
style: TextStyle(color: Colors.white, fontSize: 16),
),
),
],
),
body: Column(
children: [
// Location list
Container(
height: 60,
child: ListView.builder(
scrollDirection: Axis.horizontal,
reverse: isRtl, // RTL scroll direction
padding: EdgeInsets.all(8),
itemCount: locations.length,
itemBuilder: (context, i) {
final loc = locations[i];
return Padding(
padding: EdgeInsets.symmetric(horizontal: 4),
child: ActionChip(
label: Text(isRtl ? loc['nameAr'] : loc['nameEn']),
onPressed: () {
mapController?.animateCamera(
center: loc['position'] as Position,
zoom: 15.0,
);
},
),
);
},
),
),
// Map
Expanded(
child: MapMetricsView(
options: MapOptions(
initStyle:
'https://gateway.mapmetrics-atlas.net/styles/?fileName=YOUR_STYLE_ID/YOUR_STYLE.json&token=YOUR_API_KEY',
initCenter: Position(55.2708, 25.2048), // lng, lat
initZoom: 11.0,
),
onMapCreated: (MapController controller) {
mapController = controller;
},
mapChildren: [
WidgetLayer(
markers: locations.map((loc) {
return Marker(
point: loc['position'] as Position,
size: const Size(32, 32),
alignment: Alignment.bottomCenter,
child: const Icon(Icons.location_on, color: Colors.red, size: 32),
);
}).toList(),
),
],
),
),
],
),
),
);
}
}RTL-Supported Scripts
| Script | Language Examples | Direction |
|---|---|---|
| Arabic | Arabic, Urdu, Pashto | Right-to-Left |
| Hebrew | Hebrew, Yiddish | Right-to-Left |
| Persian | Farsi, Dari | Right-to-Left |
| Thaana | Dhivehi (Maldives) | Right-to-Left |
Key Steps for RTL
- Wrap your app/screen with
Directionalityfor UI widgets (TextDirection.rtl/TextDirection.ltr). - Use
TextDirection.rtlfor Arabic/Hebrew/Persian text in Flutter widgets — including any customWidgetLayermarker content. - Reverse scroll direction (
reverse: isRtl) on horizontal lists so item order matches reading direction. - Map-rendered labels (street names, place names baked into the base style) are shaped by the native MapLibre renderer, not by anything you configure from Dart in this SDK — there is no app-level RTL plugin toggle to call.
Next Steps
- Custom Map Styling — Style your map for any region
- Multiple Geometries — Markers, lines, polygons, circles together
- Popup on Click — Interactive marker widgets
Tip: Since WidgetLayer markers are plain Flutter widgets, any Text inside them automatically follows the ambient Directionality — wrap marker content in its own Directionality(textDirection: TextDirection.rtl, ...) if you need Arabic/Hebrew labels regardless of the surrounding app's direction.