I'm trying to setup some kind of global counter in my app.
What the best way of doing this?
I have the following code that works but it's damn ugly, I'd like to increment when the var is accessed so I don't have to call the function.
I tried to use get and set but it's not possible with property wrappers.
class GlobalDisplayInfo: ObservableObject {
#Published var nextAvailableInt : Int = 0
func getNextAvailableInt() -> Int {
nextAvailableInt += 1
return self.nextAvailableInt
}
}
What you want to achieve is not possible as it is against how Published works.
Let's say you access the published var in two places:
When the first one accesses the var, it would increment the var which would require the second place where it uses this var to refresh (access to this var again) which would require the var to increment again which would require the other place which uses this var to access and it just goes on - I think you got the point.
Your problem basically sounds rather like a design problem and I suggest you review it. Also, I think it'd be better if you just tell us what you want to achieve (without using any coding language, just a plain explanation of what you want) then an answer might come up.
It is not clear the usage, but looks like you need the following
class GlobalDisplayInfo: ObservableObject {
private var _nextAvailableInt = 0
var nextAvailableInt : Int {
_nextAvailableInt += 1
return _nextAvailableInt
}
}
Related
I'm following a video on the Firebase YouTube channel. Starting around 27:45, the instructor is trying to set a variable based on a Boolean and ends up with the following code in init(task: Task):
$task
.map { task in
task.isCompleted ? "checkmark.circle.fill" : "circle"
}
.assign(to: \.completionStateIconName, on: self)
.store(in: &cancellables)
This seems overly convoluted to me. First, I can't find documentation on using .map on a struct object, only on arrays, etc. Second, what is with this &cancellables thing? (It's defined as private var cancellables = Set<AnyCancellable>() before the init{}.) Third, why all this code, and not simply:
task.completionStateIconName = task.isCompleted ? "checkmark.circle.fill" : "circle"
This seems to give the same result, but will there be something down the line that the first code fragment works, but the second doesn't?
$task (with the $ prefix) is a projected value of the #Published property wrapper, and it returns a variable of the type Published.Publisher. In other words, its a Combine publisher, which publishes a value whenever the property - in this case Task - changes.
If you didn't learn about the Combine framework (or other reactive frameworks), this answer is definitely not going to be enough. At a high-level, a Combine publisher emits values, which you can transform through operators like .map, and eventually subscribe to, for example with .sink or .assign.
So, line-by-line:
// a publisher of Task values
$task
// next, transform Task into a String using its isCompleted property
.map { task in
task.isCompleted ? "circle.fill" : "circle"
}
// subscribe, by assigning the String value to the completionStateIconName prop
.assign(to: \.completionStateIconName, on: self)
Now, the above returns an instance of AnyCancellable, which you need to retain while you want to receive the values. So you either need to store it directly as a property, or use .store to add it to a Set<AnyCancellable> - a common approach.
So, why is it so convoluted? This is, presumably, built so that if task property ever changes, the Combine pipeline would update the completionStateIconName property.
If you just did:
completionStateIconName = task.isCompleted ? "circle.fill" : "circle"
that would assign the value just in the beginning.
That being said, in this particular case it might actually be unnecessarily too convoluted to use Combine, whereas just using didSet:
var task: Task {
didSet {
completionStateIconName ? task.isCompleted ? "circle.fill" : "circle"
}
}
I'm developing a screen in SwiftUI and have the following code:
...
#EnvironmentObject var externalState: MainStateObject
...
SelectOptionPopover(options: $externalState.depots,
selectedOption: selectedDepot,
prompt: "Depot: ")
...
SelectOptionPopover is a view that I created to handle a variety of popovers. For the options, it expects an array of [SelectOptionPopoverOption], which is declared like this:
protocol SelectOptionPopoverOption {
var displayName: String { get }
}
Now, the issue I have is that when I pass an array of SelectOptionPopoverOptions, it works just fine. But if I pass an array of another type that conforms to SelectOptionPopoverOptions, the conversion fails with something like:
'Binding<[Depot]>' is not convertible to 'Binding<[SelectOptionPopoverOption]>'
These may be the exact same objects, but work when they're identified as SelectOptionPopoverOptions but not when identified as a Depots.
I can work around this by using arrays of SelectedOptionPopoverOption and casting them as needed, but it would sure be cleaner to be able to use the conforming types instead.
Any ideas on how I could use the more specific types instead?
You can declare and adopt your custom SelectOptionPopover view as generics
struct SelectOptionPopover<T>: View where T: SelectOptionPopoverOption {
#Binding var options: [T]
...
As the question states, is there any downside in referencing the service directly in the template as such :
[disabled]="stateService.selectedClient == null || stateService.currentStep == 1"
In my opinion this doesn't seem like good practice and I'd much rather keep a "selectedClient" object in whatever component needs to use it. How can I get the state and store it into local variables, while observing the changes:
example: I want to move from step1 to step2 by changing "currentStep" in the "stateService", however I want the component that keeps "currentStep" ALSO as a local variable to reflect the change in the state?
Is it good practice to reference services in html templates in Angular
2?
I'd generally avoid it. It seems to bring more chaos than good.
Cons:
Coming from OOP background, this approach looks like it breaks the Law of Demeter, but more importantly,
It's no longer MVC, where your controller (Angular2's Component) acts like a mediator between the view and the services.
Like Ced said, what if a call to a service's member is costly and we need to refer to it multiple times in the view?
At the moment my editor of choice (VS Code) does not fully support Angular2 templates; referencing too many things outside of its own Component's scope in a template makes refactoring not fun anymore.
Pros:
Sometimes it looks more elegant (because it saves you 2 lines of code), but trust me, it's not.
How can I get the state and store it into local variables, while
observing the changes
Madhu Ranjan has a good answer to this. I'll just try to make it more complete here for your particular example:
In your StateService, define:
currentStep : Subject<number> = new Subject<number>();
selectedClient: Subject<Client> = new Subject<Client>();
changeStep(nextStep: number){
this.currentStep.next(nextStep);
}
selectClient(client: Client) {
this.selectedClient.next(client);
}
In your Component:
currentStep: number;
constructor(stateService : StateService){
stateService.currentStep.combineLatest(
stateService.selectedClient,
(currStep, client) => {
if (client == null) {
// I'm assuming you are not showing any step here, replace it with your logic
return -1;
}
return currStep;
})
.subscribe(val => {
this.currentStep = val;
});
}
You may try below,
stateService
currentStep : Subject<number> = new Subject<number>();
somestepChangeMethod(){
this.currentStep.next(<set step here to depending on your logic>);
}
component
// use this in template
currentStep: number;
constructor(stateService : stateServiceClass){
stateService.currentStep.subscribe(val => {
this.currentStep = val;
});
}
Hope this helps!!
It is probably not a good idea to expose your subject inside of your state service. Something like this would be better.
StateService
private currentStep: Subject<number> = new Subject<number>();
changeStep(value: number) {
this.currentStep.next(value);
}
get theCurrentStep(): Observable<number> {
this.currentStep.asObservable();
}
Component
currentStep: number;
constructor(private stateService: StateService) {
this.currentStep = this.stateService.theCurrentStep;
}
Template
[disabled]="(currentStep | async) == 1" // Not sure if this part would work
I'm struggling to get my Script to run an IF function. Basically I want to run a script based on specific cell contents.
I would like an IF function to run based on this and have written the following code:
function sendemail () {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var s = SpreadsheetApp.getActiveSheet();
var targetSheet = ss.getSheetByName("Response");
var vCodes = ss.getSheetByName("Codes")
var vResults = targetSheet.getRange("E2").getValues();
var emailAddresses = targetSheet.getRange("B2").getValues()
var dataRange = vCodes.getRange(1, 1, vResults, 1).getValues();
var subject = "Here are your Wi-Fi Codes!";
var vLength = vCodes.getRange("C2").getValues();
if (vLength == "24 hours"){
MailApp.sendEmail(emailAddresses, subject, dataRange);
targetSheet.deleteRows(2);
vCodes.deleteRows(1,vResults);
}
}
If the value in C2 is "24 hours" I'd like it to send an e-mail. At the moment when I run the script there are no errors but it doesn't send any e-mail as the IF function obviously isn't running correctly.
If I edit the code to say:
if (vLength == "")
then the e-mail sends. It doesn't seem to recognise "24 hours" as valid data to look up.
Can anyone see what I'm doing wrong?
The value you get from the cell is not what you think because you are using getValues() with an 's' and you probably know that this method always returns an array of arrays, even when a single cell is defined as range.
You have 2 options :
use getValue() to get the string content of the cell
use getValues()[0][0] to get the first (and only) element of this array.
I would suggest the first solution as I think it's generally a good idea to use appropriate methods... getValue() for single cell and getValues() for multiple cells...
I didn't check further but I'm pretty sure it will work with this change (applies to vResults , emailAddresses and vLength) .
It would also be careful to ensure that vResults is a number since you use it to define a range... you could use Number(vResults) as a safety measure.
My problem is how to ensure that no data will be lost while concurrent access.
I have script published as web-app. I want to add new row to DATA_SHEET. The function that handles submit button looks like this:
function onButtonSubmit(e) {
var app = UiApp.getActiveApplication();
var lock = LockService.getPublicLock();
while (! lock.tryLock(1000))
;
var ssheet = SpreadsheetApp.openById(SHEET_ID);
var sheet = ssheet.getSheetByName(DATA_SHEET);
var lastRow = sheet.getLastRow();
var lastCol = sheet.getLastColumn();
var rangeToInsert = sheet.getRange(lastRow+1, 1, 1, lastCol);
var statText = rangeToInsert.getA1Notation();
rangeToInsert.setValues(<some data from webapp form>);
app.getElementById('statusLabel').setText(statText);
lock.releaseLock();
return app;
}
But it seems that this does not work. When I open two forms and click submit button within one second, it shows same range in statsLabel and writes data into same range. So I lose data from one form.
What is wrong with this code? It seems like tryLock() does not block script.
Is there any other way how to prevent concurrent write access to sheet?
It might be worth taking a look at appendRow(), rather than using getLastRow()/setValues() etc.
Allows for atomic appending of a row to a spreadsheet; can be used
safely even when multiple instances of the script are running at the
same time. Previously, one would have to call getLastRow(), then write
to that row. But if two invocations of the script were running at the
same time, they might both read the same value for getLastRow(), and
then overwrite each other's values.
while (! lock.tryLock(1000))
;
seems a bit hinky. Try this instead:
if (lock.tryLock(30000)) {
// I got the lock! Wo000t!!!11 Do whatever I was going to do!
} else {
// I couldn’t get the lock, now for plan B :(
GmailApp.sendEmail(“admin#example.com”, “epic fail”,
“lock acquisition fail!”);
}
http://googleappsdeveloper.blogspot.com/2011/10/concurrency-and-google-apps-script.html
You must insert this code when using getLastRow()/setValues() with lock.
SpreadsheetApp.flush();
// before
lock.releaseLock();