A Viewholderless Adapter for RecyclerView, who supports builtin diffing, states (paging, empty...), events (clicking, swiping...), and more.

Overview

Logo

OneAdapter

Android Arsenal Android Weekly CN

OneAdapter is made to simplify and enhance the use of the RecyclerView's Adapter while preventing common mistakes. With multiple modules and hooks, you don't have to think about writing an adapter anymore, and just focus on what matters.

For better understanding what drove me to write this library and what use cases it solves best, please refer to my Medium post: https://medium.com/@idanatsmon/adapting-your-recyclerview-the-2019-approach-e47edf2fc4f3

What's new:

Version 2.0.0 is out with a brand new Kotlin API!
Kotlin is now the first priority of this library and as such comes a full API change, every Module, Hook and State is now created using dedicated DSLs.
Check the example below or sample project for reference for Kotlin & Java use.

Features:

Include in your project

Root build.gradle:

allprojects {
 repositories {
    jcenter()
    maven { url "https://jitpack.io" }
 }
}

App build.gradle:

dependencies {
  implementation "com.github.idanatz:OneAdapter:${LATEST_VERSION}"
}

Note that library interfaces and API may change slightly while the library design matures.
Please see the changes in the CHANGELOG file before upgrading.

Preview

Example

You can try out the example project that includes basic and advanced usage in Kotlin.

Screenshots



Basic Usage

1. Implement Item Module

Item Modules are used for the creation and binding of all ViewHolders for you. In the onBind method, you will receive as a parameter the model associated with this view and a ViewBinder class that lets you find (and cache) the views defined in the associated layout file.

class MessageModule : ItemModule<MessageModel>() {
    init {
        config {
            layoutResource = R.layout.message_model
        }
        onBind { model, viewBinder, metadata ->
            val title = viewBinder.findViewById<TextView>(R.id.title)
            title.text = model.title
        }
        onUnbind { model, viewBinder, metadata ->
            // unbind logic like stop animation, release webview resources, etc.
        }
    }
}

2. Implement Diffable

The Adapter is calculating the difference between its current data and the modified data on a background thread and posting the result to the main thread. In order for this magic to work without writing tons of DiffUtil.Callback, your models need to implement one simple interface:

class MessageModel : Diffable {
    private val id: Long = 0L
    private val title: String? = null

    override val uniqueIdentifier: Long = id
    override fun areContentTheSame(other: Any): Boolean = other is MessageModel && title == other.title
}

3. Attach To OneAdapter & Use

val oneAdapter = OneAdapter(recyclerView) {
    itemModule += MessageModule()
} 
oneAdapter.setItems(...) 



Advanced Usage

Modules

Multiple Types

Have more than one view type? not a problem, just create another ItemModule and attach it to OneAdapter in the same way.

1. Implement Multiple Item Modules

class MessageModule : ItemModule<MessageModel> { ... }
class StoryModule : ItemModule<StoryModel> { ... }

2. Attach To OneAdapter

val oneAdapter = OneAdapter(recyclerView) {
    itemModule += MessageModule()
    itemModule += StoryModule()
    ...
}



Paging Module

Paging Module is used for creating and binding a specific ViewHolder at the end of the list when the Adapter reaches a load more state. The visible threshold configuration is used to indicate how many items before the end of the list the onLoadMore callback should be invoked.


1. Implement Paging Modules

class PagingModuleImpl : PagingModule() {
    init {
        config {
            layoutResource = R.layout.load_more // can be some spinner animation
            visibleThreshold = 3 // invoke onLoadMore 3 items before the end
        }
        onLoadMore { currentPage ->
            // place your load more logic here, like asking the ViewModel to load the next page of data.
        }
    }
}

2. Attach To OneAdapter

val oneAdapter = OneAdapter(recyclerView) {
    // itemModule += ...
    pagingModule = PagingModuleImpl()
}



Emptiness Module

Emptiness Module is used for creating and binding a specific ViewHolder when the Adapter has no data to render.


1. Implement Emptiness Modules

class EmptinessModuleImpl : EmptinessModule() {
    init {
    	config {
            layoutResource = R.layout.empty_state
        }
        onBind { viewBinder, metadata -> ... }
        onUnbind { viewBinder, metadata -> ... }
    }
}

2. Attach To OneAdapter

val oneAdapter = OneAdapter(recyclerView) {
    // itemModule += ...
    emptinessModule = EmptinessModuleImpl()
}



Selection Module

Selection Module is used for enabling single or multiple selection on Items.


1. Implement Selection Modules

class ItemSelectionModuleImpl : ItemSelectionModule() {
    init {
    	config {
            selectionType = SelectionType.Multiple // Or SelectionType.Single
        }
        onStartSelection {
            // place your general selection logic here, like changing the toolbar text to indicate the selected count.
        } 
        onUpdateSelection { selectedCount -> ... }
        onEndSelection { ... }
    }
}

2. Implement Selection State

