Skip to content
This repository has been archived by the owner on Sep 12, 2019. It is now read-only.

Implement bind #81

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ before_install:
- mkdir "$ANDROID_HOME/licenses" || true
- echo "8933bad161af4178b1185d1a37fbf41ea5269c55" > "$ANDROID_HOME/licenses/android-sdk-license"
# Install the rest of tools (e.g., avdmanager)
- sdkmanager tools
- echo y | sdkmanager tools
# Install the system image
- sdkmanager "system-images;android-21;default;armeabi-v7a"
- echo y | sdkmanager "system-images;android-21;default;armeabi-v7a"
# Create and start emulator for the script. Meant to race the install task.
- echo no | avdmanager create avd --force -n test -k "system-images;android-21;default;armeabi-v7a"
- $ANDROID_HOME/emulator/emulator -avd test -no-audio -no-window &
Expand Down
23 changes: 20 additions & 3 deletions src/androidTest/kotlin/kotterknife/ViewTest.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package kotterknife

import android.app.Fragment
import android.content.Context
import android.os.Bundle
import android.widget.FrameLayout
import android.widget.TextView
import android.test.AndroidTestCase
import android.view.LayoutInflater
import android.view.View
import kotlin.test.assertNotNull
import kotlin.test.assertNull
import kotlin.test.assertEquals
import android.view.ViewGroup

