How to call async function with .onChange in SwiftUI? - asynchronous

In SwiftUI, I am trying to call an async function(which is for loading images that is picked by ImagePicker) by using .onChange. I got "'async' call in a function that does not support concurrency" error. If I make function sync and remove "Task {#MainActor in" section, then it runs but I get some purple errors during loading images which I do not want.
Is it possible to use .onChange to call async function?
func loadImage() async {
Task { #MainActor in
guard let inputImage = inputImage else {
loadingState = .failed
return
}
jpegData = inputImage.jpegData(compressionQuality: 0.8)!
image = Image(uiImage: inputImage)
loadingState = .loaded
}
}
I try to call the function as below;
.onChange (of: addPersonViewModel.inputImage, perform: { _ in addPersonViewModel.loadImage() })
If I make function sync and remove "Task {#MainActor in" section, then it runs but I get some purple errors during loading images which I do not want.

Instead of
.onChange (of: addPersonViewModel.inputImage, perform: { _ in addPersonViewModel.loadImage() })
Use
.task(id:addPersonViewModel.inputImage){
await addPersonViewModel.loadImage() }
You will have to make a slight modification to your function
func loadImage() async {
guard let inputImage = inputImage else {
loadingState = .failed
return
}
jpegData = inputImage.jpegData(compressionQuality: 0.8)!
image = Image(uiImage: inputImage)
loadingState = .loaded
}
That floating Task inside a function is not gods practice.

Related

Completion handler for Firebase Realtime DB lookup when one function needs the value of a previous function

I have the following code that fetches a schedule
func fetchSchedule(completion: #escaping () -> ()) {
scheduleRef.queryOrderedByValue().queryEqual(toValue: true).observe(.value, with: { snapshot in
self.schedule = []
if snapshot.value is NSNull {
// Null
} else {
for child in snapshot.children {
if let snapshot = child as? DataSnapshot,
let schedule = Schedule(snapshot: snapshot) {
self.schedule.append(schedule)
}
}
}
})
}
The above get the current schedule but what I am unclear on is that i need that value to then call the next function call which get the associated games for that schedule on the .onAppear() of the view in SwiftUI
func getGames() {
scheduleStore.fetchSchedule()
//
gameStore.fetchGames(weekId: self.scheduleStore.schedule[0].weekId)
}
the gameStore.fetchGames always returns null, likely because it has not finished processing the fetchSchedule function?
How do I ensure the first function finishes before it calls the fetchGames?
You have a completion handler built into your function signature on fetchSchedule, but you aren't using it.
func fetchSchedule(completion: #escaping () -> ()) {
scheduleRef.queryOrderedByValue().queryEqual(toValue: true).observe(.value, with: { snapshot in
self.schedule = []
if snapshot.value is NSNull {
// Null
} else {
for child in snapshot.children {
if let snapshot = child as? DataSnapshot,
let schedule = Schedule(snapshot: snapshot) {
self.schedule.append(schedule)
}
}
completion() //<-- Here
}
})
}
Then,
func getGames() {
scheduleStore.fetchSchedule(completion: {
gameStore.fetchGames(weekId: self.scheduleStore.schedule[0].weekId)
})
}
You're not showing all of your code, but you may also have something broken between self.schedule, which you set in fetchSchedule, and self.scheduleStore, you you send to fetchGames -- make sure you've only got one place you're storing data -- should it be self.schedule in both places?
Update, based on comments
This code is approximate, since I don't have access to your types, but it should get you started:
func fetchSchedule(completion: #escaping ([Schedule]) -> ()) {
scheduleRef.queryOrderedByValue().queryEqual(toValue: true).observe(.value, with: { snapshot in
if snapshot.value is NSNull {
// Null
} else {
let schedules = snapshot.children.compactMap { child in
if let snapshot = child as? DataSnapshot, let schedule = Schedule(snapshot: snapshot) {
return schedule
}
return nil
}
completion(schedules)
}
})
}
func getGames() {
scheduleStore.fetchSchedule { schedules in
gameStore.fetchGames(weekId: schedules[0].weekId)
}
}

perform network call and proceed - asynchronous task

