Skip to content

Latest commit

 

History

History
302 lines (249 loc) · 18.8 KB

04.md

File metadata and controls

302 lines (249 loc) · 18.8 KB

Chapter 4: Accessibility and responsive UI support with Flutter

In the previous chapters, we've learned how we can create a simple Flutter application that can already run on multiple platforms, displaying hardcoded texts in a preferred language. We have one problem with the latter: With any luck, hundreds, thousands (or millions!) could use our apps after publishing them to the app stores. Apps supporting multiple languages and accessibility features can appeal to a broader audience worldwide.

Key abbreviations:

  • a11y: accessibility
  • i18n: internationalization
  • l10n: localization

Accessibility

In a nutshell, accessibility means that as many people can use our mobile app as possible, including those with disabilities, like visual or motor impairment.

Internationalization and localization

If we want to deploy apps to users who speak a different language than our default one, having apps support the target audience's native language and culture can be appealing in those markets. Internationalization means we should develop an app in a way that makes it possible to localize values like text and layouts for each language the app supports.

Built-in solution

By default, Flutter only supports US English localization. To add support for other languages, the Flutter team recommends using a package called flutter_localizations. To use flutter_localizations, we have to add it to our pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter 

After a successfully completed pub get packages command, we have to specify additional list type properties for MaterialApp:

  • localizationsDelegates[],
  • supportedLocales[].

Next, we have to add predefined global values (GlobalMaterialLocalizations.delegate, const Locale(’hu’)) to make the application's widgets adapt to the current language.

Besides the flutter_localizations package, we can add the intl package too, which provides internationalization and localization facilities, including message translation, plurals and genders, date/number formatting and parsing, and bidirectional text support. Let's include this package too in the project's pubspec.yaml file:

dependencies:
  intl: ^0.17.0

Additionally, we should enable the generate flag in the pubspec.yaml file:

flutter:  
	generate: true

This flag is essential to generate localizations. To configure where these files are generated, we can add a new l10n.yaml file to the root directory of the project with the following content:

arb-dir: lib/l10n  
template-arb-file: app_en.arb  
output-localization-file: l10n.dart  
output-class: L10n

With this file we can specify the following settings:

  • The input files (app_en.arb and app_hu.arb) will be located in ${FLUTTER_PROJECT}/lib/l10n,
  • The app_en.arb input file will provide the template,
  • The generated localizations will be placed in the l10n.dart file

In the generated l10n.dart file, the class that we'll need is named L10n.

We can add the default English localization to the app_en.arb (ARB format = Application Resource Bundle) template file. For example, to store the localization of our home screen's title we can add the following key-value pairs:

{
  "homeTitle": "Know your Santa",
  "@homeTitle": {
    "description": "Page title.",
    "context": "HomePage"
  }
}

The homeTitle is the resource ID and its value is the localization string (“Know your Santa”). Because the app_en.arb file is the template file, we have to add an attribute item keyed by the original resource ID plus a prefix ‘@’ character. In the attribute item optional we can use predefined resource attributes like:

  • description: A short paragraph describing the resource and how it is being used by the app, and messages that need to be passed to the localization process and translators. Every attribute item must have a description as a requirement.
  • context: It describes the context in which this resource applies. When this piece of information is missing, it defaults to global context.

In the same directory, let's create the app_hu.arb file for Hungarian translations and add a translation for the title string:

{
  "homeTitle": "Ismerd meg a Mikulásod"  
}

Here's the complete app_en.arb file for the example project:

