Create parent entity and children and redirect request in fluent vapor - fluent

I´m trying to create an entity Task and one children using the same request object
func create(_ req: Request) throws -> Future<Response> {
return try req.content.decode(Task.TaskForm.self).flatMap { taskForm in
let user = try req.requireAuthenticated(User.self)
let task = Task(name: taskForm.name, userId: user.id!)
return task.save(on: req).map { t in
let interval = try Interval(taskId: t.requireID())
let t = interval.save(on: req)
return t.save(on: req).map { _ in
return req.redirect(to: "/dashboard")
}
}
}
}
The error that I'm getting is this one:
Cannot convert return expression of type 'EventLoopFuture' to return type 'Response'.
Any ideas on what's the problem?

This code should work
func create(_ req: Request) throws -> Future<Response> {
return try req.content.decode(Task.TaskForm.self).flatMap { taskForm in
let user = try req.requireAuthenticated(User.self)
let task = Task(name: taskForm.name, userId: user.id!)
return task.create(on: req).flatMap { t in
let interval = try Interval(taskId: t.requireID())
return interval.create(on: req).flatMap { _ in
return t.create(on: req).transform(to: req.redirect(to: "/dashboard"))
// or your previous variant
// return t.create(on: req).map { _ in
// return req.redirect(to: "/dashboard")
// }
}
}
}
}
There are a few things you could learn
use map when you have to return non-future result
use flatMap you have to return Future<> result
use create instead of save when you are creating object in the db
don't leave future calls without handling them like you do on line #7

Related

How do you call an Async Throws function that connects to a remote API with nested JSON properties?

To preface, I am self taught and I know there are tutorials and similar questions on how to do something like this but with the small contextual differences something just isn't clicking. So any help on a solution or a tutorial more closely related to what I am trying to do would be very helpful.
I am trying to access a remote API but am struggling with the call function to execute the Async Throws function. I am trying to get the information from the JSON to be stored in a way that I can access it later for a calculation but have no idea what to put in the Task.
Minimal Reproducible Example-
Code:
// Struct Declarations
struct Response: Decodable {
let data: [StockValues]
}
struct StockValues: Decodable {
let high: Decimal
let low: Decimal
let close: Decimal
let volume: Decimal
}
// Async Throws Function
class dataFetcher {
static func Fetch() async throws -> [StockValues] {
guard let url = URL(string: "http://api.marketstack.com/v1/eod/latest")
else {
throw APIError.invalidServerResponse
}
var request = URLRequest(url: url)
let (data, _) = try await URLSession.shared.data(from: url)
let stockValues = try JSONDecoder().decode(Response.self, from: data)
return stockValues.data
}
}
// Call Async Throws Function
func CallFunction() {
Task {
let fetchedInfo = try await dataFetcher.Fetch()
Response.data = fetchedInfo // Error: Instance member 'data' cannot be used on type 'Response'; did you mean to use a value of this type instead?
}
}
This code assumes that you have a key for the API and that your Response is correct
//Your error refers to
//`Response` is a type it can't hold a value so `Response.data` is unacceptable
struct Response: Decodable {
let data: [StockValues]
}
actor MarketStackService {
//What you are attempting to do is something like this setup
//Something global that anything can access. Using `static` to hold a response is not approriate.
static func fetchLatest() async throws -> [StockValues] {
//Add API key
guard let url = URL(string: "https://api.marketstack.com/v1/eod/latest")
else {
throw APIError.invalidURL
}
let (data, _) = try await URLSession.shared.data(from: url)
let stockValues = try JSONDecoder().decode(Response.self, from: data)
return stockValues.data
}
}
enum APIError: LocalizedError{
case invalidURL
case error(Error)
var errorDescription: String?{
switch self {
case .error(let error):
return error.localizedDescription
default:
let string = String(describing: self)
return NSLocalizedString(string, comment: "AppError")
}
}
}
There are many ways of setting this is up but this is a basic class that works well with SwiftUI. It can hold the value from the API call and trigger changes to the body
#MainActor
class MarketStackViewModel: ObservableObject{
//Variables are lowercased
//A variable can hold a value. That is why you need a `class` or `struct`
#Published var data: [StockValues] = []
#Published var alert: (isPresented: Bool, error: APIError?) = (false, nil)
//`func` are lowercased
func getLatest() async {
do{
let fetchedInfo = try await MarketStackService.fetchLatest()
data = fetchedInfo
}catch{
alert = (true, .error(error))
}
}
}
The View is where the value would be displayed and where the call can be triggered.
//`class` and `struct` are uppercased
struct MarketStackView: View {
#StateObject var vm: MarketStackViewModel = .init()
var body: some View {
VStack{
if vm.data.isEmpty{
Text("Hello, World!")
.task {
//Runs the request as soon as the View displays.
await vm.getLatest()
}
}else{
Text(vm.data.description)
//This is another way of making the call
Button("Get latest") {
Task{
await vm.getLatest()
}
}
}
}.alert(isPresented: $vm.alert.isPresented, error: vm.alert.error) {
Button("Ok") {
vm.alert = (false, nil)
}
}
}
}
struct MarketStackView_Previews: PreviewProvider {
static var previews: some View {
MarketStackView()
}
}
If you paste this code into a .swift file it should work and you can see the flow. I suggest you try the Apple SwiftUI Tutorials. They might help clear some concepts.

