How to enforce a new property has a value set at compile time in dart? - flutter

I am new to dart/flutter and I am adding internationalization on my app.
As flutter don't define a proper way to implement internationalization (according to the video I watched), devs are free to do it in any way they think its better.
The video showed an implementation using json files. So on "lang" folder I have my json files with all strings and their respective translations.
Now imagine this is a large file with lots of entries. And I have to make a big change on my app, removing some strings, adding others and adding a new language. This is very error prone as I may forget to add some key-value entry in any of the json files. It happened sometimes and the error is thrown at runtime.
In java there is a way to enforce the dev don't forget to add the same values in all variations of the same property at compile time. Maybe it's not the best way to achieve this, but one way is using an enum like in the image below.
Now if I want to add a new language, I just add a new parameter in the enum constructor and there is no way to forget adding a translation because the code would not even compile.
To fix the error I just have to add the value on the Strings (I forgot the "es" case here):
And then I can use the values doing something like this (the code to find the locale using the context could even be moved to inside the .value() method):
So is there a good way to achieve this in dart/flutter? To enforce all the languages have all the translated texts at compile time?

My method looks like this:
First, define the interface, and its implementations:
abstract class StringsInterface {
String get greet;
}
class EnglishStrings implements StringsInterface {
String get greet => "Hello";
}
class SpanishStrings implements StringsInterface {
String get greet => "Hola";
}
Next, define a class to store an instance of the current locale, as well as configure which class to use, for which language code:
class Strings {
static StringsInterface current = EnglishStrings();
static late Locale _locale;
static Locale get locale => _locale;
static set locale(Locale locale) {
_locale = locale;
current = stringsForLocale(locale);
}
static StringsInterface stringsForLocale(Locale locale) {
switch(locale.languageCode) {
// Here is where you define which class gets used for which language code
case 'es': return SpanishStrings();
default: return EnglishStrings();
}
}
}
Set the locale for the Strings class at runtime:
void main() async {
/// load from your own future
/// (e.g. pull from a persisted value stored on device in SharedPreferences/UserDefaults)
Strings.locale = Locale(await Future<String>.value('en'));
runApp(const MyApp());
}
Finally, use like this:
Text(Strings.current.greet)

As of dart 2.17 you can now use enhanced enums with members:
enum Strings {
welcome(en: 'Welcome', pt: 'Bem-vindo'),
goodbye(en: 'Good bye', pt: 'Adeus');
final String en;
final String pt;
const Strings({
required this.en,
required this.pt,
});
String value(String lang) {
switch (lang) {
case 'pt':
return pt;
case 'en':
default:
return en;
}
}
}

Related

Dynamic Type Casting in Dart/Flutter

I am in the middle of writing a library to dynamically serialise/deserialise any object in Dart/Flutter (Similar in idea to Pydantic for Python). However, I am finding it impossible to implement the last component, dynamic type casting. This is required in order convert types from JSON, such as List to List (or similar). The types are retrieved from objects using reflection.
The below is the desired implementation (though as far as I understand this is not possible in Dart).
Map<String, Type> dynamicTypes = {"key": int };
// Regular casting would be "1" as int
int value = "1" as dynamicTypes["key"];
Is there some workaround which makes this possible to implement? Or have I reached a dead end with this (hence no other dynamic serialisation/deserialisation package already exists).
Conducting more research into this issue, it seems in Dart's current implementation this is impossible due to runtime reflection being disabled as referenced here in the official docs.
There are ongoing discussions about the support for this and the associated package dart:mirrors here on GitHub, but so far though there is some desire for such functionality it is highly unlikely to ever be implemented.
As a result, the only options are:
Use code generation libraries to generate methods.
Manual serialisation/deserialisation methods.
Implement classes with complex types such as lists and maps to be dynamic, enabling (all be it limited) automatic serialisation/deserialisation.
Your question does not specify how exactly dynamicTypes is built, or how its key is derived. So there is perhaps a detail to this that is not clear to me.
But what about something like this?
class Caster<T> {
final T Function(String) fromString;
Caster(this.fromString);
}
void main() {
Map<String, Caster> dynamicTypes = { "key": Caster<int>((s) => int.parse(s)) };
int v = dynamicTypes['key']!.fromString('1');
print(v);
}
Or, if you have the value as a dynamic rather than a string:
class Caster<T> {
T cast(dynamic value) => value as T;
}
void main() {
Map<String, Caster> dynamicTypes = { "key": Caster<int>() };
dynamic a = 1;
int v = dynamicTypes['key']!.cast(a);
print(v);
}
Or even more succinctly:
void main() {
dynamic a = 1;
int v = a;
print(v);
}