public class ViewTest : AndroidTestCase() {
public fun testCast() {
Expand Down Expand Up @@ -144,6 +145,22 @@ public class ViewTest : AndroidTestCase() {
assertEquals(2, example.name.count())
}

public fun testBind() {
class ExampleFragment : Fragment() {
val name : TextView by bindView(1)

override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View {
val view = FrameLayout([email protected])
view.addView(textViewWithId(1))
bind(view)
assertNotNull(name)
return view
}
}

ExampleFragment().onCreateView(null, null, null)
}

private fun viewWithId(id: Int) : View {
val view = View(context)
view.id = id
Expand Down
151 changes: 90 additions & 61 deletions src/main/kotlin/kotterknife/ButterKnife.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,125 +6,154 @@ import android.app.DialogFragment
import android.app.Fragment
import android.support.v7.widget.RecyclerView.ViewHolder
import android.view.View
import java.util.*
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
import android.support.v4.app.DialogFragment as SupportDialogFragment
import android.support.v4.app.Fragment as SupportFragment

fun DialogFragment.bind(v: View) = ButterKnifeContainer.register(this, v)
fun SupportDialogFragment.bind(v: View) = ButterKnifeContainer.register(this, v)
fun Fragment.bind(v: View) = ButterKnifeContainer.register(this, v)
fun SupportFragment.bind(v: View) = ButterKnifeContainer.register(this, v)

public fun <V : View> View.bindView(id: Int)
: ReadOnlyProperty<View, V> = required(id, viewFinder)
: ReadOnlyProperty<View, V> = required(id, viewFinder, this)
public fun <V : View> Activity.bindView(id: Int)
: ReadOnlyProperty<Activity, V> = required(id, viewFinder)
: ReadOnlyProperty<Activity, V> = required(id, viewFinder, this)
public fun <V : View> Dialog.bindView(id: Int)
: ReadOnlyProperty<Dialog, V> = required(id, viewFinder)
: ReadOnlyProperty<Dialog, V> = required(id, viewFinder, this)
public fun <V : View> DialogFragment.bindView(id: Int)
: ReadOnlyProperty<DialogFragment, V> = required(id, viewFinder)
: ReadOnlyProperty<DialogFragment, V> = required(id, viewFinder, this)
public fun <V : View> SupportDialogFragment.bindView(id: Int)
: ReadOnlyProperty<SupportDialogFragment, V> = required(id, viewFinder)
: ReadOnlyProperty<SupportDialogFragment, V> = required(id, viewFinder, this)
public fun <V : View> Fragment.bindView(id: Int)
: ReadOnlyProperty<Fragment, V> = required(id, viewFinder)
: ReadOnlyProperty<Fragment, V> = required(id, viewFinder, this)
public fun <V : View> SupportFragment.bindView(id: Int)
: ReadOnlyProperty<SupportFragment, V> = required(id, viewFinder)
: ReadOnlyProperty<SupportFragment, V> = required(id, viewFinder, this)
public fun <V : View> ViewHolder.bindView(id: Int)
: ReadOnlyProperty<ViewHolder, V> = required(id, viewFinder)
: ReadOnlyProperty<ViewHolder, V> = required(id, viewFinder, this)

public fun <V : View> View.bindOptionalView(id: Int)
: ReadOnlyProperty<View, V?> = optional(id, viewFinder)
: ReadOnlyProperty<View, V?> = optional(id, viewFinder, this)
public fun <V : View> Activity.bindOptionalView(id: Int)
: ReadOnlyProperty<Activity, V?> = optional(id, viewFinder)
: ReadOnlyProperty<Activity, V?> = optional(id, viewFinder, this)
public fun <V : View> Dialog.bindOptionalView(id: Int)
: ReadOnlyProperty<Dialog, V?> = optional(id, viewFinder)
: ReadOnlyProperty<Dialog, V?> = optional(id, viewFinder, this)
public fun <V : View> DialogFragment.bindOptionalView(id: Int)
: ReadOnlyProperty<DialogFragment, V?> = optional(id, viewFinder)
: ReadOnlyProperty<DialogFragment, V?> = optional(id, viewFinder, this)
public fun <V : View> SupportDialogFragment.bindOptionalView(id: Int)
: ReadOnlyProperty<SupportDialogFragment, V?> = optional(id, viewFinder)
: ReadOnlyProperty<SupportDialogFragment, V?> = optional(id, viewFinder, this)
public fun <V : View> Fragment.bindOptionalView(id: Int)
: ReadOnlyProperty<Fragment, V?> = optional(id, viewFinder)
: ReadOnlyProperty<Fragment, V?> = optional(id, viewFinder, this)
public fun <V : View> SupportFragment.bindOptionalView(id: Int)
: ReadOnlyProperty<SupportFragment, V?> = optional(id, viewFinder)
: ReadOnlyProperty<SupportFragment, V?> = optional(id, viewFinder, this)
public fun <V : View> ViewHolder.bindOptionalView(id: Int)
: ReadOnlyProperty<ViewHolder, V?> = optional(id, viewFinder)
: ReadOnlyProperty<ViewHolder, V?> = optional(id, viewFinder, this)

public fun <V : View> View.bindViews(vararg ids: Int)
: ReadOnlyProperty<View, List<V>> = required(ids, viewFinder)
: ReadOnlyProperty<View, List<V>> = required(ids, viewFinder, this)
public fun <V : View> Activity.bindViews(vararg ids: Int)
: ReadOnlyProperty<Activity, List<V>> = required(ids, viewFinder)
: ReadOnlyProperty<Activity, List<V>> = required(ids, viewFinder, this)
public fun <V : View> Dialog.bindViews(vararg ids: Int)
: ReadOnlyProperty<Dialog, List<V>> = required(ids, viewFinder)
: ReadOnlyProperty<Dialog, List<V>> = required(ids, viewFinder, this)
public fun <V : View> DialogFragment.bindViews(vararg ids: Int)
: ReadOnlyProperty<DialogFragment, List<V>> = required(ids, viewFinder)
: ReadOnlyProperty<DialogFragment, List<V>> = required(ids, viewFinder, this)
public fun <V : View> SupportDialogFragment.bindViews(vararg ids: Int)
: ReadOnlyProperty<SupportDialogFragment, List<V>> = required(ids, viewFinder)
: ReadOnlyProperty<SupportDialogFragment, List<V>> = required(ids, viewFinder, this)
public fun <V : View> Fragment.bindViews(vararg ids: Int)
: ReadOnlyProperty<Fragment, List<V>> = required(ids, viewFinder)
: ReadOnlyProperty<Fragment, List<V>> = required(ids, viewFinder, this)
public fun <V : View> SupportFragment.bindViews(vararg ids: Int)
: ReadOnlyProperty<SupportFragment, List<V>> = required(ids, viewFinder)
: ReadOnlyProperty<SupportFragment, List<V>> = required(ids, viewFinder, this)
public fun <V : View> ViewHolder.bindViews(vararg ids: Int)
: ReadOnlyProperty<ViewHolder, List<V>> = required(ids, viewFinder)
: ReadOnlyProperty<ViewHolder, List<V>> = required(ids, viewFinder, this)

public fun <V : View> View.bindOptionalViews(vararg ids: Int)
: ReadOnlyProperty<View, List<V>> = optional(ids, viewFinder)
: ReadOnlyProperty<View, List<V>> = optional(ids, viewFinder, this)
public fun <V : View> Activity.bindOptionalViews(vararg ids: Int)
: ReadOnlyProperty<Activity, List<V>> = optional(ids, viewFinder)
: ReadOnlyProperty<Activity, List<V>> = optional(ids, viewFinder, this)
public fun <V : View> Dialog.bindOptionalViews(vararg ids: Int)
: ReadOnlyProperty<Dialog, List<V>> = optional(ids, viewFinder)
: ReadOnlyProperty<Dialog, List<V>> = optional(ids, viewFinder, this)
public fun <V : View> DialogFragment.bindOptionalViews(vararg ids: Int)
: ReadOnlyProperty<DialogFragment, List<V>> = optional(ids, viewFinder)
: ReadOnlyProperty<DialogFragment, List<V>> = optional(ids, viewFinder, this)
public fun <V : View> SupportDialogFragment.bindOptionalViews(vararg ids: Int)
: ReadOnlyProperty<SupportDialogFragment, List<V>> = optional(ids, viewFinder)
: ReadOnlyProperty<SupportDialogFragment, List<V>> = optional(ids, viewFinder, this)
public fun <V : View> Fragment.bindOptionalViews(vararg ids: Int)
: ReadOnlyProperty<Fragment, List<V>> = optional(ids, viewFinder)
: ReadOnlyProperty<Fragment, List<V>> = optional(ids, viewFinder, this)
public fun <V : View> SupportFragment.bindOptionalViews(vararg ids: Int)
: ReadOnlyProperty<SupportFragment, List<V>> = optional(ids, viewFinder)
: ReadOnlyProperty<SupportFragment, List<V>> = optional(ids, viewFinder, this)
public fun <V : View> ViewHolder.bindOptionalViews(vararg ids: Int)
: ReadOnlyProperty<ViewHolder, List<V>> = optional(ids, viewFinder)

private val View.viewFinder: View.(Int) -> View?
get() = { findViewById(it) }
private val Activity.viewFinder: Activity.(Int) -> View?
get() = { findViewById(it) }
private val Dialog.viewFinder: Dialog.(Int) -> View?
get() = { findViewById(it) }
private val DialogFragment.viewFinder: DialogFragment.(Int) -> View?
get() = { dialog?.findViewById(it) ?: view?.findViewById(it) }
private val SupportDialogFragment.viewFinder: SupportDialogFragment.(Int) -> View?
get() = { dialog?.findViewById(it) ?: view?.findViewById(it) }
private val Fragment.viewFinder: Fragment.(Int) -> View?
get() = { view.findViewById(it) }
private val SupportFragment.viewFinder: SupportFragment.(Int) -> View?
get() = { view!!.findViewById(it) }
private val ViewHolder.viewFinder: ViewHolder.(Int) -> View?
get() = { itemView.findViewById(it) }

private fun viewNotFound(id:Int, desc: KProperty<*>): Nothing =
: ReadOnlyProperty<ViewHolder, List<V>> = optional(ids, viewFinder, this)

private val View.viewFinder: View.(Int, View?) -> View?
get() = { id: Int, c: View? -> c?.findViewById(id) ?: findViewById(id) }
private val Activity.viewFinder: Activity.(Int, View?) -> View?
get() = { id: Int, c: View? -> c?.findViewById(id) ?: findViewById(id) }
private val Dialog.viewFinder: Dialog.(Int, View?) -> View?
get() = { id: Int, c: View? -> c?.findViewById(id) ?: findViewById(id) }
private val DialogFragment.viewFinder: DialogFragment.(Int, View?) -> View?
get() = { id: Int, c: View? -> c?.findViewById(id) ?: dialog?.findViewById(id) ?: view?.findViewById(id) }
private val SupportDialogFragment.viewFinder: SupportDialogFragment.(Int, View?) -> View?
get() = { id: Int, c: View? -> c?.findViewById(id) ?: dialog?.findViewById(id) ?: view?.findViewById(id) }
private val Fragment.viewFinder: Fragment.(Int, View?) -> View?
get() = { id: Int, c: View? -> c?.findViewById(id) ?: view.findViewById(id) }
private val SupportFragment.viewFinder: SupportFragment.(Int, View?) -> View?
get() = { id: Int, c: View? -> c?.findViewById(id) ?: view!!.findViewById(id) }
private val ViewHolder.viewFinder: ViewHolder.(Int, View?) -> View?
get() = { id: Int, c: View? -> c?.findViewById(id) ?: itemView.findViewById(id) }

private fun viewNotFound(id: Int, desc: KProperty<*>): Nothing =
throw IllegalStateException("View ID $id for '${desc.name}' not found.")

@Suppress("UNCHECKED_CAST")
private fun <T, V : View> required(id: Int, finder: T.(Int) -> View?)
= Lazy { t: T, desc -> t.finder(id) as V? ?: viewNotFound(id, desc) }
private fun <T, V : View> required(id: Int, finder: T.(Int, View?) -> View?, container: Any): ReadOnlyProperty<T, V>
= Lazy { c: View?, t: T, desc -> t.finder(id, c) as V? ?: viewNotFound(id, desc) }
.apply{ ButterKnifeContainer.add(container, this) }

@Suppress("UNCHECKED_CAST")
private fun <T, V : View> optional(id: Int, finder: T.(Int) -> View?)
= Lazy { t: T, desc -> t.finder(id) as V? }
private fun <T, V : View> optional(id: Int, finder: T.(Int, View?) -> View?, container: Any): ReadOnlyProperty<T, V?>
= Lazy { c: View?, t: T, desc -> t.finder(id, c) as V? }
.apply { ButterKnifeContainer.add(container, this) }


@Suppress("UNCHECKED_CAST")
private fun <T, V : View> required(ids: IntArray, finder: T.(Int) -> View?)
= Lazy { t: T, desc -> ids.map { t.finder(it) as V? ?: viewNotFound(it, desc) } }
private fun <T, V : View> required(ids: IntArray, finder: T.(Int, View?) -> View?, container: Any): ReadOnlyProperty<T, List<V>>
= Lazy { c: View?, t: T, desc -> ids.map { t.finder(it, c) as V? ?: viewNotFound(it, desc) } }
.apply { ButterKnifeContainer.add(container, this) }

@Suppress("UNCHECKED_CAST")
private fun <T, V : View> optional(ids: IntArray, finder: T.(Int) -> View?)
= Lazy { t: T, desc -> ids.map { t.finder(it) as V? }.filterNotNull() }
private fun <T, V : View> optional(ids: IntArray, finder: T.(Int, View?) -> View?, container: Any): ReadOnlyProperty<T, List<V>>
= Lazy { c: View?, t: T, _ -> ids.map { t.finder(it, c) as V? }.filterNotNull() }
.apply { ButterKnifeContainer.add(container, this) }

// Like Kotlin's lazy delegate but the initializer gets the target and metadata passed to it
private class Lazy<T, V>(private val initializer: (T, KProperty<*>) -> V) : ReadOnlyProperty<T, V> {
private class Lazy<T, V>(private val initializer: (View?, T, KProperty<*>) -> V) : ReadOnlyProperty<T, V> {
private object EMPTY

private var value: Any? = EMPTY
private var containerView: View? = null

override fun getValue(thisRef: T, property: KProperty<*>): V {
if (value == EMPTY) {
value = initializer(thisRef, property)
value = initializer(containerView, thisRef, property)
}
@Suppress("UNCHECKED_CAST")
return value as V
}

fun setContainerView(c: View) {
containerView = c
}
}

private object ButterKnifeContainer {
private val containerMap = WeakHashMap<Any, MutableSet<Lazy<*, *>>>()

fun add(container: Any, lazy: Lazy<*, *>) {
containerMap.getOrPut(container, { Collections.newSetFromMap(WeakHashMap()) }).add(lazy)
}

fun register(container: Any, containerView: View) {
containerMap[container]?.forEach { it.setContainerView(containerView) }
}
}