Skip to content

Commit 867e5b7

Browse files
committed
Feature: added the async diff comparing.
1 parent 343e661 commit 867e5b7

File tree

10 files changed

+216
-97
lines changed

10 files changed

+216
-97
lines changed

adaptiverecyclerview/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ android {
2121

2222
dependencies {
2323
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
24+
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
2425

2526
implementation 'androidx.appcompat:appcompat:1.0.2'
2627
implementation 'androidx.recyclerview:recyclerview:1.0.0'

adaptiverecyclerview/src/main/java/com/devrapid/adaptiverecyclerview/AdaptiveAdapter.kt

Lines changed: 148 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,20 @@ import android.view.View
55
import android.view.ViewGroup
66
import androidx.recyclerview.widget.DiffUtil
77
import androidx.recyclerview.widget.RecyclerView
8+
import com.devrapid.adaptiverecyclerview.MessageType.MESSAGE_ADD_LIST
9+
import com.devrapid.adaptiverecyclerview.MessageType.MESSAGE_ADD_SINGLE
10+
import com.devrapid.adaptiverecyclerview.MessageType.MESSAGE_APPEND_LIST
11+
import com.devrapid.adaptiverecyclerview.MessageType.MESSAGE_APPEND_SINGLE
12+
import com.devrapid.adaptiverecyclerview.MessageType.MESSAGE_DROP_ALL
13+
import com.devrapid.adaptiverecyclerview.MessageType.MESSAGE_DROP_RANGE
14+
import com.devrapid.adaptiverecyclerview.MessageType.MESSAGE_DROP_SINGLE
15+
import com.devrapid.adaptiverecyclerview.MessageType.MESSAGE_REPLACE_ALL
16+
import kotlinx.coroutines.Dispatchers
17+
import kotlinx.coroutines.GlobalScope
18+
import kotlinx.coroutines.launch
19+
import kotlinx.coroutines.runBlocking
20+
import kotlinx.coroutines.withContext
21+
import java.util.ArrayDeque
822

923
/**
1024
* An adaptive [RecyclerView] which accepts multiple type layout.
@@ -14,6 +28,9 @@ import androidx.recyclerview.widget.RecyclerView
1428
*/
1529
abstract class AdaptiveAdapter<VT : ViewTypeFactory, M : IVisitable<VT>, VH : RecyclerView.ViewHolder> :
1630
RecyclerView.Adapter<VH>() {
31+
protected abstract var typeFactory: VT
32+
protected abstract var dataList: MutableList<M>
33+
//region Header and Footer
1734
var headerEntity: M? = null
1835
set(value) {
1936
if (field == value) return // If the same, we don't do operations as the following below.
@@ -56,7 +73,8 @@ abstract class AdaptiveAdapter<VT : ViewTypeFactory, M : IVisitable<VT>, VH : Re
5673
}
5774
field = value
5875
}
59-
open var diffUtil: AdaptiveDiffUtil<VT, M> = MultiDiffUtil()
76+
//endregion
77+
open var diffUtil: AdaptiveDiffUtil<VT, M> = DefaultMultiDiffUtil()
6078
open var useDiffUtilUpdate = true
6179
val dataItemCount: Int
6280
get() {
@@ -66,23 +84,7 @@ abstract class AdaptiveAdapter<VT : ViewTypeFactory, M : IVisitable<VT>, VH : Re
6684

6785
return size
6886
}
69-
70-
protected abstract var typeFactory: VT
71-
protected abstract var dataList: MutableList<M>
72-
73-
inner class MultiDiffUtil : AdaptiveDiffUtil<VT, M>() {
74-
override var oldList = mutableListOf<M>()
75-
override var newList = mutableListOf<M>()
76-
77-
override fun getOldListSize() = oldList.size
78-
79-
override fun getNewListSize() = newList.size
80-
81-
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
82-
oldList[oldItemPosition].hashCode() == newList[newItemPosition].hashCode()
83-
84-
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = true
85-
}
87+
private val queue = ArrayDeque<Message<M>>()
8688

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

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

105-
open fun appendList(list: MutableList<M>) {
107+
open fun append(list: MutableList<M>) {
108+
queue.add(Message<M>().also {
109+
it.type = MESSAGE_APPEND_LIST
110+
it.newList = list
111+
})
112+
runUpdateTask()
113+
}
114+
115+
open fun append(item: M) {
116+
queue.add(Message<M>().also {
117+
it.type = MESSAGE_APPEND_SINGLE
118+
it.newItem = item
119+
})
120+
runUpdateTask()
121+
}
122+
123+
open fun add(position: Int, list: MutableList<M>) {
124+
queue.add(Message<M>().also {
125+
it.type = MESSAGE_ADD_LIST
126+
it.position = position
127+
it.newList = list
128+
})
129+
runUpdateTask()
130+
}
131+
132+
open fun add(position: Int, item: M) {
133+
queue.add(Message<M>().also {
134+
it.type = MESSAGE_ADD_SINGLE
135+
it.newItem = item
136+
})
137+
runUpdateTask()
138+
}
139+
140+
open fun dropRange(range: IntRange) {
141+
queue.add(Message<M>().also {
142+
it.type = MESSAGE_DROP_RANGE
143+
it.range = range
144+
})
145+
runUpdateTask()
146+
}
147+
148+
open fun dropAt(index: Int) {
149+
queue.add(Message<M>().also {
150+
it.type = MESSAGE_DROP_SINGLE
151+
it.position = index
152+
})
153+
runUpdateTask()
154+
}
155+
156+
open fun clearList(header: Boolean = true, footer: Boolean = true) {
157+
queue.add(Message<M>().also {
158+
it.type = MESSAGE_DROP_ALL
159+
it.header = header
160+
it.footer = footer
161+
})
162+
runUpdateTask()
163+
}
164+
165+
open fun replaceWholeList(newList: MutableList<M>) {
166+
queue.add(Message<M>().also {
167+
it.type = MESSAGE_REPLACE_ALL
168+
it.newList = newList
169+
})
170+
runUpdateTask()
171+
}
172+
173+
//region Inner operations
174+
protected open fun _append(list: MutableList<M>): MutableList<M> {
106175
var startIndex = dataList.size
107176
if (footerEntity != null)
108177
startIndex--
109178
// [toMutableList()] will create a new [ArrayList].
110-
val newList = dataList.toMutableList().apply { addAll(startIndex, list) }
111-
updateList { newList }
179+
return dataList.toMutableList().apply { addAll(startIndex, list) }
112180
}
113181

114-
open fun append(item: M) {
115-
val newList = dataList.toMutableList().apply {
116-
if (footerEntity != null) add(dataList.size - 1, item) else add(item)
117-
}
118-
updateList { newList }
182+
protected open fun _append(item: M) = dataList.toMutableList().apply {
183+
if (footerEntity != null) add(dataList.size - 1, item) else add(item)
119184
}
120185

121-
open fun add(position: Int, item: M) {
186+
protected open fun _add(position: Int, list: MutableList<M>) = dataList.toMutableList().apply {
187+
addAll(position + (if (headerEntity == null) 0 else 1), list)
188+
}
189+
190+
protected open fun _add(position: Int, item: M): MutableList<M> {
122191
if (dataItemCount <= 0) throw IndexOutOfBoundsException()
123192

124-
val newList = dataList.toMutableList().apply {
193+
return dataList.toMutableList().apply {
125194
add(position + (if (headerEntity == null) 0 else 1), item)
126195
}
127-
updateList { newList }
128-
}
129-
130-
open fun add(position: Int, list: MutableList<M>) {
131-
val newList = dataList.toMutableList().apply {
132-
addAll(position + (if (headerEntity == null) 0 else 1), list)
133-
}
134-
updateList { newList }
135196
}
136197

137-
open fun dropRange(range: IntRange) {
198+
protected open fun _dropRange(range: IntRange): MutableList<M> {
138199
var start = range.start
139200

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

154-
open fun dropAt(index: Int) {
155-
dropRange(index..index)
213+
return newList
156214
}
157215

158-
open fun clearList(header: Boolean = true, footer: Boolean = true): Boolean {
159-
if (header) headerEntity = null
160-
if (footer) footerEntity = null
216+
protected open fun _dropAt(index: Int) = _dropRange(index..index)
161217

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

164-
return true
224+
mutableListOf<M>().apply {
225+
headerEntity?.let(::add)
226+
footerEntity?.let(::add)
227+
}
165228
}
166229

167230
/**
@@ -170,25 +233,47 @@ abstract class AdaptiveAdapter<VT : ViewTypeFactory, M : IVisitable<VT>, VH : Re
170233
*
171234
* @param newList
172235
*/
173-
open fun replaceWholeList(newList: MutableList<M>) {
174-
val withHeaderAndFooterList = newList.toMutableList().apply {
175-
headerEntity?.let { add(0, it) }
176-
footerEntity?.let { add(newList.size, it) }
177-
}
178-
updateList { withHeaderAndFooterList }
236+
protected open fun _replaceWholeList(newList: MutableList<M>) = newList.toMutableList().apply {
237+
headerEntity?.let { add(0, it) }
238+
footerEntity?.let { add(newList.size + (if (headerEntity == null) 0 else 1), it) }
239+
}
240+
//endregion
241+
242+
//region Real doing update task
243+
private fun runUpdateTask() {
244+
if (queue.size > 1) return
245+
update(queue.peek())
179246
}
180247

181-
open fun updateList(getNewListBlock: () -> MutableList<M>) {
182-
val newList = getNewListBlock()
183-
val res = DiffUtil.calculateDiff(diffUtil.apply {
184-
oldList = dataList
185-
this.newList = newList
186-
})
248+
private fun update(message: Message<M>) {
249+
GlobalScope.launch {
250+
val list = extractUpdateList(message)
251+
val res = DiffUtil.calculateDiff(diffUtil.apply {
252+
oldList = dataList
253+
newList = list
254+
})
255+
256+
withContext(Dispatchers.Main) {
257+
dataList = list
258+
res.dispatchUpdatesTo(this@AdaptiveAdapter)
259+
queue.remove()
260+
// Check the queue is still having message.
261+
if (queue.size > 0)
262+
update(queue.peek())
263+
}
264+
}
265+
}
187266

188-
dataList = newList
189-
if (useDiffUtilUpdate)
190-
res.dispatchUpdatesTo(this)
191-
else
192-
notifyDataSetChanged()
267+
private fun extractUpdateList(message: Message<M>) = when (message.type) {
268+
MESSAGE_APPEND_LIST -> _append(message.newList)
269+
MESSAGE_APPEND_SINGLE -> _append(requireNotNull(message.newItem))
270+
MESSAGE_ADD_LIST -> _add(message.position, message.newList)
271+
MESSAGE_ADD_SINGLE -> _add(message.position, requireNotNull(message.newItem))
272+
MESSAGE_DROP_RANGE -> _dropRange(message.range)
273+
MESSAGE_DROP_SINGLE -> _dropAt(message.position)
274+
MESSAGE_DROP_ALL -> _clearList(message.header, message.footer)
275+
MESSAGE_REPLACE_ALL -> _replaceWholeList(message.newList)
276+
else -> mutableListOf()
193277
}
278+
//endregion
194279
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.devrapid.adaptiverecyclerview
2+
3+
internal class DefaultMultiDiffUtil<VT : ViewTypeFactory, M : IVisitable<VT>> : AdaptiveDiffUtil<VT, M>() {
4+
override var oldList = mutableListOf<M>()
5+
override var newList = mutableListOf<M>()
6+
7+
override fun getOldListSize() = oldList.size
8+
9+
override fun getNewListSize() = newList.size
10+
11+
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
12+
oldList[oldItemPosition].hashCode() == newList[newItemPosition].hashCode()
13+
14+
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = true
15+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.devrapid.adaptiverecyclerview
2+
3+
internal class Message<M> {
4+
var type = -1
5+
var position = -1
6+
var range = -1..-1
7+
var newList = mutableListOf<M>()
8+
var newItem: M? = null
9+
var header = true
10+
var footer = true
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.devrapid.adaptiverecyclerview
2+
3+
object MessageType {
4+
internal const val MESSAGE_APPEND_LIST = 1
5+
internal const val MESSAGE_APPEND_SINGLE = 2
6+
internal const val MESSAGE_ADD_LIST = 3
7+
internal const val MESSAGE_ADD_SINGLE = 4
8+
internal const val MESSAGE_DROP_RANGE = 5
9+
internal const val MESSAGE_DROP_SINGLE = 6
10+
internal const val MESSAGE_DROP_ALL = 7
11+
internal const val MESSAGE_REPLACE_ALL = 8
12+
}

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ buildscript {
99
jcenter()
1010
}
1111
dependencies {
12-
classpath 'com.android.tools.build:gradle:3.3.1'
12+
classpath 'com.android.tools.build:gradle:3.3.2'
1313
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
1414

1515
// NOTE: Do not place your application dependencies here; they belong

sample/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ android {
2222

2323
dependencies {
2424
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
25+
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
2526

2627
implementation 'androidx.appcompat:appcompat:1.0.2'
2728
implementation 'androidx.recyclerview:recyclerview:1.0.0'

sample/src/main/java/com/devrapid/example/DeletableActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class DeletableActivity : AppCompatActivity() {
2626
Person("Grape"),
2727
Person("Airbnb"),
2828
Person("Jieyi"))
29-
val adapter = ExpandAdapter().apply { appendList(itemList) }
29+
val adapter = ExpandAdapter().apply { add(0, itemList) }
3030

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

0 commit comments

Comments
 (0)