Epoxy is an Android library for building complex screens in a RecyclerView

Related tags

RecyclerView epoxy
Overview

Build Status Maven Central GitHub license GitHub contributors

Epoxy

Epoxy is an Android library for building complex screens in a RecyclerView. Models are automatically generated from custom views or databinding layouts via annotation processing. These models are then used in an EpoxyController to declare what items to show in the RecyclerView.

This abstracts the boilerplate of view holders, diffing items and binding payload changes, item types, item ids, span counts, and more, in order to simplify building screens with multiple view types. Additionally, Epoxy adds support for saving view state and automatic diffing of item changes.

We developed Epoxy at Airbnb to simplify the process of working with RecyclerViews, and to add the missing functionality we needed. We now use Epoxy for most of the main screens in our app and it has improved our developer experience greatly.

Installation

Gradle is the only supported build configuration, so just add the dependency to your project build.gradle file:

dependencies {
  implementation "com.airbnb.android:epoxy:$epoxyVersion"
  // Add the annotation processor if you are using Epoxy's annotations (recommended)
  annotationProcessor "com.airbnb.android:epoxy-processor:$epoxyVersion"
}

Replace the variable $epoxyVersion with the latest version : Maven Central

See the releases page for up to date release versions and details

Kotlin

If you are using Kotlin you should also add

apply plugin: 'kotlin-kapt'

kapt {
    correctErrorTypes = true
}

so that AutoModel annotations work properly. More information here

Also, make sure to use kapt instead of annotationProcessor in your dependencies in the build.gradle file.

Library Projects

If you are using layout resources in Epoxy annotations then for library projects add Butterknife's gradle plugin to your buildscript.

buildscript {
  repositories {
    mavenCentral()
   }
  dependencies {
    classpath 'com.jakewharton:butterknife-gradle-plugin:10.1.0'
  }
}

and then apply it in your module:

apply plugin: 'com.android.library'
apply plugin: 'com.jakewharton.butterknife'

Now make sure you use R2 instead of R inside all Epoxy annotations.

@ModelView(defaultLayout = R2.layout.view_holder_header)
public class HeaderView extends LinearLayout {
   ....
}

This is not necessary if you don't use resources as annotation parameters, such as with custom view models.

Basic Usage

There are two main components of Epoxy:

  1. The EpoxyModels that describe how your views should be displayed in the RecyclerView.
  2. The EpoxyController where the models are used to describe what items to show and with what data.

Creating Models

Epoxy generates models for you based on your view or layout. Generated model classes are suffixed with an underscore (_) are used directly in your EpoxyController classes.

From Custom Views

Add the @ModelView annotation on a view class. Then, add a "prop" annotation on each setter method to mark it as a property for the model.

@ModelView(autoLayout = Size.MATCH_WIDTH_WRAP_HEIGHT)
public class HeaderView extends LinearLayout {

  ... // Initialization omitted

  @TextProp
  public void setTitle(CharSequence text) {
    titleView.setText(text);
  }
}

A HeaderViewModel_ is then generated in the same package.

More Details

From DataBinding

If you use Android DataBinding you can simply set up your xml layouts like normal:

<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="title" type="String" />
    </data>

    <TextView
        android:layout_width="120dp"
        android:layout_height="40dp"
        android:text="@{title}" />
</layout>

Then, create an interface or class in any package and add an EpoxyDataBindingLayouts annotation to declare your databinding layouts.

package com.airbnb.epoxy.sample;

import com.airbnb.epoxy.EpoxyDataBindingLayouts;