instantiate object with *new* keyword and use property inside class with *this* keyword

i'm coming from mainly JS/TS world (NestJS/Angular) and recently i start to building Flutter apps..
i have 2 main questions
there is any difference when instantiate object with or without new keyword?
i saw examples in flutter when people use new Row(children: [Text('Foo'), Text('Bar'),],) instead of just Row(...)
if there is a difference which one is better to use?
inside of my Dart classes in flutter app, i can both use this.property and property again there is any difference and if so which one is better and why?
example:
class Person {
final String name;
final int age;
Person(this.name, this.age);
getNameAge() => '${this.name} is ${this.age}';
getNameAge2() => '$name is $age';
}
both looks the same to me
void main() {
final p = Person('dan', 22);
final p2 = new Person('ben', 20);
print(p.getNameAge()); // dan is 22
print(p2.getNameAge2()); // ben is 20
}
The new keyword is optional in Dart and I think the general consensus is, today, to not use it.
The use of this is useful if you have multiple variables with the same name but in different scope. E.g. (this is just an example. You would not make a setA method in Dart but use properties):
class A {
int a;
A(this.a);
void setA(int a) {
this.a = a;
}
}
Here we use this to distinguish between the argument a and the class variable a. But if you don't have variables with the same name (but in different scope), the use of this is optional. In some projects, you still use this to make it more clear that you are referring to a class variable even if it is not needed.

How to declare final class in Dart to prevent extending from it?

In Java\Kotlin we have a String class that is final and immutable.
I tried to mark the class with final keyword but looks like it's not allowable.
So, I'm a little bit confusing, how to declare final class in Dart ?
Note: the case is - I want to instantiate this class outside, but forbid to extending it. So using the private constructor - it's not my case.
You can achieve this final effect from java by having a private constructor for your class, it will prevent the class from being extended, BUT it will also prevent the class from being instantiated (only in the same file both will be possible):
class MyString {
MyString._(); // use _ for private constructor.
static void print(String s) {
print(s);
}
}
Call with
String message = "Hello World";
MyString.print(message);
Dart considers that we are all adults, preventing class extension is hence part of the design and responsability of the developers to have clear class names, and not part of the language:
AVOID extending a class that isn’t intended to be subclassed.
If a constructor is changed from a generative constructor to a factory constructor, any subclass constructor calling that constructor will break. Also, if a class changes which of its own methods it invokes on this, that may break subclasses that override those methods and expect them to be called at certain points.
Difference of meaning for final with Java
Dart has a very simple definition of what is final: a variable in dart can only be set once, id est: is immutable.
Final and const
If you never intend to change a variable, use final or const, either instead of var or in addition to a type.
A final variable can be set only once; a const variable is a compile-time constant. (Const variables are implicitly final.) A final top-level or class variable is initialized the first time it’s used.
Additionally to the approach of making the constructor private and instantiating your object via a static factory, you could use the package meta and
annotate your final class as sealed:
#sealed
class Z{}
This will signal users of your package that this class should not be extended or implemented. For example in vscode trying to extend the class Z:
class Z1 extends Z{}
results in the following warning:
The class 'Z' shouldn't be extended, mixed in,
or implemented because it is sealed.
Try composing instead of inheriting, or refer
to its documentation for more information.dart(subtype_of_sealed_class)
The issue will also be picked up by the dart analyzer:
$ dart analyze
Analyzing test... 0.8s
info • lib/src/test_base.dart:3:1 •
The class 'Z' shouldn't be extended, mixed in, or implemented because it
is sealed. Try composing instead of inheriting, or refer to its
documentation for more information. • subtype_of_sealed_class
You can use the factory unnamed constructor along with private named constructor, like this:
class NonExtendable {
NonExtendable._singleGenerativeConstructor();
// NonExtendable();
factory NonExtendable() {
return NonExtendable._singleGenerativeConstructor();
}
#override
String toString(){
return '$runtimeType is like final';
}
}
In a client code, in the same library, or another library, an instance can be created, an example:
// Create an instance of NonExtendable
print ('${NonExtendable()}');
Trying to extend it, something like
class ExtendsNonExtendableInSameLibrary extends NonExtendable {
ExtendsNonExtendableInSameLibrary._singleGenerativeConstructor() : super._singleGenerativeConstructor();
factory ExtendsNonExtendableInSameLibrary() {
return ExtendsNonExtendableInSameLibrary._singleGenerativeConstructor();
}
}
will work in the same library (same 'source file') but not in another library, making the class NonExtendable same as 'final' in Java from the perspective of any client code.

