How to force recomposition of AndroidView? - android-jetpack-compose

Disclaimer: I have seen many similar questions posted already. However in all those questions, they were not aware of the update parameter to AndroidView. I am aware of the update parameter.
The problem
I have a legacy CustomView that has two methods to set its data. Let's call them setDataSlow() and setDataFast().
The setDataSlow() method is expected to be called only once in the lifetime of the view, and it is relatively slow.
The setDataFast() method needs to be called frequently, and is very fast.
So, the natural code in Compose would be something like this:
var data1 by remember {mutableStateOf(0)}
var data2 by remember {mutableStateOf(0)}
AndroidView(factory = {
val v = CustomView(it)
v.setDataSlow(data1)
v
}, update = {
it.setDataFast(data2)
}
)
The problem is, the factory lambda gets only called once, even when data1 changes. I understand this is by design.
Question
How do I solve this? If I call setDataSlow() inside update, it will slow down the UI tremendously.
I need to somehow force the factory method to update, and the only way I can do that is to force recompose of AndroidView somehow.

Related

How do Composables get notified to recompose when a MutableState changes?

I don't understand how the Compose system "knows" that it needs to recompose certain composables when MutableStates that they're observing change value. For example, AIUI, I could write something like:
class StateHolder {
val theState by mutableStateOf("The state")
}
...
#Composable
fun StateDisplay(stateHolder: StateHolder) {
Text(stateHolder.theState)
}
Then, elsewhere in my code I can assign a new value to theState, and that will trigger a recomposition and the display will be updated to show the new value. (At the moment I'm not sure whether that means StateDisplay() gets run again, or just its Text child).
My question is, how on earth does a MutableState know what's observing it, and what it needs to do to update the observer? From the above code it looks like the MutableState can only know that something called its getter, but not know what called it, and the composables can only know that they called a getter, but not know when they need to call it again.
Similarly, I wondered if it was possible to observe a MutableState from elsewhere. It's probably not a conventional pattern in Android/Compose because all I could find on the subject was this answer about snapshotFlow. But again, how does the flow know when a new value needs to be emitted when it looks like the only relationship that's been established between the flow and state is that one called the other's getter?
To answer your question, Compose only recompose components that are actually consuming this data, as an example imagine you have a Parent component like
class MyViewModel : ViewModel(){
private val _myObservableStringInViewModel: MutableStateFlow<String> = MutableStateFlow("Hello World")
val myObservableStringInViewModel: StateFlow = _myObservableStringInViewModel.asStateFlow()
}
#Composable
fun ParentComponent(viewModel: MyViewModel){
val myObservedString by viewModel.myObservableStringInViewModel.collectAsState()
Scaffold{
MyChildComponent(data = myObservedString)
}
}
#Composable
fun MyChildComponent(data: String){
Text(text = data)
}
In this case MyChildComponent it's the only one that is going to be affected by recomposition, because compose only recompose those components that are actually using the data, also works the same with Text() components, imagine you have 3 observables MutableStateFlow, and 3 Text() components being feed by those respective, if one of those change, only the Text() component that is being feeded by this particular MutableStateFlow is the only one that is going to be re-composed.
And according to your second question, this example shows the most used approach to implement StateFlow in Compose, also you must use an activity or fragment between your viewModel and the composable, but there are many approaches to do this, so I'll leave this to your particular research, also you can use Compose ViewModel direct injection but this is in beta for now.

Add a property to an object (or at least similar outcome)

First, the context of what I'm doing. I am running an HttpServer which is handling HttpRequests.
HttpServer.bind(ADDRESS, PORT).then((HttpServer server) {
listenSubscription = server.listen(onRequest);
});
void onRequest(HttpRequest request) {
//handle request here
}
I'd like to add some logging to all this, and due to the asynchronous nature of it all, want to add some identifying marker to the requests (so I can match up the request receipts with the responses, fer example). The code inside of onRequest() calls a bunch of other functions to do different things (handle GET vs POST requests, etc.), so simply generating an id at the top is a cumbersome solution as I'd have to pass it around through all those other function calls. I am, however, already passing around the HttpRequest object, so I thought it would be nice to throw an id field on it, just like you would in Javascript, except that Dart doesn't work that way.
Thoughts then went to subclassing the HttpRequest class, but converting the HttpRequest object the onRequest() method receives seemed like much more trouble and overhead than my needs required.
So I ask, is there any idiomatic Dart way attach some data to an existing object? If there isn't something idiomatic, what is the simplest (both in code and runtime complexity) way you can think of to accomplish this?
Well, there's an Expando, but I don't know the performance implications.
Something like:
// somewhere top level. Create this once.
final loggingId = new Expando();
...
// inside of onRequest
loggingId[request] = generateId();
...
// later inside log()
print(loggingId[request]);
Expandos are like weak-reference maps, from my understanding.

Rendering Polymer element once per multiple attribute changes

I have an Polymer.dart element with multiple attributes, e.g.
<code-mirror lines="{{lines}}" widgets="{{widgets}}">
</code-mirror>
on some occasions lines and widgets change simultaneously sometimes only widgets changes.
I would like to rerender component once independently on how many properties change in the same turn of event loop.
Is there a way a good built-in way to achieve that?
Additional trouble here is that interpretation of widgets depends on content of lines and ordering in which linesChanged and widgetsChanged callbacks arrive is browser dependent, e.g. on Firefox widgetsChanged arrives first before linesChanged and component enters inconsistent state if I do any state management in the linesChanged callback.
Right now I use an auxiliary class like this:
class Task {
final _callback;
var _task;
Task(this._callback);
schedule() {
if (_task == null) {
_task = new async.Timer(const Duration(milliseconds: 50), () {
_task = null;
_callback();
});
}
}
}
final renderTask = new Task(this._render);
linesChanged() => renderTask.schedule();
widgetsChanged() => renderTask.schedule();
but this looks pretty broken. Maybe my Polymer element is architectured incorrectly (i.e. I have two attributes with widgets depending on lines)?
*Changed methods are definitely the right way to approach the problem. However, you're trying to force synchronicity in an async delivery system. Generally we encourage folks to observe property changes and react to them and not rely on methods being called in a specific order.
One thing you could use is an observe block. In that way, you could define a single callback for the two properties and react accordingly:
http://www.polymer-project.org/docs/polymer/polymer.html#observeblock
Polymer's data binding system does the least amount of work possible to rerender DOM. With the addition of Object.observe(), it's even faster. I'd have to see more about your element to understand what needs rendering but you might be creating a premature optimization.
I think there are three possible solutions:
See this: http://jsbin.com/nilim/3/edit
Use an observe block with one callback for multiple attributes (the callback will only be called once)
Create an additional attribute (i.e. isRender) that is set by the other two attributes (lines and widgets). Add a ChangeWatcher (i.e. isRenderChanged() in which you call your expensive render method)
Specify a flag (i.e. autoUpdate) that can be set to true or false. When autoUpdate = false you have to call the render method manually. If it is set to true then render() will be called automatically.
The disadvantage of solution 1 is that you can only have one behavior for all observed attributes. Sometimes you want to do different things when you set a specific attribute (i.e. size) before you call render. That's not possible with solution 1.
I don't think there is a better way. You may omit the 50ms delay (just Timer.run(() {...});) as the job gets scheduled behind the ongoing property changes anyway (my experience, not 100% sure though)

Getting multiple references to the same method = multiple objects?

I'm new to Dart, so maybe I'm missing something here:
This works:
In my main(), I have this:
var a = _someFunction;
var b = _someFunction;
print("${a == b}"); // true. correct!
Where _someFunction is another top-level function.
This does NOT work: (at least not how I'm expecting it to)
Given this class...
class Dummy {
void start() {
var a = _onEvent;
var b = _onEvent;
print(a == b); // false. ???????
}
void _onEvent() {
}
}
Instantiating it from main() and calling its start() method results in false. Apparently a new instance of some function or closure object is created and returned whenever my code obtains a reference to _onEvent.
Is this intentional behaviour?
I would expect that obtaining multiple references to the same method of the same instance returns the same object each time. Perhaps this is intended for some reason. If so; what reason? Or is this a bug/oversight/limitation of VM perhaps?
Thanks for any insights!
Currently, the behaviour seems to be intentional, but the following defect is open since May 2012: https://code.google.com/p/dart/issues/detail?id=144
If I were to guess, I'd say that setting "var a = _onEvent;" creates a bound method, which is some sort of object that contains both the function as well as this. You are asking for bound methods to be canonicalized. However, that would require the team to create a map of them, which could lead to worries about memory leaks.
I think they made "var a = _someFunction;" work early on because they needed static functions to be constants so that they could be assigned to consts. This was so that they could write things like:
const logger = someStaticLoggingFunction;
This was in the days before statics were lazily evaluated.
In any case, I would say that comparing closures for equality is a edge case for most languages. Take all of the above with a grain of salt. It's just my best guess based on my knowledge of the system. As far as I can tell, the language spec doesn't say anything about this.
Actually, now that I've read (https://code.google.com/p/dart/issues/detail?id=144), the discussion is actually pretty good. What I wrote above roughly matches it.

Can a 'while loop' be used in actionscript to monitor an event dispatch?

I am creating an action script library.I am calling some APIs which parses some xml and gets me the result. It dispatches an Event.COMPLETE when the parsing is done. I want to monitor whether this event is dispatched in some while loop like "while(eventnotdispatched)"
is it possible? I know the other way would be to addeventlistener. But please let me know if the other thing is possible.
Thanks
NO, it is not possible. Actionscript is single threaded. Thus while you are waiting in your while loop, that is the only thread running, and the process you are waiting for can never complete. This is why everything is done with events, so that's what you should use. If you need to update your display periodically while you are waiting for something to complete...again, use events. Create a Timer object which generates a TIMER event every so often, and use that to make your updates.
EDIT: Davr is right, you would not be able to use the while loop like this. You would need a timer.
Yes, it is possible to poll for it. BUT you will still need to create an event listener. It will work something like this:
private var loadCompleted = false;
private var timer:Timer= new Timer(1);
private function onInitCompleted(event:Event):void
{
timer.addEventListener(TimerEvent.TIMER, timerHandler);
timer.start();
}
private function loadCompleteEventHandler(event:Event):void
{
loadCompleted = true;
...
}
private function timerHandler()
{
if(!loadCompleted)
{
... // stop the timer or something.
timer.stop();
}
}
Please note, this is VERY BAD code. I would NEVER use it in production because Actionscript is a event driven language. There should be absolutely NO REASON for you to need to do this. Whatever you are trying to do could be accomplished using another method much simpler. Tell me what you are trying to accomplish with this and I will present a better solution.
Sorry for yelling, it's late and I am sleepy.
Doing that means forcing a synchronous model of execution on the underlying asynchronous model (that works with callbacks).
What are you trying to achieve exactly, and why not use a callback?
I agree with the statements about it probably being a bad idea and a while loop will certainly not work this way in ActionScript. However, there may be legitimate reasons for doing what you are attempting to do. Only you can prevent bad code. Instead of judging, I'll just get to an answer for your question.
First I'm going to make an assumption, that what you really want to do is monitor a property and for some reason the API for this object does not dispatch an event when this property changes. I'm making this assumption because if you have the event available, I assume you would just use the event.
So... you have an object weirdXmlObj with a property loaded that defaults to false but goes to true when the XML is loaded.
In this case with slight modifications the code posted by CookieOfFortune would in fact work. You wouldn't need the loadCompleteEventHandler function (which was never attached anyway) and in the timer handler you would simply check if( weirdXmlObj.loaded ) and then branch however you wanted to.
Ah but there may be a simpler way, depending on what you are doing.
If you have a display object handy. (i.e. something that makes sense, not just some random object.) You can attach your code to the stage's EnterFrame event instead of using a timer.
myDisplayObject.stage.addEventListner(Event.ENTER_FRAME,frameEnterHandler);
A couple of things to be aware of:
You don't really even need to go to the stage level, all display objects support the EnterFrame event, but it's a nice place to attach the event listener.
You really should keep whatever the function calls to a minimum. In particular the actual frameEnterHandler function should do nothing more than do the if( weirdXmlObj.loaded ) check.
You are attempting to circumvent event-driven programming, which is not a good idea. This is often the case when someone approaches from an older model and does not yet have a good frame of reference to appreciate the elegance of event-driven programming.
Events are your friends. They work very well. Your loadCompleteHandler is all that is required. Want to do something else in response? Add the call in that handler:
private function loadCompletedHandler(event:Event):void
{
waitingObject.fileWasLoadedSoGoDoThatThing();
}
There is no need to make it any more complicated than that. No need for a semaphore or a loop to check the semaphore. Unnecessary environmental semaphores can break the encapsulation that could shield you from unwanted side effects.

Resources