class MessageModule : ItemModule<MessageModel>() {
    init {
        // config, onBind, etc...
        
        states += SelectionState<MessageModel>().apply {
            config {
                enabled = true // decide if the selection should be enabled for this model, true by default
                selectionTrigger = SelectionTrigger.LongClick // decide what trigger the selection, long or regular click
            }
            onSelected { model, selected ->
                // insert your selected logic here. 
                // right after this call you will receive an onBind call in order to reflect your changes on the relevant Item Module.
            }
        }
    }
}

3. Attach To OneAdapter

val oneAdapter = OneAdapter(recyclerView) {
    itemModule += MessageModule()
    itemSelectionModule = ItemSelectionModuleImpl()
}




Event Hooks

Item Modules can easily be enhanced with event hooks to get access to common events like clicking or swiping on an item.

Click Event Hook

Click Hook can be attached in order to recieve click events on an item.


1. Implement Click Event Hook

class MessageModule : ItemModule<MessageModel>() {
    init {
        // config, onBind, etc...
        
        eventHooks += ClickEventHook<MessageModel>().apply {
            onClick { model, viewBinder, metadata -> 
                // place your on click logic here. 
            }
        }
    }
}

2. Attach To OneAdapter

val oneAdapter = OneAdapter(recyclerView) {
    itemModule += MessageModule()
}



Swipe Event Hook

Swipe Hook can be attached in order to receive swiping (during and when completed) events on an item.


1. Implement Swipe Event Hook

class MessageModule : ItemModule<MessageModel>() {
    init {
        // config, onBind, etc...
        
        eventHooks += SwipeEventHook<MessageModel>().apply {
            config {
                swipeDirection = listOf(SwipeEventHook.SwipeDirection.Start, SwipeEventHook.SwipeDirection.End)
            }
            onSwipe { canvas, xAxisOffset, viewBinder ->
                // draw your swipe UI here.
                // like painting the canvas red with a delete icon.
            }
            onSwipeComplete { model, viewBinder, metadata ->
                // place your swipe logic here.
                // like removing an item after it was swiped right.
            }
        }
    }
}

2. Attach To OneAdapter

val oneAdapter = OneAdapter(recyclerView) {
    itemModule += MessageModule()
}




Others

First Bind Animation

The provided Animator will be animated on the first bind of the corresponding ItemModule's models.


class MessageModule : ItemModule<MessageModel>() {
    init {
        config {
            layoutResource = R.layout.message_model
            
            // can be implemented by inflating Animator Xml
            firstBindAnimation = AnimatorInflater.loadAnimator(this@FirstBindAnimationActivity, R.animator.item_animation_example)
			
            // or can be implemented by constructing ObjectAnimator
            firstBindAnimation = ObjectAnimator().apply {
                propertyName = "translationX"
                setFloatValues(-1080f, 0f)
                duration = 750
            }
        }
        onBind { model, viewBinder, metadata -> ... }
    }
}

View Binding