i just started learning Swift a year ago, so please be patient with me :)
i am downloading JSON data with a network call, and as soon as i successfully received those rows, i then continue to clear the rows inside my coreData entity, and rewrite those new rows into coredata..
i am having a hard time understanding this asynchronous procedure..
what i've learned is that i have to use completion handlers, but i still can't use it the way i need to.. especialy when i need to proceed after those 3 steps were executed..
First call from button action:
#IBAction func updateButtonPressed(_ sender: Any) {
self.myCoreData.update() {(success) in // calls my update method
print(success!)
textField.text = success! // not possible bc not in the Mainthread
}
textField.text = "blabla" // gets executed before the result is available
methods:
func update(completion: #escaping (String?) -> Void) { //parent method which calls sub methods
var returnValue = ""
Step1getJson {_ in. // step 1
self.Step2Delete { // step 2
self.Step3Save { // step 3
returnValue = "return Value: \(self.step1Result)"
completion(returnValue)
}
}
}
}
func Step1getJson(completion: #escaping (Bool) -> ()) {
var success = false
if let url = URL(string: "https:foo") {
URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else { return }
do {
let parsedJSON = try JSONDecoder().decode([RemoteWire].self, from: data)
print("-- Successfully received \(parsedJSON.count) datarows ")
self.JSON = parsedJSON
self.step1Result = "-- Successfully received \(parsedJSON.count) datarows "
success = true
} catch {
print(error)
}
completion(success)
}.resume()
}
}
func Step2Delete(completion: () -> Void) {
...delete entity rows
completion()
}
func Step3Save(completion: () -> Void) {
.. save new JSON rows to coreData
completion()
}
Everything is working fine that far, and step 2 and step 3 get successfully called when network download has finished..
but how can i proceed after those steps were executed inside my updateButtonPressed function?
if i try to write those results into any UI element inside my completion block, a textField or whatever, i get an error that this has to happen in the main thread, and if i execute it outside the completion block those lines get executed far too early, when no results are available yet.
i feel like i have understanding problem with this, i hope you guys can help me out and guide me in the right direction.
As swift allows any changes or updates in UI element only from main thread, you need to call the main thread to update the UI.
Replace the below code
#IBAction func updateButtonPressed(_ sender: Any) {
self.myCoreData.update() {(success) in // calls my update method
print(success!)
textField.text = success! // not possible bc not in the Mainthread
}
}
with the new code
#IBAction func updateButtonPressed(_ sender: Any) {
self.myCoreData.update() {(success) in // calls my update method
print(success!)
DispatchQueue.main.async {
textField.text = success! // Now possible because it is in main thread
}
}
}

Unexpected result when using Sinon onCall method

I am experiencing an unexpected result :"should get data and has multiple recordSets" test case. My intention is to have the "stub.getRefundItems" method call return one set of results on the first call and a second set of results on the second call. The test works as expected if there are no other tests that use that method. However when I add in the additional test "should get data and has 1 recordSet" I get a single result (LineItemsHasNextFalse) no matter what I do. I should also mention the stub gets injected via awilix as a singleton. Am I wiring the stub up incorrectly? Is it not possible to change the results using onCall after you have set the returns value?
let stub = {
getRefund: sandbox.stub(),
getRefunds: sandbox.stub(),
getRefundItems: sandbox.stub(),
getRefundTransactions: sandbox.stub()
};
var sandbox = require('sinon').createSandbox();
describe('#refunds', function() {
beforeEach(function () {
sandbox.spy(logger)
})
afterEach(function () {
// completely restore all fakes created through the sandbox
sandbox.restore();
})
context('getRefundItems', function() {
it('should get data and has 1 recordSet', test(async function() {
stub.getRefundItems.returns(LineItemsHasNextFalse)
let resp = await refunds.getRefundItems({"gid":"shjdjs"})
expect(resp).to.deep.equal(LineItemsHasNextFalse.data.refund.refundLineItems.edges)
}))
it('should get data and has multiple recordSets', test(async function() {
let correctResp = new Array()
correctResp.push(...LineItemsHasNextTrue.data.refund.refundLineItems.edges)
correctResp.push(...LineItemsHasNextFalse.data.refund.refundLineItems.edges)
stub.getRefundItems.onCall(0).returns(LineItemsHasNextTrue)
stub.getRefundItems.onCall(1).returns(LineItemsHasNextFalse)
let resp = await refunds.getRefundItems({"gid":"shjdjs"})
console.log(resp)
expect(resp).to.deep.equal(correctResp)
}))
})
})
async getRefundItems(msgObj)
{
let hasNext = false
let items = []
let count = 0
do
{
let response = await stub.getRefundItems(msgObj)
if(response.data.refund != null)
{
items.push(...response.data.refund.refundLineItems.edges)
if(response.data.refund.refundLineItems.pageInfo.hasNextPage)
{
let cursor = response.data.refund.refundLineItems.edges[0].cursor
msgObj.after = cursor
hasNext = true
}
else
{
hasNext=false
}
}
}
while(hasNext)
return items
}

correct way to fire out a fail result in page reponse event listener in codeceptJS

