Be a pal: SupportsRtl

David Chen
Gett Tech
Published in
6 min readAug 5, 2024

As a native Hebrew speaker, reading and writing from right to left (RTL) is second nature. Hebrew, Arabic and Persian are among the most widely used RTL languages, but there are many others. Computer science has long supported RTL, but Android’s approach has evolved through the years. In this post, I’ll share how I achieved full RTL support for the Gett Android Rider app.

The Old Days of Android RTL

Before Android Jelly Bean (version 17), RTL support meant creating duplicate layouts for each locale. For instance, a simple error_layout.xml with an image and text would need a mirrored version (layout-he or layout-iw) for Hebrew. This quickly became cumbersome with complex layouts and numerous RTL languages.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="180dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:paddingRight="16dp" << Right
android:paddingLeft="16dp"> << Left

<ImageView
android:id="@+id/error_img"
android:layout_width="196dp"
android:layout_height="98dp"
app:srcCompat="@drawable/ic_no_connection"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"/>

<TextView
android:id="@+id/title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_alignParentLeft="true" << Left
android:text="Houston, we have a problem."
android:layout_below="@+id/error_img"/>

</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="150dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:paddingRight="16dp"
android:paddingLeft="16dp">

<ImageView
android:id="@+id/error_img"
android:layout_width="196dp"
android:layout_height="98dp"
app:srcCompat="@drawable/ic_no_connection"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"/>

<TextView
android:id="@+id/title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_alignParentRight="true" << Right
android:text="יוסטון, יש לנו בעיה."
android:layout_below="@+id/error_img"/>

</RelativeLayout>

These two layouts are almost identical, with the exception that on RTL — we need to change the alignment of the TextView to the right. Everything else is the same. In Computer Science, one well known principle is DRY — Don’t Repeat Yourself, and duplicating layouts for locales definitely breaks it as we constantly repeating the same code with minor alignment adjustments.

The supportsRtl="true" Revolution

Android 17 introduced the android:supportsRtl attribute in the AndroidManifest.xml. When enabled, Android uses semantic values start and end for layout alignment instead of fixed left and right. This means a single layout can adapt to both LTR and RTL languages dynamically. The value of start & end will be determined in runtime by the system based on the user’s preferred locale.

Let’s consider the example above of error-layout.xml. If we do use supportsRtl="true", and adjust our main error-layout.xml to use start & end attributes, we can achieve the needed UI with a single error_layout.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="150dp"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:paddingEnd="16dp" << End
android:paddingStart="16dp"> << Start

<ImageView
android:id="@+id/error_img"
android:layout_width="196dp"
android:layout_height="98dp"
app:srcCompat="@drawable/ic_no_connection"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"/>

<TextView
android:id="@+id/title_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_alignParentStart="true" << Start
android:text="@string/we_have_a_problem" << strings.xml
android:layout_below="@+id/error_img"/>

</RelativeLayout>

There is no need for a separate matching layout file, in a locale resource subfolder. In fact, if we had any locale layout folder, we should delete them and use single layout file in the root layout folder.

“What was, was.”

Gett over the years had partial RTL support on android due to capacity constraints. Some parts of the app used duplicate layouts, while others, like the new order flow, didn’t and was only available in LTR layouts. This inconsistency bothered me, and so I set out to implement full RTL support throughout the app.

After getting the green light from design and product teams, I refactored the main order flow as well as other parts of the app which had limited support.

Now Let’s view our Gett main flow: home screen, confirmation and in ride screens, pre and post RTL support:

Main order flow. left Pre, right Post.

While The differences might seem subtle to a non Hebrew reader, they do exist. On Pre-RTL we had a mixture of alignments between the order flow and the ride flow. After the integration, everything is now aligned in Hebrew as it should be: from Right to Left — easier to read and interact with. The side drawer, toolbar management, google maps icons — everything is now as aligned as it can.

Pain & Gain.

One would assume that just by turning the flag from false to true of supportsRtl in the manifest will be enough, but it is really not. From here we are faced with two approaches:

  1. Use the built in RTL migration tool of Android Studio.
  2. Manually go over each file and replace left & right with. start & end attributes.

The first option is very intuitive and tempting — it goes over all the xml files and in code, and simply replaces the occurrences of right & left on alignment tags all over the app. But it comes with a price — as not always we would want to align right as it will not make much sense.

Consider this use-case of adding a credit card: 1234–5678–8910–1111. Even on RTL languages, it’s not a good idea to write it as: 1111–8910–5678–1234 as it can be confusing. The Same can be applied to phone numbers or passwords. So there are exceptions — and because of that, I had to go manual. It is not a big deal as the Android Studio has a very handy Find and Replace interface which allows us to examine the context. This required replacing tags in XML and also on code. Another interesting issue was that on Jetpack Compose, which we heavily integrated into our app, a lot of components were encapsulated with this:

val localizationComposition = when (LocalizationManager.isRTL()) {
true -> LayoutDirection.Rtl
else -> LayoutDirection.Ltr
}

CompositionLocalProvider(LocalLayoutDirection provides localizationComposition) {
Box(...)
}

This was now redundant, as the system knows by itself if it should be RTL or LTR like alignment. Other common issues were:

  • Icons mirrored in code
  • Old android viewpagers (v1) are LTR by nature and do not support RTL natively (viewpager 2 does)
  • Compatibility issues with some vendors after changing the locale
  • TextViews width defined on some layouts as MATCH_PARENT: For example, the user is on Hebrew, but the TextView content is in English — the text would align at the end of the TextView (needed a TextAlignment attr set).
  • Other mismatches here and there.

But the end result looked and felt much more consistent throughout the app:

Side Drawer Pre (Left) and Post side by side
Setting screen on both versions — Content is the same and aligned properly, but the toolbar (back icon, actions) on the right (post) image is more coherent with the general flow of the app

The entire process took a couple of weeks including Automation and QA.

A lot of bugs surfaced as our codebase is comprised of active app development of over a decade, but eventually it was done: full RTL support from onboarding to completing a ride and beyond.

Not all of our Israeli users will notice, as some have their app on english, but on android, over 60% of our IL users use Hebrew as their app language. Providing a unified and consistent user experience was my main motivation in doing this infra.

Supported languages spread on Gett rider android app, IL users

These small things, UX/UI wise — do matter. So if your app do support Hebrew, Arabic or any other RTL language, be a pal — Support RTL.

Pals.

--

--