Built in support for Android View Binding (https://developer.android.com/topic/libraries/view-binding) Full example is provided in the example project.

class MessageModule : ItemModule<MessageModel>() {
    init {
        config {
            layoutResource = R.layout.message_model
        }
        onBind { model, viewBinder, _ ->
            viewBinder.bindings(MessageModelBinding::bind).run {
                title.text = model.title
                body.text = model.body
                Glide.with(viewBinder.rootView).load(model.avatarImageId).into(avatarImage)
            }
        }
    }
}

Data Binding

Built in support for Android Data Binding (https://developer.android.com/topic/libraries/data-binding) Full example is provided in the example project.

class MessageModule : ItemModule<ObservableMessageModel>() {
    init {
        config {
            layoutResource = R.layout.message_model
        }
        onBind { model, viewBinder, metadata ->
            viewBinder.dataBinding?.run {
                setVariable(BR.messageModel, model)
                lifecycleOwner = this@DataBindingActivity
                executePendingBindings()
            }
        }
    }
}



License

Copyright (c) 2019 Idan Atsmon

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Comments
  • Invalid view holder adapter position

    Invalid view holder adapter position

    Hi, the library is really great due to its modular design. It really does help a lot.

    Unfortunately I have encountered an issue. I will first show some code, but I do not have a small, executable example at the moment, I might add it later.

    My code uses the OneAdapter as follows:

    Within the fragment:

    recyclerView = view.findViewById(R.id.ingredients_list);
    recyclerView.setHasFixedSize(true);
    recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
    oneAdapter = new OneAdapter(recyclerView);
    oneAdapter.attachEmptinessModule(new PantryEmptinessModule());
    oneAdapter.attachPagingModule(new PantryPagingModule());
    oneAdapter.attachItemModule(new PantryItemModule());
    prepopulateAdapter();
    

    Within prepopulateAdapter (on main thread):

    oneAdapter.add(recipes); //recipes is an ArrayList
    

    Within PantryPagingModule:

    @Override
    public PagingModuleConfig provideModuleConfig() {
        return new PagingModuleConfig() {
            @Override
            public int withVisibleThreshold() {
                return 3;
            }
            @Override
            public int withLayoutResource() {
                return R.layout.load_more_item;
            }
        };
    }
    
    @Override
    public void onLoadMore(int i) {
         //Recipes are obtained in the background...
         //on main thread
          oneAdapter.add(recipes); //recipes is an ArrayList
    }
    

    The item looks as follows:

    @PrimaryKey
        public int id;
    
        @ColumnInfo(name = "name")
        public String name;
    
        @ColumnInfo(name = "amount")
        public int amount;
    
        @Override
        public boolean areContentTheSame(@NotNull Object o) {
            if (!(o instanceof Recipe)) {
                return false;
            }
            Ingredient other = (Recipe) o;
            boolean names = this.name.equals(other.name);
            boolean amount = this.amount == other.amount;
            return names && amount;
        }
    
        @Override
        public long getUniqueIdentifier() {
            return id;
        }
    

    And the error message that sometimes comes when scrolling is:

    ava.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder
    

    Now, I have no idea where this error might come from. Maybe we can find a solution to this and fix it. If any more information is needed, I'll be happy to help. Thanks in advance.

    opened by Minification 24
  • use setItem, but the onBind method not be invoke

    use setItem, but the onBind method not be invoke

    i use setItem(), the onBind{} not be invoke, but UI has changed a little, and then i try invoke notifyDataSetChanged() of recycleview's adapter on next line, the onBind{} is invoked. when i want to delete an item, i use the data to setItem() that has been deleted the item, but the onBind{} not working, throught i have invoke notifyDataSetChanged() on next line, like this: oneAdapterForOther?.setItems(it) sharedForOtherRecycler?.adapter?.notifyDataSetChanged()

    opened by SoBeautifulChicken 12
  • Inconsistency detected. Invalid view holder adapter positionViewHolder

    Inconsistency detected. Invalid view holder adapter positionViewHolder

    I followed a basic usage tutorial and I got a exception:

    Process: com.example, PID: 13642
        java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionViewHolder{232efed position=2 id=-969824770, oldPos=0, pLpos:0 scrap [attachedScrap] tmpDetached no parent} androidx.recyclerview.widget.RecyclerView{3f2d670 VFED..... ......I. 0,0-1080,1516 #7f0900e6 app:id/recyclerView}, adapter:com.idanatz.oneadapter.internal.InternalAdapter@65b9de9, layout:androidx.recyclerview.widget.LinearLayoutManager@a556a6e, context:com.example.MainActivity@6c3203e
            at androidx.recyclerview.widget.RecyclerView$Recycler.validateViewHolderForOffsetPosition(RecyclerView.java:5953)
            at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6137)
            at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6097)
            at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6093)
            at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2303)
            at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1627)
            at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
            at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:665)
            at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep1(RecyclerView.java:4066)
            at androidx.recyclerview.widget.RecyclerView.onMeasure(RecyclerView.java:3515)
            at android.view.View.measure(View.java:24510)
            at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
            at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
            at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
            at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
            at android.view.View.measure(View.java:24510)
            at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
            at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
            at android.view.View.measure(View.java:24510)
            at androidx.recyclerview.widget.RecyclerView$LayoutManager.measureChildWithMargins(RecyclerView.java:9352)
            at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1653)
            at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
            at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:665)
            at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4115)
            at androidx.recyclerview.widget.RecyclerView.onMeasure(RecyclerView.java:3521)
            at android.view.View.measure(View.java:24510)
            at android.view.ViewGroup.measureChild(ViewGroup.java:6799)
            at androidx.viewpager2.widget.ViewPager2.onMeasure(ViewPager2.java:482)
            at android.view.View.measure(View.java:24510)
            at android.widget.RelativeLayout.measureChildHorizontal(RelativeLayout.java:735)
            at android.widget.RelativeLayout.onMeasure(RelativeLayout.java:481)
            at android.view.View.measure(View.java:24510)
            at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
            at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
            at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
            at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
            at android.view.View.measure(View.java:24510)
            at android.widget.ScrollView.measureChildWithMargins(ScrollView.java:1414)
            at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
            at android.widget.ScrollView.onMeasure(ScrollView.java:452)
            at android.view.View.measure(View.java:24510)
            at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
            at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
            at androidx.appcompat.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:143)
            at android.view.View.measure(View.java:24510)
    2019-08-13 12:33:05.879 13642-13642/com.example E/AndroidRuntime:     at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
            at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
            at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
            at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
            at android.view.View.measure(View.java:24510)
            at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
            at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
            at android.view.View.measure(View.java:24510)
            at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
            at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
            at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
            at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
            at android.view.View.measure(View.java:24510)
            at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6828)
            at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
            at com.android.internal.policy.DecorView.onMeasure(DecorView.java:736)
            at android.view.View.measure(View.java:24510)
            at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:3004)
            at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1833)
            at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2122)
            at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1721)
            at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7595)
            at android.view.Choreographer$CallbackRecord.run(Choreographer.java:966)
            at android.view.Choreographer.doCallbacks(Choreographer.java:790)
            at android.view.Choreographer.doFrame(Choreographer.java:725)
            at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:951)
            at android.os.Handler.handleCallback(Handler.java:883)
            at android.os.Handler.dispatchMessage(Handler.java:100)
            at android.os.Looper.loop(Looper.java:214)
            at android.app.ActivityThread.main(ActivityThread.java:7319)
            at java.lang.reflect.Method.invoke(Native Method)
            at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:934)
    

    Below you can find my classes.

    Model:

    class Model(private val id: Long) : Diffable {
    
        override fun areContentTheSame(other: Any) = other is Model && other.id == id
    
        override fun getUniqueIdentifier() = id
    }
    

    ItemModule:

    class ExampleItemModule : ItemModule<Model>() {
    
        override fun onBind(model: Model, viewBinder: ViewBinder) {
            //unused for now
        }
    
        override fun provideModuleConfig(): ItemModuleConfig = object : ItemModuleConfig() {
            override fun withLayoutResource() = R.layout.example_item
        }
    
    }
    

    Adapter initialization:

    recyclerView.layoutManager = LinearLayoutManager(context)
    val oneAdapter = OneAdapter()
            .attachItemModule(ExampleItemModule())
            .attachTo(recyclerView)
    
    oneAdapter.setItems(listOf(Model(1L), Model(2L)))
    

    I execute all of the code on the main thread, so modification on the background thread is not the reason for the exception. I would be very grateful if you consider my request ASAP.

    bug 
    opened by patrykserek 12
  • SetItems very slow

    SetItems very slow

    Disclaimer: I measured the execution time of this function by displaying timestamp just before and just after call. Apparently, it only takes 1ms to execute. If this function does not use different threads, the problem may not come from it.

    I am trying to display some objects but it seems to be taking much longer than expected. The GUI freezes for 6~7s so I am sure it's a process executed on the Main Thread.

    Here my Diffable :

    class ExpansionDiffable(val expansion: Expansion): Diffable {
    
        override fun areContentTheSame(other: Any): Boolean {
            return other is ExpansionDiffable
                    && other.expansion.count == expansion.count
                    && other.expansion.countOwned == expansion.countOwned
                    && other.expansion.price == expansion.price
                    && other.expansion.priceOwned == expansion.priceOwned
                    && other.expansion.body == expansion.body
        }
    
        override fun getUniqueIdentifier(): Long {
            return expansion.idExpansion.toLong()
        }
    } 
    

    And here my Module

    class ExpansionModule: ItemModule<ExpansionDiffable>() {
        override fun onBind(model: ExpansionDiffable, viewBinder: ViewBinder) {
            val name: TextView = viewBinder.findViewById(R.id.name)
            val progress: CircleProgressView = viewBinder.findViewById(R.id.progress)
            val abbreviation: TextView = viewBinder.findViewById(R.id.abbreviation)
            val count: TextView = viewBinder.findViewById(R.id.count)
            val worth: TextView = viewBinder.findViewById(R.id.worth)
            val releaseDate: TextView = viewBinder.findViewById(R.id.releaseDate)
    
    
            name.text = model.expansion.name
            progress.set(model.expansion.countRatio)
            abbreviation.text = model.expansion.abbreviation
            releaseDate.text = model.expansion.releaseDateLimited
    
            if (!model.expansion.isReleased)
                releaseDate.textColor = context.getColor(R.color.red_A200)
            else
                releaseDate.textColor = context.getColor(R.color.md_black_1000)
    
            count.text = "${model.expansion.countOwned}/${model.expansion.count}"
            worth.text = model.expansion.priceOwned.toPriceString()
        }
    
        override fun provideModuleConfig(): ItemModuleConfig = object: ItemModuleConfig() {
            override fun withLayoutResource(): Int = R.layout.expansion_module
        }
    }
    

    For information, there are around 500 Expansions to display. Do you think that the slowdown observed could come from your library?

    opened by NoahReyson 10
  • Multiple ViewType

    Multiple ViewType

    Hi!

    I have this use case. Show admob native Ads in the recycler view. I can use multi modulo adapter, but how can i show Every 3 items of the modulo A, One item of the module B?

    Thanks a lot

    question 
    opened by francescogatto 9
  • CalledFromWrongThreadException in 2.1.0

    CalledFromWrongThreadException in 2.1.0

    Hello, thanks for update.

    Seems like calling oneAdapter.setItems(...) from background thread no longer supported in 2.1.0, but works fine in 2.0.2. Is it expected behaviour?

    Code:

    lifecycleScope.launch(Dispatchers.Default) {
        val items = ...
        oneAdapter.setItems(items)
    }
    

    Result:

    2021-04-05 10:48:06.193 29570-29650/a.b.c E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-2
        Process: a.b.c, PID: 29570
        android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
            at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8648)
            at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1533)
    		...
    

    Seems like this can easily be fixed with: withContext(Dispatchers.Main) { oneAdapter.setItems(items) }, but probably this should be done on the library side for backwards compatibility?

    bug resolved in the next version 
    opened by vellrya 9
  • Get Emptiness module programmatically

    Get Emptiness module programmatically

    Hi! very nice library I'm trying to change programmatically text and lottieview of the emptiness module. This is my flow:

    1. show emptiness module
    2. loading some data in recycler view
    3. swipe to refresh and data now is empty
    4. emptiness module is not showing

    or

    1. show emptiness module
    2. loading some data in recycler view
    3. search for some data
    4. data is empty
    5. show emptiness module with a text like "Nothing found during the research"

    Thanks!

    bug resolved in the next version 
    opened by francescogatto 9
  • Scroll to first added element

    Scroll to first added element

    Hello, I use MVVM pattern with OneAdapter in my app. When new element added to 0 position in items list, we need to manually scroll up to see recently added element. If, for example, new element has index 1, then all work correct and there is animation of adding element. Do you know any way to automatically scroll to top of list when new element has index 0 and old element with index 0 was visible by user before updating? Or, probably, scroll to top of list always - it also acceptable for me)

    Short code fragment to update adapter:

            dialogViewModel.allDialogs.observe(this) { dialogs ->
                oneAdapter.setItems(dialogs)
            }
    

    Update: When elements in the list takes smaller space then available on screen, animation of adding element with 0 index works correct, but after empty space on screen left, list just stop updating even if the user is at the very beginning of the list and expects to see new items from the top.

    opened by vellrya 7
  • Adapter stops loading data if scrolled rapidly

    Adapter stops loading data if scrolled rapidly

    I am using OneAdapter along with the Paging module to load images gradually from an API. The problem is that if I scroll the list rapidly the paging stops and no more data is loaded after 3-4 pages.

    I am attaching the all the necessary code that I am using below.

    HomeFragment

    class HomeFragment : BaseFragment() {
    private val viewModel by viewModel<HomeViewModel>() // Lazy inject ViewModel
    private lateinit var homeAdapter: OneAdapter
    
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_home, container, false)
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
    
        setupRecyclerView()
        observeImagesLiveData()
    }
    
    private fun setupRecyclerView() {
        rvHome.layoutManager = GridLayoutManager(context, 2)
        homeAdapter = OneAdapter()
            .attachItemModule(HomeItem(context!!))
            .attachPagingModule(HomePagingModule(viewModel))
            .attachTo(rvHome)
    
        homeAdapter.setItems(emptyList())
    }
    
    private fun observeImagesLiveData() {
        viewModel.getPicsumImages(1).observe(this, Observer { result ->
            when (result) {
                is Result.Loading -> {
                    Timber.e("Loading")
                }
    
                is Result.Success<*> -> {
                    val imagesList = result.data as List<PicsumPhoto>
                    homeAdapter.add(imagesList)
                }
    
                is Result.Error -> {
                    Timber.e(result.errorMessage)
                }
            }
        })
    }}
    

    ItemModule

    class HomeItem(context: Context) : ItemModule<PicsumPhoto>() {
    
    private val displayMetrics = context.resources.displayMetrics
    private val columnWidth = displayMetrics.widthPixels / 2
    
    override fun provideModuleConfig() = object : ItemModuleConfig() {
        override fun withLayoutResource() = R.layout.item_home
    }
    
    override fun onBind(model: PicsumPhoto, viewBinder: ViewBinder) {
        val mainImage: ImageView = viewBinder.findViewById(R.id.ivMainHomeItem)
    
        mainImage.apply {
            layoutParams.height = columnWidth
            layoutParams.width = columnWidth
            load(model.download_url)
        }
    
    }}
    

    PagingModule

    class HomePagingModule(private val homeViewModel: HomeViewModel) : PagingModule() {
    
    override fun provideModuleConfig() = object : PagingModuleConfig() {
        override fun withLayoutResource() = R.layout.load_more
        override fun withVisibleThreshold() = 3
    }
    
    override fun onLoadMore(currentPage: Int) {
        homeViewModel.getPicsumImages(currentPage + 1)
    }}
    
    bug 
    opened by mehul4795 6
  • View Binding with Kotlin 1.6.0 not working

    View Binding with Kotlin 1.6.0 not working

    Hey there,

    as of today i tried migrating an older app and now i am required to use Kotlins Android View Binding with kotlin 1.6.0.

    I followed the example in the readme but i cant make it to work. This is how my code looks:

    class DiscoveredDeviceModule(
        val context: Context,
        private val callback: DeviceModuleCallbackListener?
    ) : ItemModule<DiffableDevice>() {
        init {
            config {
                layoutResource = R.layout.component_discover_bt_list_item
    
                firstBindAnimation = AnimatorInflater.loadAnimator(
                    context,
                    R.animator.animator_new_bt_device_discovered
                )
            }
    
            onBind { item, viewBinder, _ ->
                viewBinder.bindings(ComponentDiscoverBtListItemBinding::bind).run {
                    btListItemTitle.text = item.getReadableName(context.resources)
                    // PushDownAnim.setPushDownAnimTo(btListItemWrapper)
    
                    btListItemWrapper.setOnClickListener {
                        btListItemProgress.visibility = View.VISIBLE
                        callback?.onClick(item)
                    }
                }
            }
        }
    
        interface DeviceModuleCallbackListener {
            fun onClick(device: DiffableDevice) {}
        }
    }
    

    Everytime the onBind should have been called, nothing appears in the view. Any help?

    The version i'm using is 2.1.1

    opened by vsxed 5
  • Can we call next page with Paging Module to load without scrolling

    Can we call next page with Paging Module to load without scrolling

    Hi @ironsrc,

    Thanks for the library. Have been using it quite a number of my projects but have recently ran into a problem that the API response has only 10 items and the UI doesn't cover whole page and due to that the scroll event of recyclerview is not called.

    Is there any way or function can make the Paging module load the next page without scrolling.

    Any help would be appreciated.

    bug resolved in the next version 
    opened by palVikas9 5
  • Question:Paging with Room Datasource

    Question:Paging with Room Datasource

    Hi, this is more a question than an issue, related to Room Datasource+LiveData PagedList. I want to use selectable item feature of the OneAdapter, so i am trying to change currently working RecyclerView with PagedListAdapter. But for OneAdapter, i need to ask PagedList from Room datasource to load next items , which i could not find how can it be done. As i understand PagedListAdapter requests next loading of items automatically by calculating Item size and recyclerview viewport size. and calls pagedlist.loadAround(Int) . I am not sure about the Int value passed here, its probably the internally maintained enumerated index of current list. I could not find how can i request next page to load in viewmodel. Did anyone can direct me how to use Room Paging Datasource with OneAdapter.?

    OneAdapter Impl

    object OneAdapters {
    
        fun create(recyclerView: RecyclerView, onClick:(ModelEntity)->Unit,onCurrentBind:(Int)->Unit,loadMoreCall:(Int)->Unit) =OneAdapter(recyclerView){
            itemModules+=ExperimentModelItem(onClick,onCurrentBind)
            pagingModule=PagingModuleImpl(loadMoreCall)
        }
    
    
        internal class ExperimentModelItem(onClick: (ModelEntity) -> Unit,onCurrentBind: (Int) -> Unit) : ItemModule<ModelEntity>() {
    
            init {
                config {
                    layoutResource = R.layout.item_tile_list
                }
                onBind { model, viewBinder, metadata ->
    
                    viewBinder.bindings(ItemTileListBinding::bind).run {
                        onCurrentBind(metadata.position)
                        title.text = model.modelname
                        model.modified_at?.let { d ->
                            subtitle.text = "modified ${DateFormatHelper.readable(d)}"
                        }
                    }
                }
                onUnbind { model, viewBinder, _ ->
                }
    
                eventHooks += ClickEventHook<ModelEntity>().apply {
                    onClick { model, viewBinder, _ ->
                        onClick(model)
                    }
                }
            }
    
        }
        internal class PagingModuleImpl(loadMoreCall:(Int)->Unit) : PagingModule() {
            init {
                config {
                    layoutResource = R.layout.item_loading_progress// can be some spinner animation
                    visibleThreshold = 0 // invoke onLoadMore 3 items before the end
                }
                onLoadMore { currentPage ->
                    // place your load more logic here, like asking the ViewModel to load the next page of data.
                    loadMoreCall(currentPage)
                }
            }
        }
    }
    

    Activity

    private var mListMaxPosition: Int = 0
        private val mRecyclerAdapter: OneAdapter by lazy {
            OneAdapters.create(binding.explistActivityRecylerview, onClick = {
                // navigate
            },
                onCurrentBind = {
                    if (it > mListMaxPosition)
                        mListMaxPosition = it 
                },
                loadMoreCall = {
                    viewmodel.add(VMEvent.LoadMore(mListMaxPosition))
                })
        }
    

    viewmodel

    init{
     val factory =repo.getAllModelsFactory()
            val config = PagedList.Config.Builder()
                .setEnablePlaceholders(false) // when true, the oneadapter's internal adapter crash on validating list having null models   
                .setInitialLoadSizeHint(15)
                .setPageSize(5).build()
    
            val pagedListBuilder: LivePagedListBuilder<Int, ModelEntity>  = LivePagedListBuilder<Int,ModelEntity>(
                factory,
                config
                )
    models=pagedListBuilder.build() // observed LiveData in activity
    }
    fun    onLoadMore(currentPage:Int) // called from activity, 
    {
       models.value?.let {
         // it.loadAround(currentPage) // max intPosition comming from activity
        //it.loadAround(it.lastKey as Int)
    
       }
     
    }
    
    question 
    opened by thexaib 2