I wrote a helper methods to add a network response listener over Puppeteer page instance. the code looks like this
let Helper = codecept_helper;
class CheckHelper extends Helper {
async listenRequest(listener)
{
const helper = this.helpers['Puppeteer'];
await helper.page.setRequestInterception(true);
helper.page.on("request",listener);
return helper._waitForAction();
}
async listenResponse(listener)
{
const helper = this.helpers['Puppeteer'];
helper.page.on("response",listener);
return helper._waitForAction();
}
}
module.exports = CheckHelper;
then in the test script
let self=this;
I.listenResponse((response)=>{
if(response.url().match(/github.*\.js/) && response.headers()['content-length']>1000) {
//codeceptjs.event.emit(codeceptjs.event.test.failed, self, 'js file is too big!');
//codeceptjs.recorder.throw('js file is too big!')
//codeceptjs.recorder.stop();
//throw new Error('js file is too big!')
}
})
I.amOnPage("https://www.github.com");
i first add response listener, then i goto "github", when some js file size is too big,i will throw out an error,in order too check content size is correctly.
however, even i throw error out (like the comments codes did), the main test flow just not stop, how do i do is the right way?
well,i found a solution later
i recorded all the page response into a custom object in the page instance.
later i wrote a help methods to check whole records.
//in helper.js
startRecordResponse() {
const helper = this.helpers['Puppeteer'];
helper.page.on("response", (res) => {
//record all response instance into savedResponse object inside page, we'll use it later
helper.page.savedResponse = helper.page.savedResponse || {};
helper.page.savedResponse[res.url()] = res;
});
return helper._waitForAction();
}
checkFileIsTooBig(filter, sizeLimit) {
const helper = this.helpers['Puppeteer'];
//use the data recorded in savedResponse object
Object.keys(helper.page.savedResponse).forEach((url) => {
var res = helper.page.savedResponse[url];
if (((filter instanceof RegExp && filter.test(url)) || (typeof filter == "string" && url.indexOf(filter) != -1)) && res.headers()['content-length'] > sizeLimit) {
throw new Error(`file ${url} is too big,${res.headers()['content-length']} > ${sizeLimit}`)
}
})
return helper._waitForAction();
}
then in test file
Before((I) => {
I.startRecordResponse();
I.amOnPage("https://www.github.com");
});
Scenario('github_test', (I) => {
//check a js file contain github is less than 100 bytes
I.checkFileIsTooBig(/github.*\.js/,100);
}
);

DART async/await not waiting