The transition from "fetching" to "displaying" JSON API data Swift Node.js

I understand how to "fetch" data from a JSON API (my local server, in fact), but how should I think about the pipeline from merely having the data to displaying it in views? What I intuitively want to do is "return" the data from the fetching function, though I know that's not the paradigm that the Swift URL functions operate with. My thought is that if I can "return" the data (as a struct) it will be easy to pass into a view for visualization.
Sample Code:
This is the structure of the fetched JSON and the kind of variable I want to pass into views.
struct User: Codable {
let userID: String
let userName: String
let firstName: String
let lastName: String
let fullName: String
}
My hope is that the printUser function can return instead of print a successful fetch.
func printUser() {
fetchUser { (res) in
switch res {
case .success(let user):
print(user.userName) // return here?
// I know it won't work, but that's what seems natural
case .failure(let err):
print("Failed to fetch user: ", err)
}
}
}
func fetchUser(completion: #escaping (Result<User, Error>) -> ()) {
let urlString = "http://localhost:4001/user"
guard let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, resp, err) in
if let err = err {
completion(.failure(err))
return
}
do {
let user = try JSONDecoder().decode(User.self, from: data!)
completion(.success(user))
} catch let jsonError {
completion(.failure(jsonError))
}
}.resume()
}
Sample view that would take a user struct
struct DisplayUser: View {
var user: User
var body: some View {
Text(user.userID)
Text(user.userName)
Text(user.lastName)
Text(user.firstName)
Text(user.fullName)
}
}
The reason that you can't just "return" is that your fetchUser is asynchronous. That means that it might return relatively instantaneously or it may take a long time (or not finish at all). So, your program needs to be prepared to deal with that eventuality. Sure, it would be "be easy to pass into a view for visualization" as you put it, but unfortunately, it just doesn't fit the reality of the situation.
What you can do (in your example) is set the User as an Optional -- that way, if it hasn't been set, you can display some sort of loading view and if it has been set (ie your async function has returned a value), you can display it. That would look something like this:
class ViewModel : ObservableObject {
#Published var user : User? //could optionally store the entire Result here
func runFetch() {
fetchUser { (res) in
switch res {
case .success(let user):
DispatchQueue.main.async {
self.user = user
}
case .failure(let err):
print("Failed to fetch user: ", err)
//set an error message here? Another #Published variable?
}
}
}
func fetchUser(completion: #escaping (Result<User, Error>) -> ()) {
//...
}
}
struct DisplayUser: View {
#StateObject private var viewModel = ViewModel()
var body: some View {
VStack {
if let user = viewModel.user {
Text(user.userID)
Text(user.userName)
Text(user.lastName)
Text(user.firstName)
Text(user.fullName)
} else {
Text("Loading...")
}
}.onAppear {
viewModel.fetchUser()
}
}
}
Note: I'd probably refactor the async stuff to use Combine if this were my program, but it's a personal preference issue

Swift detect json parsing detect end

