creativecreatorormaybenot
creativecreatorormaybenot

Reputation: 127024

Remote Config Device Language Changes in Flutter

I am encountering a problem, where localization works fine, but the applications needs to be restarted in order for the changes to propagate.

Orientation changes

I know about OrientationBuilder, which will call its builder whenever it detects a change in the device's orientation, which in e.g. Android would be considered as a configuration change, just like device language changes.

Language changes

Is there something like LanguageBuilder? I could not find anything on my own and not on flutter.io nor on pub. I have read this tutorial and know about Locale, but I do not see a Stream for Locale.

My problem is that changing the language in iOS and Android native is really smooth. It gets handled automatically and perfectly integrates with services like Firebase Remote Config.

I really wonder if there is some method that will allow me to refresh my localization.

Question

So I am asking how I can refresh my Remote Config when the device language changes.

Upvotes: 3

Views: 5551

Answers (2)

creativecreatorormaybenot
creativecreatorormaybenot

Reputation: 127024

Device language changes can be detected using a WidgetsBindingObserver.

It is the simplest to use it with a StatefulWidget in your State (with WidgetsBindingObserver):

class _MyWidgetState extends State<MyWidget> with WidgetsBindingObserver {
  @override
  void didChangeLocales(List<Locale> locale) {
    // The device language was changed when this is called.
  }

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }
  ...
}

This means that you can now reload your RemoteConfig in didChangeLocales:

@override
void didChangeLocales(List<Locale> locale) {
  _updateRemoteConfig();
}

Future<void> _updateRemoteConfig() async {
  final remoteConfig = await RemoteConfig.instance;
  await remoteConfig.activateFetched(); // This will apply the new locale.
}

Upvotes: 3

R&#233;mi Rousselet
R&#233;mi Rousselet

Reputation: 277707

No there's no Builder for Locale.

Instead, there's an InheritedWidget which you can subscribe to using Localizations.of.

Since it is an InheritedWidget, all widgets that call Localizations.of will automatically refresh on locale change.

EDIT :

A example on how to live reload text using Flutter Locale system :

Let's assume you have the following class that holds translations :

class MyData {
  String title;

  MyData({this.title});
}

You'd then have a LocalizationsDelegate that contains such data. A dumb implementation would be the following :

class MyLocale extends LocalizationsDelegate<MyData> {
  MyData data;

  MyLocale(this.data);

  @override
  bool isSupported(Locale locale) {
    return true;
  }

  @override
  Future<MyData> load(Locale locale) async {
    return data;
  }

  @override
  bool shouldReload(MyLocale old) {
    return old.data != data;
  }
}

To use it simply pass it to MaterialApp.localizationsDelegates (be sure to add flutter_localizations to your pubspec.yaml) :

LocalizationsDelegate myLocale = MyLocale(MyData(title: "Foo"));
...


MaterialApp(
  localizationsDelegates: [
    myLocale,
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
  ],
);

You can then freely live reload your translations by replacing myLocale with a new MyLocale instance.

Here's a full example of a click counter app. But where the current count is instead stored inside Locale (because why not ?)

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

class MyCount {
  String count;

  MyCount({this.count});
}

class MyCountLocale extends LocalizationsDelegate<MyCount> {
  MyCount data;

  MyCountLocale(this.data);

  @override
  bool isSupported(Locale locale) {
    return true;
  }

  @override
  Future<MyCount> load(Locale locale) async {
    return data;
  }

  @override
  bool shouldReload(MyCountLocale old) {
    return old.data != data;
  }
}

Future<void> main() async {
  runApp(MyApp());
}

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

class _MyAppState extends State<MyApp> {
  ValueNotifier<int> count = ValueNotifier<int>(0);
  LocalizationsDelegate myLocale;

  @override
  void initState() {
    count.addListener(() {
      setState(() {
        myLocale = MyCountLocale(MyCount(count: count.value.toString()));
      });
    });
    myLocale = MyCountLocale(MyCount(count: count.value.toString()));

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      localizationsDelegates: [
        myLocale,
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
      ],
      home: MyHomePage(count: count),
    );
  }
}

class MyHomePage extends StatefulWidget {
  final ValueNotifier<int> count;

  MyHomePage({this.count});

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      primary: true,
      appBar: AppBar(),
      body: Column(
        children: <Widget>[
          FloatingActionButton(
            onPressed: () => widget.count.value++,
            child: Icon(Icons.plus_one),
          ),
          ListTile(
            title: Text(Localizations.of<MyCount>(context, MyCount).count),
          ),
        ],
      ),
    );
  }
}

Upvotes: 7

Related Questions