Releases(2.1.3)
  • 2.1.3(Dec 15, 2022)

  • 2.1.2(Dec 10, 2022)

  • v2.1.1(May 17, 2021)

    Version 2.1.1

    • Added: new selection trigger - Manual
    • Added: support for Android view binding
    • Fixed: selection metadata not updated correctly
    Source code(tar.gz)
    Source code(zip)
  • v2.1.0(Apr 4, 2021)

    Version 2.1.0

    • Updated: internal diffing mechanism to fix the famous inconsistency crash
    • Updated: removed deprecated bitray publish and moved to jitpack
    Source code(tar.gz)
    Source code(zip)
  • v2.0.2(Jan 25, 2021)

    Version 2.0.2

    • Added: new selection trigger - Manual
    • Added: support for Android view binding
    • Fixed: selection metadata not updated correctly
    Source code(tar.gz)
    Source code(zip)
  • v2.0.1(Oct 20, 2020)

  • v2.0.0(Sep 5, 2020)

    Version 2.0.0

    • Breaking Changes: Kotlin is now the first priority of this library and as such comes a full API change, every Module, Hook and State is now created using dedicated DSLs
    • Added:
      • Ability to trigger selection with click and long click
      • More "update" function to OneAdapter API
      • More tests
    • Fixed: bug in paging threshold not calculated correctly
    Source code(tar.gz)
    Source code(zip)
  • v1.5.2(Jul 1, 2020)

    Version 1.5.2

    • Improved ItemSelectionModule with the ability to:
      • Start manual selection
      • New callbacks: onSelectionStarted, onSelectionEnded
      • New queries: isSelectionActive & isPositionSelected
    • Improved SwipeEventHook with Up and Down swipe direction support
    • Breaking Changes:
      • Right and Left swiping direction changed to Start and End for better support LTR and RTL
    Source code(tar.gz)
    Source code(zip)
  • v1.5.1(Jul 1, 2020)

    Version 1.5.1

    • Fixed Bug: recycler view crash due to race condition with background and ui threads while request multiple diffing quickly
    • Breaking Changes:
      • Wrapped event hooks models with Item object in order to get additional information like item position, isSelected, etc... (using the Metadata object)
    Source code(tar.gz)
    Source code(zip)
  • v1.5.0(Jul 1, 2020)

    Version 1.5.0

    • Fixed Bug: clear() not triggering onBind of emptiness module when the adapter was already empty
    • Breaking Changes:
      • Wrapped each model with Item object in all module's OnBind and onUnbind call in order to get additional information like item position, isSelected, etc... (using the Metadata object)
      • Removed modules actions class, each module now holds its own actions
    Source code(tar.gz)
    Source code(zip)
  • v1.4.1(Jul 1, 2020)

    Version 1.4.1

    • Added the ability to get view types from the adapter to support GridLayoutManager.SpanSizeLookup
    • Added more tests to cover paging
    Source code(tar.gz)
    Source code(zip)
  • v1.4.0(Jul 1, 2020)

    Version 1.4.0

    • Added support for ButterKnife and DataBinding
    • Added more utility functions like item visibility
    • Added examples for nested RecyclerView, ButterKnife and DataBinding in the sample project
    • Fixed Bug: Sync issues with background and ui threads when using nested RecyclerViews with OneAdapter
    • Breaking Changes:
      • ViewBinder getRootView function changed to a property
      • Added HookConfig for SwipeEventHook to specify the supported swipe directions
    Source code(tar.gz)
    Source code(zip)
  • v1.3.0(Jul 1, 2020)

    Version 1.3.0

    • Added First Bind Animation Config
    • Improved threading mechanism
    • Added common UI tests
    • Breaking Changes:
      • RecyclerView is now a constructor parameter instead of an attachment function
      • OnUnbind functions now includes the model as a parameter
    Source code(tar.gz)
    Source code(zip)
  • v1.2.0(Jul 1, 2020)

    Version 1.2.0

    • Change: Diffable is now mandatory when working with OneAdapter
    • Bug Fixes:
      • Paging Module not working properly with some layout manager
      • onUnbind call now called when the view is recycled instead of detached from window
    Source code(tar.gz)
    Source code(zip)
  • v1.1.1(Jul 1, 2020)

  • v1.1.0(Jul 1, 2020)

  • v1.0.0(Jul 1, 2020)

    Version 1.0.0

    First Release Includes:

    • Modules:
      • Item Module
      • Emptiness Module
      • Paging Module
      • Selection Module
    • Event Hooks:
      • Click Event Hook
    • Diffable Interface
    Source code(tar.gz)
    Source code(zip)
