This article complements the previous article I wrote about internationalization.

Difficulty: Beginner

Introduction

In my previous article, I explained a solution that works for me to make a Flutter application multi-lingual and allow to dynamically change the working language.

This article complements that article in such a way it provides a Widget to:

  • list the supported languages
  • show the current language
  • allow the user to select another language
  • once another language is selected, force the application to “refresh” and use the new language

This article assumes that you read my previous article and that you are familiar with the topics I covered.

The LanguageSelector Widget

This Widget is based on an ExpansionTile which:

  • expands when the user hits it
  • shows the list of supported languages and highlights the current working language
  • collapses when the user hits one language
  • forces a refresh of the application, in terms of translations

The main difficulty of this LanguageSelector widget resides in the fact it should auto-collapse when the user taps one of its ListTile.

Browsing the Internet, I found an answer on Stack Overflow. The solution given by Simon seemed a bit too complex to me (at least as regard my own needs), therefore I opted for the other solution, given by Alex Radzishevsky.

Here is my implementation based on that solution (language-selector.dart).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import 'dart:math';
import 'package:flutter/material.dart';
import 'application.dart';
import 'translations.dart';

class LanguageSelector extends StatefulWidget {
  @override
  _LanguageSelectorState createState() => new _LanguageSelectorState();
}

class _LanguageSelectorState extends State<LanguageSelector> {
  int _key;

  @override
  void initState() {
    super.initState();
    _collapse();
  }

  @override
  Widget build(BuildContext context) {
    return _buildTiles(context);
  }

  List<Widget> _languageItems() {
    List<Widget> list = [];
    final _transl = Translations.of(context);

    applic.supportedLanguages.forEach((lang) {
      list.add(new ListTile(
        title: new Text(_transl.text('language_$lang')),
        trailing: _transl.currentLanguage == lang
            ? new Icon(Icons.check, color: Colors.green)
            : null,
        selected: _transl.currentLanguage == lang,
        onTap: () {
            applic.onLocaleChanged(new Locale(lang, ''));
            setState(() {
              _collapse();
            });
        },
      ));
    });

    return list;
  }

  Widget _buildTiles(BuildContext context) {
    return new ExpansionTile(
      key: new Key(_key.toString()),
      initiallyExpanded: false,
      title: new Row(
        children: [new Text(Translations.of(context).text('language'))],
      ),
      children: _languageItems(),
    );
  }

  _collapse() {
    int newKey;
    do {
      _key = new Random().nextInt(10000);
    } while (newKey == _key);
  }
}

This solution which allows the ExpansionTile to auto-collapse each time the user taps one of its children is to recreate it.

  • This is ensured by the _collapse() method which generates a random _key each time it is called. This _key is used to uniquely identify the ExpansionTile in the rendering tree (line #50).
  • At initialization time, the overridden initState method invokes this _collapse() method, before the initial rendering.
  • The _languageItems() method builds a list of ListTile, based on the list of supported languages. The ListTile that corresponds to the working language is highlighted, via a trailing ‘check’ icon.

Language Selection

Lines #36-39.

When the user taps one of the ListTile items, a call is made to the application’s main routine to force a full application refresh. Sub-sequently, we force the collapsing of the ExpansionTile via a call to the _collapse() method.

Integration at the level of the application

To show a running use case of this widget, we will add a Drawer to the main application page. This drawer will only contain an instance of this LanguageSelector widget.

The resulting code will be the following one:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'translations.dart';
import 'application.dart';
import 'language-selector.dart';

void main() => runApp(new MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => new _MyAppState();
}

class _MyAppState extends State<MyApp> {
  SpecificLocalizationDelegate _localeOverrideDelegate;

  @override
  void initState(){
    super.initState();
    _localeOverrideDelegate = new SpecificLocalizationDelegate(null);
    ///
    /// Let's save a pointer to this method, should the user wants to change its language
    /// We would then call: applic.onLocaleChanged(new Locale('en',''));
    /// 
    applic.onLocaleChanged = onLocaleChange;
  }

  onLocaleChange(Locale locale){
    setState((){
      _localeOverrideDelegate = new SpecificLocalizationDelegate(locale);
    });
  }

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: new Text('My Application'),
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      localizationsDelegates: [
        _localeOverrideDelegate,
        const TranslationsDelegate(),
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
      ],
      supportedLocales: appli.supportedLocales(),
      home: new Scaffold(
          appBar: new AppBar(
            title: new Text(Translations.of(context).text('main_title')),
          ),
          drawer: new Drawer(
              child: new LanguageSelector(),
              elevation: 2.0,
          ),
          body: new Container(
          ),
      ),        
    );
  }
}

That’s it.

Conclusions

This is a very basic widget that shows how to implement a dynamic change of the working language.

The most interesting part of this widget gives a solution to allow an ExpansionTile to auto-collapse when one of its children is tapped.

Hoping that this might be useful.

Stay tuned for new articles and happy coding.