I am parsing a certain json url data to plot in a map and I need to detect that I have all the data to show a spinner while nothing is happening. I have created a variable that goes from false to true after I have all the data but that variable only exists as true inside the for loop
This is part of the code that gets the data
import SwiftUI
import MapKit
var locationsFillTest : Int = 0
var allLocations = [MKPointAnnotation]()
var doneGettingData : Bool = false
struct MapView: UIViewRepresentable {
var startdate : String
func makeUIView(context: Context) -> MKMapView{
MKMapView(frame: .zero)
}
func makeCoordinator() -> MapViewCoordinator{
MapViewCoordinator(self)
}
func updateUIView(_ uiView: MKMapView, context: Context){
uiView.removeAnnotations(allLocations)
allLocations = []
doneGettingData = false
print("Done = \(doneGettingData)")
let url = URL(string: "https://XXXXXX")!
URLSession.shared.dataTask(with: url) {(data,response,error) in
do {
if let d = data {
let decodedLists = try JSONDecoder().decode(emsc.self, from: d)
DispatchQueue.main.async {
locationsFillTest = allLocations.count
doneGettingData = false
for locations in decodedLists.features {
let lat = Double(locations.properties.lat)
let long = Double(locations.properties.lon)
let annotation = MKPointAnnotation()
annotation.coordinate = CLLocationCoordinate2D(latitude: lat , longitude: long )
if locationsFillTest == 0 {
allLocations.append(annotation)}
}
uiView.addAnnotations(allLocations)
uiView.delegate = context.coordinator
uiView.showAnnotations(allLocations, animated: true)
doneGettingData = true
print("Done = \(doneGettingData)")
}
}else {
print("No Data")
}
} catch {
print("Error decoding JSON: ", error, response!)
}
}.resume()
}
}
The variable doneGettingData becomes false and true by watching the print but if I need to use it for example to create a spinner its false all the time since its only true inside.
How can I make it global ?
Thank you
Unless you have another declaration for doneGettingData inside the closure the instance level property is getting set to true. It may be getting set later than you expect though. Try the following to see when it changes (and to get you setup to react to those changes):
var doneGettingData : Bool = false {
didSet {
if doneGettingData {
print("The instance property doneGettingData is now true.")
} else {
print("The instance property doneGettingData is now false.")
}
}
}
You may want to make this into a custom enum though with cases along the lines of fetching, done, noData, and jsonError. Right now if there is no data you will never have a trigger to either retry, move on, notify the user, etc. The same applies when there is a decoding error. Or at the very least set the flag to true at the very end of the loop so something happens no matter what.
Something like:
enum DataCollectionState {
case fetching, done, noData, jsonError
var doneGettingData : DataCollectionState = fetching {
didSet {
switch doneGettingData {
case fetching:
// Show a spinner or something
case done:
// Hide the spinner
case noData:
// Tell the user there was no data? Try again?
case jsonError:
// Tell the user there was an error? Try again?
}
}
}
Note: I don't have Xcode open right now so syntax may not be exact.

Swift Global Variables

I'm a beginner at Swift so let me know if this doesn't quite make sense, but i have a JSON file that i can access in swift and parse into an array, from there i can get a string from the array and store it in a var. I want to be able to access this variable globally but i'm not sure how to do it.
With the help of another user "rmaddy". I have this code:
struct Games: Decodable {
let videoLink: String
}
class BroadService {
static let sharedInstance = BroadService()
func fetchBroadcasts(completion: #escaping ([Games]?) -> ()) {
let jsonUrlString = "LINK IS HERE."
guard let url = URL(string: jsonUrlString) else {
completion(nil)
return
}
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {
completion(nil)
return
}
do {
let games = try JSONDecoder().decode([Games].self, from: data)
completion(games)
} catch let jsonErr {
print("Error deserializing json:", jsonErr)
completion(nil)
}
}.resume()
}
}
I can then access it in another class from here:
BroadService.sharedInstance.fetchBroadcasts { (games) in
if let games = games {
let game = games[indexPath]
let videoLink = game.videoLink
}
I want to be able to access the contents of "videoLink" globally, without having to use "BroadService.sharedInstance.fetchBroadcasts { (games) in" how would i go about doing this
You shouldn't use global variables, I don't think that's recommended in any language.
Now here you have what looks like a Singleton class (BroadService), that's good because it's a nice solution for what you're looking for.
Next all you need to do is add a property to that class. Let's say videoLink is a string, you can add a string property to BroadService, for example storedVideoLink as an optional String, and the next time you need to obtain that value after you have already fetched it, you can access it like so: BroadService.sharedInstance.storedVideoLink.
One more thing, to have BroadService work properly as a singleton, you should make its init private.
To sum up, here's what I'm suggesting:
class BroadService {
static let sharedInstance = BroadService()
var storedVideoLink: String?
private init() {} // to ensure only this class can init itself
func fetchBroadcasts(completion: #escaping ([Games]?) -> ()) {
// your code here
}
}
// somewhere else in your code:
BroadService.sharedInstance.fetchBroadcasts { (games) in
if let games = games {
let game = games[indexPath]
let videoLink = game.videoLink
BroadService.sharedInstance.storedVideoLink = videoLink
}
}
// now you can access it from anywhere as
// BroadService.sharedInstance.storedVideoLink
This way it all stays cohesive in the same class. You can even add a getter method for storedVideoLink so you don't have to access it directly, and in this method you could state that if the string is nil then you fetch the data, store the link to the string, and then return the string.
You could create a file with a struct called something like Global and create a static var and set that inside your completion block once you have fetched the games.
Here is an example.
struct Global {
static var games:[Any]? = nil
static func setGames(games:[Any]) {
Global.games = games
}
}
Then you fetch the data once upon load of the app or somewhere before you use the Global and set that property:
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else {
completion(nil)
return
}
do {
let games = try JSONDecoder().decode([Games].self, from: data)
Global.setGames(games: games)
completion(games)
} catch let jsonErr {
print("Error deserializing json:", jsonErr)
completion(nil)
}
}.resume()
Please note that this will make the Global.games accessible from everywhere but it will also not be a constant so you should be careful not to override it.
This way Global.games will be accessible from anywhere.

