use of 'read' before 'watch' - RiverPod - dart

I have this setup:
final totalStatesOfAssignmentsByRoom = StreamProvider.family<List<int>?, Room>(
(ref, room) async* {
List<int> statusCount = [];
// some logic
yield statusCount;
},
);
At some point when I need to get the List for a room (in a function):
var list = ref
.read(totalStatesOfAssignmentsByRoom(
Room(
roomId: roomId,
notificationType: notificationType,
),
))
.value;
This returns null.
If I change 'read' to 'watch' I get the List.
AFAIK, it appears that the StreamProvider must be 'watched' before it is 'read'.
As per recommended guidelines we are to 'read' and not 'watch' unless we are in the build method.
What am I doing wrong?

Related

SplayTreeSet contains duplicate values

I have a SplayTreeSet of the objects ChatRoomListModel. I'd like to order my set based on the objects DateTime createTime value.
I'm not sure where I'm going wrong because there's duplicate items being added item.
I later down the line perform a _latestMessages.add(newMessage) and it's not actually calling the overloads and there's duplicates being added.
I tested by using _latestMessage.contains(newMessageButSameChatRoomId), it returns false.
When I perform _latestMessage.toSet() every duplicate goes away.
How can I get SplayTreeSet to use my overloading equals?
Thanks!
ObservableSet<ChatRoomListModel> _latestMessages = ObservableSet.splayTreeSetFrom(
ObservableSet(),
compare: (a, b) => b.compareTo(a),
);
The ChatRoomListModel model has the following methods and overloads:
int compareTo(ChatRoomListModel other){
return messagesModel.createTime.compareTo(other.messagesModel.createTime);
}
ChatRoomListModel copyWith({
String? firstName,
String? otherUserId,
MessagesModel? messagesModel,
}) {
return ChatRoomListModel(
firstName: firstName ?? this.firstName,
otherUserId: otherUserId ?? this.otherUserId,
messagesModel: messagesModel ?? this.messagesModel,
);
}
#override
bool operator ==(Object other) =>
identical(this, other) ||
other is ChatRoomListModel &&
runtimeType == other.runtimeType &&
messagesModel.chatRoomId == other.messagesModel.chatRoomId;
#override
int get hashCode => messagesModel.chatRoomId.hashCode;
Your issue is that you have two completely different notions of what it means for two ChatRoomListModel objects to be "equal". You provide both compareTo and operator == implementations, but they consider different sets of properties, so compareTo can return 0 when operator == returns false, which is confusing at best. SplayTreeMap considers only compareTo, not operator ==. From the SplayTreeSet documentation:
Elements of the set are compared using the compare function passed in the constructor, both for ordering and for equality. If the set contains only an object a, then set.contains(b) will return true if and only if compare(a, b) == 0, and the value of a == b is not even checked.
I'm presuming what you call "duplicates" are elements that have equal chatRoomIds but that have different creation times, and creation times are the only things that your SplayTreeSet cares about.
If your goal is to maintain only the latest message per chatRoomId, you need to maintain a data structure that uses the chatRoomId (a String) as a key. The natural collection for that would be a Map<String, ChatRoomListModel>. (Since the ChatRoomListModel knows its own chatRoomId, it also could just be a HashSet with explicit equals and hashCode callbacks.)
If you additionally want to keep messages in chronological order, you either will need to explicitly sort them afterward or maintain a separate data structure that keeps them in chronological order. You could continue using a SplayTreeSet for that. Basically before you add any entry to the SplayTreeSet, check the Map first to see if an existing entry for that chatRoomId.
I don't fully understand your data structures, but here's an example that you presumably can adapt:
import 'dart:collection';
class Message {
Message(this.creationTime, {required this.chatRoomId, required this.text});
final DateTime creationTime;
final String chatRoomId;
final String text;
#override
String toString() => '$creationTime: [$chatRoomId] $text';
}
class Messages {
final _latestMessages = <String, Message>{};
final _orderedMessages = SplayTreeSet<Message>((message1, message2) {
var result = message1.creationTime.compareTo(message2.creationTime);
if (result != 0) {
return result;
}
result = message1.chatRoomId.compareTo(message2.chatRoomId);
if (result != 0) {
return result;
}
return message1.text.compareTo(message2.text);
});
void add(Message message) {
var existingMessage = _latestMessages[message.chatRoomId];
if (existingMessage != null &&
message.creationTime.compareTo(existingMessage.creationTime) < 0) {
// An existing message exists with a later creation date. Ignore the
// incoming message.
return;
}
_latestMessages[message.chatRoomId] = message;
_orderedMessages.remove(existingMessage);
_orderedMessages.add(message);
}
void printAll() => _orderedMessages.forEach(print);
}
void main() {
var messages = Messages();
messages.add(Message(
DateTime(2023, 1, 1),
chatRoomId: 'foo',
text: 'Hello foo!',
));
messages.add(Message(
DateTime(2023, 1, 2),
chatRoomId: 'bar',
text: 'Goodbye bar!',
));
messages.add(Message(
DateTime(2023, 1, 2),
chatRoomId: 'foo',
text: 'Goodbye foo!',
));
messages.add(Message(
DateTime(2023, 1, 1),
chatRoomId: 'bar',
text: 'Hello bar!',
));
messages.printAll();
}

