Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How create my own DiffUtils? #893

Closed
yura-f opened this issue May 12, 2020 · 10 comments
Closed

How create my own DiffUtils? #893

yura-f opened this issue May 12, 2020 · 10 comments
Assignees
Labels

Comments

@yura-f
Copy link

yura-f commented May 12, 2020

I would like to create my own DiffUtil.Callback and use it for my FastAdapter.
How can I do it?

I've watched this example:
https://github.com/mikepenz/FastAdapter/blob/develop/app/src/main/java/com/mikepenz/fastadapter/app/DiffUtilActivity.kt

  1. But I don't control compare functions(I had to overridden var identifier: Long)
  2. And I have to set my adapter from a fragment for my presenter(MVP) but I don't want to do it :)

@mikepenz
May be do U have a little example on this?
Thank U!

@mikepenz
Copy link
Owner

So the sample showcases the default implementation which uses the DiffCallbackImpl as base.

This basically uses the identifier to see if a item is the same, and the quals method to see if the content of the items are the same

If your items have a proper identifier defined and implement the correct equals method then this will already cover 95% of all usecases.

As there are still 5% of the cases left then you can implement your own DiffCallback (implement a class using the interface, you can use the DiffCallbackImpl as example)
And then instead of the used method in the DiffUtilActivity which does not pass a diffcallback find a calculateDiff which allows you to pass it in.

If in the end neither matches your requirements.

The FastAdapter is a regular RecyclerView.Adapter implementation so yo can go barebones and simply use the original API for the diffUtil.
Be aware that the provided API takes care of quite a few thigns, so you save a ton of work by simply using what the FastAdapter offers :)

@mikepenz mikepenz self-assigned this May 12, 2020
@yura-f
Copy link
Author

yura-f commented May 13, 2020

@mikepenz thank U for your response,
but I meant that if I want split getting diff's result(with help Rx into my Presenter) and then set it and new items to my Fragment.
And I can't do it, since asFastAdapterDiffUtil.calculateDiff needs to an adapter, but it's a bad idea that to pass an adapter into the Presenter.

And I forced to split this logic.

@mikepenz
Copy link
Owner

In this case you most likely will be best off doing it manually. Have a look at the impl of the calculate diff to get a better understanding what it does for you:

https://github.com/mikepenz/FastAdapter/blob/develop/library-extensions-diff/src/main/java/com/mikepenz/fastadapter/diff/FastAdapterDiffUtil.kt#L36-L68

@yura-f
Copy link
Author

yura-f commented May 13, 2020

@mikepenz
Yeah, I've seen it.

And I've a question, I've created my own DiffCallback like this:
DiffUtil.calculateDiff(MyDiffCallback(items, newItems)))
into my Presenter, then I get back result to a Fragment and set for adapter like this:

//don't scrolling up my RV
val recyclerViewState: Parcelable? = rv.layoutManager?.onSaveInstanceState()

myAdapter.clear()
myAdapter.set(items)
FastAdapterDiffUtil[myAdapter.itemAdapter] = result

rv.layoutManager?.onRestoreInstanceState(recyclerViewState)

I've tried like this:

myAdapter.set(items)
FastAdapterDiffUtil[myAdapter.itemAdapter] = result

If I write only set(), without clear(), then I see a blink effect after update the data even it hasn't changed!
And I think it's strange.

Or perhaps, did I do anything wrong?

P.S.

In this case you most likely will be best off doing it manually.

It'll be cool if the next version of lib could be do it how the auto as the manually.

@mikepenz
Copy link
Owner

It's kind of like we do internally here: https://github.com/mikepenz/FastAdapter/blob/develop/library-extensions-diff/src/main/java/com/mikepenz/fastadapter/diff/FastAdapterDiffUtil.kt#L56-L65

we update the list of items from the adapter (without letting it know about the changes yet)

One thing you do different though is that you do not update the list, but the adapter directly. so it will already call the respective notify methods. so if you do it like above there is no need to use a diff util anymore.

what you want to do instead is, is to replace and update the list of items only (not the adapter which will result in the notifies) and after that set the result of the diff util. which will result in the diff util calling the notify methods based on the changes.

this will allow the diff util to tell the adapter if things were moved, removed, or added.

@yura-f
Copy link
Author

yura-f commented May 14, 2020

@mikepenz
You are right and now I've split a diff logic like this:

private fun <A : ModelAdapter<Model, Item>, Model, Item : GenericItem> setItems(adapter: A, items: List<Item>): A {
        if (adapter.isUseIdDistributor) {
            adapter.idDistributor.checkIds(items)
        }
 
        // The FastAdapterDiffUtil does not handle expanded items. Call collapse if possible
        collapseIfPossible(adapter.fastAdapter)
 
        //if we have a comparator then sort
        if (adapter.itemList is ComparableItemListImpl<*>) {
            Collections.sort(items, (adapter.itemList as ComparableItemListImpl<Item>).comparator)
        }
 
        //remember the old items
        val adapterItems = adapter.adapterItems
 
        //make sure the new items list is not a reference of the already mItems list
        if (items !== adapterItems) {
            //remove all previous items
            if (adapterItems.isNotEmpty()) {
                adapterItems.clear()
            }
 
            //add all new items to the list
            adapterItems.addAll(items)
        }
 
        return adapter
    }
 
fun <Item : GenericItem> calculateDiff(oldItems: List<Item>,
                                           newItems: List<Item>,
                                           callback: DiffCallback<Item> = YDiffCallbackImpl(),
                                           detectMoves: Boolean = true): DiffUtil.DiffResult {
        return DiffUtil.calculateDiff(FastAdapterCallback(oldItems, newItems, callback), detectMoves)
    }

operator fun <A : ModelAdapter<Model, Item>, Model, Item : GenericItem> set(adapter: A, result: DiffUtil.DiffResult): A {
        result.dispatchUpdatesTo(FastAdapterListUpdateCallback(adapter))
        return adapter
    }
 
operator fun <A : ModelAdapter<Model, Item>, Model, Item : GenericItem> set(adapter: A, items: List<Item>, result: DiffUtil.DiffResult): A {
        setItems(adapter, items)
       
        return set(adapter, result)
    }

And then I can get diff result into my presenter(on the background) and set an items and diff's result into my Fragment.

Perhaps it'll be helpful and this opportunity will be available into new version's this lib :)

Thx for your responses!

@mikepenz
Copy link
Owner

If I am not mistaken there may be issues if you do


        //make sure the new items list is not a reference of the already mItems list
        if (items !== adapterItems) {
            //remove all previous items
            if (adapterItems.isNotEmpty()) {
                adapterItems.clear()
            }
 
            //add all new items to the list
            adapterItems.addAll(items)
        }
 

before the diff got calculated. at least that's what I remember

@mikepenz
Copy link
Owner

@Z-13 perhaps you could have a look at the PR and see if it would serve your requirements too

@yura-f
Copy link
Author

yura-f commented May 14, 2020

@Z-13 perhaps you could have a look at the PR and see if it would serve your requirements too

Wow, it looks nice)
But in my realization the method calculateDiff haven't an adapter param.

Why?

Since as my case I count a diffResult into the Presenter and it doesn't know about the adapter from my View(Fragment). My Presenter loads items and on background thread get a diffResult. Then it sends it to my Fragment, like this:

Presenter:

interactor.loadItems()
.subscribeOn(schedulersFacade.io())
.map { newItems ->
       Pair(newItems, MyFastAdapterDiffUtil.calculateDiff(items, newItems))
}
.observeOn(schedulersFacade.ui())
.subscribe({
       items = it.first
       viewState.showData(items, it.second)
}

Fragment:

fun showData(items: List<MyItem>, result: DiffUtil.DiffResult) {
        if(myAdapter.adapterItems.isEmpty()){
            //if presenter relive fragment and you get back from the previous fragment and our presenter reapeat set saved data. The new adapter don't have an items and we have to set new items without diffResult and notify to our RV.
            myAdapter.setNewList(items)
        }else{
            MyFastAdapterDiffUtil[myAdapter.itemAdapter, items] = result
        }
}

I understand that perhaps it looks like too splittable and needs keep track of items, diffResult and types of items.

@mikepenz
Copy link
Owner

Sadly this won't be possible to include int he library directly, as we require the adapter for those methods to apply order (if provided) to apply identifiers (via the id distributor)

also we need the list which is inside the adapter. so we can keep a copy to ensure the diffing does not get affected by anything else possibly changing the list in the adapter while diffing.
and afterwards we have so we can update the referenced list in the fastAdapter which we require to display the items.

I still hope though that something may be of help for you from the PR :)

Glad you could resolve your requirement though.

Keep in mind. the FastAdapter is trying to provide simple APIs for the 99% of the usecases, but stay super flexible and as close to standard behavior as possible to allow embracing the functionality from the RV itself

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants