adwaita-swift
adwaita-swift copied to clipboard
Support inherited signals
Is your feature request related to a problem? Please describe.
I would like to use the changed signal with the EntryRow widget.
Describe the solution you'd like
Most widgets seem to only support some of the available signals available to them. It would be nice to add support for more signals that are inherited from their ancestors and interface implementations. For my specific use case I would like to use the changed signal on the Adw.EntryRow that comes from GtkEditable. On closer inspection, seems like there are even more signals available provided by GtkEditable such as insert-text and delete-text which also could be useful.
I am not sure how much work is required to add support for more signals especially for all the widgets but something to consider going forwards!
Thank you again for all your work on this wonderful library ;)
PS1: I tried the newly added custom CSS support and is working great!
PS2: I am still relatively new to Swift, but eventually I would like to help out by contributing to your library. Maybe when we have some simple good first issue type of requests I will dive in ;)
Describe alternatives you've considered
I tried experimenting with the currently available signals for EntryRow such as onSubmit and the more generic onUpdate but unfortunately none of them provide the desired behaviour.
Basically I would like to be able to respond to changes to the entry field as the user is interacting with the widget such as inserting or deleting text.
Additional context
I am not sure if it is related in any way, but my goal is to call an async function when the signal is emitted. As mentioned above, I tried experimenting with the onUpdate signal and while I had some success the program eventually crashes. From my debugging, I believe the onUpdate signal is called is much more rapidly than I actually need it to and combining it with async leads to all kinds of issues.
Thanks for opening the request. Cool that you're interested in contributing!
The support for inherited signals is definitely something sensible to add. I'll work on this.
In your specific case, you can also work with the text binding instead of explicitly calling the signal as the text is updated when the changed signal is emitted. The most elegant way might be using binding's onSet:
struct TestView: View {
@State private var text = ""
var view: Body {
EntryRow("Title", text: $text.onSet { print($0) })
}
}
About the async problem: If I understand you correctly, you're updating the UI from an asynchronous context. For this, you should wrap the call updating the UI (e.g. an assignment to a state variable) with Idle:
Task {
// Something asynchronous
let result = try await someAsyncFunction()
Idle {
// Update the UI
self.result = result
}
}
Hello David thank you very much for the detailed response.
I was not aware of the onSet functionality. After adjusting my entry rows to use that callback I was able to achieve the desired effect including the async part, so now all is working great!
I guess this onSet function should work with any widget that uses a binding of some form so it might be a good idea to make it a bit easier to discover. The current documentation does not mention this callback (in the widget pages), and you must specifically search about bindings to find it.
One easy way might be to add an example in the Demo app. Maybe a more general SignalDemo by showing how we can react to various widget events.
I will be happy to help out with this so if you think it makes sense just let me know!
Thank you again for your work and support.
I was able to achieve the desired effect including the async part, so now all is working great!
Did you use Idle for this? It seems that often, it works also without it, but then it crashes sometimes randomly, so I would really wrap it with Idle.
I will be happy to help out with this so if you think it makes sense just let me know!
I think in general some articles should be added to the docs as well, covering useful shortcuts such as onSet, how to correctly update from asynchronous contexts, etc. A page in the demo app would also be cool!
Hello David,
Did you use Idle for this? It seems that often, it works also without it, but then it crashes sometimes randomly, so I would really wrap it with Idle.
I tried both approaches, and although I could not actually replicate any of the previous crashes I did end up using Idle just to be safe. I did some reading up on the main GTK docs about the main event loop, and using Idle seems like a reasonable thing to do.
I think in general some articles should be added to the docs as well, covering useful shortcuts such as onSet, how to correctly update from asynchronous contexts, etc. A page in the demo app would also be cool!
OK cool, I will see what I can do! I will work on this and ping you in a PR.
I have another question regarding onSet. I am not sure if it is a limitation of the current implementation or if I am doing something wrong but seems like @Binding variables do not get updated in the context of the onSet function. Is that the case or?
I will give a brief example to help you understand.
- I have a view B with an
EntryFieldthat uses thetext binding onSet - The view has a
@Bindingfor a bool, let us just call itisValid - The
isValidvariable is set as@Stateunder a different view A and is set to use a folder for storing its state - The application preferences allow updating the value of
isValidthrough a toggle
Now, when I load view B as a debug method I am printing the value of isValid and that is correct based on the JSON value from the state folder param. Next, If open the preferences dialog and update its value, upon exiting (preferences window) the view B correctly updates the value automatically.
The problem is the EntryField's text on set:
View A
@State("isValid", forceUpdates:True)
private var isValid = false
.....
on button press load View B and pass isValid
View B
struct TestView: View {
@Binding
var isValid
@State
private var text = ""
var view: Body {
Text(String(isValid)) --> correctly displays the value even after preferences window updates it
EntryRow("Title", text: $text.onSet { print(String(isValid)) }) --> displays the initial value correctly but updating via preferences still displays the old value
}
}
Seems like some sort of window refresh is needed for the new value to get picked up. Again as a debug test, I added a sort option to move widgets around, doing so refreshes the window after which if I try the EntryField the correct value is displayed.
Sorry for the long and convoluted example, but it is a bit difficult to explain.
Essentially, what I want to know is if there is a way to update @Binding variables within the onSet function or maybe a way to force refresh a window or something.
Thank you for your help in advance!
Very strange problem! Thanks for reporting!
seems like
@Bindingvariables do not get updated in the context of the onSet function.
I'm not able to reproduce this using a simple example. It should work because the onSet closure is reloaded in each update. The example I tried (I modified the counter demo):
private struct CountButton: View {
@Binding var count: Int
@State private var test = ""
var icon: Icon.DefaultIcon
var action: (inout Int) -> Void
var view: Body {
Button(icon: .default(icon: icon)) {
action(&count)
}
.circular()
EntryRow("Test", text: $test.onSet { _ in print(count) })
}
}
when I load view B as a debug method
Unfortunately, I don't understand what you mean by loading a view. This might be important for solving the problem, as it did work for me in a "normal" context.
Hello David.
Thank you very much for following up.
seems like
@Bindingvariables do not get updated in the context of the onSet function.I'm not able to reproduce this using a simple example. It should work because the onSet closure is reloaded in each update.
Indeed, you are correct. After some more testing I verified that @Binding variables do get updated in the onSet closure but unfortunately my issue still persists. I will need to do some more testing, but I believe it might have something to do with the number of times the property is being passed to child views. I will try to offer some more context just in case you see something that might be the cause.
- The data used in the
onSetclosure is part of a complex objectStructso I am accessing it as follows: $complex.isValid - The data is initialised in View A and is passed to multiple children (View A(init)->View B ->View C->View D(EntryField)) before reaching the view that includes the
EntryField. - Testing results with EntryField's
onSetupdate- Updating and displaying a
@Statevariable both simple and complex(struct) declared in View D --> WORKS - Updating and displaying a
@Bindingvariable both simple and complex(struct) declared in View D --> WORKS - Updating and displaying a
@Statevariable both simple and complex(struct) declared in View C passed as a@Bindingin View D --> WORKS - Recreating the problematic complex object in View C and passing that fresh copy to View D as a
@Binding--> WORKS- so instead of A->B->C->D is just C->D using the exact same object struct
- Recreating the problematic complex object in View B and passing that fresh copy to View D as a
@Binding--> WORKS- so instead of A->B->C->D is just B->C->D using the exact same object struct
- Passing complex object from A to D (4 times) -> DOES NOT WORK
- Updating and displaying a
- One more thing that I noticed is that unless you explicitly use
forceUpdates: trueon@Stateproperties that are deeply shared (maybe >3 times?) fail to update in all places not only inonSetclosures.
when I load view B as a debug method
Unfortunately, I don't understand what you mean by loading a view. This might be important for solving the problem, as it did work for me in a "normal" context.
Apologies for the confusion, what I meant by load view B is basically passing the property to a child view. In our example above we just used 2 views but in my actual app as described here is passing the property 4 times.
I will try to recreate the issue in a sample app of sorts and maybe provide a small demo so that we can look into it.
Thank you once more for your time!
This issue has moved to https://git.aparoksha.dev/aparoksha/adwaita-swift/issues/33