Handle Alamofire asynchronous request using SwiftyJSON

I am trying to parse JSON data using SwiftyJSON into an array to use in my TableView. However even though I can successfully request the data and parse it into an array, I cannot return it from the getObjects function as it is done asynchronously. I have tried to use a completion handler, and after following several tutorials it seems I am missing something.
Does anybody know how I can return the array to use in my TableViewController ?
Table View Controller
let objects = [Objects]()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let urlString = "URLSTRING"
objects = dataManager.getObjects(urlString)
print("objects in view controller products array \(objects.count)")
self.tableView.reloadData
}
Request Functions
class DataManager {
func requestObjects(_ stringUrl: String, success:#escaping (JSON) -> Void, failure:#escaping (Error) -> Void) {
print("Request Data")
Alamofire.request(stringUrl, method: .get).validate().responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
print("responce success")
success(json)
case .failure(let error):
print(error)
} //End of switch statement
} //End of alamofire request
} //End of request function
func getObjects(_ urlString:String) -> [Object] {
var objects = [Object]()
requestObjects(urlString, success: { (JSONResponse) -> Void in
let json = JSONResponse
for item in json["items"] {
let title = item.1["title"].string
objects.append(Object(title: title!))
}
print("Number of objects = \(objects.count)")
}) {
(error) -> Void in
print(error)
}
print(objects) // Prints empty array
return objects // Array is empty
}
}
You need to use completionHandler to return data to TableViewController.
func getObjects(completionHandler : #escaping ([Object]) -> (),_ urlString:String) -> [Sneaker] {
var objects = [Object]()
requestObjects(urlString, success: { (JSONResponse) -> Void in
let json = JSONResponse
for item in json["items"] {
let title = item.1["title"].string
objects.append(Object(title: title!))
}
completionHandler(objects)
print("Number of objects = \(objects.count)")
}) {
(error) -> Void in
print(error)
}
print(objects) // Prints empty array
return objects // Array is empty
}
}
In your TableViewController
dataManager.getObject(completionHandler: { list in
self.objects = list
}, urlString)
There could be some syntax error i didnt test it