Skip to content
This repository was archived by the owner on Nov 14, 2018. It is now read-only.

Commit d79900e

Browse files
committed
clean up code, add documentation and comments
1 parent fafa216 commit d79900e

3 files changed

Lines changed: 131 additions & 66 deletions

File tree

api/current.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -655,10 +655,10 @@ package androidx.core.widget {
655655

656656
public final class AdapterViewKt {
657657
ctor public AdapterViewKt();
658-
method public static <ITEM> void onItemClick(android.widget.AdapterView<?>, kotlin.jvm.functions.Function1<? super ITEM,kotlin.Unit> onItemClick);
659-
method public static <ITEM> void onItemLongClick(android.widget.AdapterView<?>, kotlin.jvm.functions.Function1<? super ITEM,java.lang.Boolean> onItemLongClick);
658+
method public static <T> void onItemClick(android.widget.AdapterView<?>, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> onItemClick);
659+
method public static <T> void onItemLongClick(android.widget.AdapterView<?>, kotlin.jvm.functions.Function1<? super T,java.lang.Boolean> onItemLongClick);
660660
method public static void onItemSelected(android.widget.AdapterView<?>, kotlin.jvm.functions.Function1<? super android.widget.AdapterView<?>,kotlin.Unit> onNothingSelected = "{}", kotlin.jvm.functions.Function4<? super android.widget.AdapterView<?>,? super android.view.View,? super java.lang.Integer,? super java.lang.Long,kotlin.Unit> onItemSelected);
661-
method public static <ITEM> void onItemSelected(android.widget.AdapterView<?>, kotlin.jvm.functions.Function0<kotlin.Unit> onNothingSelected = "{}", kotlin.jvm.functions.Function1<? super ITEM,kotlin.Unit> onItemSelected);
661+
method public static <T> void onItemSelected(android.widget.AdapterView<?>, kotlin.jvm.functions.Function0<kotlin.Unit> onNothingSelected = "{}", kotlin.jvm.functions.Function1<? super T,kotlin.Unit> onItemSelected);
662662
}
663663

664664
public final class ToastKt {

src/androidTest/java/androidx/core/widget/AdapterViewTest.kt

Lines changed: 74 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@
1717
package androidx.core.widget
1818

1919
import android.support.test.InstrumentationRegistry
20+
import android.widget.AbsListView.CHOICE_MODE_SINGLE
2021
import android.widget.AdapterView
22+
import android.widget.AdapterView.INVALID_POSITION
23+
import android.widget.AdapterView.INVALID_ROW_ID
2124
import android.widget.ArrayAdapter
2225
import android.widget.ListView
2326
import android.widget.Spinner
@@ -39,17 +42,17 @@ class AdapterViewTest {
3942
.apply { setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) }
4043

4144
private val listView: ListView
42-
get() = ListView(context).apply { adapter = arrayAdapter }
45+
get() = ListView(context).apply { adapter = arrayAdapter; choiceMode = CHOICE_MODE_SINGLE }
4346
private val spinner: Spinner
4447
get() = Spinner(context).apply { adapter = arrayAdapter }
4548

4649
private var testItem: Any? = null
47-
private var testOnNothingSelectedFired = false
50+
private var testOnNothingSelectedTriggered = false
4851

4952
@Before
5053
fun setup() {
5154
testItem = null
52-
testOnNothingSelectedFired = false
55+
testOnNothingSelectedTriggered = false
5356
}
5457

5558
@Test
@@ -59,19 +62,17 @@ class AdapterViewTest {
5962
for (position in data.indices) {
6063
assertTrue(
6164
"listener not set",
62-
adapterView.performItemClick(null, position, AdapterView.INVALID_ROW_ID)
65+
adapterView.performItemClick(null, position, INVALID_ROW_ID)
6366
)
6467
assertEquals(data[position], testItem)
6568
}
6669
}
6770

68-
@Test
71+
@Test(expected = ClassCastException::class)
6972
fun onItemClickCastExceptionOnWrongClass() {
7073
val adapterView = listView
7174
adapterView.onItemClick { item: WrongClass -> testItem = item }
72-
assertThrows<ClassCastException> {
73-
adapterView.performItemClick(null, 1, AdapterView.INVALID_ROW_ID)
74-
}
75+
adapterView.performItemClick(null, 1, INVALID_ROW_ID)
7576
}
7677

7778

@@ -80,7 +81,9 @@ class AdapterViewTest {
8081
spinner.onItemClick { _: Any? -> }
8182
}
8283

83-
// borrowed from https://android.googlesource.com/platform/cts/+/master/tests/tests/widget/src/android/widget/cts/AdapterViewTest.java#255
84+
/**
85+
* borrowed from [AdapterViewTest line:279](https://android.googlesource.com/platform/cts/+/42fbcbb2518ea10cc729c44614a93b182bf58696/tests/tests/widget/src/android/widget/cts/AdapterViewTest.java#279)
86+
*/
8487
@Test
8588
fun onItemLongClick() {
8689
val adapterView = listView
@@ -117,78 +120,97 @@ class AdapterViewTest {
117120
}
118121
}
119122

123+
@Test
124+
fun onItemSelectedWithCastIgnoresOnNothingSelectedActions() {
125+
listOf(listView, spinner).forEach { adapterView ->
126+
adapterView.onItemSelected { item: String -> testItem = item }
127+
assertFalse(testOnNothingSelectedTriggered)
128+
assertNull(testItem)
129+
selectAndFireOnSelected(adapterView, INVALID_POSITION)
130+
assertFalse(testOnNothingSelectedTriggered)
131+
assertNull(testItem)
132+
}
133+
}
134+
120135
@Test
121136
fun onItemSelectedWithCastExceptionOnWrongClass() {
122137
listOf(listView, spinner).forEach { adapterView ->
123138
adapterView.onItemSelected<WrongClass> { item -> testItem = item }
124139
assertThrows<ClassCastException> {
125-
checkSelectionForPosition(adapterView, 1)
140+
for (i in data.indices) checkSelectionForPosition(adapterView, i)
126141
}
127142
}
128143
}
129144

130145
@Test
131146
fun onItemSelectedWithHandledOnNothingSelected() {
132-
val adapterView = spinner
133-
adapterView.onItemSelected(
134-
onNothingSelected = { _: AdapterView<*> -> testOnNothingSelectedFired = true },
135-
onItemSelected = { parent, _, position, _ ->
136-
testItem = parent.getItemAtPosition(position)
137-
})
138-
checkSelectionForPosition(adapterView, AdapterView.INVALID_POSITION)
139-
for (i in data.indices) checkSelectionForPosition(adapterView, i)
140-
checkSelectionForPosition(adapterView, AdapterView.INVALID_POSITION)
147+
listOf(listView, spinner).forEach { adapterView ->
148+
adapterView.onItemSelected(
149+
onNothingSelected = { _: AdapterView<*> -> testOnNothingSelectedTriggered = true },
150+
onItemSelected = { parent, _, position, _ ->
151+
testItem = parent.getItemAtPosition(position)
152+
})
153+
checkSelectionForPosition(adapterView, INVALID_POSITION)
154+
for (i in data.indices) checkSelectionForPosition(adapterView, i)
155+
checkSelectionForPosition(adapterView, INVALID_POSITION)
156+
}
141157
}
142158

143159
@Test
144160
fun onItemSelectedWithCastWithHandledOnNothingSelected() {
145-
val adapterView = spinner
146-
adapterView.onItemSelected(
147-
onNothingSelected = { testOnNothingSelectedFired = true },
148-
onItemSelected = { item: String -> testItem = item }
149-
)
150-
checkSelectionForPosition(adapterView, AdapterView.INVALID_POSITION)
151-
for (i in data.indices) checkSelectionForPosition(adapterView, i)
152-
checkSelectionForPosition(adapterView, AdapterView.INVALID_POSITION)
161+
listOf(listView, spinner).forEach { adapterView ->
162+
adapterView.onItemSelected(
163+
onNothingSelected = { testOnNothingSelectedTriggered = true },
164+
onItemSelected = { item: String -> testItem = item }
165+
)
166+
checkSelectionForPosition(adapterView, INVALID_POSITION)
167+
for (i in data.indices) checkSelectionForPosition(adapterView, i)
168+
checkSelectionForPosition(adapterView, INVALID_POSITION)
169+
}
153170
}
154171

155-
class WrongClass
156-
157172
private fun checkSelectionForPosition(adapterView: AdapterView<*>, position: Int) {
158-
assertFalse(testOnNothingSelectedFired)
173+
assertFalse(testOnNothingSelectedTriggered)
159174
assertNull(testItem)
160-
adapterView.setSelection(position)
161-
fireOnSelected(adapterView)
175+
selectAndFireOnSelected(adapterView, position)
162176
if (position < 0) {
163-
assertTrue(testOnNothingSelectedFired)
177+
assertTrue(testOnNothingSelectedTriggered)
164178
assertNull(testItem)
165179
} else {
180+
assertFalse(testOnNothingSelectedTriggered)
166181
assertEquals(data[position], testItem)
167-
assertFalse(testOnNothingSelectedFired)
168182
}
183+
testOnNothingSelectedTriggered = false
169184
testItem = null
170-
testOnNothingSelectedFired = false
171-
}
172-
173-
/**
174-
* reflection used to test to trigger selection
175-
*
176-
* workaround for using ActivityRule like here: https://android.googlesource.com/platform/cts/+/master/tests/tests/widget/src/android/widget/cts/AdapterViewTest.java#286
177-
*/
178-
private fun fireOnSelected(adapterView: AdapterView<*>) {
179-
try {
180-
AdapterView::class.java.getDeclaredMethod("fireOnSelected")
181-
.apply {
182-
isAccessible = true
183-
invoke(adapterView)
184-
}
185-
} catch (e: InvocationTargetException) {
186-
throw e.targetException
187-
}
188185
}
189186

190187
companion object {
191188
private const val LAYOUT_WIDTH = 200
192189
private const val LAYOUT_HEIGHT = 200
190+
191+
/**
192+
* Reflection used to shortcut trigger selection via AdapterView#fireOnSelected()
193+
*
194+
* More comprehensive test would involve ActivityRule like in [AdapterViewTest line:286](https://android.googlesource.com/platform/cts/+/42fbcbb2518ea10cc729c44614a93b182bf58696/tests/tests/widget/src/android/widget/cts/AdapterViewTest.java#286)
195+
*
196+
* @see android.widget.AdapterView
197+
*/
198+
private fun selectAndFireOnSelected(adapterView: AdapterView<*>, position: Int) {
199+
try {
200+
AdapterView::class.java
201+
.getDeclaredMethod("setNextSelectedPositionInt", Int::class.java)
202+
.apply { isAccessible = true }
203+
.invoke(adapterView, position)
204+
AdapterView::class.java
205+
.getDeclaredMethod("fireOnSelected")
206+
.apply { isAccessible = true }
207+
.invoke(adapterView)
208+
} catch (e: InvocationTargetException) {
209+
throw e.targetException
210+
}
211+
}
212+
213+
class WrongClass
214+
193215
}
194216
}

src/main/java/androidx/core/widget/AdapterView.kt

Lines changed: 54 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,24 +19,47 @@ package androidx.core.widget
1919
import android.view.View
2020
import android.widget.AdapterView
2121

22-
inline fun <ITEM> AdapterView<*>.onItemClick(
23-
crossinline onItemClick: (item: ITEM) -> Unit
24-
) {
22+
/**
23+
* Sets click listener with automatic item casting
24+
* (ClassCastException will be thrown if adapter's item type does not match)
25+
*/
26+
inline fun <T> AdapterView<*>.onItemClick(crossinline onItemClick: (item: T) -> Unit) {
2527
setOnItemClickListener { parent, _, position, _ ->
2628
@Suppress("UNCHECKED_CAST")
27-
onItemClick(parent.getItemAtPosition(position) as ITEM)
29+
onItemClick(parent.getItemAtPosition(position) as T)
2830
}
2931
}
3032

31-
inline fun <ITEM> AdapterView<*>.onItemLongClick(
32-
crossinline onItemLongClick: (item: ITEM) -> Boolean
33-
) {
33+
/**
34+
* Sets long click listener with automatic item casting
35+
* (ClassCastException will be thrown if adapter's item type does not match)
36+
*/
37+
inline fun <T> AdapterView<*>.onItemLongClick(crossinline onItemLongClick: (item: T) -> Boolean) {
3438
setOnItemLongClickListener { parent, _, position, _ ->
3539
@Suppress("UNCHECKED_CAST")
36-
onItemLongClick(parent.getItemAtPosition(position) as ITEM)
40+
onItemLongClick(parent.getItemAtPosition(position) as T)
3741
}
3842
}
3943

44+
/**
45+
* Simple use case (default empty `onNothingSelected` set):
46+
* ```kotlin
47+
* spinner.onItemSelected { parent, _, position, _ ->
48+
* val item = parent.getItemAtPosition(position)
49+
* ???
50+
* }
51+
* ```
52+
* Use case with `onNothingSelected` handling:
53+
* ```kotlin
54+
* spinner.onItemSelected(
55+
* onNothingSelected = { _: AdapterView<*> -> ??? },
56+
* onItemSelected = { parent, _, position, _ ->
57+
* val item = parent.getItemAtPosition(position)
58+
* ???
59+
* })
60+
* ```
61+
* @see android.widget.AdapterView.OnItemSelectedListener
62+
*/
4063
inline fun AdapterView<*>.onItemSelected(
4164
crossinline onNothingSelected: (parent: AdapterView<*>) -> Unit = {},
4265
crossinline onItemSelected: (
@@ -55,16 +78,36 @@ inline fun AdapterView<*>.onItemSelected(
5578
}
5679
}
5780

58-
inline fun <ITEM> AdapterView<*>.onItemSelected(
81+
/**
82+
* Sets selection listener with automatic item casting
83+
* (ClassCastException will be thrown if adapter's item type does not match)
84+
*
85+
* Simple use case (default empty `onNothingSelected` set):
86+
* ```kotlin
87+
* spinner.onItemSelected { item: T -> ??? }
88+
* ```
89+
* Use case with `onNothingSelected` handling:
90+
* ```kotlin
91+
*
92+
* spinner.onItemSelected(
93+
* onNothingSelected = { ??? },
94+
* onItemSelected = { item: String -> ??? }
95+
* )
96+
* ```
97+
* @param onNothingSelected optional action, default `{}`
98+
* @param onItemSelected action with casted item passed
99+
* @see android.widget.AdapterView.OnItemSelectedListener
100+
*/
101+
inline fun <T> AdapterView<*>.onItemSelected(
59102
crossinline onNothingSelected: () -> Unit = {},
60-
crossinline onItemSelected: (item: ITEM) -> Unit
103+
crossinline onItemSelected: (item: T) -> Unit
61104
) {
62105
onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
63106
override fun onNothingSelected(parent: AdapterView<*>) = onNothingSelected()
64107

65108
override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
66109
@Suppress("UNCHECKED_CAST")
67-
onItemSelected(parent.getItemAtPosition(position) as ITEM)
110+
onItemSelected(parent.getItemAtPosition(position) as T)
68111
}
69112
}
70113
}

0 commit comments

Comments
 (0)