Owner
ironSource
ironSource
Android library which makes playing with sensor events & detecting gestures a breeze.

Sensey Android library which makes playing with sensor events & detecting gestures a breeze. The library is built for simplicity and ease of use. It e

Nishant Srivastava 2.7k Dec 29, 2022
Android Annotation Processor library to generate adapter class easily from your model with a lot of customization

Android Annotation Processing Library to generate your adapters only with Annotations on your model, support working with Kapt and KSP Processors

Amr Hesham 63 Nov 24, 2022
Location tracking & geofencing the easy way. Supports background, killed app, rebooted device different update intervals.

Geofencer Convience library to receive user location updates and geofence events with minimal effort. Features: supports Android-Q receive updates on

null 85 Dec 15, 2022
Awesome keyboard animator that supports Android SDK 23 ✨

KeyboardBeautify Awesome keyboard animator that supports Android SDK 23 ✨ This library was created based on the android/user-interface-samples. Previe

Ji Sungbin 9 Dec 8, 2022
🍭🚀💗 Tutorials about animations with Animators, Animated Vector Drawables, Shared Transitions, and more

?????? Tutorials about animations with Animators, Animated Vector Drawables, Shared Transitions, and more

Smart Tool Factory 696 Dec 28, 2022
Web-based media manager with duplication detection, tagging, and more

