-
Notifications
You must be signed in to change notification settings - Fork 730
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Async diffing and model building #425
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
* | ||
* Also adds support for canceling an in progress diff. | ||
*/ | ||
class AsyncListDifferWithPayload<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a @UiThread
annotation to the whole class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually want to figure out if we should support setting a new list from any thread. That could be nice when the models are built on a background thread. It would complicate this to synchronize things though. cc @gpeal
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the most important thing is that it happens off of the main thread rather than on a specific executor
@@ -10,6 +10,10 @@ | |||
private List<? extends EpoxyModel<?>> currentModels; | |||
private boolean insideSetModels; | |||
|
|||
public SimpleEpoxyController() { | |||
setDebugLoggingEnabled(true); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just curious, why make this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this was just for debugging purposes, I should have removed it.
thanks for the catch!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exciting!
* | ||
* Also adds support for canceling an in progress diff. | ||
*/ | ||
class AsyncListDifferWithPayload<T> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the most important thing is that it happens off of the main thread rather than on a specific executor
|
||
final DiffCallback<T> wrappedCallback = new DiffCallback<>(list, newList, diffCallback); | ||
|
||
DIFF_EXECUTOR.execute(new Runnable() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be easier with coroutines :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah :( not ready to add that dependency yet though
There are two things left I need to address: Update documentation to reflect that duplicate unique ids won't crash anymore. I could build debug handling to check this case however. Right now some people rely on posting a runnable after requesting a model build to make sure something runs after item changes are notified (such as scrolling to the top of the list after an item is inserted). This will no longer work, so will be a breaking change. I'd like to make sure I have a good alternative to present, and possibly enable this conditionally to give people time to update |
@elihart , any idea when this is getting released? Looking forward to trying it out on my upcoming android app which uses Firebase real time db. thx Aldrin |
ad2493c
to
196bd54
Compare
196bd54
to
05ede30
Compare
@ngsilverman @gpeal @BenSchwab This is updated to make the differ completely thread safe (it can now handle read/write from any thread), and also adds support for async model building. This is my approach:
I feel pretty good about these changes, but it definitely adds a fair amount of complexity that is hard to test. I played around with it in the sample app and it seems good, but we'll want to test this carefully in airbnb too. I took great care to make sure we could release this without changing user's model building behavior at all. Default behavior is still posted, but then runs synchronously until adapter changes are notified. This isn't too trivial, because by default the differ posts it's work to whatever thread it is given. I set up a wrapper class to execute Runnables asynchronously if its looper is the same. This is another reason I need to use Handlers instead of an Executor. I also added a new listener that can be registered for when model build finishes. Please review the concurrency logic carefully and lmk if you see room for improvement. |
@appsailor sorry, I just saw your comment. The work is now done, and I'll be releasing this week. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@elihart looks great overall. It's tough to fully grok the whole thing but overall, it looks good :) Just a couple of questions.
* Marks the generation as done, and updates the list if the generation is the most recent. | ||
* Calls to this MUST be synchronized on "this" so list object and generation always stay in sync. | ||
* This method isn't synchronized directly so callers have flexibility to includes other | ||
* actions in their synchronized block |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Java uses reentrant locks for synchronized blocks. If you synchronize the method and call it from within another synchronized block, that should be fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes a lot of sense, I should have looked that up :P
* @return True if the given generation is the most recent, in which case the given list was | ||
* set. False if the generation is old and the list was ignored. | ||
*/ | ||
private boolean tryLatchList(@Nullable List<? extends EpoxyModel<?>> newList, int runGeneration) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this @WorkerThread
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nope, anythread. annotated
|
||
if (newList == previousList) { | ||
// nothing to do | ||
onRunCompleted(runGeneration, newList, new DiffResult(previousList, newList, null)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe you can create static functions for these like:
DiffResult.noop()
, DiffResult.clear()
, and DiffResult.insert()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good idea
@Nullable final DiffUtil.DiffResult differResult; | ||
|
||
@SuppressWarnings("unchecked") | ||
public DiffResult( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add a javadoc for what null means?
First pass at #286
cc @ngsilverman @gpeal