@EpoxyDataBindingLayouts({R.layout.header_view, ... // other layouts })
interface EpoxyConfig {}

From this layout name Epoxy generates a HeaderViewBindingModel_.

More Details

From ViewHolders

If you use xml layouts without databinding you can create a model class to do the binding.

@EpoxyModelClass(layout = R.layout.header_view)
public abstract class HeaderModel extends EpoxyModelWithHolder<Holder> {
  @EpoxyAttribute String title;

  @Override
  public void bind(Holder holder) {
    holder.header.setText(title);
  }

  static class Holder extends BaseEpoxyHolder {
    @BindView(R.id.text) TextView header;
  }
}

A HeaderModel_ class is generated that subclasses HeaderModel and implements the model details.

More Details

Using your models in a controller

A controller defines what items should be shown in the RecyclerView, by adding the corresponding models in the desired order.

The controller's buildModels method declares which items to show. You are responsible for calling requestModelBuild whenever your data changes, which triggers buildModels to run again. Epoxy tracks changes in the models and automatically binds and updates views.

As an example, our PhotoController shows a header, a list of photos, and a loader (if more photos are being loaded). The controller's setData(photos, loadingMore) method is called whenever photos are loaded, which triggers a call to buildModels so models representing the state of the new data can be built.

public class PhotoController extends Typed2EpoxyController<List<Photo>, Boolean> {
    @AutoModel HeaderModel_ headerModel;
    @AutoModel LoaderModel_ loaderModel;

    @Override
    protected void buildModels(List<Photo> photos, Boolean loadingMore) {
      headerModel
          .title("My Photos")
          .description("My album description!")
          .addTo(this);

      for (Photo photo : photos) {
        new PhotoModel()
           .id(photo.id())
           .url(photo.url())
           .addTo(this);
      }

      loaderModel
          .addIf(loadingMore, this);
    }
  }

Or with Kotlin

An extension function is generated for each model so we can write this:

class PhotoController : Typed2EpoxyController<List<Photo>, Boolean>() {

    override fun buildModels(photos: List<Photo>, loadingMore: Boolean) {
        header {
            id("header")
            title("My Photos")
            description("My album description!")
        }

        photos.forEach {
            photoView {
                id(it.id())
                url(it.url())
            }
        }

        if (loadingMore) loaderView { id("loader") }
    }
}

Integrating with RecyclerView

Get the backing adapter off the EpoxyController to set up your RecyclerView:

MyController controller = new MyController();
recyclerView.setAdapter(controller.getAdapter());

// Request a model build whenever your data changes
controller.requestModelBuild();

// Or if you are using a TypedEpoxyController
controller.setData(myData);

If you are using the EpoxyRecyclerView integration is easier.

epoxyRecyclerView.setControllerAndBuildModels(new MyController());

// Request a model build on the recyclerview when data changes
epoxyRecyclerView.requestModelBuild();

Kotlin

Or use Kotlin Extensions to simplify further and remove the need for a controller class.

epoxyRecyclerView.withModels {
        header {
            id("header")
            title("My Photos")
            description("My album description!")
        }

        photos.forEach {
            photoView {
                id(it.id())
                url(it.url())
            }
        }

        if (loadingMore) loaderView { id("loader") }
    }
}

More Reading

And that's it! The controller's declarative style makes it very easy to visualize what the RecyclerView will look like, even when many different view types or items are used. Epoxy handles everything else. If a view only partially changes, such as the description, only that new value is set on the view, so the system is very efficient

Epoxy handles much more than these basics, and is highly configurable. See the wiki for in depth documentation.

Documentation

See examples and browse complete documentation at the Epoxy Wiki

If you still have questions, feel free to create a new issue.

Min SDK

We support a minimum SDK of 14. However, Epoxy is based on the v7 support libraries so it should work with lower versions if you care to override the min sdk level in the manifest.

Contributing

Pull requests are welcome! We'd love help improving this library. Feel free to browse through open issues to look for things that need work. If you have a feature request or bug, please open a new issue so we can track it.

License

Copyright 2016 Airbnb, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Comments
  • Crash on Android 12

    Crash on Android 12

    According to Firebase Crashlytics users with Google Pixel Devices and Android 11 are experiencing crashes every time they start up our app. I belive that that data is not correct and its actually Android 12 Beta as a few users stated that they can't start the app and I can't see any A12 non fatal errors logged aswell.

    Unfortunately I can't reproduce the error on emulators or physical devices (including Pixel devices) running Android 11 or 12.

    Fatal Exception: com.airbnb.epoxy.IllegalEpoxyUsage: You must set an id on a model before adding it. Use the @AutoModel annotation if you want an id to be automatically generated for you.
           at com.airbnb.epoxy.EpoxyController.addInternal(EpoxyController.java:502)
           at com.airbnb.epoxy.EpoxyModel.addTo(EpoxyModel.java:337)
    

    The error is a bit missleading as I am setting an id via the builder pattern with .id("***") before calling .addTo(this) But I have no idea what the real cause is.

    epoxy Version: 4.6.1 (it's also happening on older versions e.g. 4.0.0-beta6)

    opened by woodii 56
  • Epoxy Recyclerview displays duplicate items that are not in the dataset

    Epoxy Recyclerview displays duplicate items that are not in the dataset

    I've run into an issue that seems to be related to some kind of race condition going on in the Epoxy code.

    I'm use a PagedListEpoxyController. I do a lot of rapid updates to the data set and Epoxy is usually great for that given the diffing and everything.

    But I seem to run into an issue when I have am editing my data set and quickly requesting new model builds. I edit my data set by copying (using kotlin data classes).

    The issue I'm seeing is that the recycler view is displaying items twice even though they aren't in my data set.

    The example is for a messaging app

    1. I send a message: a new model gets added to my data set with a sending state that is reflected visually on the model view (loader)
    2. I immediately send another message, new data gets added again
    3. The first message API call returns, the data gets manipulated so the message is no longer in sending state, view gets updated
    4. The second message API call returns immediately after, that view gets updated correctly.

    The problem is... All the sudden the first set of data that was originally added to my data list is at the bottom of the list. I.E. My first X messages that were loaded when I opened my app are now duplicated below my last 2 messages I just sent.

    I debugged and I'm stumped. I verified that the data set I'm feeding to my controller does not contain the duplicates and thus the recycler view is not reflecting my data set. The behavior is not consistent. It seems to happen when calls to loadBefore in my PageKeyedDataSource (Paging library) get triggered. So I can more easily reproduce if I send multiple messages, scroll up to load more data thru paging. I've found the paging library to be kinda finicky so calls to loadBefore can happen a lot.

    But even if a lot of calls are being made, I can't figure out why my RecyclerView would be displaying these duplicate items that are NOT in the data set. I verified this with logs...debugging doesn't work because it has to do with some kind of race.

    Has this issue come up before? The view displaying things that are not in the data set?

    opened by JayyyR 43
  • Build failed from version 2.0.0 to 2.2.0

    Build failed from version 2.0.0 to 2.2.0

    throw below excepotion:

    Error:Execution failed for task ':app:compileDevDebugJavaWithJavac'. java.lang.NoSuchMethodError: com.squareup.javapoet.ClassName.reflectionName()Ljava/lang/String;

    and it's looks like occurs on Model with EpoxyModelClass annotation, such as

    @EpoxyModelClass(layout = R.layout.footer_divider)
    public abstract class BaseFooterDividerModel extends EpoxyModel<View> {
    }
    

    my dependences:

    epoxyVersion = '2.2.0'
    
    def libs = [
        epoxy   : "com.airbnb.android:epoxy:${epoxyVersion}",
        epoxyprocessor  : "com.airbnb.android:epoxy-processor:${epoxyVersion}",
    ]
    
    appDependencies = [
        [configuration: "compile", dependency: libs.epoxy],
        [configuration: "annotationProcessor", dependency: libs.epoxyprocessor],
    ]
    

    thanks for your help

    opened by rorschach 41
  • Completely generate DataBinding models

    Completely generate DataBinding models

    I am almost done with https://github.com/airbnb/epoxy/issues/183 and it got me thinking that all somebody should really have to do is define a list of xml layouts that they want to use with databinding, and we could generate the whole EpoxyModel for them.

    For example, with the upcoming support they would have to write a model like

    @EpoxyModelClass(layout = R.layout_my_databinding_layout)
    public abstract class MyModel extends DataBindingEpoxyModel {
       @EpoxyAttribute int variable1;
       @EpoxyAttribute boolean variable2;
        ... more attributes
    }
    

    but instead we could have them define layouts in an annotation like @EpoxyDataBindingLayouts({R.layout_my_databinding_layout1, R.layout_my_databinding_layout1}) and the annotation processor could inspect the R tree at process time to get the data variables and build the model completely. This is safer too because it guarantees that the variable names of the model stay in sync with the data names in the layout.

    I don't think this is too hard - the R parsing would be the hardest part. It isn't super important because it isn't a lot of work to create a model like above, but it would be a nice boilerplate saver and I think it would be pretty cool.

    enhancement 
    opened by elihart 32
  • Migration to androidx

    Migration to androidx

    How long will it take to migrate the Epoxy library to AndroidX? I realized when I did the "Refactor to Android X" (Out of curiosity) that the epoxy library broke (All recyclerview were android.support instead of androidx. inside epoxy)

    opened by ghost 31
  • Support for sticky headers

    Support for sticky headers

    We are working on this, and hope to release support in a 2.x release.

    There are two ways to support this.

    1. An item decoration to copy the view of a model. This is simpler, but you lose touch functionality. This would be what we initially support.
    2. Using a custom layout manager to create a fully functional duplicate view. There are some libraries that use this approach, but I am hesitant to pursue it because layout managers are very complex and using Linear/GridLayoutManager is the safest/optimized approach. I have an idea on how we might extend LinearLayoutManager though, which I can hopefully work on over the next few months.

    If anyone is interested in helping with these, please let me know!

    enhancement 
    opened by elihart 30
  • Android ViewBinding: adding an example in the sample project.

    Android ViewBinding: adding an example in the sample project.

    This will require everyone to use AndroidStudio 3.6+

    AndroidStudio 3.6 + is required to develop on the epoxy project, and is required if you want to use ViewBinding in your app project. You can still use epoxy without ViewBinding with older versions of AndroidStudio.

    Provide a sample for those who wants to use ViewBinding with a data class. A new ViewBinding is created when the VH is bound, and nullified when unbound.

    Updates were required for AndroidPlugin 3.5.3 -> 3.6.1 and Gradle 5.6.3 -> 5.6.4

    I didn't note all not-relevant layout for ViewBinding, can do it if you prefer. (Other layouts could be ignored for better build time, but it's a simple, so may be better to keep it simple?)

    The chosen strategy of bind/unbind with a new ViewBinding could be debatable, this is simpler to implement and ensure there is no memory leak, but I presume that ViewBinding should be coupled with the view (as a ViewHolder is supposed to do). I could easily use the inflate method on the generated binder in the buildView() and store the ViewBinding object in the View tag for example. What's your take on this @elihart ?

    Eventually I got an AndroidStudio warning, even if it builds fine. Kotlin plugin issue? image

    opened by glureau-betclic 27
  • iOS/Swift version

    iOS/Swift version

    Hi,

    First of all, thank you for sharing this awesome library with the community! As far as I understand, at Airbnb you have also developed a version of Epoxy for iOS based on Swift. Is there any chance it will be open sourced as well?

    opened by filipwiech 25
  • Add support for Sticky Headers

    Add support for Sticky Headers

    Description

    • Add sticky header linear layout manager to the library
    • Add sample for sticky header using EpoxyAdapter
    • Implement sticky header functions in BaseEpoxyAdapter and EpoxyController with no-op as default behaviour so the sticky headers are available for both EpoxyController and EpoxyAdapter, YAY 🎉
    • Update the StickyHeaderLinearLayoutManager to use BaseEpoxyAdapter so it can be used with Epoxy controller or adapter
    • Update the sample app to showcase sticky header with EpoxyController

    Solves issue #164, please take a look at the issue thread to understand different solutions.

    Note

    • The experimental flag was added for Parcelize. It feels a bit overkill only for 1 use-case, but let me know your views

    Kindly feel free to provide suggestions to improve this or point potential issues. I'd also love to update the Wiki with a new section for sticky header once the PR is merged.

    Inspired from StickyHeadersLinearLayoutManager

    opened by AkshayChordiya 24
  • [epoxy-paging] Crash in the PagedListEpoxyController when trying to invoke `addTo` on a null model

    [epoxy-paging] Crash in the PagedListEpoxyController when trying to invoke `addTo` on a null model

    While trying to implement paged list + paged controller, I run into a crash that's not reliably reproducible. The crash seems to happen around 1 in 7 times, completely anecdotally.

    I'm attaching the stack trace: note how my custom controller (that extends PagedListEpoxyController) is not in the stack trace because the crash happens in PagedListEpoxyController internals.

    I will try to get a small sample app with a reliable reproducible test case. In the meantime, some information that could be useful:

    • In MyImplementationPagedListController, I have these two params:
      • modelBuildingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
      • diffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
    • I'm using an RxPagedListBuilder with setFetchScheduler(Schedulers.io())
    • The problem reproduces more frequently with a very small page size: I used size 4 on a total list size 22

    I noticed that PagedListModelCache holds null models, I am guessing one of those is likely mistakenly being passed through in getModels().

    java.lang.NullPointerException: Attempt to invoke virtual method 'void com.airbnb.epoxy.EpoxyModel.addTo(com.airbnb.epoxy.EpoxyController)' on a null object reference
    at com.airbnb.epoxy.EpoxyController.add(EpoxyController.java:465)
    at com.airbnb.epoxy.paging.PagedListEpoxyController.addModels(PagedListEpoxyController.kt:78)
    at com.airbnb.epoxy.paging.PagedListEpoxyController.buildModels(PagedListEpoxyController.kt:70)
    at com.airbnb.epoxy.EpoxyController$1.run(EpoxyController.java:267)
    at android.os.Handler.handleCallback(Handler.java:751)
    at android.os.Handler.dispatchMessage(Handler.java:95)
    at android.os.Looper.loop(Looper.java:154)
    at android.os.HandlerThread.run(HandlerThread.java:61)
    
    opened by rohandhruva 23
  • Add support for incremental annotation processing in Gradle

    Add support for incremental annotation processing in Gradle

    With the release of Gradle 4.7, it now supports incremental annotation processing.

    I'm really not sure if Epoxy falls into the two categories supported but assuming it is, it would likely help improve build times.

    opened by jeremy-techson 20
  • No value passed for parameter

    No value passed for parameter

    In EpoxyModel i'm trying to pass data through constructor and not using @EpoxyAttribute annotation. Parameter has a default value. Before I was using kapt and there was no problem. When I switched to KSP there is such an error: No value passed for parameter 'isFullScreen'

    Can't I use default parameter values?

    @EpoxyModelClass(layout = R.layout.component_loading)
    abstract class ProgressModel(
        private val isFullScreen: Boolean = false
    ) : EpoxyModelWithHolder<ProgressModel.ProgressHolder>() {
    
        inner class ProgressHolder : EpoxyHolder() {
    
            lateinit var binding: ComponentLoadingBinding
                private set
    
            override fun bindView(itemView: View) {
                binding = ComponentLoadingBinding.bind(itemView)
                
                itemView.layoutParams = itemView.layoutParams.apply {
                    height = if (isFullScreen) MATCH_PARENT else WRAP_CONTENT
                }
            }
        }
    }
    
    opened by zokirjonkodirov 3
  • ACTIVITY_RECYCLER_POOL leak when use Carousel inside EpoxyModelGroup

    ACTIVITY_RECYCLER_POOL leak when use Carousel inside EpoxyModelGroup

    Tested on Android API 30 and API 33, Epoxy version 5.1.1, Leakcanary version 2.10

    Simple app with default Carousel inside EpoxyModelGroup:

    CarouselEpoxyModel:

    @EpoxyModelClass
    abstract class CarouselExampleModel(
        carouselModel: EpoxyModel<out Carousel>
    ) : EpoxyModelGroup(R.layout.epoxy_carousel_model, carouselModel)
    

    R.layout.epoxy_carousel_model

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    

    BannerView:

    @ModelView(autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT)
    internal class BannerView : MaterialCardView {
    
        constructor(context: Context) : super(context)
        constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
    
        @set:ModelProp
        var model: Data? = null
    
        @AfterPropsSet
        fun bind() {
            // no ops 
        }
    }
    

    buildModels():

    override fun buildModels(data: List<Data>) {
        val models = data.mapIndexed { index, it ->
                BannerViewModel_()
                .id(index)
                .model(it)
        }
    
        val carouselModel = CarouselModel_()
                    .id("carousel", "id")
                    .models(models)
    
         carouselExample(carouselModel) {
              id("carousel_exaple", "example_id")
         }
    }
    

    And in Fragment onDestroyView I add this line: epoxyRecycler.clear() (without this line behavior the same)

    Leak happens when I navigate to another fragment.

    But there is no leak if I don' t use EpoxyModelGroup:

    override fun buildModels(data: List<Data>) {
        val models = data.mapIndexed { index, it ->
                BannerViewModel_()
                .id(index)
                .model(it)
        }
    
        val carouselModel = CarouselModel_()
                    .id("carousel", "id")
                    .models(models)
    
         add(carouselModel) // No leak if I just add model without EpoxyModelGroup
    }
    

    PS: the code is simplified, the EpoxyModelGroup is needed for a more complex ui, but the leak is reproducible with this simple EpoxyModelGroup.

    Leak info:

    ┬───
    │ GC Root: Thread object
    │
    ├─ android.net.ConnectivityThread instance
    │    Leaking: NO (PathClassLoader↓ is not leaking)
    │    Thread name: 'ConnectivityThread'
    │    ↓ Thread.contextClassLoader
    ├─ dalvik.system.PathClassLoader instance
    │    Leaking: NO (EpoxyRecyclerView↓ is not leaking and A ClassLoader is never
    │    leaking)
    │    ↓ ClassLoader.runtimeInternalObjects
    ├─ java.lang.Object[] array
    │    Leaking: NO (EpoxyRecyclerView↓ is not leaking)
    │    ↓ Object[241]
    ├─ com.airbnb.epoxy.EpoxyRecyclerView class
    │    Leaking: NO (a class is never leaking)
    │    ↓ static EpoxyRecyclerView.ACTIVITY_RECYCLER_POOL
    │                               ~~~~~~~~~~~~~~~~~~~~~~
    ├─ com.airbnb.epoxy.ActivityRecyclerPool instance
    │    Leaking: UNKNOWN
    │    Retaining 52 B in 3 objects
    │    ↓ ActivityRecyclerPool.pools
    │                           ~~~~~
    ├─ java.util.ArrayList instance
    │    Leaking: UNKNOWN
    │    Retaining 40 B in 2 objects
    │    ↓ ArrayList[0]
    │               ~~~
    ├─ com.airbnb.epoxy.PoolReference instance
    │    Leaking: UNKNOWN
    │    Retaining 44 B in 2 objects
    │    ↓ PoolReference.viewPool
    │                    ~~~~~~~~
    ├─ com.airbnb.epoxy.UnboundedViewPool instance
    │    Leaking: UNKNOWN
    │    Retaining 592,5 kB in 11066 objects
    │    ↓ UnboundedViewPool.scrapHeaps
    │                        ~~~~~~~~~~
    ├─ android.util.SparseArray instance
    │    Leaking: UNKNOWN
    │    Retaining 592,0 kB in 11054 objects
    │    ↓ SparseArray.mValues
    │                  ~~~~~~~
    ├─ java.lang.Object[] array
    │    Leaking: UNKNOWN
    │    Retaining 591,9 kB in 11052 objects
    │    ↓ Object[2]
    │            ~~~
    ├─ java.util.LinkedList instance
    │    Leaking: UNKNOWN
    │    Retaining 573,8 kB in 10473 objects
    │    ↓ LinkedList[0]
    │                ~~~
    ├─ com.airbnb.epoxy.EpoxyViewHolder instance
    │    Leaking: UNKNOWN
    │    Retaining 573,8 kB in 10471 objects
    │    ↓ EpoxyViewHolder.epoxyHolder
    │                      ~~~~~~~~~~~
    ├─ com.airbnb.epoxy.ModelGroupHolder instance
    │    Leaking: UNKNOWN
    │    Retaining 571,7 kB in 10426 objects
    │    ↓ ModelGroupHolder.modelGroupParent
    │                       ~~~~~~~~~~~~~~~~
    ├─ com.airbnb.epoxy.EpoxyRecyclerView instance
    │    Leaking: UNKNOWN
    │    Retaining 571,6 kB in 10423 objects
    │    View not part of a window view hierarchy
    │    View.mAttachInfo is null (view detached)
    │    View.mID = R.id.epoxyRecycler
    │    View.mWindowAttachCount = 1
    │    mContext instance of com.myapp.MyActivity with mDestroyed = false
    │    ↓ View.mParent
    │           ~~~~~~~
    ├─ android.widget.FrameLayout instance
    │    Leaking: UNKNOWN
    │    Retaining 4,7 kB in 103 objects
    │    View not part of a window view hierarchy
    │    View.mAttachInfo is null (view detached)
    │    View.mID = R.id.root
    │    View.mWindowAttachCount = 1
    │    mContext instance of com.myapp.MyActivity with mDestroyed = false
    │    ↓ View.mParent
    │           ~~~~~~~
    ╰→ androidx.coordinatorlayout.widget.CoordinatorLayout instance
          Leaking: YES (ObjectWatcher was watching this because com.myapp.
          MyFragment received Fragment#onDestroyView()
          callback (references to its views should be cleared to prevent leaks))
          Retaining 538,6 kB in 9486 objects
          key = 3f91241b-7fd8-412a-a0ae-3c5346fedd32
          watchDurationMillis = 5534
          retainedDurationMillis = 533
          View not part of a window view hierarchy
          View.mAttachInfo is null (view detached)
          View.mID = R.id.rootView
          View.mWindowAttachCount = 1
          mContext instance of com.myapp.MyActivity with mDestroyed = false
    
    
    opened by bitvale 1
  • Strange ImmutableModelException

    Strange ImmutableModelException

    Our crashlytics reports ImmutableModelException

    com.airbnb.epoxy.ImmutableModelException: The model was changed during the bind call. Position: 2 Model: IncomingMessageEpoxyModel_{message=IncomingMessageModel(id=2, title=Title text, text=Message text, time=15:18, avatar=Local(image=2131231072))}IncomingMessageEpoxyModel_{id=71303202, viewType=2131558589, shown=true, addedToAdapter=false}
    
    Epoxy attribute fields on a model cannot be changed once the model is added to a controller. Check that these fields are not updated, or that the assigned objects are not mutated, outside of the buildModels method. The only exception is if the change is made inside an Interceptor callback. Consider using an interceptor if you need to change a model after it is added to the controller and before it is set on the adapter. If the model is already set on the adapter then you must call `requestModelBuild` instead to recreate all models.
        at com.airbnb.epoxy.EpoxyModel.validateStateHasNotChangedSinceAdded(EpoxyModel.java:466)
    

    The problem is (as i see) that the model was not changed during the bind call. Epoxy model has only one @EpoxyAttribute field IncomingMessageModel, which is data class with only val params

    sealed class MessageModel(
        open val id: String
    )
    
    data class IncomingMessageModel(
        override val id: String,
        val title: String?,
        val text: CharSequence?,
        val time: String,
        val avatar: Image // image here is always Image.Local
    ) : MessageModel(id) 
    
    sealed class Image {
        data class Remote(val image: List<ImageVariant>): Image()
        data class Local(@DrawableRes val image: Int): Image()
    }
    

    and single onBind listener which defined as val field inside controller

    private val messageModelBind: (EpoxyModel<*>, Any, Int) -> Unit = { _, _, position ->
        if (position > messages.size - ITEMS_BEFORE_LOADING) loadNewPageListener.invoke()
    }
    

    which used in buildModels() method like this

    is IncomingMessageModel -> {
        IncomingMessageEpoxyModel_()
            .id(index)
            .message(message)
            .onBind(messageModelBind)
            .addTo(this)
    }
    

    index here is an index in messages list which never changes for already added models (new messages added to the end of list)

    I can't reproduce this error (as i see from crashlytics it constantly occures only on single device for one user with Horor 10I, right after screen open), and have no ideas what is the reason, this model used only in one controller on single screen and has unique layout id (so i think its not model caching problem or something like that)

    Epoxy version is 4.4.4

    opened by densakh 1
  • Task :app:processDebugMainManifest FAILED

    Task :app:processDebugMainManifest FAILED

    <manifest android:hardwareAccelerated="true"

    android:appComponentFactory="someString"
    android:versionCode="10000"
    android:versionName="1.0.0"
    package="com.lesath.tabs"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    <supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:resizeable="true" android:smallScreens="true" android:xlargeScreens="true" />
    <uses-permission android:name="android.permission.INTERNET"  />
    <application android:hardwareAccelerated="true"
        android:icon="@mipmap/ic_launcher"
        tools:replace="android:appComponentFactory,allowBackup,icon,theme,label,name" android:label="@string/app_name"
    

    android:theme="@style/Theme.AppCompat.Light" android:allowBackup="" android:name="" android:networkSecurityConfig="@xml/network_security_config" android:appComponentFactory="someString" android:supportsRtl="true">

                <action android:name="android.intent.action.MAIN" />
    
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="shop" />
            </intent-filter>
            <intent-filter android:autoVerify="true">
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:host="tcshop.ionicecommerce.com" android:pathPrefix="/" android:scheme="http" />
                <data android:host=" " android:pathPrefix="/" android:scheme=" " />
                <data android:host=" " android:pathPrefix="/" android:scheme=" " />
                <data android:host=" " android:pathPrefix="/" android:scheme=" " />
                <data android:host=" " android:pathPrefix="/" android:scheme=" " />
            </intent-filter>
        </activity>
        <activity android:name="com.sarriaroman.PhotoViewer.PhotoActivity"
            android:theme="@android:style/Theme.Holo.NoActionBar.Fullscreen"
            android:exported="true"/>
        <provider android:authorities="${applicationId}.emailcomposer.provider"
            android:exported="true"
            android:grantUriPermissions="true"
            android:name="de.appplant.cordova.emailcomposer.Provider">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/emailcomposer_provider_paths" />
        </provider>
        <receiver android:enabled="true"
            android:name="nl.xservices.plugins.ShareChooserPendingIntent"
            android:exported="true">
    
            <intent-filter>
                <action android:name="android.intent.action.SEND" android:exported="true" />
    
            </intent-filter>
        </receiver>
        <provider android:authorities="${applicationId}.sharing.provider"
            android:exported="true"
            android:grantUriPermissions="true"
            android:name="nl.xservices.plugins.FileProvider">
            <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/sharing_paths" />
        </provider>
        <amazon:enable-feature
            android:name="com.amazon.device.messaging"
            android:required="false"
            xmlns:amazon="http://schemas.amazon.com/apk/res/android" />
        <service android:exported="true"
            android:name="com.onesignal.ADMMessageHandler" />
        <receiver
            android:name="com.onesignal.ADMMessageHandler$Receiver"
            android:permission="com.amazon.device.messaging.permission.SEND"
            android:exported="true">
            <intent-filter>
                <action android:name="com.amazon.device.messaging.intent.REGISTRATION" />
                <action android:name="com.amazon.device.messaging.intent.RECEIVE" />
                <category android:name="com.lesath.tabs" />
            </intent-filter>
        </receiver>
    </application>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-feature android:name="android.hardware.location.gps" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="com.amazon.device.messaging.permission.RECEIVE" />
    <permission android:name="com.lesath.tabs.permission.RECEIVE_ADM_MESSAGE" android:protectionLevel="signature" />
    <uses-permission android:name="com.lesath.tabs.permission.RECEIVE_ADM_MESSAGE" />
    
    opened by pheto123 1
  • Unable to refresh the listening callback

    Unable to refresh the listening callback

    eg: private fun buildHeader(state: HeaderState){

        headerViewModelBulder{ // headerViewModelBulder is generated by Expoxy
            btnClick{
                 Log.d("get data:${state.name}")     
            }
        }
    

    }

    When the data changes, headerViewModelBulder() executes again, but the data retrieved from the log remains unchanged because the state object points to the previous address.

    The reason is that the callback was not updated and was set for the first time。

    Corresponding generated code logic:

    if (((btnClick_Function0 == null) != (that.btnClick_Function0 == null))) {
    object.seBtnClick(......); //Update callback again, but not executed here. }

    I think the ability to repeatedly set callbacks should be supported

    opened by TonyYu668 0
Releases(5.1.1)
  • 5.1.1(Nov 3, 2022)

  • 5.1.0(Oct 21, 2022)

    Updates Kotlin to 1.7.20 and KSP to 1.7.20-1.0.7, as well as the room compiler processing (xprocessing) library to 2.5.0-beta01.

    Also deletes the epoxy-paging artifact in favor of the newer epoxy-paging3

    Source code(tar.gz)
    Source code(zip)
  • 5.0.0(Sep 29, 2022)

    5.0.0

    This adds support for Kotlin Symbol Processing, while maintaining backwards compatibility with java annotation processing via the xprocessing library from Room.

    This includes a major version bump to 5.0.0 because there may be slight behavior differences with KSP, especially for generic types in generated code. For example, if you previously had an epoxy attribute in java source code with a raw type it may now appear in the generated code with a wildcard type, which may require tweaking the type that is passed to the model.

    Additionally, some type checking was improved, for example more accurate validation of proper equals and hashcode implementations.

    To use Epoxy with KSP, simply apply it with the ksp gradle plugin instead of kapt (https://github.com/google/ksp/blob/main/docs/quickstart.md). See the new epoxy-kspsample module for an example.

    Note that unfortunately the databinding processor does NOT support KSP, simply because Android databinding itself uses KAPT and KSP cannot currently depend on KAPT sources. The code changes are in place to enable KSP with databinding once the databinding plugin from Android supports KSP (although this is unlikely) - alternatively it may be possible to configure the KSP plugin to run after KAPT and depend on its outputs (you're on your own if you want to try that).

    Also, parallel processing support was removed because it is not compatible with KSP.

    We have also added easy interop with Jetpack Compose via functions in the epoxy-composeinterop artifact. See the epoxy-composesample module for example usage.

    Source code(tar.gz)
    Source code(zip)
  • 5.0.0beta03(Nov 10, 2021)

    Fixes an empty list crash in the processor when a view with @ModelView extends a subclass that has a @ModelProp annotation with a default parameter value.

    Source code(tar.gz)
    Source code(zip)
  • 5.0.0beta02(Nov 3, 2021)

    This adds support for Kotlin Symbol Processing, while maintaining backwards compatibility with java annotation processing via the xprocessing library from Room. Note that compilation speed with epoxy in KSP is not yet faster than KAPT due to some optimizations that remain to be made, but those should be made before long.

    This includes a major version bump to 5.0.0 because there may be slight behavior differences with KSP, especially for generic types in generated code. For example, if you previously had an epoxy attribute in java source code with a raw type it may now appear in the generated code with a wildcard type, which may require tweaking the type that is passed to the model.

    Additionally, some type checking was improved, for example more accurate validation of proper equals and hashcode implementations.

    To use Epoxy with KSP, simply apply it with the ksp gradle plugin instead of kapt (https://github.com/google/ksp/blob/main/docs/quickstart.md). See the new epoxy-kspsample module for an example.

    Note that unfortunately the databinding processor does NOT support KSP, simply because Android databinding itself uses KAPT and KSP cannot currently depend on KAPT sources. The code changes are in place to enable KSP with databinding once the databinding plugin from Android supports KSP (although this is unlikely) - alternatively it may be possible to configure the KSP plugin to run after KAPT and depend on its outputs (you're on your own if you want to try that).

    Also, parallel processing support was removed because it is not compatible with KSP.

    KSP Generated Sources in IDE

    Note, that as of the current KSP version generated java sources are detected by the IDE but NOT generated kotlin sources. This means that generated epoxy kotlin extensions will not automatically be resolved in the IDE. You must manually configure your source sets to include ksp generated folders.

    You can add this to your root build.gradle file to work around this

    subprojects { project ->
        afterEvaluate {
            if (project.hasProperty("android")) {
                android {
                    if (it instanceof com.android.build.gradle.LibraryExtension) {
                        libraryVariants.all { variant ->
                            def outputFolder = new File("build/generated/ksp/${variant.name}/kotlin")
                            variant.addJavaSourceFoldersToModel(outputFolder)
                            android.sourceSets.getAt(variant.name).java {
                                srcDir(outputFolder)
                            }
                        }
                    } else if (it instanceof com.android.build.gradle.AppExtension) {
                        applicationVariants.all { variant ->
                            def outputFolder = new File("build/generated/ksp/${variant.name}/kotlin")
                            variant.addJavaSourceFoldersToModel(outputFolder)
                            android.sourceSets.getAt(variant.name).java {
                                srcDir(outputFolder)
                            }
                        }
                    }
               }
          }
    }
    

    Of if you use kotlin build files you can apply it like this to a project.

        private fun Project.registerKspKotlinOutputAsSourceSet() {       
            afterEvaluate {
                val android: BaseExtension by lazy { extensions.findByType(BaseExtension::class.java) }
    
                requireAndroidVariants().forEach { variant ->
                    val variantName = variant.name
                    val outputFolder = File("build/generated/ksp/$variantName/kotlin")
                    variant.addJavaSourceFoldersToModel(outputFolder)
                    android.sourceSets.getAt(variantName).java {
                        srcDir(outputFolder)
                    }
                }
            }
        }
    
    /**
     * Return the Android variants for this module, or error if this is not a module with a known Android plugin.
     */
    fun Project.requireAndroidVariants(): DomainObjectSet<out BaseVariant> {
        return androidVariants() ?: error("no known android extension found for ${project.name}")
    }
    
    /**
     * Return the Android variants for this module, or null if this is not a module with a known Android plugin.
     */
    fun Project.androidVariants(): DomainObjectSet<out BaseVariant>? {
        return when (val androidExtension = this.extensions.findByName("android")) {
            is LibraryExtension -> {
                androidExtension.libraryVariants
            }
            is AppExtension -> {
                androidExtension.applicationVariants
            }
            else -> null
        }
    }
    
    Source code(tar.gz)
    Source code(zip)
  • 4.6.3(Sep 9, 2021)

    • Add EpoxyModel#preBind hook(#1225)
    • Add unbind extension to ItemViewBindingEpoxyHolder (#1223)
    • Add missing loadStateFlow to PagingDataEpoxyController (#1209)
    Source code(tar.gz)
    Source code(zip)
  • 4.6.2(Jun 11, 2021)

  • 4.6.1(May 13, 2021)

    4.6.1 (May 13, 2021)

    Adds "epoxyDisableDslMarker" annotation processor flag which you can use to delay migration to the model building scope DLSMarker introduced in 4.6.0 if it is a large breaking change for your project.

    Note that this only applies to your project modules that you apply it to, and does not apply to the handful of models that ship with the Epoxy library (like the Carousel or group builder).

    For example:

    project.android.buildTypes.all { buildType ->
        buildType.javaCompileOptions.annotationProcessorOptions.arguments =
                [
                        epoxyDisableDslMarker     : "true",
                ]
    }
    
    Source code(tar.gz)
    Source code(zip)
  • 4.6.0(May 13, 2021)

    4.6.0 (May 12, 2021)

    New Feature!

    Epoxy View Binder (#1175) Bind epoxy models to views outside of a RecyclerView.

    Potentially Breaking

    • Use kotlin dsl marker for model building receivers (#1180)

    This change uses Kotlin's DSL marker annotation to enforce proper usage of model building extension functions. You may now need to change some references in your model building code to explicitly reference properties with this.

    Source code(tar.gz)
    Source code(zip)
  • 4.5.0(Apr 14, 2021)

    Fix generated code consistency in builder interfaces (#1166) Provided support to invalidate modelCache in PagingDataEpoxyController (#1161) Explicitly add public modifier (#1162) Unwrap context to find parent activity in order to share viewpool when using Hilt (#1157)

    Source code(tar.gz)
    Source code(zip)
  • 4.4.4(Mar 24, 2021)

  • 4.4.3(Mar 18, 2021)

  • 4.4.2(Mar 17, 2021)

    A minor regression was introduced from this PR(https://github.com/airbnb/epoxy/commit/fa61cfbc4059f88a3e89644ab21b3f8d70aa7c57). Avoid using this release and wait for the next one with the fix.

    Source code(tar.gz)
    Source code(zip)
  • 4.4.1(Feb 19, 2021)

  • 4.3.1(Dec 2, 2020)

    • Fix ANR and view pool resolution in nested group (#1101)
    • ModelGroupHolder get recycle pool from parent (#1097)
    • Add support for EpoxyModelGroup in the EpoxyVisibilityTracker (#1091)
    • Convert EpoxyVisibilityTracker code to Kotlin (#1090)

    Breaking Changes

    Note that due to the conversion of EpoxyVisibilityTracker to kotlin you now need to access - EpoxyVisibilityTracker.partialImpressionThresholdPercentage as a property epoxyVisibilityTracker.setPartialImpressionThresholdPercentage(value) -> epoxyVisibilityTracker.partialImpressionThresholdPercentage = value`

    Also, the ModelGroupHolder improvement required the ModelGroupHolder#createNewHolder function to change its signature to accept a ViewParent parameter.

    If you override createNewHolder() anywhere you will need to change it to createNewHolder(@NonNull ViewParent parent)

    Source code(tar.gz)
    Source code(zip)
  • 4.2.0(Nov 11, 2020)

  • 4.1.0(Sep 18, 2020)

    • Fix some synchronization issues with the parallel Epoxy processing option
    • Add view visibility checks to EpoxyVisibilityItem and decouple RecyclerView #1052
    Source code(tar.gz)
    Source code(zip)
  • 4.0.0(Sep 8, 2020)

    New

    • Incremental annotation processing for faster builds
    • Support for Android Jetpack Paging v3 library in new epoxy-paging3 artifact
    • Model group building with Kotlin DSL (#1012)
    • Update to Kotlin 1.4.0
    • A new annotation processor argument logEpoxyTimings can be set to get a detailed breakdown of how long the processors took and where they spent their time (off by default)
    • Another new argument enableParallelEpoxyProcessing can be set to true to have the annotation processor process annotations and generate files in parallel (via coroutines).

    You can enable these processor options in your build.gradle file like so:

    project.android.buildTypes.all { buildType ->
      buildType.javaCompileOptions.annotationProcessorOptions.arguments =
          [
              logEpoxyTimings  : "true",
              enableParallelEpoxyProcessing     : "true"
          ]
    }
    

    Parallel processing can greatly speed up processing time (moreso than the incremental support), but given the hairy nature of parallel processing it is still incubating. Please report any issues or crashes that you notice. (We are currently using parallel mode in our large project at Airbnb with no problems.)

    • Add options to skip generation of functions for getters, reset, and method overloads to reduce generated code
      • New annotation processor options are:
        • epoxyDisableGenerateOverloads
        • epoxyDisableGenerateGetters
        • epoxyDisableGenerateReset

    Fixes

    • Synchronize ListUpdateCallback and PagedListModelCache functions (#987)
    • Avoid generating bitset checks in models when not needed (reduces code size)
    • Fix minor memory leak

    Breaking

    • Annotations that previously targeted package elements now target types (classes or interfaces). This includes: EpoxyDataBindingPattern, EpoxyDataBindingLayouts, PackageModelViewConfig, PackageEpoxyConfig This was necessary to work around an incremental annotation processor issue where annotation on package-info elements are not properly recompiled

    • In order to enable incremental annotation processing a change had to be made in how the processor of @AutoModel annotations work. If you use @AutoModel in an EpoxyController the annotated Model types must be either declared in a different module from the EpoxyController, or in the same module in the same java package.

      Also make sure you have kapt error types enabled.

      However, generally @AutoModel is considered legacy and is not recommended. It is a relic of Java Epoxy usage and instead the current best practice is to use Kotlin with the Kotlin model extension functions to build models.

    • Removed support for generating Epoxy models from Litho components

    Source code(tar.gz)
    Source code(zip)
  • 4.0.0-beta6(Aug 6, 2020)

  • 4.0.0-beta5(Jul 10, 2020)

    Fixes:

    • An occasional processor crash when the option to log timings is enabled
    • Incremental annotation processing of databinding models would fail to generate models (https://github.com/airbnb/epoxy/issues/1014)

    Breaking!

    • The annotation that support databinding, EpoxyDataBindingLayouts and EpoxyDataBindingPattern, must now be placed on a class or interface instead of in a package-info.java file. The interface or class must be in Java, Kotlin is not supported. This is necessary to support incremental processing.

    Example usage:

    package com.example.app;
    import com.airbnb.epoxy.EpoxyDataBindingLayouts;
    import com.airbnb.epoxy.EpoxyDataBindingPattern;
    @EpoxyDataBindingPattern(rClass = R.class, layoutPrefix = "my_view_prefix")
    @EpoxyDataBindingLayouts({R.layout.my_model_layout})
    interface EpoxyDataBindingConfig {} 
    
    Source code(tar.gz)
    Source code(zip)
  • 4.0.0-beta4(Jun 2, 2020)

  • 4.0.0-beta3(Jun 1, 2020)

    • Sorts functions in generated kotlin extension function files deterministically to prevent generated sources from changing. Can fix unexpected build cache misses.
    • Avoid generating bitset checks in models when not needed. Can decrease size of generated code and slightly improve runtime performance.
    • Add options to skip generation of functions for getters, reset, and method overloads to reduce generated code

    New annotation processor options are:

    • epoxyDisableGenerateOverloads
    • epoxyDisableGenerateGetters
    • epoxyDisableGenerateReset

    These can also be controlled (and overridden) on a per package level with the PackageModelViewConfig package annotation.

    Source code(tar.gz)
    Source code(zip)
  • 4.0.0-beta1(May 23, 2020)

    • Support for incremental annotation processing as an Aggregating processor (#972)
    • Removed Litho support
    • A new annotation processor argument logEpoxyTimings can be set to get a detailed breakdown of how long the processors took and where they spent their time (off by default)
    • Another new argument enableParallelEpoxyProcessing can be set to true to have the annotation processor process annotations and generate files in parallel (via coroutines).

    You can enable these processor options in your build.gradle file like so:

    project.android.buildTypes.all { buildType ->
      buildType.javaCompileOptions.annotationProcessorOptions.arguments =
          [
              logEpoxyTimings  : "true",
              enableParallelEpoxyProcessing     : "true"
          ]
    }
    

    Parallel processing can greatly speed up processing time (up to 8x), but given the nature of parallel processing it is still incubating. Please report any issues or crashes that you notice. (We are currently using parallel mode in our large project at Airbnb with no problems.)

    Breaking

    In order to enable incremental annotation processing a change had to be made in how the processor of @AutoModel annotations work. If you use @AutoModel in an EpoxyController the annotated Model types must be either declared in a different module from the EpoxyController, or in the same module in the same java package.

    Also make sure you have kapt error types enabled.

    However, generally @AutoModel is considered legacy and is not recommended. It is a relic of Java Epoxy usage and instead the current best practice is to use Kotlin with the Kotlin model extension functions to build models.

    Source code(tar.gz)
    Source code(zip)
  • 3.11.0(May 21, 2020)

  • 3.10.0(May 16, 2020)

    • Carousel building with Kotlin DSL (#967)
    • Android ViewBinding: added an example in the sample project. (#939)
    • Fix setter with default value lookup in kotlin 1.4 (#966)
    • Change "result" property name in generated model (#965)
    • Add support for Sticky Headers (#842)
    • Use measured width/height if it exists in Carousel. (#915)
    • Add a getter to EpoxyViewHolder.getHolder(). (#952) (#953)
    • Fix visibility tracking during RecyclerView animations (#962)
    • Fix leak in ActivityRecyclerPool ((#906)
    • Rename ResultCallack to ResultCallback in AsyncEpoxyDiffer (#899)
    • Fix incorrect license attributes in POM file (#898)
    Source code(tar.gz)
    Source code(zip)
  • 3.9.0(Dec 17, 2019)

    • Fix reading EpoxyDataBindingPattern enableDoNotHash (#837)
    • Make EpoxyRecyclerView.setItemSpacingPx() open (#829)
    • Use same version for Mockito Core and Inline (#860)
    • Minor documentation and variable name updates. (#870)
    • Move epoxy-modelfactory tests to their own module (#834)
    • Remove executable bit from non-executable files (#864)
    • Various repo clean ups and version bumps
    Source code(tar.gz)
    Source code(zip)
  • 3.8.0(Sep 16, 2019)

    • Add support for Kotlin delegation via annotated interface properties #812
    • Fix checked change crash and improve debug errors #806
    • Remove extra space in Kotlin extensions #777
    • Update project to AGP 3.5, Kotlin 1.3.50, Gradle 5.6
    Source code(tar.gz)
    Source code(zip)
  • 3.7.0(Jul 1, 2019)

  • 3.6.0(Jun 18, 2019)

    • New Image prefetching system with glide extensions https://github.com/airbnb/epoxy/pull/766
      • Docs at https://github.com/airbnb/epoxy/wiki/Image-Preloading
    • Fixed model click listener crashing on nested model https://github.com/airbnb/epoxy/pull/767
    Source code(tar.gz)
    Source code(zip)
  • 3.5.1(Jun 5, 2019)

ANDROID. ChipsLayoutManager (SpanLayoutManager, FlowLayoutManager). A custom layout manager for RecyclerView which mimicric TextView span behaviour, flow layouts behaviour with support of amazing recyclerView features

ChipsLayoutManager This is ChipsLayoutManager - custom Recycler View's LayoutManager which moves item to the next line when no space left on the curre

Oleg Beloy 3.2k Dec 25, 2022
TikTok-RecyclerView - This is a demo app built using 'Koin' a new dependency injection framework for Android along with RecyclerView and ExoPlayer2.

TikTok-RecyclerView Demo About This is a demo app built using 'Koin' a new dependency injection framework for Android along with RecyclerView and ExoP

Baljeet Singh 19 Dec 28, 2022
A RecyclerView that implements pullrefresh and loadingmore featrues.you can use it like a standard RecyclerView

XRecyclerView a RecyclerView that implements pullrefresh , loadingmore and header featrues.you can use it like a standard RecyclerView. you don't need

XRecyclerView 5.3k Dec 26, 2022
A RecyclerView that implements pullrefresh and loadingmore featrues.you can use it like a standard RecyclerView

XRecyclerView a RecyclerView that implements pullrefresh , loadingmore and header featrues.you can use it like a standard RecyclerView. you don't need

XRecyclerView 5.3k Dec 26, 2022
Carousel Recyclerview let's you create carousel layout with the power of recyclerview by creating custom layout manager.

Carousel Recyclerview let's you create carousel layout with the power of recyclerview by creating custom layout manager.

Jack and phantom 504 Dec 25, 2022
RecyclerView : SleepQualityTracker with RecyclerView app

RecyclerView - SleepQualityTracker with RecyclerView app SleepQualityTracker with RecyclerView This app builds on the SleepQualityTracker developed pr

Kevin 2 May 14, 2022
Pagination-RecyclerView - Simple and easy way to Paginating a RecyclerView

Pagination-RecyclerView Simple and easy way to Paginating a RecyclerView Android

Rakshit Nawani 0 Jan 3, 2022
An Android Animation library which easily add itemanimator to RecyclerView items.

RecyclerView Animators RecyclerView Animators is an Android library that allows developers to easily create RecyclerView with animations. Please feel

Daichi Furiya 11.2k Jan 8, 2023
Android library providing simple way to control divider items (ItemDecoration) of RecyclerView

RecyclerView-FlexibleDivider Android library providing simple way to control divider items of RecyclerView Release Note [Release Note] (https://github

Yoshihito Ikeda 2.4k Dec 18, 2022
Android Library to provide swipe, click and other functionality to RecyclerView

RecyclerViewEnhanced Android Library to provide swipe, click and other functionality to RecyclerView Usage Add this to your build.gradle file dependen

Nikhil Panju 1k Dec 29, 2022
Android library defining adapter classes of RecyclerView to manage multiple view types

RecyclerView-MultipleViewTypeAdapter RecyclerView adapter classes for managing multiple view types Release Note [Release Note] (https://github.com/yqr

Yoshihito Ikeda 414 Nov 21, 2022
Android library for RecyclerView to manage order of items and multiple view types.

recyclerview-binder Android Library for RecyclerView to manage order of items and multiple view types. Features Insert any items to wherever you want

Satoru Fujiwara 185 Nov 15, 2022
Android Library to provide swipe, click and other functionality to RecyclerView

RecyclerViewEnhanced Android Library to provide swipe, click and other functionality to RecyclerView Usage Add this to your build.gradle file dependen

Nikhil Panju 1k Dec 29, 2022
An android library for quick setup of RecyclerView

SmartRecyclerView for Android An android library to quickly setup RecyclerView(List) with SwipeRefreshLayout Support, written entirely in Kotlin. Supp

Kunal Pasricha 16 Oct 18, 2022
RecyclerView extension library which provides advanced features. (ex. Google's Inbox app like swiping, Play Music app like drag and drop sorting)

Advanced RecyclerView This RecyclerView extension library provides Google's Inbox app like swiping, Play Music app like drag-and-drop sorting and expa

Haruki Hasegawa 5.2k Dec 23, 2022
An efficient TabLayout library implemented with RecyclerView.

RecyclerTabLayout An efficient TabLayout library implemented with RecyclerView. Features Efficient when having many tabs Easy setup with ViewPager (sa

Shinichi Nishimura 1.3k Dec 9, 2022
the library is a loop RecyclerView(expression), can show some effects when display

CircleRecyclerView the library is a loop RecyclerView, can show some effects when display screenshot CircularViewMode ScaleXViewMode & ScaleYViewMode

Matt Yao 704 Jan 5, 2023
Elegant design and convenient to use RecyclerView adapter library based on Kotlin DSL.

xAdapter: Kotlin DSL 风格的 Adapter 封装 1、简介 该项目是 KotlinDSL 风格的 Adapter 框架封装,用来简化 Adapter 调用,思想是采用工厂和构建者方式获取 Adapter 避免代码中定义大量的 Adapter 类。该项目在 BRVAH 的 Ada

ShouHeng 17 Oct 9, 2022