Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Cross-Platform UIs with Flutter

You're reading from   Cross-Platform UIs with Flutter Unlock the ability to create native multiplatform UIs using a single code base with Flutter 3

Arrow left icon
Product type Paperback
Published in Aug 2022
Publisher Packt
ISBN-13 9781801810494
Length 260 pages
Edition 1st Edition
Languages
Tools
Arrow right icon
Authors (2):
Arrow left icon
Ryan Edge Ryan Edge
Author Profile Icon Ryan Edge
Ryan Edge
Alberto Miola Alberto Miola
Author Profile Icon Alberto Miola
Alberto Miola
Arrow right icon
View More author details
Toc

Table of Contents (12) Chapters Close

Preface 1. Building a Counter App with History Tracking to Establish Fundamentals 2. Building a Race Standings App FREE CHAPTER 3. Building a Todo Application Using Inherited Widgets and Provider 4. Building a Native Settings Application Using Material and Cupertino Widgets 5. Exploring Navigation and Routing with a Hacker News Clone 6. Building a Simple Contact Application with Forms and Gesturess 7. Building an Animated Excuses Application 8. Build an Adaptive, Responsive Note-Taking Application with Flutter and Dart Frog 9. Writing Tests and Setting Up GitHub Actions 10. Index 11. Other Books You May Enjoy

Setting up the project

Before we start creating the app, we need to prepare the environment and make sure we care about the UX from the beginning.

Create a new Flutter project in your favorite IDE and make sure that you enable web support by clicking on the Add Flutter web support checkbox. A basic analysis_options.yaml file will be created for you. Even if it’s not strictly required, we strongly recommend that you add more rules to enhance your overall code quality.

Tip

If you want to easily set up the analysis_options.yaml file with the configuration we’ve recommended, just go to this project’s GitHub repository and copy the file into your project! You can also find a quick overview of the rules in the Setting up the project section of Chapter 1, Building a Counter App with History Tracking to Establish Fundamentals.

Since we aren’t uploading this project to https://pub.dev/, make sure that your pubspec.yaml file has the publish_to: 'none' directive uncommented. Before we start coding, we still need to set up localization, internationalization, route management, and custom text fonts.

Localization and internationalization

Localizing an app means adapting the content according to the device’s geographic settings to appeal to as many users as possible. In practical terms, for example, this means that an Italian user and an American user would see the same date but in different formats. In Italy, the date format is d-m-y, while in America, it’s m-d-y, so the app should produce different strings according to the device’s locale. It’s not only about the date, though, because localizing can also mean the following changes occur:

  • Showing prices with the proper currency (Euro, Dollar, Sterling, and so on)
  • Taking into account time zones and offsets while displaying dates for events
  • Choosing between a 24-hour or 12-hour time format
  • Deciding which decimal separator is used (a comma, a full stop, an apostrophe, and so on)

Internationalizing, which is part of the localization process, means translating your app’s text according to the device’s locale. For example, while an Italian user would read Ciao!, an American user would read Hello!, and all of this is done automatically by the app.

Setting up localization support in Flutter is very easy! Start by adding the SDK direct dependency to the pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter
  intl: ^0.17.0

The intl package is maintained by the Dart team and offers numerous internationalization and localization utilities we will explore throughout this chapter, such as AppLocalization and DateFormat.

Still in the pubspec file, we need to add another line at the bottom of the flutter section:

flutter:
  generate: true

We must do this to bundle the various localization files into our app so that the framework will be able to pick the correct one based on the user’s locale settings.

The last file we need to create must be located at the root of our project, it must be called l10n.yaml exactly, and it must have the following contents:

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

This file is used to tell Flutter where the translations for the strings are located so that, at runtime, it can pick up the correct files based on the locale. We will be using English as the default, so we will set app_en.arb to template-arb-file.

Now, we need to create two ARB files inside lib/localization/l10n/ that will contain all of our app strings. The first file, app_en.arb, internationalizes our app in English:

{
    "app_title": "Results and standings",
    "results": "Results",
    "standings": "Standings",
}

The second file, app_it.arb, internationalizes our app in Italian:

{
    "app_title": "Risultati e classifiche",
    "results": "Risultati",
    "standings": "Classifica",
}

Every time you add a new entry to the ARB file, you must make sure that the ARB keys match! When you run your app, automatic code generation will convert the ARB files into actual Dart classes and generate the AppLocalization class for you, which is your reference for translated strings. Let’s look at an example:

final res = AppLocalizations.of(this)!.results;

If you’re running your app on an Italian device, the value of the res variable will be Risultati. On any other device, the variable would hold Results instead. Since English is the default language, when Flutter cannot find an ARB file that matches the current locale, it will fall back to the default language file.

Tip

Whenever you add or remove strings to/from your ARB files, make sure you always hit the Run button of your IDE to build the app! By doing this, the framework builds the newly added strings and bundles them into the final executable.

Extension methods, which are available from Dart 2.7 onwards, are a very nice way to add functionalities to a class without using inheritance. They’re generally used when you need to add some functions or getters to a class and make them available for any instance of that type.