Assign const list variable to a non const list variable and modify it

I have created a constant list variable and I want to put it to a new list variable and modify its items. But I get an error Unhandled Exception: Unsupported operation: Cannot remove from an unmodifiable list
const List<String> constantList = [
'apple',
'orange',
'banana'
];
List<String> newList = [];
newList= constantList;
newList.remove('banana');
The const'nes of an object is on the object and not the variable. So even if you change the type of the variable, the object is still going to be const.
One problem in your example:
List<String> newList = [];
newList= constantList;
This is not doing what you think it does. What it actually does is it creates a new empty list, and assings newList to point to this new list.
You are then changing newList to point at the list instance pointed at by constantList. So after this code is done, newList and constantList points to the same constant list object.
If you want to make a copy of the list refer to by constantList, you can do this:
void main() {
const List<String> constantList = ['apple', 'orange', 'banana'];
List<String> newList = constantList.toList();
// Alternative: List<String> newList = [...constantList];
newList.remove('banana');
print(newList); // [apple, orange]
}
Also, you can try with .addAll()
void main(List<String> args) {
const List<String> constantList = ['apple', 'orange', 'banana'];
List<String> newList = [];
newList.addAll(constantList);
newList.remove('banana');
print(newList); //[apple, orange]
}
This copy is not a const list and can therefore be manipulated.
More about pass-by-reference

Seeking a safe translation of a Python dict as a return type to Dart

Once in a while we all need to quickly return multiple values from a function, and look for a way to create a new type on the fly.
In Python I can return a tuple
def get_trio1():
return (True, 23, "no")
(_, first1, second1) = get_trio1()
print(first1)
print(second1)
ignore one of the values, and retrieve both of the other two on the fly in one assignment.
I can likewise return an array.
def get_trio2():
return [True, 23, "no"]
[_, first2, second2] = get_trio2()
print(first2)
print(second2)
But both of these are brittle. If I edit the code to add a value, particularly if it's within the three already defined, the revised code could fail silently.
Which is why the nicest solution is to create a dict on the fly.
def get_trio3():
return {"b": True, "i": 23, "s": "no"}
r = get_trio3()
print(r["i"])
print(r["s"])
The use of named members means that maintaining the code is considerably safer.
What is the closest I can do to get the same safety in Dart? Is defining a class for the return type necessary?
In case it matters, the context is avoiding List<List<dynamic>> when returning a future.
Future<List<dynamic>> loadAsset() async =>
return await Future.wait([
rootBundle.loadString('assets/file1.txt'),
rootBundle.loadString('assets/file2.txt'),
]);
Update
Using Stephen's answer for a future introduces a problem. Future.wait is hardwired to use an array Iterable.
Future<Map<String, dynamic>> loadAsset() async =>
return await Future.wait({
"first": rootBundle.loadString('assets/file1.txt'),
"second": rootBundle.loadString('assets/file2.txt'),
});
Your loadAsset function returns a Future<List<dynamic>> because that's how you declared it. You could have declared it to return a Future<List<String>> instead.
Future.wait is hardwired to use an array.
Especially since Dart is a statically-typed language, you can't really expect it to take both a List and some Map with your custom semantics. You could write your own version:
Future<Map<String, T>> myFutureWait<T>(Map<String, Future<T>> futuresMap) async {
var keys = futuresMap.keys.toList();
var values = futuresMap.values.toList();
var results = await Future.wait<T>(values);
return Map.fromIterables(keys, results);
}
Use a map.
Map<String, dynamic> foo() => {'A': 'a', 'B': 'b', 'C': false }
var result = foo();
print result['B']; // prints b;

How to find an object by a property from a List of List of objects in dart?