{
  "@@locale": "en",
  "homeTitle": "Know your Santa",
  "@homeTitle": {
    "description": "Page title.",
    "context": "HomePage"
  },
  "homeAppbarTitle": "HomePage",
  "@homeAppbarTitle": {
    "description": "Page title of appbar.",
    "context": "HomePage"
  },
  "homeCurrentLanguage": "Current language: {language}",
  "@homeCurrentLanguage": {
    "description": "Show the current language on the Home page",
    "context": "HomePage",
    "placeholders": {
      "language": {}
    }
  },
  "homeYourSanta": "Your santa",
  "@homeYourSanta": {
    "description": ""
  },
  "santaName": "Santa Claus",
  "@santaName": {
    "description": ""
  },
  "santaHat": "Hat",
  "@santaOutfit1": {
    "description": ""
  },
  "santaTie": "Tie",
  "@santaOutfit2": {
    "description": ""
  },
  "santaDescription": "Jingle bells jingle bells\nJingle all the way\nOh what fun it is to ride\nIn a one horse open sleigh\nJingle bells jingle bells\nJingle all the way\nOh what fun it is to ride\nIn a one horse open sleigh\n\nDashing through the snow\nIn a one horse open sleigh\nOver the fields we go\nLaughing all the way\nBells on bob tail ring\nMaking spirits bright\nOh What fun it is to ride \nA sleighing song tonight\n\nOh, oh jingle bells jingle bells\nJingle all the way\nOh what fun it is to ride\nIn a one horse open sleigh\n\nYou better not pout\nYou better not cry\nYou better not shout\nI'm telling you why\nSanta Claus is coming to town\n\nHe's making a list\nHe's checking it twice\nGonna find out\nWho's naughty or nice\nSanta Claus is coming to town\n\nHe sees you when you're sleeping\nHe knows if you're awake\nHe knows if you've been bad or good\nSo be good for goodness sake\n\nI am dreaming of a white Christmas\nJust like the ones I used to know\nWhere the tree tops glisten\nAnd children listen\nTo hear sleighbells in the snow\nI am dreaming of a white Christmas\nWith every Christmas card I write\nMay your days be merry and bright\nAnd may all your Christmases be\n\nOh, the weather outside is frightful\nBut the fire is so delightful\nAnd since we've got no place to go\nLet it snow, let it snow, let it snow\n\nOh, it doesn't show signs of stopping\nAnd I've brought some corn for popping\nThe lights are turned way down low\nLet it snow, let it snow, let it snow\n\nWhen we finally kiss goodnight\nHow I'll hate going out in the storm\nBut, if you really hold me tight\nAll the way home I'll be warm\n\nThe fire is slowly dying\nAnd my dear, we're still good by ing\nBut, as long as you love me so\nLet it snow, let it snow, let it snow\n\nHere comes Santa Claus\nHere comes Santa Claus\nRight down Santa Claus Lane\nVixen and Blitzen and all his reindeer\nPullin' on the reins\nBells are ringin', children singin'\nAll is merry and bright\nHang your stockings and say your prayers\nSanta Claus comes tonight\n\nHere comes Santa Claus\nHere comes Santa Claus\nRight down Santa Claus Lane\nHe's got a bag that's filled with toys\nFor boys and girls again\nHear those sleigh bells jingle jangle\nOh what a beautiful sight\nJump in bed, and cover your head\nSanta Claus comes tonight\nSanta Claus comes tonight",
  "@santaDescription": {
    "description": ""
  }
}

And this is the complete app_hu.arb file:

{
  "@@locale": "hu",
  "homeTitle": "Ismerd meg a Mikulásod",
  "homeAppbarTitle": "Kezdőlap",
  "homeCurrentLanguage": "Aktuális nyelv: {language}",
  "homeYourSanta": "A mikulásod",
  "santaName": "Mikulás",
  "santaHat": "Kalap",
  "santaTie": "Nyaktekerészeti mellfekvenc",
  "santaDescription": "Hull a pelyhes fehér hó,\njöjj el kedves télapó.\nMinden gyermek várva vár,\nvidám ének hangja száll.\nVan zsákodban minden jó,\npiros alma, mogyoró.\nJöjj el hozzánk, várunk rád,\nkedves öreg télapó!\n\nNagyszakállú télapó,\njó gyermek barátja.\nCukrot, diót, mogyorót\nrejteget a zsákja.\nAmerre jár reggelig,\nkis cipőcske megtelik.\nMegtölti a télapó,\nha üresen látja."
}

Whenever we run the application the code generation takes place. We can see the generated files under ${FLUTTER_PROJECT}/.dart_tool/flutter_gen/gen_l10n:

  • l10n.dart,
  • l10n_en.dart,
  • l10n_hu.dart.

In the main.dart file MaterialApp needs to include L10n.delegate() in the localisationDelegates list, and the locales we support in the supportedLocales list:

import 'package:flutter/material.dart';
import 'gen_l10n/l10n.dart';

import 'presentation/page/home/home_page.dart';

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter l10n demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primaryColor: Colors.red,
      ),
      localizationsDelegates: L10n.localizationsDelegates,
      supportedLocales: L10n.supportedLocales,
      home: HomePage(),
    );
  }
}

