Comments (8)
Ah, that's the cool part of the new Anvil actually.
Of course TextWatcher is the ugliest listener in the whole Android SDK: there is no way to tell if it's already added to the view or not, you can not call setText() from inside it etc etc.
It required me to make a lot of kludges to make it work in the old Anvil.
In the new Anvil I still provide a TextWatcher setter, which removes the previous TextWatcher and adds the new one if you use lambdas. Of course, having one TextWatcher instance stored somewhere in class attributes is a solution to avoid extra work.
But there is a cool two-way data binding: text(StringBuilder)
. It allows you to get user input into the StringBuilder, or to modify the StringBuilder and thus update the text. No infinite recursion happens in this case.
from anvil.
Is there a way to subscribe to changes using the text(StringBuilder)
binding?
from anvil.
Oh.. I think the easiest way for now would be to override StringBuilder's replace
method. Or cache TextWatcher instance and call Anvil.render() from there only when your text has actually been changed.
from anvil.
It would be nice to have the TextWatcher DSL as it was in the old Anvil, but:
- One should be careful about Anvil.render() inside TextWatcher callbacks - it would lead to long recursion if you call it when your input has been changed
- One should keep the previously known user input somewhere to compare on the next callback run to "debounce" them
- One should be careful about adding new TextWatcher instances to the views - it seems like TextWatcher's callbacks are fired when it's added to the view (even if there is no user input happening at that time).
from anvil.
I ended up slightly tweaking my UX to remove the need to watch for changes. I'm using the text(StringBuilder)
binding now and it seems to be working fine for me.
from anvil.
@zserge Having problems getting this to work with RenderableAdapter
. A few observations:
-
Watchers set via
onTextChanged()
don't seem to be removed when callingonTextChanged()
again in a subsequent render cycle. This creates issues if the watcher references row-specific data (e.g. setting text for a specific row in the dataset) as obsolete watchers end up being called on mutation.The only way I've been able to work around this is to keep record of watchers assigned to views and manually remove them during the render, e.g.
Map<EditText, TextWatcher> watchers = new HashMap<>(); editText() { EditText view = Anvil.currentView(); TextWatcher previousWatcher = watchers.get(view); if (previousWatcher != null) { view.removeTextChangedListener(previousWatcher); } TextWatcher newWatcher = new TextWatcher(/* … */); watchers.put(view, newWatcher); view.addTextChangedListener(newWatcher); } -
-
Not sure if I've understood correctly, but isn't
text(Stringbuilder)
limited in that it can only render 'raw' model data? By that, I mean it's not possible to compute values in the render function, e.g.text(Integer.toString(value))
without implementing a separate watcher to update the model. -
It's not possible to override
StringBuilder.replace()
(at least in regular java) as the class is final.
from anvil.
@danhawkes Yeah... TextWatcher is so far the most painful part of Anvil. It relates to a broader topic of two-way data bindings and is on my todo list for a long time already. Still, I don't see a perfect solution yet because Android's TextWatcher is broken too much.
- I think
BaseDSL.setTextChanged()
should replace the textwatcher if the new instance is different from the old one. If it doesn't replace them - it's a bug. If you have a look atBaseDSL.setTextChanged()
you may find it behaves very similar to your code. text(StringBuilder)
is a hack I had to add to achieve simple two-way data binding. It a mutable string (which is a StringBuilder in Java) as a parameter. If you pass other (immutable) strings, like the ones received fromtoString()
- you will have to add your own text watcher to get two-way data binding.- Yes, unfortunately it is so, and I admit that StringBuilder is a poor choice for mutable strings and probably our own mutable string class would serve better.
I'm open to a discussion about how two-way data bindings should work in Anvil and how to simplify this process. I would prefer a solution to be more generic (e.g. cover not only EditTexts, but also Sliders, ToggleButtons and other views used for input).
from anvil.
So far I could only think of two approaches:
- Use mutable data classes, so one could use
text(mutableString)
orprogress(mutableInt)
or checked(mutableBoolean)`. Each mutable class when changed may emit Anvil.render() and cause UI updates, also at any time one can get the value and receive the current input. To get notified when user does input one would have to override the class. This also forces user to use our own data classes in their view-models, which means view-model code becomes dependent on Anvil. - Use simplified listeners (lambdas) with
fromUser
parameter. Then one could bind one (immutable) data to the view and register a simple lambda (fortunately lambdas a cheap and new Jack/Jill will bring them by default very soon). Inside a lambda one would write something likeif (fromUser) { value = newValue; Anvil.render(); }
.
Solution 1 requires less code from user and gives less control if you don't want to subclass your data classes.
Solution 2 makes user to write more code but is more explicit and gives more control.
from anvil.
Related Issues (20)
- RecyclerView not found in XML layout
- Anvil fails silently when you set a property on a view that won't accept it HOT 1
- Anvil render question
- Anvil + Yoga HOT 2
- How to use anvil with ConstraintLayout? HOT 2
- Crash on setting text via DSL.text(CharSequence)
- Xml views are recreated on every view() call. HOT 1
- NoSuchElementException in Anvil.render() HOT 3
- Approaching React Native HOT 2
- Bug on swipeRefreshLayout
- Anvil.render() and ripple effect drawable problem HOT 1
- How to work with TypedArray parameters with Anvil? HOT 1
- I can't run in android studio 3.0 - module not specified HOT 10
- RenderableRecyclerViewAdapter is really recycling views?!
- BottomNavigationBar not visible
- Material components
- Still maintained HOT 5
- Transfer repo to org HOT 9
- Ask a question HOT 1
- Would be possible to run the diff tree algorithm in a non UI thread? HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from anvil.