Hello I am new to dart and trying to find an item by property name in a list of list.
class Product{
String id;
String title;
Product(this.id,this.title);
}
void main(){
List<List<Product>> allProdcuts=[
//New Prodcuts
[
Product("1","Hammer"),
Product("3","Nails"),
Product("2","Screws"),
],
futureItems,
//Old Prodcuts
[
Product("4","Rock"),
Product("5","Paper"),
Product("6","Scissor"),
],
//Rare Items
[
Product("7","Plank"),
Product("8","Wires"),
Product("9","Box"),
],
];
print(allProdcuts.where((itemsList)=>itemsList.contains((product)=>product.title='Wires')));
//Returns ()
}
I have tried using for a single List:
List<Product> futureItems= [
Product("101","Galactic Hammer"),
Product("301","Galactic Nails"),
Product("201","Galactic Screws"),
];
print(newProduct.firstWhere((p)=>p.title=='Hammer'));
//Instance of 'Product'
Also tried this:
print(allProdcuts.map((itemList)=>itemList.firstWhere((p)=>p.title=='Nails')));
// Bad state: No elementError: Bad state: No element.
But there is an element with the title='Nails'.I don't understand what I am doing wrong.
You are calling itemList.firstWhere((p)=>p.title=='Nails') on each list, also the ones with no element with title "Nails". Since firstWhere throws if there is no matching value, it does that for two of your three lists. Also, in the example, itemsList.contains(...) does not take a callback, so you are just checking whether a function is in the list, which it isn't. You might want to use any for that, but it won't solve the problem here.
To do this efficiently, I'd probably create helper function:
Product findByTitle(List<List<Product>> allProducts, String title) {
for (var products in allProducts) {
for (var product in products) {
if (product.title == title) return product;
}
}
// Or return `null`.
throw ArgumentError.value(title, "title", "No element with that title");
}
The return in the middle allows you to skip out of the double iteration the moment you have a match, something which is harder to do with firstWhere/map/forEach etc.
One alternative solutions would be:
var product = allProducts.expand((l) => l.where((p) => p.title == title)).first;
which finds all the products with the given title and flattens them into a single iterable, then picks the first one (if there are any). Because iterables are lazy, it will actually stop at the first match.
There are many ways to solve this.
One example is to use the forEach() method:
allProdcuts.forEach(
(List<Product> l)=>l.forEach(
(Product p){
if (p.title=="Nails")
print(p.id);
}
)
);
The for each method receives a function and applies this function to every element on the list. If you have a lists of lists, you can do this twice to get a function applied to each element of the sub lists.
The above code prints 3, which is the desired result.
Another solution would be to flatten the list first, so you can have an easier search later.
print(allProdcuts.any((innerListOfProducts) =>
innerListOfProducts.any((product) => product.title == 'Wires')));
This code will return true if 'Wires' is in the inner list, and false otherwise.

Dart (Flutter) map<String, String>.map has no toList()

I have a a map structure and I want to make a DropdownMenuItem<String> for each entry. What I try to do is to call .map().toList() like this
var _languages = {
'en': 'English πŸ‡ΊπŸ‡Έ/πŸ‡¬πŸ‡§',
'fr': 'French πŸ‡«πŸ‡·',
'nl': 'Dutch',
'es': 'Spanish'
};
languages.map((key, value) => DropdownMenuItem(
value: key,
child: Text(v)
)).toList();
(if you are on a Windows, the US/GB is printed as text, if you are on any other platform, it will be flags). The problem now is twofold:
The return type 'DropdownMenuItem' isn't a 'MapEntry', as required by the closure's context.
The method 'toList' isn't defined for the type 'Map'.
How would I properly create a list out of a map? Is this not possible because a map is not an ordered collection?
Map<K, V>.map returns another Map, which isn't what you want.
You instead can create an Iterable from the Map first, and use Iterable.map, which returns another Iterable:
var menuItems = languages.entries
.map((mapEntry) =>
DropdownMenuItem(value: mapEntry.key, child: Text(mapEntry.value)))
.toList();
alternatively:
var menuItems = [
for (var mapEntry in languages.entries)
DropdownMenuItem(value: mapEntry.key, child: Text(mapEntry.value)),
];
The map method om Map is not the same as the map method on iterables (such as List). Instead of returning an Iterable, it returns a Map. As such, the inner method needs to return a MapEntry so the method can construct a new Map object to return. In order to convert this into a list, you need to convert the map itself into a list.
I'm assuming what you want to do is to take the entries in the map and map them to DropDownButton, where the language code is the button's value and the language text is the button's text. As such, you want to call map on _langages.entries, which gives you an Iterable of all the keys and values in the map. You can then call map on this iterable and it will do what you expect:
var _languages = {
'en': 'English πŸ‡ΊπŸ‡Έ/πŸ‡¬πŸ‡§',
'fr': 'French πŸ‡«πŸ‡·',
'nl': 'Dutch',
'es': 'Spanish'
};
languages.entries.map((entry) => DropdownMenuItem(
value: entry.key,
child: Text(entry.value),
)).toList();
You cannot call a .map directly in a scenario like yours as returned type is a Map<K, V>, means is a reply of the original but transformed (if you need to transform it). In your scenario you need another kind of map that is an Iterable<dynamic>. To have that you have to pass by .entries which gives you a Iterable<MapEntry<K, V>> and then call on that .map which gives you a Iterable<T> where the returned T can be even pizza :).
here a small piece of code that helps you understand the concept:
final Map<String, String> _languages = {
'en': 'English πŸ‡ΊπŸ‡Έ/πŸ‡¬πŸ‡§',
'fr': 'French πŸ‡«πŸ‡·',
'nl': 'Dutch',
'es': 'Spanish'
};
#override
Widget build(BuildContext context) {
return DropdownButton(
items: _languages.entries
.map((MapEntry element) => DropdownMenuItem(value: element.key, child: Text(element.value)))
.toList(),
onChanged: (value) {
/**/
},
);
}

Resources