reelchest ?? ??️ ??️ A basic web-based media manager. Download or upload clips,

Sebastian Aigner 7 Jan 3, 2022
☯️Sophisticated and cool intro with Material Motion Animations(No more viewpager transformer or Memory leak)

Material Intro Sophisticated and cool intro with Material Motion Animations. Who's using Material Intro? ?? Check out who's using Material Intro Inclu

Ranbir Singh 34 Sep 8, 2022
A Flutter plugin thats support share files to social media like TikTok, Instagram, Facebook, WhatsApp, Telegram and more others...

Social Share Kit A Flutter plugin that's support share files to social media like Tiktok, Instagram, Facebook, WhatsApp, Telegram and more. This plugi

Kaique Gazola 2 Sep 2, 2022
Android Animation Easing Functions. Let's make animation more real!

Android Easing Functions This project is originally from my another project, AndroidViewAnimation, which is an animation collection, to help you make

代码家 2.5k Jan 4, 2023
Automatically manipulates the duration of animations dependent on view count. Quicksand .. the more you struggle.

QuickSand When showing a really enchanting explanatory animation to your users, but you know that after a while it'll get tedious and would stop users

Paul Blundell 385 Sep 9, 2022
FadingToolbar is an animation library which fades out your footer view in a ScrollView/RecyclerView and fades in a toolbar title