Flutter 1.22 Internationalization with variable as key

I implemented the new (official) localization for Flutter (https://pascalw.me/blog/2020/10/02/flutter-1.22-internationalization.html) and everything is working fine, except that I don't know how to get the translation for a variable key.
The translation is in the ARB file, but how can I access it?
Normally I access translations using Translations.of(context).formsBack, but now I would like to get the translation of value["labels"]["label"].
Something like Translations.of(context).(value["labels"]["label"]) does not work of course.
I don't think this is possible with gen_l10n. The code that is generated by gen_l10n looks like this (somewhat abbreviated):
/// The translations for English (`en`).
class TranslationsEn extends Translations {
TranslationsEn([String locale = 'en']) : super(locale);
#override
String get confirmDialogBtnOk => 'Yes';
#override
String get confirmDialogBtnCancel => 'No';
}
As you can see it doesn't generate any code to perform a dynamic lookup.
For most cases code generation like this is a nice advantage since you get auto completion and type safety, but it does mean it's more difficult to accommodate these kinds of dynamic use cases.
The only thing you can do is manually write a lookup table, or choose another i18n solution that does support dynamic lookups.
A lookup table could look something like this. Just make sure you always pass in the current build context, so the l10n code can lookup the current locale.
class DynamicTranslations {
String get(BuildContext context, String messageId) {
switch(messageId) {
case 'confirmDialogBtnOk':
return Translations.of(context).confirmDialogBtnOk;
case 'confirmDialogBtnCancel':
return Translations.of(context).confirmDialogBtnCancel;
default:
throw Exception('Unknown message: $messageId');
}
}
}
To provide an example for https://stackoverflow.com/users/5638943/kristi-jorgji 's answer (which works fine):
app_en.arb ->
{
"languages": "{\"en\": \"English\", \"ro\": \"Romanian\"}"
}
localization_controller.dart ->
String getLocaleName(BuildContext ctx, String languageCode) {
return jsonDecode(AppLocalizations.of(ctx)!.languages)[languageCode];
}
getLocaleName(context, 'ro') -> "Romanian"
You can store a key in translation as json string.
Then you read it, parse it to Map<string,string> and access dynamically what you need.
Been using this approach with great success

What is the point of GlobalMaterialLocalizations and flutter_localizations?

The Flutter article for internationalization states the following:
To add support for other languages, an application must specify additional MaterialApp properties, and include a separate package called flutter_localizations.
However, I do not understand what the point of flutter_localizations is and why I "must [...] include" it in my app if I want to add support for other languages.
If I take a look at GlobalMaterialLocalizations, which is what flutter_localizations apparently adds (along with the Widgets and Cupertino versions), I can only find a bunch of seemengly random strings I have never seen translated into a bunch of languages.
I have never seen these strings in my Flutter app.
Do I really need to include flutter_localizations if I want to internationalize my app, i.e. are these strings used anywhere by default?
Actually no.
It depends how you want to implement you internationalization.
You can get current local by:
/// Because of flutter issue
/// https://github.com/flutter/flutter/issues/38323
/// So return `locals[1]` instead of `window.locale`;
var locals = window.locales;
var local = (locals.length > 1) ? locals[1] : window.locale;
Benefit of current flutter_localizations
I think the benefit of this lib is that it also handle the text direction stuff in
The other way
If you only need to handle the Strings' translation, no need to handle text direction(ltr(left to right) or rtl(right to left)):
You handle your strings by the value returned above:
abstract class Strings {
String hello;
factory Strings() // => instance;
{
var locals = window.locales;
var local = (locals.length > 1) ? locals[1] : window.locale;
if (local != null) {
// log('Strings.load(${local.languageCode})');
if (local.languageCode == 'zh') {
return _StringsZH.instance;
}
}
return _StringsEN.instance;
}
}
class _StringsEN implements Strings {
String hello = 'Hello 2019';
_StringsEN._();
static _StringsEN instance = _StringsEN._();
}
class _StringsZH implements Strings {
String hello = '你好 2019';
_StringsZH._();
static _StringsZH instance = _StringsZH._();
}
And use it like
Strings().hello;