Dart encapsulated list (just for observing values) - flutter

I have the following lists
List<ProductVariation> _productVariations = []; avoid null pointer exceptions
List<ProductVariation> get productVariations => _productVariations;
The main goal for adding List<ProductVariation> get productVariations => _productVariations; was to avoid _productVariations to be modified, however, I'm able to add, delete or do any operations inside the list, how can I prevent productVariations to be modified when accessed from another file?

An alternative solution is to use UnmodifiableListView from dart:collection which can be a lot more efficient since you are not making a new copy of the list but are instead just serving a protected view of your List:
import 'dart:collection';
class A {
final List<int> _data = [1, 2, 3];
UnmodifiableListView<int> get data => UnmodifiableListView(_data);
}
void main() {
final a = A();
print(a.data); // [1, 2, 3]
a.data.add(5); // Unhandled exception: Unsupported operation: Cannot add to an unmodifiable list
}
A downside of this solution is that UnmodifiableListView is a normal List seen from the analyzers point of view so you will not get any statically errors from doing this. But you will get an exception on runtime if you try modify the list itself. For making it more clear for the developer I think it is nice to specify that the returned type is UnmodifiableListView even if you could just write List instead.
Another point is that this solution (or the one suggested by Jigar Patel) does not prevents you from modifying the objects itself in the list if the objects are not immutable. So if you also want to prevent these changes you need to make deep copies of the objects.

You can make the getter return a copy of the list like this.
List<ProductVariation> get productVariations => [..._productVariations];
Or
List<ProductVariation> get productVariations => _productVariations.toList();

Related

Removing empty entry in List<dynamic> that only shows while inspecting

I have a list that I am getting the values from an API,
it is List<dynamic> type, while I print it I am getting this output (for example): [cat, female], but when I use inspect it has three values: "cat", "female", "". The last empty value is making some problems in my code, so I wanted to remove it, but I don't know how to do this.
As it is a List<dynamic> I used removeLast() and also toString() but none of them worked for me. I appreciate any help on this.
The solution is to filter items and get the new list:
final newList = list.where((e) => e != null && e != '').toList();
removeLast() does not work as you expected. If you read the comment of the method it says Removes and returns the last object in this list.
PS: I recommend you use a functional way to deal with a list which means do not modify the state of the original list instead, get a new list.

Is there no simple way to make a deep of lists in flutter?

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');
}

Iterate over _InternalLinkedHashMap not working

I'm trying to iterate over the following data structure: {String: [{String, List<SomethingResponse>}]} where SomethingResponse = {String: dynamic}). I created this model:
class SomethingsResponse {
final Map<String, List<SomethingResponse>> SomethingsResponse;
SomethingsResponse({this.SomethingsResponse});
factory SomethingsResponse.fromJson(data) {
return SomethingsResponse(SomethingsResponse: data.map<String, List<SomethingResponse>>((String key, dynamic value) {
final dataFromCategory = List<SomethingResponse>.from(value.map((x) => SomethingResponse.fromJson(x)));
return MapEntry(key, dataFromCategory);
}));
}
}
When I try getting the keys like this: data.somethingsResponse.toList(), I get an error saying:
Class '_InternalLinkedHashMap<String, List>' has no instance method 'toList'.
I can't iterate over it or really get any kind of data out of it. What am I doing wrong and how can I fix it? I have a feeling the issue is at this line return MapEntry(key, dataFromCategory);, but I tried creating a Map a couple of different ways, and none worked.
If you consult the documentation for Map, you will see that it does not derive from Iterable and therefore cannot be directly iterated over. I presume that this is because it's not obvious what you want to iterate over: keys, values, or key-value pairs?
If you want to iterate over keys, use Map.keys. (In your case: data.somethingsResponse.keys.toList())
If you want to iterate over values, use Map.values.
If you want to iterate over key-value pairs (i.e. MapEntry objects), use Map.entries.

Why would you create a Copy of a List to check if the List contains an Element of the Copy?

Copied lists
I stumbled across the following snippet while browsing through the Flutter framework repo:
for (final ValueChanged<RawKeyEvent> listener in List<ValueChanged<RawKeyEvent>>.from(_listeners)) {
if (_listeners.contains(listener)) {
listener(event);
}
}
As far as I know, List.from should create a copy with the exact same elements as the original list, so they should have the same elements, I guess?
For context, the _listeners variable is declared and initialized like this:
final List<ValueChanged<RawKeyEvent>> _listeners = <ValueChanged<RawKeyEvent>>[];
Question rephrased
Basically, is there ever a way the if-condition would not be true?
Type cast in List.from
The author did not use List.of, so maybe List.from discards some elements when the types of the elements do not match?
But then, the _listeners variable already enforced the exact same type, so no element with non-matching types can be added anyway, right?
This is something that ChangeNotifier also does.
The reason for both the list clone and the contains call is to support having listeners add and remove listeners. Otherwise, there could be a ConcurrentModificationError.
For example:
ChangeNotifier notifier;
final listener = () => print('hey');
notifier.addListener(() {
if (something) {
notifier.removeListener(listener);
}
});

Unhandled Exception: type 'List<dynamic>' is not a subtype of type 'List<String>' in type cast

This exception is thrown in the lins myList = results['users'];. I also tried myList = results['users'] as List<String>;. The type of results['users'] is List<dynamic>. Actually it contains Strings so why can't it be converted?
List<String> myList = List<String>();
results = await ApiService.searchUser();
setState(() {
myList = results['users'];
}
You can build a new list
myList = new List<String>.from(results['users']);
or alternatively, use a cast:
myList = results['users'].cast<String>();
Note that myList.runtimeType will differ:
List<String> in case of a new list
CastList<dynamic, String> in case of a cast
See discussion on Effective Dart: When to use "as", ".retype", ".cast"
I would suggest that you hardly ever use cast or retype.
retype wraps the list, forcing an as T check for every access, needed or not.
cast optionally wraps the list, avoiding the as T checks when not needed, but this comes at a cost of making the returned object polymorphic (the original type or the CastList), which interferes with the quality of the code that can be generated.
If you are going to touch every element of the list, you might as well copy it with
new List<T>.from(original)
So I would only use cast or retype if my access patterns are sparse or if I needed to update the original.
Note that the discussion above refers to retype method, which was removed from Dart 2, but other points are still valid.