FadingToolbar is an animation library which fades out your footer view in a ScrollView/RecyclerView and fades in a toolbar title (analogue of the LargeTitle animation in iOS)

Hanna 9 Nov 3, 2022
An Android library which provides simple Item animations to RecyclerView items

RecyclerViewItemAnimators Library Travis master: This repo provides: Appearance animations Simple animators for the item views Quick start You can now

Gabriele Mariotti 3.1k Dec 16, 2022
Open Source Library for Holdable ViewHolder in RecyclerView

HoldableSwipeHandler Open Source Library for Holdable ViewHolder in RecyclerView

null 10 May 11, 2022
Render After Effects animations natively on Android and iOS, Web, and React Native

Lottie for Android, iOS, React Native, Web, and Windows Lottie is a mobile library for Android and iOS that parses Adobe After Effects animations expo

Airbnb 33.5k Jan 4, 2023
A simple and customizable Android full-screen image viewer with shared image transition support, "pinch to zoom" and "swipe to dismiss" gestures

Stfalcon ImageViewer A simple and customizable full-screen image viewer with shared image transition support, "pinch to zoom" and "swipe to dismiss" g

Stfalcon LLC 1.9k Jan 5, 2023
Dynamic Speedometer and Gauge for Android. amazing, powerful, and multi shape :zap:

SpeedView Dynamic Speedometer, Gauge for Android. amazing, powerful, and multi shape ⚡ , you can change (colors, bar width, shape, text, font ...every

Anas Altair 1.2k Jan 7, 2023
A Simple Todo app design in Flutter to keep track of your task on daily basis. Its build on BLoC Pattern. You can add a project, labels, and due-date to your task also you can sort your task on the basis of project, label, and dates

WhatTodo Life can feel overwhelming. But it doesn’t have to. A Simple To-do app design in flutter to keep track of your task on daily basis. You can a

Burhanuddin Rashid 1k Jan 1, 2023
Chandrasekar Kuppusamy 799 Nov 14, 2022
FilePicker is a small and fast file selector library that is constantly evolving with the goal of rapid integration, high customization, and configurability~

Android File Picker ??️ 中文简体 Well, it doesn't have a name like Rocky, Cosmos or Fish. Android File Picker, like its name, is a local file selector fra

null 786 Jan 6, 2023