Skip to content
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

Add support for Async Layout inflation with EPoxy #1292

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

varungu
Copy link

@varungu varungu commented Apr 27, 2022

EPoxy currently supports only synchronous layout inflation with most of the time during first layout is spent on the inflation of the views.

To make the first layout faster, we should support asynchronous layout inflation. With this approach, the application developer can make heavy views (like video player) asynchronous and improve the page load time

I investigated if we can use a different view like LinearLayout and merge tag in the xml. It won't work with Asynchronous layout inflation since merge tag requires "attachToParent" as true, and asyncronousLayoutInflator inflates with it as false. We will need to use FrameLayout for asynchronous inflation.

TODO

  • Make Asynchronous inflation optional, so that developers can extend all views from AsyncFrameLayout and set a property to choose between sync and async inflation based on use case.
Screen.Recording.2022-04-27.at.11.45.22.AM.mov

import android.widget.FrameLayout

/**
* Base class to support Async layout inflation with EPoxy.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't upper case the P

Suggested change
* Base class to support Async layout inflation with EPoxy.
* Base class to support Async layout inflation with Epoxy.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

package com.airbnb.epoxy

/**
* Interface to support Async layout inflation with EPoxy
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe expand more on what this does/why someone would want to use it and give small usage example

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

* onInflationComplete method that MUST be called after the view is asyncronously inflated.
* It runs all pending runnables waiting for view inflation.
*/
fun onInflationComplete() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should provide a convenience function here that calls this. Maybe something like

fun ViewGroup.inflateAsync(@LayoutRes layoutRes: Int, parent: ViewGroup, callback: OnInflateFinishedListener) {
    AsyncLayoutInflater(context).inflate(R.layout.async_custom_view_item, this) {
            view, resId, parent ->
        addView(view)
        callback.onInflateFinished(view, resId, parent)
        onInflationComplete()
    }
}

That might make for less boiler plate when using it in the model view

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though I guess that would then force a dependency on the async layout inflator in the core module which we probably don't want to do.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think so, we should keep it optional to keep the library as small as possible.

VERSION_NAME=5.0.0-beta05
VERSION_NAME=5.0.0-beta06
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure to roll this version bump back as we do that as part of the release process

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

* inflated view. If the view is already inflated, the runnable will immediately run,
* otherwise it is added to the list of pending runnables.
*/
fun executeWhenInflated(runnable: Runnable) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a way of clearing these pending executions when the view is recycled?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Done.

@@ -339,4 +348,12 @@ public boolean isStickyHeader(int position) {
}

//endregion

protected void executeWhenInflated(EpoxyViewHolder holder, Runnable runnable) {
if (holder.itemView instanceof AsyncInflatedView) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@varungu Have we considered making the async layout a property of the EpoxyModel or EpoxyViewHolder so we can avoid needing to do an instanceof check here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a TODO for this in the description. It is not very straight forward to make it a property. Biggest problem is all views will need to extend from this interface, that we don't want to do. If we don't enforce the extension, we will need the check in addition to the property.

My plan is to test this change before putting effort into adding a property.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@varungu I just realized an additional concern with this approach. On each bind and attach/detach from the window this will do an object allocation of the runnable which may cause some performance hits for those not using async layouts. I think we'd probably want to better integrate this into the model or view holder before merging.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can separate this and create runnable only for AsyncViews, would that resolve your concern?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -108,6 +108,15 @@ public void onBindViewHolder(EpoxyViewHolder holder, int position) {

@Override
public void onBindViewHolder(EpoxyViewHolder holder, int position, List<Object> payloads) {
if (holder.itemView instanceof AsyncInflatedView) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Peter might've mentioned this earlier and I'm not completely familiar with the internals of Epoxy but is there a way where we could avoid these checks? We discussed an implementation where we potentially create an AsycEpoxyModel class (similar to how we handled it for Compose interoperability - https://github.com/airbnb/epoxy/blob/master/epoxy-compose/src/main/java/com/airbnb/epoxy/ComposeInterop.kt)

@elihart
Copy link
Contributor

elihart commented Jun 25, 2022

@varungu @vinaygaba can we merge this?

@vinaygaba
Copy link
Collaborator

@elihart I think Varun is running a test with these changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants