It's my first project with Easy Localization. One of the feature of my project is to dinamically enable and disable new Locale for my app using a remote db (Firestore).
Everytime the app is already running with some Locale and it fetches new data (new locales or just a change in a localized string) I get this error from EasyLocalization: Bad state: Stream has already been listened to.
I'm using the easy_localization version 2.3.0.
In my project I'm using the Bloc pattern but I simplified the problem in this code:
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
void main() => runApp(Localization());
class Localization extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: _getTranslation(),
builder: (BuildContext context, AsyncSnapshot<Translation> snapshot) {
if (snapshot.hasData) {
Translation translation = snapshot.data;
// This will raise the error: Bad state: Stream has already been listened to
return EasyLocalization(
path: "resources/langs",
supportedLocales: [
Locale(translation.languageCode, translation.countryCode)
],
assetLoader: TranslationAssetLoader([translation]), // not mandatory to raise the error
child: MyApp(),
);
}
Map<String, dynamic> loadedMap = Map();
loadedMap["title"] = "Not loaded text";
Translation translation = Translation(
languageCode: "en", countryCode: "US", stringsMap: loadedMap);
return EasyLocalization(
path: "resources/langs",
supportedLocales: [
Locale(translation.languageCode, translation.countryCode)
],
assetLoader: TranslationAssetLoader([translation]), // not mandatory to raise the error
child: MyApp(),
);
},
);
}
Future<Translation> _getTranslation() async {
await new Future.delayed(const Duration(seconds: 5));
Map<String, dynamic> loadedMap = Map();
loadedMap["title"] = "Loaded text";
return Translation(
languageCode: "en", countryCode: "US", stringsMap: loadedMap);
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: context.localizationDelegates,
supportedLocales: context.supportedLocales,
locale: context.locale,
title: 'Flutter Demo',
home: Scaffold(body: Center(child: Container(child: Text('title').tr()))),
);
}
}
class Translation {
final String languageCode;
final String countryCode;
final Map<String, dynamic> stringsMap;
Translation({this.languageCode, this.countryCode, this.stringsMap});
}
class TranslationAssetLoader extends AssetLoader {
final List<Translation> translations;
TranslationAssetLoader(this.translations);
@override
Future<Map<String, dynamic>> load(String path, Locale locale) {
String languageCode = locale.languageCode;
String countryCode = locale.countryCode;
Translation translation = translations
.where((translation) =>
translation.languageCode == languageCode &&
translation.countryCode == countryCode)
.toList()
.first;
return Future.value(translation.stringsMap);
}
}
I included an example of custom AssetLoader but you can omit it, the error will be raised even without it.
Am I doing something wrong?
I'm new on Flutter, but I think it happens because inside EasyLocalizations's bloc there is a StreamController: by default a Stream could be listened only one time; so when the app rebuilds because of FutureBuilder it throws this error. The developer could use broadcast instead, but maybe it could break something else...
@enricoquarantini
Use key, it will help
class Localization extends StatelessWidget {
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: _getTranslation(),
builder: (BuildContext _, AsyncSnapshot<Translation> snapshot) {
if (snapshot.hasData) {
Translation translation = snapshot.data;
//EasyLocalization.of(context).bloc.dispose();
// This will raise the error: Bad state: Stream has already been listened to
return EasyLocalization(
key: Key('EZ1'), // <--- add key
path: "resources/langs",
supportedLocales: [
Locale(translation.languageCode, translation.countryCode)
],
assetLoader: TranslationAssetLoader(
[translation]), // not mandatory to raise the error
child: MyApp(),
);
}
Map<String, dynamic> loadedMap = Map();
loadedMap["title"] = "Not loaded text";
Translation translation = Translation(
languageCode: "en", countryCode: "US", stringsMap: loadedMap);
return EasyLocalization(
key: Key('EZ2'), // <--- add key
path: "resources/langs",
supportedLocales: [
Locale(translation.languageCode, translation.countryCode)
],
assetLoader: TranslationAssetLoader(
[translation]), // not mandatory to raise the error
child: MyApp(),
);
},
);
}
Using keys will show Flutter that these are different widgets and will call dispose() when creating a new
I'm new on Flutter, but I think it happens because inside EasyLocalizations's bloc there is a StreamController: by default a Stream could be listened only one time; so when the app rebuilds because of FutureBuilder it throws this error. The developer could use
broadcastinstead, but maybe it could break something else...
Yep, only one Easy Localization instance can exist.
@enricoquarantini but why you not use preloaderWidget parametr?
@Overman775 you're right, using the key does the trick. Thank you!
I didn't know about preloaderWidget. Anyway in my real project I'm using streams to handle new translations because my goal is to be able update the strings or the enabled locales multiple times in a single app session.
Right now I'm generating the Key with the hashCode of the model I'm using to handle the translations: Key(translations.hashCode.toString())