Skip to content

Both copy text to your clipboard — Build with AI copies a setup prompt to paste into Claude Code, Cursor, Codex or Copilot; Copy page as Markdown copies this page to paste into a chat. How it works

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:

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:

dart
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:

dart
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

ScriptLanguage ExamplesDirection
ArabicArabic, Urdu, PashtoRight-to-Left
HebrewHebrew, YiddishRight-to-Left
PersianFarsi, DariRight-to-Left
ThaanaDhivehi (Maldives)Right-to-Left

Key Steps for RTL

  1. Wrap your app/screen with Directionality for UI widgets (TextDirection.rtl/TextDirection.ltr).
  2. Use TextDirection.rtl for Arabic/Hebrew/Persian text in Flutter widgets — including any custom WidgetLayer marker content.
  3. Reverse scroll direction (reverse: isRtl) on horizontal lists so item order matches reading direction.
  4. 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


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.