I try to learn to flutter but notions are quite difficult for me.
I was working with local json files which I load at the start and then do calculations with the values inside.
But I see everywhere that everyone uses class to parse JSON.
Classes are a fairly complex concept for me. I find it more complicated to handle.
In my example if I get a particular user and I want to add a KEY / VALUE to it, I can't do it if I go through a class. How to do ?
Is it useful to use class if I just load json files and retrieve values inside to do statistics?
My homeController.dart
import 'package:flutter/material.dart';
import 'dart:convert';
import 'dart:async' show Future;
import 'package:flutter/services.dart' show rootBundle;
import 'package:myapp/widgets/users.dart';
Future<Map<String, dynamic>> loadJson() async {
final myJson = await rootBundle.loadString('assets/jsons/myjson.json');
return {
'myJson': myJson,
};
}
class HomeController extends StatefulWidget {
HomeController({Key key, this.title}) : super(key: key);
final String title;
#override
_HomeControllerState createState() => _HomeControllerState();
}
class _HomeControllerState extends State<HomeController> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: (() => FocusScope.of(context).requestFocus(FocusNode())),
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: FutureBuilder(
future: loadJson(),
builder: (context, snapshot) {
if(snapshot.hasError) print(snapshot.error);
if (!snapshot.hasData) {
return LinearProgressIndicator();
}
print('WITH CLASS =====================================');
var myJson1 = jsonDecode(snapshot.data['myJson'])['user'] as List;
List<User> myList1 = myJson1.map((e) => User.fromJson(e)).toList();
List selectedInList1 = myList1.where((e) => e.arg2>= 250).toList();
User chooseObject1 = selectedInList1.first;
chooseObject1.putIfAbsent('newKey', () => 'test'); // ???????????????????????
print(chooseObject1);
print('WITHOUT CLASS =====================================');
var myJson = json.decode(snapshot.data['myJson']) ;
List myList = myJson['user'];
List selectedInList = myList.where((e) => e['arg2'] >= 250).toList();
Map chooseObject = selectedInList.first;
chooseObject['newKey'] = "test";
print(chooseObject);
// Some code ...
My json
{
"user" : [
{
"arg1" : 1,
"arg2" : 200
},
{
"arg1" : 1,
"arg2" : 250
},
{
"arg1" : 1,
"arg2" : 300
}
]
}
My User Class
class User{
final int arg1;
final int arg2;
User(
this.arg1,
this.arg2
);
factory User.fromJson(dynamic json){
return User(
json['arg1'] as int,
json['arg2'] as int
);
}
#override
String toString() {
return '{ ${this.arg1}, ${this.arg2} }';
}
}
Studying a framework, library, engine, a new programming language, other technology (any abstraction) without knowing something that is required to know to be able to use it is more difficult and longer (a priori) task. It's like studying physics without some math knowledge. Of course, it doesn't mean that it's impossible. More than that many people are learning like that all the way. It's like not learning from bottom to top, but from top to bottom. It means that you want to make something, and to do it, you don't learn everything and then do it, but trying to do something, then learn the next things that a required to go closer to your goal. And everything is OK with that method. Everyone decides which method fits him better.
Everyone and everywhere use classes because classes in OOP languages (and Dart is an OOP language) are one of the base blocks using which we are creating our programs. More than that, I should mention, that all your program is written with classes. Classes are everywhere in Dart, practically everything is a class in Dart (if not everything literally).
You've said that you can't add a key/value to your class. You can't because you've not added such a behavior to your User class. Map is also just a class (that has been written for you by Dart creators). And it doesn't mean that you could not write something like that. You can just add a method (or operator overloading) to accomplish this task. It could be like that:
class User{
// ...
void add(String key, dynamic value) {
// ... implementation
}
}
And then you could do just the same.
Another open question is why do you really need to add a new key/value pair to your User? What problem that ability will help you to solve? Maybe you just declare this field in the User class? Or maybe you need to define another class? I couldn't answer this question, because I don't know what are you going to achieve.
So, I suppose the real question is: why should everyone prefer parse JSON data into custom classes instead of using a predefined Map class? There are many reasons to do that. First of all: it is to be able to reason about (and not only for humans but for a compilers either). What does Map class represents? It represents a collection of key and value pairs. No more no less. So there is no guarantee of what a particular collection contains? It could be empty, or it could contain something else or whatever. And working with such a collection is more difficult and error-prone (you are to remember everything by yourself, and the compiler will not help you). In our case, we could say that a map is raw data, i.e. a collection of unknown raw data. We do not really know what it is exactly until we'll check it. But when we define a class for our data we have not just a collection of raw data but a collection of User (a collection with known keys (data) and behavior (methods)). No more no less, with some guarantees. Like every object in that collection has a type of User. Every object has all fields that are declared in the User class. All of those fields have a specified type. It is very convenient. And now we not only know with what data we are working but the compiler either. And it will help us all the time. It will show type errors, it will generate more reliable and fast programs. And many other advantages. For example, when you access a property like that: e['arg2'] a compiler doesn't know whether the e contains such a key or not. But when you access to property like that: e.arg2 than the compiler 100% sure that such a key exists. Then, for example, let's imagine that we need to change a key name for some reason. In the first example, we can change it in one place and we can forget (easily) in every other place. So the program wouldn't work well in such a case. If we change a key name in the User class we wouldn't able to compile a program until we change every line of code with the old key name. It's also a huge difference. Of course, when you are writing a small program like that it's not a big deal to change it and to remember everything. But software complexity grows extremely fast and even in a medium program, it becomes a real problem. 20% of the time we are writing a program and 80% we are debugging it and fixing bugs. Classes allow us to greatly reduce the number of things that we should think about ourselves.
Of course, there are many other reasons (and it's difficult to put them all down in one answer), it's just a simple example to answer why. You can continue to write programs in a way that is convenient for you. It's OK. Some day, with some experience you will grasp the advantages of classes either. If you want to have some deeper know you are to read some OOP book and have some practice with it.
Related
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);
}
I tried List.from and [...orginalObject], but neither does a deep copy. In both cases it's clearly just copying the references. Which means if I change any data in the list in which I copied the data, the original data gets changed too.
It seems to me that the only way to do a for loop and define each of the entries the copied list with a new operator and the data from each of corresponding entries from the original list. Something as shown in the following image.
Seems like quite a tedious approach. Is there any simpler approach?
Thanks.
In vanilla Dart, not really, since this would require the ability to copy the leaf objects, and there is no way for the language to know how to do that for arbitrary objects.
However, the built_value and built_collection packages may solve your issue. One of their main features is deep immutability. First, you write your classes like this:
class Item {
final String foo;
final int bar;
Item(this.foo, this.bar);
}
// becomes
part 'item.g.dart';
abstract class Item extends Built<Item, ItemBuilder> {
Item._();
factory Item([void Function(ItemBuilder) updates]) = _$Item;
// boilerplate, can use tooling to auto-generate
String get foo;
int get bar;
}
This lets you create an immutable object using a mutable Builder and then finalizing it, e.g.:
final item1 = Item((b) {
// here b is an ItemBuilder, the mutable version of Item
b.foo = 'Hello';
b.bar = 123;
});
final item2 = item1.rebuild((b) => b.foo = 'New Value'); // Item(foo: 'New Value', bar: 123)
You can then use built_collection to work with deeply immuatble collections of built values, e.g.:
final list = BuiltList<Item>([item1, item2, item3]);
final deepCopy = list.rebuild((b) {}); // rebuild with no changes
final withNewElement = list.rebuild((b) => b.add(item4));
The important thing with all of these is that they are all immutable, and are "value objects", meaning (among other things) that they are considered equal if all their corresponding values are equal. You can consider each rebuild call to be returning you a brand new list with all the values copied over.
In fact, the library is smart enough to only create new objects for values that have actually changed, but because it is immutable, it acts like a deep copy, without the potential performance costs.
The main downside with these libraries is that they rely on code generation, so it requires a bit more setup than just putting it in your dependencies. In return, you get a lot of useful features in addition to having deeply immutable objects (like json serialization, null checking in pre-null-safety code, #memoized to cache results of expensive computations`, etc)
Dart does not have copy constructors nor have required clone() methods on objects, so there is no way to copy an arbitrary object, and therefore there is no way to make a deep copy of a collection of arbitrary objects.
cameron1024's answer suggesting the use of package:built_value and package:built_collection is good. If they're not suitable for you, however, you could make a function that copies elements via a callback:
List<T> copyList<T>(Iterable<T> items, T Function(T element) copier) {
return [
for (var item in items) copier(item),
];
}
class Foo {
int i;
String s;
Foo(this.i, this.s);
#override
String toString() => 'Foo($i, "$s")';
}
void main() {
var list = [Foo(1, 'one'), Foo(2, 'two'), Foo(3, 'three')];
print('Original: $list');
var copy = copyList<Foo>(list, (foo) => Foo(foo.i, foo.s));
list[0].i = 100;
print('Mutated: $list');
print('Copy: $copy');
}
For example, I expect <T extends type> can works like this:
Class Parent {
String data;
Parent({ this.data });
}
Class Child extends Parent {
Child({ this.data }) : Parent(data: data);
void showData() { print(data); }
}
T wrapper<T extends Parent>(String value) {
var result = T(data: value);
return result;
}
void main() {
var trial = wrapper<Child>("Hello world");
trial.showMessage(); // print "Hello world"
}
But turns out it gives me error at var result = T(data: value);, saying that T is not a function. When I specified , I expect that T can be operated like Parent class, and if I supplied its descendant like Child, the operation done will be Child instead. But the constructor will work either way because T extends Parent. Is such thing possible?
Constructors are not inherited. You know that already, because you wrote one yourself in your child class that does nothing but call the base class with the same parameters.
One could as well write a different constructor. So "X extends Y" says a lot about X, but it does not say anything about how the constructor of X looks (or whether it even has a constructor accessible in that scope). So a constructor call is not in the properties available to you when you specify your generic to "extend Y", because Y can do exactly nothing to make sure all it's derivates follow a specific construction method.
Different languages deal with the problem of "but how do I construct a new instance of my generic type" in different ways, but the underlying concept is common to almost all concepts of generics where the generic code is compiled before knowing the specific types of all T's handled. A constructor is not inherited in most OOP languages, therefor it is not guaranteed to be there for any "X extends Y" even if Y has it.
It might be easy to overlook when you have all your code in one compilation unit. The compiler should be able to figure it out, right? But your code might not be in a single compilation unit:
Codebase one:
Class Parent {
String data;
Parent({ this.data });
}
T wrapper<T extends Parent>(String value) {
var result = T(data: value);
return result;
}
At this point, the compiler has no idea what "Child" might look like. It cannot possibly determine that the child class that will be used in the Future has a constructor like that.
Codebase 2:
Class Child extends Parent {
Child({ this.data }) : Parent(data: data);
void showData() { print(data); }
}
void main() {
var trial = wrapper<Child>("Hello world");
trial.showMessage(); // print "Hello world"
}
Now, at this point, a compiler could figure out that the program it's given would actually work. Some concepts of generics do that, where generics cannot be compiled into independent libraries, they always come as source code, because only the final compiler producing the executable can determine whether it would work with a specific class. Flutter does not do this. Flutter needs the generic itself be valid for the constraints given.
All newer language's versions of generics have followed the path of knowing the constraints beforehand and only allowing code operating inside those constraints. And I think it's good because while it has it's shortcomings, it leaves less room for errors or cryptic error messages.
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
My question is pretty straightforward, i've a Dart class like so
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';
class Weather extends Equatable {
final String cityName;
final double tempCelsius;
final double tempFahrneit;
const Weather({
#required this.cityName,
#required this.tempCelsius,
this.tempFahrneit,
});
#override
List<Object> get props => [
cityName,
tempCelsius,
tempFahrneit,
];
#override
String toString() {
return [
cityName,
tempCelsius,
tempFahrneit,
].toString();
}
}
I'm using Equatable to ease the objects comparison, i also used the const keyword on the class constructor (not sure about this i've heard when used on a class constructor it makes Dart look first if it has the same class with same properties before instanciate it).
When i look up on DevTools, i always get multiple class instances when calling a function although it's always the same parameters, and the garbage collector keep it event though i pop up / destroy the view (Scaffold in Flutter context).
For now i'm just testing it with a small class, but this'll be a mess if it's one big of a class, even though i think in this case the garbage collector will surely dispose the unused classes, but i want to know if i can solve this "problem" with some sort of Dart/Flutter ways.
If you create your objects with const Weather(...) you should not see multiple instances anymore.
Watching the garbage collector is usually not a good idea, you can trust that it will destroy your objects when the GC algorithm gets to it.
Dart's garbage collector is optimized for creating many small objects and throwing them away together. So you probably do not have to worry about this at all.