So I've been working on experimenting with DART (whereby my core languages are C++, and embedded C derivates). Hence my code is probably not pretty as I'm more of a procedural programmer, but I'm getting by and learning... I've been struggling around Futures pertaining to await sync, and basically, I simply can't get DART to WAIT. The following code establishes a socket connection to a small embedded device and extracts info. That all works, but notice the order of operations SHOULD be main() gets some info from the console, then should call the method cardStatus to run off and get the info from the embedded device via the socket connection. This is where the await should occur. When the Future is returned, it should go off to the printstuff() method. I've added print statements that should go in order and read:
This should print 1st
This should print 2nd
This should print 3rd
Instead since the wait is not occurring on the cardstatus call (which is time consuming), I get:
This should print 1st
This should print 3rd
This should print 2nd
I've followed another thread on the use of async, and seem to be at least following one solid way of using this Other thread (I tried a .then with a completer with a similar result, so there is something core I feel I'm missing).. but I've been stuck on this for a week.
Code below, along with the console output.
import 'dart:io';
import 'dart:async' show Future;
const String STATUS = "#111111;";
String defaultIP = "10.1.14.202";
int defaultConfigPort = 5111;
int defaultControlPort = 6722;
var card = new Map();
getInput(String defaults) {
String takenin = stdin.readLineSync();
if (takenin == '') takenin = defaults;
return takenin;
}
Future main() async {
stdout.write('What is the IP address of the card ($defaultIP): ');
String ipaddress = getInput(defaultIP);
defaultIP = ipaddress;
print ("This should print 1st");
stdout.writeln("Connecting to $defaultIP");
await cardStatus(defaultIP, defaultConfigPort, STATUS, card);
printstuff();
}
printstuff() {
stdout.writeln(card['subnet']);
print ("This should print 3rd");
}
Future cardStatus(String ip, int port, String message, Map card) {
return new Future.delayed(Duration.ZERO, () {
Socket.connect(ip, port).then((socket) {
print('Connected to: '
'${socket.remoteAddress.address}:${socket.remotePort}');
socket.listen((data) {
print(new String.fromCharCodes(data).trim());
List str1 = (new String.fromCharCodes(data).trim().split(','));
print(str1);
print ("This should print 2nd");
//var card = new Map();
card['ip'] = str1[0];
card['subnet'] = str1[1];
card['gateway'] = str1[2];
card['unknown'] = str1[3];
card['persist'] = str1[4] == 'true';
card['build'] = str1[5];
card['serial'] = str1[6].substring(0, 14);
card['cloudpassword'] = str1[6].substring(14, 20);
card['DNS'] = str1[7];
card['cloudhost'] = str1[8];
card['cloudenabled'] = str1[9] == 'true';
print(card['ip']);
},
onDone: () {
print("Done");
socket.destroy();
});
//Send the request
socket.write(message);
});
});
}
and this is the current console output. notice the null shouldn't be a null if the cardStatus would have completed it would be printed str1.
What is the IP address of the card (10.1.14.202):
This should print 1st
Connecting to 10.1.14.202
null
This should print 3rd
Connected to: 10.1.14.202:5111
>10.1.14.202,255.255.255.0,10.1.14.1,,0,435,F44900A60040F8000000,192.168.1.1,connect.tutuuu.com,0;
[>10.1.14.202, 255.255.255.0, 10.1.14.1, , 0, 435, F44900A60040F8000000, 192.168.1.1, connect.tutuuu.com, 0;]
This should print 2nd
10.1.14.202
Done
Process finished with exit code 0
Thanks for all the help!
You are missing return before Socket.connect. As it stands now your code just starts connecting but never awaits it through future. I would highly recommend using as much as possible the new await / async syntax.
Here is a running example that does get google homepage:
import 'dart:io';
import 'dart:async' show Future;
Future main() async {
print("This should print 1st");
await cardStatus('www.google.com', 80, 'GET /\nHTTP 1.1\n\n');
printstuff();
}
printstuff() {
print("This should print 3rd");
}
Future cardStatus(String ip, int port, String message) {
return new Future.delayed(Duration.ZERO, () {
return Socket.connect(ip, port).then((socket) {
print('Connected to: '
'${socket.remoteAddress.address}:${socket.remotePort}');
socket.listen((data) {
List str1 = (new String.fromCharCodes(data).trim().split(','));
print(str1.first);
print("This should print 2nd");
}, onDone: () {
print("Done");
socket.destroy();
}, onError: (e) {
print("Error while listening: $e");
});
socket.write(message);
});
});
}
Below slightly redacted version that uses awaits, and try / catch to handle errors:
import 'dart:io';
import 'dart:async' show Future;
Future main() async {
print("This should print 1st");
await cardStatus('www.google.com', 80, 'GET /\nHTTP 1.1\n\n');
print("This should print 3rd");
}
Future<String> cardStatus(String ip, int port, String message) async {
var socket = await Socket.connect(ip, port);
print('Connected to: '
'${socket.remoteAddress.address}:${socket.remotePort}');
socket.write(message);
print("Sent request");
try {
var response = await socket.fold(
'',
(String acc, List<int> data) =>
acc + new String.fromCharCodes(data).trim());
print("Received response: ${response.substring(0, 10)}");
return response;
} finally {
socket.close();
}
}
I know it was answered but the question is great and I struggled myself with the concept so here is another element that got me to understand. in the dartpad (https://dartpad.dartlang.org/) try this (comments are in the code):
import 'dart:async';
//Just creating a duration to use later
Duration duration = new Duration(milliseconds: 500);
void main() {
//This is what tricked me, printStuff is async so running in parallel processing by default
//There is no need to call the function in a certain way (like a go xxx for a goroutine)
//there is an await in the function so it will wait inside the function only
//i.e. printStuff('a') starts then printStuff('b') starts straight away...already in prallel processing
//Run it and check the output
printStuff('a');
printStuff('b');
//Basically the await is in the function so printStuff is still returning a Future
//i.e. printStuff('a') starts but doesn't wait to complete to start printStuff('b')
}
Future<void> printStuff(String id) async {
for(int i = 0; i <= 5; ++i) {
//this await is waiting for the command to complete to move to the next iteration...
//the i iterations are done one after the other
await new Future.delayed(duration, () {
print(id + i.toString());
});
}
}
Then try this:
import 'dart:async';
Duration duration = new Duration(milliseconds: 500);
//becuase I use await in main now, I must make it return a future and be async
Future main() async {
//to make it happen one after the other, you need await at a call function level
await printStuff('a');
await printStuff('b');
//Basically this says complete printStuff('a'), then start printStuff('b')...
//and yes technically one doesn't need the second await becuase there is nothing after
}
Future<void> printStuff(String id) async {
for(int i = 0; i <= 5; ++i) {
await new Future.delayed(duration, () {
print(id + i.toString());
});
}
}
So my personal misunderstanding was that an async function is called in parallel straight away and an await in a function waits for real but in the function itself, with no impact on other parallel processing happening.

Resources