Porting an App from Swift to Flutter. In my App, I have a class, MyClass, and am dealing with a list of about 250 instances. At various times, I need to group the objects based on a particular property.
In Swift, I was able to create a grouped list of my objects like so:
var groupedList = Dictionary<String, Array<MyClass>>()
I was then able to loop through my list of objects, and assign items to the right Array as necessary. I thought it might work to make a Map of Lists in Flutter like this:
Map groupedList = Map<String, List<MyClass>>();
Then I could loop through the items, test the property, create a Map entry for each unique value and append the item to the correct List:
for (var item in listOfObjects) {
if (!groupedList.containsKey(item.someproperty)) {
List<MyClass> sublist = [];
groupedList[item.someproperty] = sublist;
}
groupedList[item.someproperty].add(item);
}
What I get, however, is a Map with all the correct Keys, but each List contains only one instance of MyClass, rather than an actual List of MyClasses.
There's a more succinct syntax using putIfAbsent. This gives the results you expect:
void main() {
var groupedList = <String, List<MyClass>>{};
var listOfObjects = <MyClass>[
MyClass('Europe', 'France'),
MyClass('Europe', 'Germany'),
MyClass('Europe', 'Italy'),
MyClass('North America', 'USA'),
MyClass('Asia', 'Japan'),
MyClass('Asia', 'China'),
];
for (var item in listOfObjects) {
groupedList.putIfAbsent(item.someProperty, () => <MyClass>[]).add(item);
}
print(groupedList);
}
class MyClass {
String someProperty;
String someValue;
MyClass(this.someProperty, this.someValue);
#override
String toString() => '$someProperty->$someValue';
}
Related
I have the following list.
Public Class Car
{
int id;
string info;
}
List<Car> cars = {
new Car(1, "[{'color': 'blue','model': 'toyota'}]"),
new Car(2, "[{'color': 'red','model': 'tesla'}]"),
new Car(3, "[{'color': 'green','model': 'honda'}]")
}
I want to return the id(3) of the Honda.
Do I need to convert the whole list to a Json, then query for the specific item.
Or can I do something like
var chosenCar = cars.Where(s => JObject.Parse(s.info)["model"].Value == "honda");
I am trying to create a small app were the user can create flash cards. At first, I have them create the title, then all titles will be listed, once the user clicks on that title they'll be taken to a different screen where they can create the question and answer.
My Issue is that I created a List of type Map but can't figure out how to add and save to the lists that are created in the maps.
Model
class Cards {
//final List<String> question;
//final List<String> answer;
//final String title;
final String uid;
final List<Map<String, dynamic>> classes;
Cards({ this.uid, this.classes });
}
Home
This is where it starts, the elList.add only runs once when the user creates a title.
Cards indexData = snapshot.data;
List<Map<String, dynamic>> elList = [];
for (var i = 0; i < indexData.classes.length; i++) {
elList.add(indexData.classes[i]);
}
elList.add({
"title": title,
"question": [],
"answer": []
});
DatabaseService(uid: userId.uid).settingUserData(elList);
// Send the values to another screen where the user creates Q&A
MaterialPageRoute(
builder: (context) => ViewIndex(
questions: elList[index]["question"],
answers: elList[index]["answer"]
),
),
Service
This is where it gets saved to the db. My issue here is trying to figure out how to add on to the lists inside the map because this only adds to the list classes, which indexes all the maps. This runs when the title is created. Trying to figure out how to add to lists inside of map and save them? I tried something like: "classes[question]" and "classes.question", but none work.
Future settingUserData(List<Map<String, dynamic>> listCard) async {
return await _collref.document(uid).setData({
"classes": listCard
});
}
ViewIndex
This is where I receive them from the home file as params via the widget. This is where the user creates the questions and answers. Trying to add to them gives me an error, saying they are fixed-length. So here is where I also need to save them to the database, but as seen in my service file above, I don't know how to save these separately without having to create a whole new index to the list classes, which isn't what I want.
widget.answers.add("foo")
widget.questions.add("foo")
DatabaseService(uid: userId.uid).settingUserData();
I think the better solution for you is u define another class for your Classes as a model to become
class Cards {
//final List<String> question;
//final List<String> answer;
//final String title;
final String uid;
final List<YourClass> classes;
Cards(this.uid, this.classes);
}
class YourClass {
final Map<String, dynamic> yourObject;
YourClass(this.yourObject);
}
Then, initialized yourClass with the object you wanted, and assign it to your Cards.
List<YourClass> classes = List<YourClass>();
Map<String, dynamic> myItem = jsonDecode('{"hello": "dart","world": {"json": "blabla"}}');
// create your classItem
YourClass class1 = YourClass(myItem);
// add your classItem to a your classes list
classes.add(class1);
// add your classes to your card
Cards card = Cards('myUid', classes);
print(jsonEncode(card.classes[0].yourObject['hello']));
play around here: example
I can't believe I didn't notice this... But all I needed to do was to add the passed arguments from home to view index AND then move them to an array and loop through them
ViewIndex
List<dynamic> userAnswer = [];
List<dynamic> userQuestion = [];
for (var i = 0; i < widget.questions.length; i++) {
userQuestion.add(widget.questions[i]);
}
for (var i = 0; i < widget.answers.length; i++) {
userAnswer.add(widget.answers[i]);
}
DatabaseService(uid: userId.uid)
.updattingUserData(widget.index, userQuestion, userAnswer);
Service
Here I then used dot notation to read and write to that specific list in the maps. I also needed to pass the index to know, well, to know which one I was writing to. I made a separate function for this. The reason it was a fixed length was that I didn't have it in a list.
Future updattingUserData(int index, List<dynamic> question, List<dynamic> answer) async {
return await _collref.document(uid).updateData({
"classes.$index.questions": FieldValue.arrayUnion(question),
"classes.$index.answers": FieldValue.arrayUnion(answer)
});
}
Everything else is exactly the same as I have in my First post.
How to transform a List into a new List by excluding a property in T.
For instance if User data class has 10 properties, I need to transform List into a new List without one particular property in User . New List like List
data class User(val name: String, val age: Int)
var userList = mutableListOf<User>()
var nameList= userList.map { it.name }
If a List to be created without property 'age'. Like
var withoutAgeList
In your first example:
var userList = mutableListOf<User>()
var nameList= userList.map { it.name }
The question "What's the type of nameList?" has a simple answer: List<String>. So let me ask you a similar question: What's the type of withoutAgeList? The answer to that question informs the answer to your question.
Perhaps a user without the age property is a separate AgelessUser class, meaning withoutAgeList is of type List<AgelessUser>. In that case, I suggest either a constructor or a factory function that builds AgelessUser from User, and you want one of these:
val withoutAgeList = userList.map { AgelessUser(it) } // constructor
val withoutAgeList = userList.map { agelessUserOf(it) } // factory
Alternatively, maybe the age property in User is nullable and immutable, and you want to represent users without an age as a regular User where age=null. In this case, you could copy the Users and override the age field
// TODO: pass all the other fields too
val withoutAgeList = userList.map { User(it.name, null) }
Assuming Users is a data class, we can avoid explicitly naming all fields by making use of copy():
val withoutAgeList = userList.map { it.copy(age = null) }
Maybe the age property is nullable and mutable — and you actually want to change the users in place instead of copying them. This is somewhat risky and I don't advocate doing it this way unless you really know what you're doing though.
userList.forEach { it.age = null }
// They're actually the same list!
val withoutAgeList = userList
In such a simple case you can map a list of Users into a list of strings:
val names: List<String> = userList.map(User::name)
Or you can declare a DTO and map into the latter:
class UserWithoutAge(val name: String)
val usersWithoutAge: List<UserWithoutAge> = userList.map { UserWithoutAge(it.name) }
P.S. you don't have to write an explicit type
You can use the Object Oriented approach:
data class User(val name: String, val age: Int)
data class UserNoAge(var name: String) {
constructor(user: User) : this(user.name)
}
var userList = listOf(User("John", 25), User("Jane", 30))
var userNoAge: List<UserNoAge> = mutableListOf<UserNoAge>()
userNoAge = userList.map{ UserNoAge(it) }
println(userNoAge) // [UserNoAge(name=John), UserNoAge(name=Jane)]
Hi guys looking for some basic advice.
I have four models: BoardViewModel, List, Card, Member
var Member = function (id, name, avatar) {
var self = this;
self.id = id;
self.name = name;
self.avatar = avatar;
self.isChecked = ko.observable(false);
};
I am instantiating members property inside BoardViewModel. But I want to use a copy of this model inside each Card model to instantiate a list of assigned members.
Each card stores comma separated list of member references like
",1,2,4,5"
I am writing a loop to BoardViewModel.members and mark members as checked if id references match bore I assign it as Card.members.
The last piece of the puzzle I am missing is reference to the BoardViwModel.members.
I have a lovely example fiddler that would somewhat help to build a picture of what I am talking about.
Just bear in mind that once I have this working properly I want to replace view() binding
foreach: $root.members
with
foreach: members
If at all possible I would like to avoid passing BoardViewModel.members as parameter into List and then into Card.
Update 1
As suggested by #Jeroen here's a simplified version of my fiddler.
The top view() model which encompases a concept of lists:
var BoardViewModel = function (lists, members) {
var self = this;
// in reality members are fetched via ajax call to the server
// and instantiate as a ko.observableArray()
self.groupMembers = ko.observableArray(members);
self.lists = ko.observableArray(lists);
...
}
In reality this has a signature like this:
var boardViewModel = function (initialData)
moving on.
The child List model which encompases a concept of cards:
var List = function (id, name, cards, sortOrder, groupMembers) {
var self = this;
self.cards = ko.observableArray(cards);
...
}
in reality:
var list = function (item, groupMembers)
nothing special there really.
The child Card model which encompases the concept of card items (but lets not go there yet):
var Card = function (id, title, description, contentItemId, members, groupMembers) {
var self = this;
self.members = ko.observableArray(parseMembers(members));
// now remember each card has a members property
// which stores comma separated list ",1,4"
function (members) {
var memberList = groupMembers;
var memberRefList = members.split(',');
ko.utils.arrayForEach(memberList, function(member){
ko.utils.arrayForEach(memberRefList, function(memberId){
if(member.id === meberId) {
member.isChecked(true);
}
});
});
return memberList;
}
...
}
in reality:
var card = function (item, groupMembers)
nothing too fancy there either.
I currently have something like this working on my dev environment.
Problem:
Those with keen eyes probably noticed the way I was passing groupMembers all the way up. I am not particularly hyped about the idea.
Anyone know a better way of implementing this? i.e. why can't I just do something like
var memberList = self.parent.parent.groupMembers;
for instance.
As per me, the better way to do is to have the child viewmodels inside the parent view-model. like this where you can access the parent data members as well as methods directly.
ViewModel
var BoardViewModel = function(){
var self = this;
this.members = ko.observableArray();
this.lists = ko.observableArray();
// Child View Models
this.Member = function(member){
this.id = member.id;
this.name = member.name;
this.avatar = member.avatar;
this.isChecked = ko.observable(false);
}
this.List = function(list){
// same for this
};
this.Card = function(card){
// same for this
};
// a Method to bind the data with the observables and arrays
// Assuming data is a json object having Members, List objects
this.applyData = function(data){
self.members(jQuery.map(data.Members, function(item){
return new self.Member(item);
}));
self.lists(jQuery.map(data.Lists, function(item){
return new self.List(item);
}));
}
}
onDom ready
// json object holding your data
var data = {
"Members" : [
],
"Lists" : [
],
"Cards" : [
]
};
var vm = new BoardViewModel();
$(function(){
ko.applyBindings(vm, document.getElementById('myModule'));
vm.applyData(data);
});
I am searching for a method to intersect my array collections.
I have one collection: allItems and another subSet. I want to create another ArrayCollection where all items which do not exist in subSet will be stored. Is there a way to do this?
working answer provided by eemeli ... here is an alternative implementation optimized for speed (array access instead of calls) and scalability (approach provides O(m+n) instead of O(m*n))...
public static function difference(a:ArrayCollection, b:ArrayCollection):ArrayCollection {
var entry:*, map:Dictionary = new Dictionary(), intersection:Array = [];
for each (entry in a.source) map[entry] = entry;
for each (entry in b.source) delete map[entry];
for each (entry in map) intersection.push(entry);
return new ArrayCollection(intersection);
}
For getting a collection of items not in another you need a set difference algorithm (allItems minus subSet).
public function minus(a:ArrayCollection, b:ArrayCollection):ArrayCollection {
var result:ArrayCollection = new ArrayCollection()
for each (i in a) {
if (!b.contains(i)) {
result.addItem(i)
}
}
return result
}
var allLength:Number = allItems.length;
var intersection:ArrayCollection = new ArrayCollection();
for(var i:Number = 0; i < allLength; i++)
if(subSet.getItemIndex(allItems.getItemAt(i)) == -1)
intersection.addItem(allItems.getItemAt(i));
Note that this will work only if the subset contains the same objects as the super set. If the subset contains different objects with the same property values as of the super set object, you are gonna have to compare their properties separately.