# AGENTS.md
Guidance for AI coding agents that **integrate ByRecyclerView into an Android app** (and, secondarily, work on this repository). Consult this file explicitly when working in another repository: it is not automatically loaded there. API names below were checked against the source of release 1.4.2; code samples were not compiled.
## What this library is
ByRecyclerView is an Android **View-system** `RecyclerView` subclass (`me.jingbin.library.ByRecyclerView`) with pull-to-refresh, pull-up/automatic load more, header/footer/state (empty/loading/error) views, item and child click listeners, item decorations, sticky headers, skeleton screens and simplified adapters. It is **not** a Jetpack Compose component; do not recommend it for Compose-only screens.
- Status: actively maintained. Latest release: `1.4.2`, published through JitPack.
- `ByRecyclerView` can wrap a normal `RecyclerView.Adapter`; its `setPageData` convenience method requires a `BaseByRecyclerViewAdapter`. Evaluate migration from other libraries against their APIs rather than assuming a drop-in replacement.
- Pull-to-refresh is built in but optional (you can use `SwipeRefreshLayout` instead); the default load-more mode uses a pull-up gesture for short lists and triggers at the bottom for full lists. Automatic mode is opt-in.
## Integrating into an app
### 1. Add the JitPack repository (once)
Add it where the project declares repositories, keeping `google()` and `mavenCentral()`:
- `settings.gradle(.kts)` → `dependencyResolutionManagement { repositories { ... } }`, or
- root `build.gradle` → `allprojects { repositories { ... } }` for older projects.
```groovy
maven { url 'https://jitpack.io' }
```
```kotlin
maven { url = uri("https://jitpack.io") }
```
Do not add it in both places. If the project uses `RepositoriesMode.FAIL_ON_PROJECT_REPOS`, only use `settings.gradle`.
### 2. Add dependencies in the app module
```groovy
implementation 'com.github.youlookwhat:ByRecyclerView:1.4.2'
implementation 'androidx.recyclerview:recyclerview:1.3.0'
implementation 'com.google.android.material:material:1.10.0'
```
- RecyclerView and Material are `compileOnly` in the library, so **the app must declare them**. If the project already has them, keep its existing compatible versions.
- The coordinate is `com.github.youlookwhat:ByRecyclerView` (JitPack), **not** `me.jingbin.library`.
- AndroidX only. The old support-library build (`1.0.18-support`) is no longer supported; migrate to AndroidX first.
- Declared library `minSdk` is 14; the app's effective minimum also depends on the selected dependencies. The demo app uses 21.
- No ProGuard/R8 rules are needed.
### 3. Layout
```xml
```
`ByRecyclerView` extends `RecyclerView`. When replacing an existing view, verify the existing adapter, layout manager and event handling against the app's behavior.
### 4. Minimal setup (Kotlin)
Add these imports to an Activity:
```kotlin
import androidx.recyclerview.widget.LinearLayoutManager
import android.widget.TextView
import me.jingbin.library.ByRecyclerView
import me.jingbin.library.adapter.BaseByViewHolder
import me.jingbin.library.adapter.BaseRecyclerAdapter
```
After `setContentView(...)`, use the following local-data example. Replace the simulated responses with the app's data source. `android.R.layout.simple_list_item_1` supplies the item layout.
```kotlin
val recyclerView = findViewById(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(this)
val adapter = object : BaseRecyclerAdapter(android.R.layout.simple_list_item_1) {
override fun bindView(holder: BaseByViewHolder, bean: String, position: Int) {
holder.setText(android.R.id.text1, bean)
}
}
recyclerView.adapter = adapter // must come before setPageData()
adapter.setPageEmptyView(TextView(this).apply { text = "No items" })
recyclerView.setOnRefreshListener {
adapter.setPageData(true, arrayListOf("First", "Second"))
}
recyclerView.setOnLoadMoreListener(true, 1) { // true = automatic load more
adapter.setPageData(false, arrayListOf()) // simulated end of list
}
adapter.setPageData(true, arrayListOf("First", "Second"))
```
Java uses `new ByRecyclerView.OnRefreshListener() {...}` / `OnLoadMoreListener() {...}`. For real data, follow the pagination and error-handling rules below. More examples: `README_EN.md` and `app/src/main/java/me/jingbin/byrecyclerview/`.
## Pagination and state: the most important rules
1. **Set the adapter before `setPageData()`.** `setPageData()` silently does nothing if the adapter is not attached to a `ByRecyclerView`.
2. **`setPageData(isFirstPage, data[, emptyLayoutId | emptyView])`**:
- first page, non-empty → hides state view, enables load more, replaces data (`setNewData`);
- first page, empty → shows the empty view, disables load more, clears data;
- later page, non-empty → appends and calls `loadMoreComplete()`;
- later page, empty → calls `loadMoreEnd()`.
Pass a **mutable** list. The empty view comes from `adapter.setPageEmptyView(...)` or the third argument. Do not use it when you want a state view (e.g. loading/error) above existing items.
3. **Failures are not empty pages.** On refresh failure call `recyclerView.setRefreshing(false)`; on load-more failure call `recyclerView.loadMoreFail()` and show an error. Advance the page counter only after success. After `loadMoreFail()`, tapping the failure view or pulling up again calls the load-more listener again.
4. **Manual mode** (without `setPageData`): `adapter.setNewData(list)` for the first page; `adapter.addData(list)` then `recyclerView.loadMoreComplete()` for later pages; `recyclerView.loadMoreEnd()` when there is no more data. `setNewData()` also stops the refresh animation.
5. `setRefreshing(true)` starts a refresh programmatically and invokes the refresh listener. `setRefreshing(false)` ends it and resets load-more state.
6. Call UI updates (`setPageData`, `addData`, `loadMore*`) on the main thread.
## ByRecyclerView API guide
### Pull-to-refresh
```java
recyclerView.setOnRefreshListener(() -> { /* load data, then adapter.setNewData(list) */ });
recyclerView.setOnRefreshListener(listener, delayMillis); // delays the callback; 300 ms base is added
recyclerView.setRefreshEnabled(false); // set AFTER the listener (setting the listener enables refresh)
recyclerView.setRefreshEnabled(true); // animation works even without a listener
recyclerView.setRefreshing(true); // start manually
recyclerView.setRefreshing(false); // finish; also resets load-more state
```
To use `SwipeRefreshLayout` instead, wrap the view in it and do **not** set `ByRecyclerView`'s refresh listener. In `ViewPager2`, drag interruption can be disabled with `setDispatchTouch(false)`.
### Load more
Setting a load-more listener enables load more; without one it stays off. Pick the mode through the overload:
```java
recyclerView.setOnLoadMoreListener(listener); // pull up and release (default)
recyclerView.setOnLoadMoreListener(listener, delayMillis); // delayed callback
recyclerView.setOnLoadMoreListener(true, listener); // automatic
recyclerView.setOnLoadMoreListener(true, preLoadNumber, listener); // automatic, load when N items from end (default 1)
recyclerView.setOnLoadMoreListener(true, preLoadNumber, listener, delayMillis);
recyclerView.loadMoreComplete(); // finished, can load again
recyclerView.loadMoreEnd(); // no more data, shows "no more" view
recyclerView.loadMoreFail(); // failed, tap or pull up to retry
recyclerView.setLoadMoreEnabled(false); // turns load more off entirely, not only automatic mode
recyclerView.setLoadMoreEnabled(true); // re-enable; has no effect unless a load-more listener is set (the load-more view is only active with a listener)
recyclerView.setNotFullScreenNoLoadMore(); // pull-up mode only: no load more when content is shorter than the screen
recyclerView.setLoadingMoreBottomHeight(50); // dp, extra space below the load-more view (e.g. for a translucent nav bar)
```
Pull-up mode: when the content is shorter than one screen it loads on pull-up; once it fills the screen it loads when scrolled to the bottom. Automatic mode loads without a pull gesture.
### Item and child click listeners
```java
recyclerView.setOnItemClickListener((v, position) -> { Bean b = adapter.getItemData(position); });
recyclerView.setOnItemLongClickListener((v, position) -> false);
// anti double-click variants
recyclerView.setOnItemClickListener(new OnItemFilterClickListener() {
@Override protected void onSingleClick(View v, int position) { }
});
// child views: register in the holder first, then listen on the recycler view
holder.addOnClickListener(R.id.tv_text); // in bindView(); chainable: holder.setText(...).addOnClickListener(...)
holder.addOnLongClickListener(R.id.tv_text);
recyclerView.setOnItemChildClickListener((view, position) -> { /* branch on view.getId() */ });
recyclerView.setOnItemChildClickListener(new OnItemChildFilterClickListener() {
@Override public void onSingleClick(View view, int position) { }
});
recyclerView.setOnItemChildLongClickListener((view, position) -> { });
```
`position` is already the data position (headers excluded). Filter classes live in `me.jingbin.library.view`.
### Header, footer and state views
```java
recyclerView.addHeaderView(view /* or layoutResId */); // each header is its own item type
recyclerView.removeHeaderView(view); // View only, no layoutId overload
recyclerView.removeAllHeaderView();
recyclerView.setHeaderViewEnabled(false); // hide all; then setNewData()/notifyDataSetChanged()
recyclerView.addFooterView(view /* or layoutResId */); // all footers share one item type
recyclerView.removeFooterView(view); // View only
recyclerView.removeAllFooterView();
recyclerView.setFootViewEnabled(false); // then refresh the adapter
recyclerView.setStateView(view /* or layoutResId */); // one view for empty / loading / error
recyclerView.setStateViewEnabled(false); // then setNewData()/notifyDataSetChanged()
recyclerView.getLayoutView(layoutResId); // inflate helper
```
- Order top to bottom: refresh view → header views → state view → content → footer views → load-more view.
- **Do not repeatedly add/remove headers.** Add once and show/hide the header's inner content, or use `setHeaderViewEnabled`.
- Headers and the state view can appear together (unlike `XRecyclerView`).
- With DataBinding, inflate a header against the parent: `DataBindingUtil.inflate(inflater, R.layout.layout_header, (ViewGroup) recyclerView.getParent(), false)`.
- Prefer `setStateViewEnabled(boolean)`. The two-argument overload still exists in the 1.4.2 source, but its immediate-removal code is commented out; do not rely on the second argument to refresh the adapter.
### Custom refresh / load-more views
```java
recyclerView.setRefreshHeaderView(new MyRefreshHeader(context)); // implement BaseRefreshHeader
recyclerView.setLoadingMoreView(new MyLoadMoreView(context)); // implement BaseLoadMore
```
A custom `BaseLoadMore` must implement `setState(int)` **and `getState()`** (store the state in a field); states are `STATE_LOADING`, `STATE_COMPLETE`, `STATE_NO_MORE`, `STATE_FAILURE`. Reference implementations: `SimpleLoadMoreView`, and in the demo `NeteaseRefreshHeaderView` / `NeteaseLoadMoreView`.
### Partial (payload) refresh
```java
adapter.refreshNotifyItemChanged(position, PAYLOAD_COLLECT); // adds header offset automatically
// in the adapter (optional override; falls back to bindView if not overridden)
@Override
protected void bindViewPayloads(BaseByViewHolder holder, Bean bean, int position, List