Skip to content

Commit

Permalink
Feature: added the async diff comparing.
Browse files Browse the repository at this point in the history
  • Loading branch information
pokk committed Mar 7, 2019
1 parent 343e661 commit 867e5b7
Show file tree
Hide file tree
Showing 10 changed files with 216 additions and 97 deletions.
1 change: 1 addition & 0 deletions adaptiverecyclerview/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ android {

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'

implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.devrapid.adaptiverecyclerview.MessageType.MESSAGE_ADD_LIST
import com.devrapid.adaptiverecyclerview.MessageType.MESSAGE_ADD_SINGLE
import com.devrapid.adaptiverecyclerview.MessageType.MESSAGE_APPEND_LIST
import com.devrapid.adaptiverecyclerview.MessageType.MESSAGE_APPEND_SINGLE
import com.devrapid.adaptiverecyclerview.MessageType.MESSAGE_DROP_ALL
import com.devrapid.adaptiverecyclerview.MessageType.MESSAGE_DROP_RANGE
import com.devrapid.adaptiverecyclerview.MessageType.MESSAGE_DROP_SINGLE
import com.devrapid.adaptiverecyclerview.MessageType.MESSAGE_REPLACE_ALL
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import java.util.ArrayDeque

/**
* An adaptive [RecyclerView] which accepts multiple type layout.
Expand All @@ -14,6 +28,9 @@ import androidx.recyclerview.widget.RecyclerView
*/
abstract class AdaptiveAdapter<VT : ViewTypeFactory, M : IVisitable<VT>, VH : RecyclerView.ViewHolder> :
RecyclerView.Adapter<VH>() {
protected abstract var typeFactory: VT
protected abstract var dataList: MutableList<M>
//region Header and Footer
var headerEntity: M? = null
set(value) {
if (field == value) return // If the same, we don't do operations as the following below.
Expand Down Expand Up @@ -56,7 +73,8 @@ abstract class AdaptiveAdapter<VT : ViewTypeFactory, M : IVisitable<VT>, VH : Re
}
field = value
}
open var diffUtil: AdaptiveDiffUtil<VT, M> = MultiDiffUtil()
//endregion
open var diffUtil: AdaptiveDiffUtil<VT, M> = DefaultMultiDiffUtil()
open var useDiffUtilUpdate = true
val dataItemCount: Int
get() {
Expand All @@ -66,23 +84,7 @@ abstract class AdaptiveAdapter<VT : ViewTypeFactory, M : IVisitable<VT>, VH : Re

return size
}

protected abstract var typeFactory: VT
protected abstract var dataList: MutableList<M>

inner class MultiDiffUtil : AdaptiveDiffUtil<VT, M>() {
override var oldList = mutableListOf<M>()
override var newList = mutableListOf<M>()

override fun getOldListSize() = oldList.size

override fun getNewListSize() = newList.size

override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldList[oldItemPosition].hashCode() == newList[newItemPosition].hashCode()

override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = true
}
private val queue = ArrayDeque<Message<M>>()

//region Necessary override methods.
override fun getItemCount() = dataList.size
Expand All @@ -102,39 +104,98 @@ abstract class AdaptiveAdapter<VT : ViewTypeFactory, M : IVisitable<VT>, VH : Re

fun listDescription() = dataList.joinToString("\n") { it.toString() }

open fun appendList(list: MutableList<M>) {
open fun append(list: MutableList<M>) {
queue.add(Message<M>().also {
it.type = MESSAGE_APPEND_LIST
it.newList = list
})
runUpdateTask()
}

open fun append(item: M) {
queue.add(Message<M>().also {
it.type = MESSAGE_APPEND_SINGLE
it.newItem = item
})
runUpdateTask()
}

open fun add(position: Int, list: MutableList<M>) {
queue.add(Message<M>().also {
it.type = MESSAGE_ADD_LIST
it.position = position
it.newList = list
})
runUpdateTask()
}

open fun add(position: Int, item: M) {
queue.add(Message<M>().also {
it.type = MESSAGE_ADD_SINGLE
it.newItem = item
})
runUpdateTask()
}

open fun dropRange(range: IntRange) {
queue.add(Message<M>().also {
it.type = MESSAGE_DROP_RANGE
it.range = range
})
runUpdateTask()
}

open fun dropAt(index: Int) {
queue.add(Message<M>().also {
it.type = MESSAGE_DROP_SINGLE
it.position = index
})
runUpdateTask()
}

open fun clearList(header: Boolean = true, footer: Boolean = true) {
queue.add(Message<M>().also {
it.type = MESSAGE_DROP_ALL
it.header = header
it.footer = footer
})
runUpdateTask()
}

open fun replaceWholeList(newList: MutableList<M>) {
queue.add(Message<M>().also {
it.type = MESSAGE_REPLACE_ALL
it.newList = newList
})
runUpdateTask()
}

//region Inner operations
protected open fun _append(list: MutableList<M>): MutableList<M> {
var startIndex = dataList.size
if (footerEntity != null)
startIndex--
// [toMutableList()] will create a new [ArrayList].
val newList = dataList.toMutableList().apply { addAll(startIndex, list) }
updateList { newList }
return dataList.toMutableList().apply { addAll(startIndex, list) }
}

open fun append(item: M) {
val newList = dataList.toMutableList().apply {
if (footerEntity != null) add(dataList.size - 1, item) else add(item)
}
updateList { newList }
protected open fun _append(item: M) = dataList.toMutableList().apply {
if (footerEntity != null) add(dataList.size - 1, item) else add(item)
}

open fun add(position: Int, item: M) {
protected open fun _add(position: Int, list: MutableList<M>) = dataList.toMutableList().apply {
addAll(position + (if (headerEntity == null) 0 else 1), list)
}

protected open fun _add(position: Int, item: M): MutableList<M> {
if (dataItemCount <= 0) throw IndexOutOfBoundsException()

val newList = dataList.toMutableList().apply {
return dataList.toMutableList().apply {
add(position + (if (headerEntity == null) 0 else 1), item)
}
updateList { newList }
}

open fun add(position: Int, list: MutableList<M>) {
val newList = dataList.toMutableList().apply {
addAll(position + (if (headerEntity == null) 0 else 1), list)
}
updateList { newList }
}

open fun dropRange(range: IntRange) {
protected open fun _dropRange(range: IntRange): MutableList<M> {
var start = range.start

when {
Expand All @@ -148,20 +209,22 @@ abstract class AdaptiveAdapter<VT : ViewTypeFactory, M : IVisitable<VT>, VH : Re
// Count the range.
if (headerEntity != null) start++
repeat(range.count()) { newList.removeAt(start) }
updateList { newList }
}

open fun dropAt(index: Int) {
dropRange(index..index)
return newList
}

open fun clearList(header: Boolean = true, footer: Boolean = true): Boolean {
if (header) headerEntity = null
if (footer) footerEntity = null
protected open fun _dropAt(index: Int) = _dropRange(index..index)

dropRange(0..(dataItemCount - 1))
protected open fun _clearList(header: Boolean = true, footer: Boolean = true): MutableList<M> = runBlocking {
withContext(Dispatchers.Main) {
if (header) headerEntity = null
if (footer) footerEntity = null
}

return true
mutableListOf<M>().apply {
headerEntity?.let(::add)
footerEntity?.let(::add)
}
}

/**
Expand All @@ -170,25 +233,47 @@ abstract class AdaptiveAdapter<VT : ViewTypeFactory, M : IVisitable<VT>, VH : Re
*
* @param newList
*/
open fun replaceWholeList(newList: MutableList<M>) {
val withHeaderAndFooterList = newList.toMutableList().apply {
headerEntity?.let { add(0, it) }
footerEntity?.let { add(newList.size, it) }
}
updateList { withHeaderAndFooterList }
protected open fun _replaceWholeList(newList: MutableList<M>) = newList.toMutableList().apply {
headerEntity?.let { add(0, it) }
footerEntity?.let { add(newList.size + (if (headerEntity == null) 0 else 1), it) }
}
//endregion

//region Real doing update task
private fun runUpdateTask() {
if (queue.size > 1) return
update(queue.peek())
}

open fun updateList(getNewListBlock: () -> MutableList<M>) {
val newList = getNewListBlock()
val res = DiffUtil.calculateDiff(diffUtil.apply {
oldList = dataList
this.newList = newList
})
private fun update(message: Message<M>) {
GlobalScope.launch {
val list = extractUpdateList(message)
val res = DiffUtil.calculateDiff(diffUtil.apply {
oldList = dataList
newList = list
})

withContext(Dispatchers.Main) {
dataList = list
res.dispatchUpdatesTo(this@AdaptiveAdapter)
queue.remove()
// Check the queue is still having message.
if (queue.size > 0)
update(queue.peek())
}
}
}

dataList = newList
if (useDiffUtilUpdate)
res.dispatchUpdatesTo(this)
else
notifyDataSetChanged()
private fun extractUpdateList(message: Message<M>) = when (message.type) {
MESSAGE_APPEND_LIST -> _append(message.newList)
MESSAGE_APPEND_SINGLE -> _append(requireNotNull(message.newItem))
MESSAGE_ADD_LIST -> _add(message.position, message.newList)
MESSAGE_ADD_SINGLE -> _add(message.position, requireNotNull(message.newItem))
MESSAGE_DROP_RANGE -> _dropRange(message.range)
MESSAGE_DROP_SINGLE -> _dropAt(message.position)
MESSAGE_DROP_ALL -> _clearList(message.header, message.footer)
MESSAGE_REPLACE_ALL -> _replaceWholeList(message.newList)
else -> mutableListOf()
}
//endregion
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.devrapid.adaptiverecyclerview

internal class DefaultMultiDiffUtil<VT : ViewTypeFactory, M : IVisitable<VT>> : AdaptiveDiffUtil<VT, M>() {
override var oldList = mutableListOf<M>()
override var newList = mutableListOf<M>()

override fun getOldListSize() = oldList.size

override fun getNewListSize() = newList.size

override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldList[oldItemPosition].hashCode() == newList[newItemPosition].hashCode()

override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.devrapid.adaptiverecyclerview

internal class Message<M> {
var type = -1
var position = -1
var range = -1..-1
var newList = mutableListOf<M>()
var newItem: M? = null
var header = true
var footer = true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.devrapid.adaptiverecyclerview

object MessageType {
internal const val MESSAGE_APPEND_LIST = 1
internal const val MESSAGE_APPEND_SINGLE = 2
internal const val MESSAGE_ADD_LIST = 3
internal const val MESSAGE_ADD_SINGLE = 4
internal const val MESSAGE_DROP_RANGE = 5
internal const val MESSAGE_DROP_SINGLE = 6
internal const val MESSAGE_DROP_ALL = 7
internal const val MESSAGE_REPLACE_ALL = 8
}
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.3.1'
classpath 'com.android.tools.build:gradle:3.3.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

// NOTE: Do not place your application dependencies here; they belong
Expand Down
1 change: 1 addition & 0 deletions sample/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ android {

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'

implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class DeletableActivity : AppCompatActivity() {
Person("Grape"),
Person("Airbnb"),
Person("Jieyi"))
val adapter = ExpandAdapter().apply { appendList(itemList) }
val adapter = ExpandAdapter().apply { add(0, itemList) }

ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(UP or DOWN, LEFT or RIGHT) {
override fun onMove(
Expand Down

0 comments on commit 867e5b7

Please sign in to comment.