FusionAdapter is a modern, fail-safe RecyclerView adapter library designed for the Kotlin era.
It eliminates the tedious boilerplate of ViewHolders, ViewTypes, and DiffUtils, allowing you to build complex heterogeneous lists using a concise Kotlin DSL. With built-in Data Sanitization, Native Paging 3 Support, and ViewBinding, it bridges the gap between your data and UI with zero friction.
🔗 GitHub: https://github.com/woniu0936/FusionAdapter
FusionAdapter is not just another MultiType library. It is a next-generation adapter solution built to tackle "Boilerplate Explosion", "Concurrency Safety", and "Paging 3 Integration Pain" in large-scale Android projects.
| Feature | FusionAdapter | Epoxy (Airbnb) | MultiType | BRVAH (v4) |
|---|---|---|---|---|
| Paradigm | Reactive DSL (In-place) | Annotation Driven | Class Mapping | Inheritance Driven |
| Boilerplate | Zero (Minimalist) | High (Many Model classes) | Medium (Binders req.) | Medium (Base classes) |
| Data Safety | Sanitization Engine Silently drops bad data |
Implicit Ignore | Fail-Fast (Crash) | Undefined (Visual glitches) |
| Paging 3 | First-class Support Auto Placeholder/ID mgmt |
Extension lib req. | No native support | Compatibility mode |
| Build Penalty | Zero (Runtime only) | Significant (KAPT/KSP) | Zero | Zero |
| Concurrency | Immutable Runtime | Internal Sync | Thread-Unsafe | Thread-Unsafe |
| Learning Curve | Ultra Low | Very High | Low | Medium |
- No More "Class Explosion": Traditional solutions require a new
ViewHolderorItemBinderclass for every single UI style. With FusionAdapter, you just add a few lines to your DSL, keeping your codebase lean and maintainable. - Built for Production Stability: In large projects, backend responses might occasionally contain undefined types. FusionAdapter's Sanitization mechanism ensures that in Release builds, these invalid items are safely filtered out instead of crashing your app.
- Zero Build Overhead: Unlike Epoxy, which can add seconds to your build time via annotation processing, FusionAdapter has zero build-time impact, keeping your development cycle fast.
- Flawless Animation UX: Combined with Cascading Stable IDs generated by the FNV-1a 64-bit algorithm, FusionAdapter provides textbook-perfect RecyclerView animations, even in complex asynchronous Paging scenarios.
- ⚡ Minimalist DSL: Launch a multi-type list in a single block of code. No more creating separate Adapter/ViewHolder classes.
- 🛡️ Robust Sanitization:
- Debug: Crashes immediately on unregistered types to catch bugs early (Fail-Fast).
- Release: Silently drops invalid data to prevent crashes and layout corruption (Fail-Safe).
- 🧵 Concurrency Safe: Built on an Immutable Runtime for thread-safe operations and extreme performance.
- 📄 Native Paging 3: A dedicated
FusionPagingAdapterdeeply integrated with Paging 3. Supports deterministic placeholder IDs to fix UI flickering. - 🔀 Cascading Stable ID Strategy:
- Supports Router-level (Shared) and Delegate-level (Override) ID configurations.
- Built-in FNV-1a 64-bit hashing to resolve ID collisions across heterogeneous types.
- 📐 Layout Intelligence: Declare
spanSizeandfullSpanlogic directly within the item configuration. - 🚀 Memory & Logging Safety:
- Auto Leak Prevention: Automatically clears view tags in
onViewRecycledto strictly prevent memory leaks. - Enterprise Logging: High-performance async logging. Support for stripping debug logs in Release builds via ProGuard.
- Auto Leak Prevention: Automatically clears view tags in
- ☕ Java Friendly: Beyond Kotlin DSL, it provides full Builder Pattern support for Java developers.
Add the dependency to your module-level build.gradle.kts:
dependencies {
implementation("io.github.woniu0936:fusion-core:0.8.0")
// Optional: Native Paging 3 support
implementation("io.github.woniu0936:fusion-paging:0.8.0")
}Map a data type to a layout and bind it.
// In Activity / Fragment
val adapter = recyclerView.setupFusion {
// Register: Data Type (String) -> Layout (ItemTextBinding)
register(ItemTextBinding::inflate) {
// Configure Stable ID for performance (Optional)
stableId { it }
// onBind: 'this' is the ViewBinding, 'item' is the data
onBind { item ->
tvTitle.text = item
}
// onItemClick: Handle click events
onItemClick { item ->
toast("Clicked: $item")
}
}
}
// Submit list
adapter.submitList(listOf("Hello", "Fusion", "Adapter"))Handle scenarios where the same data class (Message) renders different layouts based on its state.
data class Message(val id: Long, val type: Int, val content: String)
recyclerView.setupFusion {
register<Message> {
// [Level 1] Router Level ID config
stableId { it.id }
// Define routing logic
match { it.type }
// [Inherit] Inherits Level 1 stableId automatically
map(TYPE_TEXT, ItemMsgTextBinding::inflate) {
onBind { msg -> ... }
}
// [Override] Override ID for special cases to prevent conflicts
map(TYPE_TIMELINE, ItemTimeLineBinding::inflate) {
// [Level 2] Delegate Level ID (Higher priority)
stableId { "${it.id}_time" }
onBind { msg -> ... }
}
}
}Dedicated adapter for Paging 3 with seamless integration.
val pagingAdapter = FusionPagingAdapter<User>()
pagingAdapter.apply {
// Regular registration
register(ItemUserBinding::inflate) {
onBind { user -> ... }
}
// Optional: Register a custom placeholder (Skeleton)
registerPlaceholder(ItemSkeletonBinding::inflate) {
onBind { /* Setup shimmer animation */ }
}
}
// Submit PagingData
lifecycleScope.launch {
viewModel.pagingFlow.collectLatest { pagingData ->
pagingAdapter.submitData(pagingData)
}
}Control spans directly in the DSL. Fusion handles SpanSizeLookup automatically.
val layoutManager = GridLayoutManager(context, 2)
recyclerView.layoutManager = layoutManager
recyclerView.setupFusion(layoutManager) {
// Header: Always full span
register<Header>(ItemHeaderBinding::inflate) {
onBind { ... }
fullSpanIf { true }
}
// Grid Item: Dynamic span
register<GridItem>(ItemGridBinding::inflate) {
onBind { ... }
spanSize { item, position, scope ->
if (item.isPromoted) scope.totalSpans else 1
}
}
}Fusion is friendly to Java developers. You can use TypeRouter.Builder for type-safe registration.
// Java Example
FusionAdapter adapter = new FusionAdapter();
// Configure routing using the Builder pattern
TypeRouter<User> userRouter = new TypeRouter.Builder<User>()
.match(user -> user.getRole())
.map("ADMIN", new AdminDelegate())
.map("USER", new UserDelegate())
.build();
adapter.register(User.class, userRouter);
recyclerView.setAdapter(adapter);By combining onPayload with Kotlin property references, FusionAdapter achieves "View-specific" updates. Only the code corresponding to the changed property is executed, completely eliminating flickering in complex items.
register<Post>(ItemPostBinding::inflate) {
onBind { post -> /* Full Binding */ }
// [Single Property] Only updates tvLikeCount when likeCount changes
onPayload(Post::likeCount) { count ->
tvLikeCount.text = count.toString()
}
// [Multi-Property] Triggered if either avatar or nickname changes
onPayload(Post::avatar, Post::nickname) { avatar, name ->
ivAvatar.load(avatar)
tvName.text = name
}
}In non-paging mode, you can manipulate placeholders just like regular data:
// 1. Register placeholder style
adapter.registerPlaceholder(ItemSkeletonBinding::inflate) {
onBind { /* Configure skeleton animations */ }
}
// 2. Show placeholders (Skeleton mode)
adapter.showPlaceholders(count = 10)
// 3. Clear them when async data arrives
adapter.clearPlaceholders()
adapter.setItems(realData)FusionAdapter provides full support for Java developers.
// 1. Implement Delegate
public class UserDelegate extends JavaDelegate<User, ItemUserBinding> {
@Override
public Object getStableId(@NonNull User item) {
return item.getId();
}
@Override
protected ItemUserBinding onCreateBinding(@NonNull LayoutInflater inflater, @NonNull ViewGroup parent) {
return ItemUserBinding.inflate(inflater, parent, false);
}
@Override
protected void onBind(@NonNull ItemUserBinding binding, @NonNull User item) {
binding.tvName.setText(item.getName());
}
@Override
protected void onCreate(@NonNull ItemUserBinding binding) {
// [Advanced] Bind payloads in Java to achieve granular updates
bindPayload(User::getName, (binding, name) -> binding.tvName.setText(name));
}
}
// 2. Register
adapter.register(User.class, new TypeRouter.Builder<User>()
.stableId(User::getId)
.map("DEFAULT", new UserDelegate())
.build()
);FusionAdapter introduces a strict Sanitization mechanism to ensure UI consistency.
Initialize Fusion in your Application class:
Fusion.initialize {
setDebug(BuildConfig.DEBUG) // Fast-fail in Debug, Safe-drop in Release
setErrorListener { item, e ->
// Monitor unregistered types or data errors
Log.e("Fusion", "Error on item: $item", e)
}
}FusionAdapter includes a powerful built-in Diagnostics System to help you inspect registration status and identify performance bottlenecks (e.g., slow onCreateViewHolder).
Ensure debug mode is enabled in your configuration (metrics are only collected in debug mode to avoid overhead in release).
Fusion.initialize {
setDebug(true)
}Call dump() on any Fusion adapter instance to print a formatted report to Logcat.
// Dump stats to Logcat
adapter.dump()====================================================================================================
FUSION ADAPTER DIAGNOSTICS
====================================================================================================
Debug Mode: true
Total Items: 100
Registered Delegates: 2
----------------------------------------------------------------------------------------------------
ViewType | Key | Delegate | Create | Bind | Avg Create
----------------------------------------------------------------------------------------------------
10001 | User:Fusion:BindingDelegate | UserDelegate | 12 | 150 | 0.450 ms
10002 | Ad:Fusion:JavaDelegate | AdDelegate | 5 | 5 | 2.100 ms
----------------------------------------------------------------------------------------------------
Copyright 2024 FusionAdapter Authors
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.