Callers can look up localized strings using an instance of the L10n class returned by L10n.of(context). For instance, in the home_page.dart file at the top of the build() method of the HomePage Widget we can get a reference to the L10n object and pass its homeTitle value to the title text:

import 'package:flutter_gen/gen_l10n/l10n.dart';
/// (...)

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final L10n l10n = L10n.of(context);
    return Scaffold(
      /// (...)
      body: Container(
        width: double.infinity,
        padding: EdgeInsets.symmetric(horizontal: 16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            /// (...)
            Text(l10n.homeTitle),
            /// (...)
          ],
        ),
      ),
    );
  }
}

As a result, users can see the home page according to the system language of their device:

en hu
en hu

RTL support

In some languages, for instance, in Arabic writing goes from the right to the left (RTL) instead of from the left to the right like in English or Hungarian. Localization for RTL languages needs the text direction to be changed. To demonstrate RTL support we can add and .arb file for Arabic string resources, and we use the start and end properties of the EdgeInsetsDirectional class instead of the basic left and right of the EdgeInsets class.

image

Text-to-Speech

People with visual disabilities often use the default TalkBack (Android) or VoiceOver (iOS) assistants to read the content of screens for them. If we want to specify exactly what will these assistants say about our apps' screens, we can do that using the Semantics widget which has tons of properties regarding such settings. It can be used for analytics too.

For example, we can wrap one of our Text widgets into a Semantics widget, and we can add a label property.

title: Semantics(
      child: Text(l10n.homeAppbarTitle),
      label: "This is the HomePage.",
    ),

Responsive UI

Once we need to support multiple platforms with different display sizes and factors, we'll need to learn about responsive layouts and how to deal with them in Flutter.

SizedBox vs. FittedBox

We've learned that we can use the SizedBox widget for presenting any space between widgets and it’s simpler than a Container widget when we don’t need to specify borders and more properties. To achieve better scalability, we can consider using the FittedBox widget. With FittedBox, we can easily and automatically scale up/down and position its child within itself using the fit property, which can be familiar from the BoxFit widget. It’s easy to use while being really powerful.

Wrap

The Wrap widget is another very useful tool to handle scaling. This Wrap widget arranges its children in multiple horizontal or vertical runs. When there is not enough space to fit one of its children, Wrap creates a new run adjacent to the previous one on the cross axis.

Layoutbuilder vs. MediaQuery

MediaQuery always provides the size of the full application, info about the physical screen, etc, while Layoutbuilder provides the current parent's size only.

In this section, we will see how the flutter_screenutil package wraps the MediaQuery into a size config under the hood. First of all, we need to initialize and set the fit size in the app.dart file before we show any resizable UI like this:

import 'package:flutter_screenutil/flutter_screenutil.dart';

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    /// Set the fit size (fill in the screen size of the device in the design,in dp)
    return ScreenUtilInit(
      designSize: const Size(375, 812),
      allowFontScaling: false,
      builder: () => MaterialApp(
      /// (...)
      ),
    );
  }
}

Note that the designSize property should be equal to the size of the design draft. Ideally, our UI development should be based on an existing design like the following with 375x812 size:

image

FIGMA LINK

Under the hood, MediaQuery provides important properties like:

  • pixelRatio (device pixel density),
  • screen width,
  • screen height,
  • viewInsets and viewPadding (these properties could be useful when we want to handle some padding issues caused by software keyboard),
  • size of the appBar, and the notch - if the device has one.

The MediaQuery widget also provides information about the current orientation. With that, we can easily handle the landscape mode too if we don’t want to disable it. We could enable and disable the different orientations in the main.dart file if we add or remove one of them from the setPreferredOrientations list of the SystemChrome class before we run our application.

After successfully initializing the ScreenUtil we could use the setHeight() and setWidth() functions to adapt to screen height/width.

Besides those, we can also use the .w(), .h(), and .r() extension functions to make our code more readable.

🚨 Don’t forget that we can only achieve responsiveness if we use the above-mentioned extension functions for all size properties (including font sizes) on each widget for each screen. If we follow this rule, we should not be surprised even when our app runs on a small screen, or in a small browser window.

As a result, we can see that our UI display a reasonable layout on different screen sizes.

Normal Android Device (1440x2560) dpi Small Device (240x320) ldpi Galaxy Tab S7 (1600x2560) hdpi
basic_android_after small_device_after tablet_after
iPhone 11 Pro WEB
iOS_after web_after_2

Further reading, materials