Let’s create a very convenient extension method to be called directly on any Flutter string, which reduces the boilerplate code and saves some import statements. This is a very convenient shortcut to easily access the localization and internationalization classes that’s generated by Flutter. The following is the content of the lib/localization/localization.dart file:

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
export 'package:flutter_gen/gen_l10n/app_localizations.dart';
/// Extension method on [BuildContext] which gives a quick 
/// access to the `AppLocalization` type.
extension LocalizationContext on BuildContext {
  /// Returns the [AppLocalizations] instance.
  AppLocalizations get l10n => AppLocalizations.of(this)!;
}

With this code, we can simply call context.l10n.results to retrieve the internationalized value of the Results word.

Last, but not least, we need to make sure that our root widget installs the various localization settings we’ve created so far:

/// The root widget of the app.
class RaceStandingsApp extends StatelessWidget {
  /// Creates an [RaceStandingsApp] instance.
  const RaceStandingsApp ({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // Localized app title
      onGenerateTitle: (context) => context.l10n.app_title,
      // Localization setup
      localizationsDelegates: 
        AppLocalizations.localizationsDelegates,
      supportedLocales: AppLocalizations.supportedLocales,
      // Routing setup
      onGenerateRoute: RouteGenerator.generateRoute,
      // Hiding the debug banner
      debugShowCheckedModeBanner: false,
    );
  }
}

If you’re dealing with multiple languages and a lot of strings, manually working on ARB files may become very hard and error-prone. We suggest that you either look at Localizely, an online tool that handles ARB files, or install an ARB plugin manager in your IDE.

In the preceding code, you may have noticed the RouteGenerator class, which is responsible for route management. That’s what we’re going to set up now!

Routes management

In this app, we’re using Flutter’s built-in routing management system: the Navigator API. For simplicity, we will focus on Navigator 1.0; in Chapter 5, Exploring Navigation and Routing with a Hacker News Clone, we will cover the routing topic in more depth.

Let’s create a simple RouteGenerator class to handle all of the routing configurations of the app:

abstract class RouteGenerator {
  static const home = '/';
  static const nextRacesPage = '/next_races';
  /// Making the constructor private since this class is 
  /// not meant to be instantiated.
  const RouteGenerator._();
  static Route<dynamic> generateRoute(RouteSettings 
    settings) {
    switch (settings.name) {
      case home:
        return PageRouteBuilder<HomePage>(
          pageBuilder: (_, __, ___) => const HomePage(),
        );
      case nextRacesPage:
        return PageRouteBuilder<NextRacesPage>(
          pageBuilder: (_, __, ___) => 
            const NextRacesPage(),
        );
      default:
        throw const RouteException('Route not found');
    }
  }
}
/// Exception to be thrown when the route doesn't exist.
class RouteException implements Exception {
  final String message;
  /// Requires the error [message] for when the route is 
  /// not found.
  const RouteException(this.message);
}

By doing this, we can use Navigator.of(context).pushNamed(RouteGenerator.home) to navigate to the desired page. Instead of hard-coding the route names or directly injecting the PageRouteBuilders in the methods, we can gather everything in a single class.

As you can see, the setup is very easy because all of the management is done in Flutter internally. We just need to make sure that you assign a name to the page; then, Navigator will take care of everything else.

Last but not least, let’s learn how to change our app’s look a bit more with some custom fonts.

Adding a custom font

Instead of using the default text font, we may wish to use a custom one to make the app’s text look different from usual. For this purpose, we need to reference the google_fonts package in the dependencies section of pubspec and install it:

return MaterialApp(
  // other properties here…
  theme: ThemeData.light().copyWith(
    textTheme: GoogleFonts.latoTextTheme(),
  ),
);

It couldn’t be easier! Here, the Google font package fetches the font assets via HTTP on the first startup and caches them in the app’s storage. However, if you were to manually provide font files assets, the package would prioritize those over HTTP fetching. We recommend doing the following:

  1. Go to https://fonts.google.com and download the font files you’re going to use.
  2. Create a top-level directory (if one doesn’t already exist) called assets and then another sub-folder called fonts.
  3. Put your font files into assets/fonts without renaming them.
  4. Make sure that you also include the OFL.txt license file, which will be loaded at startup. Various license files are included in the archive you downloaded from Google Fonts.

Once you’ve done that, in the pubspec file, make sure you have declared the path to the font files:

assets:
  - assets/fonts/

The package only downloads font files if you haven’t provided them as assets. This is good for development but for production, it’s better to bundle the fonts into your app executable to avoid making HTTP calls at startup. In addition, font fetching assumes that you have an active internet connection, so it may not always be available, especially on mobile devices.

Finally, we need to load the license file into the licenses registry:

void main() {
  // Registering fonts licences
  LicenseRegistry.addLicense(() async* {
    final license = await rootBundle.loadString(
      'google_fonts/OFL.txt',
    );
    yield LicenseEntryWithLineBreaks(['google_fonts'],
      license);
  });
  // Running the app
  runApp(
    const RaceStandingsApp(),
  );
}

Make sure that you add the LicenseRegistry entry so that, if you use the LicensePage widget, the licensing information about the font is bundled into the executable correctly.

Now that we’ve set everything up, we can start creating the app!

You have been reading a chapter from
Cross-Platform UIs with Flutter
Published in: Aug 2022
Publisher: Packt
ISBN-13: 9781801810494
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime