Use iteration helpers like .map(), .where(), etc in an async manner? - dart

Short: toList() executes before makeMaker causing the markers to have null objects.
Long: In Firestore, I have table and game collections and inside of table, there is a game field(type=reference). With a StreamBuilder, I can access tables. And I iterate through tables and try to fill in their game fields with real data by using get as seen in below;
if (snapshot.hasData) {
tabledocs = snapshot.data.documents;
markers = tabledocs
.map((tabledoc) {
DocumentReference gameref = tabledoc.data['game'];
//game field is a reference type field which points to another collection
gameref.get().then((gdoc) {
tabledoc.data['game'] = gdoc;
Marker marker = makeMarker(tabledoc); //<--------Executes later
return marker;
});
}).toList(); //<--------Executes first
}
Because gameref.get().then() takes time, the toList() at the bottom executes before each marker is generated and added in to markers.
If there are 3 markers returned from Firestore, our markers is an array of 3 null markers. I think makeMarker(..) did not execute yet most probably.
Is there a way for the map method to wait for the gets to finish and then initialize markers array with non-null values? Or can you show me another way to accomplish what I want.

You can either use
await for(var tabledoc of tabledocs) {
}
or if it is not necessary that the items are executed in order (the result will be in the order of the original items though)
var markers = await Future.wait(tabledocs
.map((tabledoc) {
DocumentReference gameref = tabledoc.data['game'];
//game field is a reference type field which points to another collection
var gdoc = await gameref.get();
tabledoc.data['game'] = gdoc;
Marker marker = makeMarker(tabledoc);
return marker;
});

Related

AmCharts4: getPolygonById method for custom map from GeoJSON?

I am attempting to use the getPolygonById method with a map polygon series created using custom GeoJSON data (this is in order to zoom to a polygon with a specific ID). I have followed the instructions provided on creating custom maps.
The Map Polygon Series from the custom GeoJSON data renders and events function correctly (eg to zoom to a polygon or color change on "hit").
However, when calling customSeries.getPolygonById("ExampleID"), the method returns "undefined". Note that the GeoJSON source for customSeries includes the corresponding id field (ie "id": "ExampleID").
That is, despite rendering correctly, the following code returns "undefined":
var customSeries = map.series.push(new am4maps.MapPolygonSeries());
customSeries.geodataSource.url = "/geojson/customSeries.geojson";
customSeries.useGeodata = true;
console.log(customSeries.getPolygonById("ExampleID"));
This contrasts with the return of the JSON object with specified Map Polygon when using an Amcharts map template. For example, the following code returns an object corresponding to the Map Polygon with "US" id:
var worldLow = map.series.push(new am4maps.MapPolygonSeries();
worldLow.useGeodata = true;
worldLow.geodata = am4geodata_worldLow;
console.log(worldLow.getPolygonById("US"));
Is this an issue with my data? Or is something additional required to call getPolygonById on a Map Polygon Series from custom GeoJSON?
Loading data through the DataSource / GeoDataSource object is done asynchronously, so your getPolygonById call is executing before the data has been parsed and loaded into the series. You can either use the done event in the DataSource or datavalidated event in the series to check for whether the data has been loaded before calling getPolygonById
polygonSeries.geodataSource.events.on('done', function() {
// timeout needed as the data isn't complately loaded at this point
setTimeout(function() {
console.log('done: ', polygonSeries.getPolygonById('BCH'))
}, 100)
})
// OR
polygonSeries.events.on('datavalidated', function() {
// check if there's data loaded in the array before looking up
// the polygon
if (polygonSeries.data.length) {
console.log('data loaded', polygonSeries.getPolygonById('BCH'))
}
})
Demo

LazyColumn and mutable list - how to update?

I'm new to Jetpack Compose and I've spent some hours to find how to make a LazyColumn update what I update my list. I've read that it needs to be a immutable list to update LazyColumn, but I can't seem to get it to work.
The code looks like:
#Composable
fun CreateList() {
var myList : List<DailyItem> by remember { mutableStateOf(listOf())}
myList = getDailyItemList() // Returns a List<DailyItem> with latest values and uses mutable list internally
// Function to refresh the list
val onUpdateClick = {
// Do something that updates the list
...
// Get the updated list to trigger a recompose
myList = getDailyItemList()
}
// Create the lazy column
...
}
I have tried several things and either is the list never updated when tapping the update button or only the first item is updated but not the rest of the items in the list. I looked in the documentation and there it says this, but I don't understand it:
Instead of using non-observable mutable objects, we recommend you use
an observable data holder such as State<List> and the immutable
listOf().
How to update the list so the LazyColumn is updated?
Use SnapshotStateList, the list is mutable. Any modification (add, remove, clear, ...) to the list will trigger an update in LazyColumn.
Similar to mutableListOf() (for MutableList) there is mutableStateListOf() to create a SnapshotStateList.
Extention function swapList() just combines clear() and addAll() calls to replace old list with new list.
fun <T> SnapshotStateList<T>.swapList(newList: List<T>){
clear()
addAll(newList)
}
#Composable
fun CreateList() {
val myList = remember { mutableStateListOf<DailyItem>() }
myList.swapList(getDailyItemList()) // Returns a List<DailyItem> with latest values and uses mutable list internally
// Function to refresh the list
val onUpdateClick = {
// Do something that updates the list
...
// Get the updated list to trigger a recompose
myList.swapList(getDailyItemList())
}
// Create the lazy column
...
}
See the basic idea is to get compose treat the list as state. Now that, you are able to achieve using mutableStateOf(initialValue),
Okay, the process is this,.
We create a variable, initialising it as a mutable state of something
Then we assign that variable to the lazy column. It is not necessary to assign it to the items parameter of the column, but that is our use case here. Otherwise, inside the Composable containing the lazy column, you could just type the name of the variable and even that will work since all we want, is compose to get a message that this variable is being read by the Composable.
Back to the question,
We create a variable, say val mList: List<Int> by remember { mutableStateOf (listOf()) }
Lazycolumn{
items(items = mList){
Text(it)
}
}
Button(onClick = { mList = mList + listOf(mList.size())})
Clicking the button adds a new number to the list, which is reflected in the LazyColumn's UI.

Compare the two lists and change the value of the properties of the objects in one of the lists

I have the two lists of AddItem objects. AddItem is a custom object made of data downloaded from the web. The second one list is also AddItem, but this one is saved in the database. I use it to create another list, but in this case user of the app decides which object are important for him.
This want I want to achieve is to mark every single object of the first AddItem list (not saved in the database, create during the start of the view), to show in the TableView which one is saved in the database, so I already use him in another view. You know what I mean. There is a TableView list and if I am interested in a cell I select it and add it to the database.
I hope I have described it clearly. If not, ask for questions.
The first AddItem list (not saved in the database):
func setAddItemList(stations: [Station], sensors: [Sensor]) {
var addItems = [AddItem]()
var sensorItems = [SensorItem]()
let falseValue = RealmOptional<Bool>(false)
addList = try persistenceService.fetchAddItems().toArray(ofType: AddItem.self) //The second list with saved data in the database
let addItem = stations.map { station in
AddItem(
id: station.id,
stationId: station.id,
cityName: station.city?.name ?? "",
addressStreet: station.addressStreet!,
added: falseValue,
sensor: [])
}
addItems.append(contentsOf: addItem)
As you can see, it's create by already downloaded data. I decided to add the property - added, which is the bool property and mark it as true if selected the right cell. Unfortunately I don't know how to do this when creating a list of AddItem objects. The saved array is almost the same. There is only more data, but ids, names, addresses and so on are same, so there are loads of the same data for comparison
I made the solution myself:
addItem.forEach { item in
guard let index = addList2.firstIndex(where: { $0.id == item.id})
else {
print("Failed to find the SavedAddItem for the AddItem \(item.id)")
return
}
addItems[index + 1].added = trueValue
}

Computed property not updating with mutating function values

I seem to have a very perplexing Swift problem.
I have a struct called "Graph" which has a number of mutating functions that change its stored properties. The struct's initializer reads from a text file and saves the data in various stored properties.
struct Graph {
var dists: [[Int]]
.
.
.
func tourValue() -> Int {
return dists.count
}
mutating func swapHeuristic() {
dists = [[0], [1]]
}
mutating func twoOpt() {
dists = [[1]]
}
.
.
.
}
I also have a function makeFile() which creates a text file for the graph to read from.
The main issues lie with my ContentView.swift file, where I create and use instances of Graph.
ContentView.swift
import SwiftUI
struct ContentView: View {
var nodes: Int
var graph: Graph { // Graph object is initialized and stored perfectly
set {
makeFile(nodes: nodes, selection: .Euclidean) // this works perfectly
}
get {
Graph(flag: -1, filename: "\(nodes)nodesEUCLIDEAN.txt") // this works perfectly
}
}
var originalTour: Double {
graph.tourValue() // saves the original tour value perfectly
}
var swapValue: Double {
set {
graph.swapHeuristic() // does not change Graph's dists property like it should
}
get {
return graph.tourValue() // as a result of dists not changing, the tourValue is also unchanged
}
}
var body: some View {
Text("Hello, World!")
}
}
Thus, the initialized graph instance never has its property changed, and I can't derive values from it.
How do I solve this?
Your graph variable is computed, so unless its mutated form is reflected somewhere else (other objects/properties, a file, etc) the mutation is inevitably lost.
When you set swapValue = 0, first the getter for var graph is called, creating a new Graph instance. Then that instance executes swapHeuristic(). Since that function mutates the value, it triggers the setter for var graph. The serious red flag here is that your setter ignores newValue. newValue in the setter is at that point the only piece of your code which has the mutated form of your graph. But it does nothing with that value, so when the setter finishes it's simply deallocated.
The question is already answered, but in attempt to be helpful, let me come at the same point from a slightly different direction.
The line
var graph: Graph { // Graph object is initialized and stored perfectly
is a form of self delusion. There is no Graph object that is initialized and stored. This is, as you've been told, a computed property. So consider it in full:
var graph: Graph { // Graph object is initialized and stored perfectly
set {
makeFile(nodes: nodes, selection: .Euclidean) // this works perfectly
}
get {
Graph(flag: -1, filename: "\(nodes)nodesEUCLIDEAN.txt") // this works perfectly
}
}
What we see in the get part (the getter) is not the retrieval of a stored Graph. It is the creation of a new graph. Every single time we ask for graph, a completely new and different Graph is created at that moment and passed out to the caller.
Thus there is no existing object to mutate. The line graph.swapHeuristic() causes a completely new Graph to come into existence, calls the method, and throws that Graph away.

How to convert Firebase result to List in SwiftUI

I wanted to make a list in swiftui from firebase data. So here's my UI
I've already make a data for that username "mike_one", and it's perfectly working,
this is my xcode result
{
IC2ol5bimucKu2d89u2YBz0Bqot2 = {
name = mike;
photo = "https://firebasestorage.googleapis.com/v0/b/instagram-ios-1ed09.appspot.com/o/photo%2Fa2.jpg?alt=media&token=9b6c58f1-eedc-4190-bc63-3f3325c84d77";
username = "mike_one";
};}
And this is my database
So what i'm asking right now is, How to make the result of the database to be a model? so I can use it as a list.
Please help me.
I appreciate your answer!
Strictly speaking, it would not be possible to convert textual firebase data to a List object. A SwiftUI List is defined as
A container that presents rows of data arranged in a single column
in other words it's a UI element, not a data element.
That being said a List is often backed by an Array or other data storage element. So you'll read Firebase data and store that in an array. Then that array backs your UI List object.
In this case, I would suggest creating a UserClass to hold the Firebase data
class UserClass {
var name = ""
var photoUrl = ""
var username = ""
}
and then array to store your users in
var userArray = [UserClass]()
then as data is read from Firebase, create the user objects and populate the array. Your Firebase code wasn't included so in brief
firebase.observe.... { snapshot in
let user = UserClass()
...populate user properites from the snapshot
self.userArray.append(user)
}
Then in your view, access the array elements to show them in the List object
struct ContentView: View {
var body: some View {
List(userArray) { user in
//do something with each user object
// like Text(user.username)
}
}
}

Resources