Skip to content

woniu0936/FusionAdapter

Repository files navigation

🚀 FusionAdapter

Maven Central Kotlin License Paging3

🇨🇳 中文文档 | 🇺🇸 English

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


🆚 Why 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

💡 Core Value: Why it belongs in your project?

  1. No More "Class Explosion": Traditional solutions require a new ViewHolder or ItemBinder class for every single UI style. With FusionAdapter, you just add a few lines to your DSL, keeping your codebase lean and maintainable.
  2. 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.
  3. 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.
  4. 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.

✨ Key Features

  • ⚡ 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 FusionPagingAdapter deeply 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 spanSize and fullSpan logic directly within the item configuration.
  • 🚀 Memory & Logging Safety:
    • Auto Leak Prevention: Automatically clears view tags in onViewRecycled to strictly prevent memory leaks.
    • Enterprise Logging: High-performance async logging. Support for stripping debug logs in Release builds via ProGuard.
  • ☕ Java Friendly: Beyond Kotlin DSL, it provides full Builder Pattern support for Java developers.

📦 Installation

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")
}

🔨 Usage Guide

1. Simple List (DSL)

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"))

2. Polymorphism (Cascading Stable ID)

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 -> ... }
        }
    }
}

3. Paging 3 Integration

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)
    }
}

4. Grid & Staggered Support

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 
        }
    }
}

5. Java Interoperability (Builder Pattern)

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);

⚙️ Advanced Features

1. Partial Updates & Property-Level Binding (Payloads)

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
    }
}

2. Manual Skeleton Control (Skeleton API)

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)

☕ Java Interoperability

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()
);

🛡️ Robustness & Safety

FusionAdapter introduces a strict Sanitization mechanism to ensure UI consistency.

Global Configuration

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)
    }
}

🔍 Diagnostics & Performance Monitoring

FusionAdapter includes a powerful built-in Diagnostics System to help you inspect registration status and identify performance bottlenecks (e.g., slow onCreateViewHolder).

1. Enable Debug Mode

Ensure debug mode is enabled in your configuration (metrics are only collected in debug mode to avoid overhead in release).

Fusion.initialize {
    setDebug(true)
}

2. Dump Report

Call dump() on any Fusion adapter instance to print a formatted report to Logcat.

// Dump stats to Logcat
adapter.dump()

3. Output Example

====================================================================================================
                                      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  
----------------------------------------------------------------------------------------------------

📄 License

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.

About

Flexible multiple types for Android RecyclerView.

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors