diff --git a/README.md b/README.md index cd3c11b..b0963ce 100644 --- a/README.md +++ b/README.md @@ -44,16 +44,15 @@ This will provide the same instance of `name` and a new instance of `Manager` fo ### Step 2: Inject properties -Let's say you have a class `Screen` that needs these values. You need to create an injection Kapsule (hence the name) and invoke it as a function to map uninitialized delegates to your properties. +Let's say you have a class `Screen` that needs these values. You need to indicate the target module by implementing `Injects` interface with the target module and request uninitialized delegates for your properties. ```kotlin -class Screen { - private val kap = Kapsule() - private val name by kap { name } - private val manager by kap { manager } +class Screen : Injects { + private val name by required { name } + private val manager by required { manager } init { - kap.inject(Application.module) + inject(Application.module) } } ``` @@ -139,25 +138,39 @@ object Application { ### Optional delegates -So far you've only seen non-null values, but what happens if you need to inject a nullable value? You can use the `opt` function on your Kapsule: +So far you've only seen non-null values, but what happens if you need to inject a nullable value? You can use the `optional` function on your Kapsule: ```kotlin -val kap = Kapsule() -val firstName by kap { firstName } -val lastName by kap.opt { lastName } +val firstName by required { firstName } +val lastName by optional { lastName } ``` -Given both fields are strings, `firstName` is `String`, while `lastName` is `String?`. The default `kap {}` is actually shorthand for `kap.req {}`, so either can be used interchangeably. +Given both fields are strings, `firstName` is `String`, while `lastName` is `String?`. Unlike non-null properties, nullable ones can be read even before injection (the former would throw `KotlinNullPointerException`). +### Manual injection + +While the more convenient way to inject modules is by implementing the `Injects` interface, you may want to split the injection of separate modules (e.g. for testing). This can be done by creating separate instances of `Kapsule` and calling the injection methods on it. + +```kotlin +class Screen { + private val kap = Kapsule() + private val name by kap.required { name } + private val manager by kap.required { manager } + + init { + kap.inject(Application.module) + } +} +``` + ### Variable delegates In most cases you would make the injected properties `val`, however there's no reason it can't be a `var`, which would allow you to reassign it before or after injection. ```kotlin -val kap = Kapsule() -var firstName by kap { firstName } +var firstName by required { firstName } init { firstName = "before" @@ -173,15 +186,15 @@ Note that any delegates can be injected repeatedly, regardless of whether they'r Kotlin 1.1 infers property types from the delegates, which allows for a simpler definitions: ```kotlin -val firstName by kap { firstName } -val lastName by kap.opt { lastName } +val firstName by required { firstName } +val lastName by optional { lastName } ``` However, when using 1.0, you have to specify the types explicitly: ```kotlin -val firstName by kap { firstName } -val lastName by kap.opt { lastName } +val firstName by required { firstName } +val lastName by optional { lastName } ``` ## Samples @@ -194,7 +207,7 @@ To use Kapsule in your project, include it as a dependency: ```gradle dependencies { - compile "space.traversal.kapsule:kapsule-core:0.1" + compile "space.traversal.kapsule:kapsule-core:0.2" } ``` diff --git a/kapsule-core/src/main/kotlin/space/traversal/kapsule/Delegate.kt b/kapsule-core/src/main/kotlin/space/traversal/kapsule/Delegate.kt index 42f1d97..2fd9299 100644 --- a/kapsule-core/src/main/kotlin/space/traversal/kapsule/Delegate.kt +++ b/kapsule-core/src/main/kotlin/space/traversal/kapsule/Delegate.kt @@ -21,7 +21,8 @@ import kotlin.reflect.KProperty */ sealed class Delegate(internal val initializer: M.() -> T?) { - internal var value: T? = null + var value: T? = null + internal set /** * Initializes value from the injection module. diff --git a/kapsule-core/src/main/kotlin/space/traversal/kapsule/Injects.kt b/kapsule-core/src/main/kotlin/space/traversal/kapsule/Injects.kt new file mode 100644 index 0000000..41015bd --- /dev/null +++ b/kapsule-core/src/main/kotlin/space/traversal/kapsule/Injects.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2017 Traversal Space + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package space.traversal.kapsule + +/** + * Injection interface. + */ +interface Injects { + + /** + * Fetches [Kapsule] instance and calls [Kapsule.required]. + */ + fun required(initializer: M.() -> T) = Kapsules.get(this).required(initializer) + + /** + * Fetches [Kapsule] instance and calls [Kapsule.optional]. + */ + fun optional(initializer: M.() -> T?) = Kapsules.get(this).optional(initializer) + + /** + * Fetches [Kapsule] instance and calls [Kapsule.inject]. + */ + fun Injects.inject(module: M) { + Kapsules.get(this).inject(module) + } +} diff --git a/kapsule-core/src/main/kotlin/space/traversal/kapsule/Kapsule.kt b/kapsule-core/src/main/kotlin/space/traversal/kapsule/Kapsule.kt index d6b9868..2500406 100644 --- a/kapsule-core/src/main/kotlin/space/traversal/kapsule/Kapsule.kt +++ b/kapsule-core/src/main/kotlin/space/traversal/kapsule/Kapsule.kt @@ -32,20 +32,23 @@ class Kapsule { * Creates and registers delegate for a required (non-null) injectable property. * * @param initializer Initializer function from the module context to value. + * @return Required (non-null) property delegate. */ - fun req(initializer: M.() -> T) = Delegate.Required(initializer).apply { delegates += this } + fun required(initializer: M.() -> T) = Delegate.Required(initializer).apply { delegates += this } /** * Creates and registers delegate for an optional (nullable) injectable property. * * @param initializer Initializer function from the module context to value. + * @return Optional (nullable) property delegate. */ - fun opt(initializer: M.() -> T?) = Delegate.Optional(initializer).apply { delegates += this } + fun optional(initializer: M.() -> T?) = Delegate.Optional(initializer).apply { delegates += this } /** - * Shortcut for [req] by invoking the class like a function. + * Shortcut for [required] by invoking the class like a function. * * @param initializer Initializer function from the module context to value. + * @return Required (non-null) property delegate. */ - operator fun invoke(initializer: M.() -> T) = req(initializer) + operator fun invoke(initializer: M.() -> T) = required(initializer) } diff --git a/kapsule-core/src/main/kotlin/space/traversal/kapsule/Kapsules.kt b/kapsule-core/src/main/kotlin/space/traversal/kapsule/Kapsules.kt new file mode 100644 index 0000000..4021b06 --- /dev/null +++ b/kapsule-core/src/main/kotlin/space/traversal/kapsule/Kapsules.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2017 Traversal Space + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package space.traversal.kapsule + +import space.traversal.kapsule.util.CallerMap + +/** + * Static storage of [Kapsule] instances. + */ +object Kapsules { + + internal val instances = CallerMap, Kapsule<*>>() + + /** + * Retrieves active instance of [Kapsule] or creates a new one. + * + * @param caller Injection caller instance, used as lookup key. + * @return Stored or new instance. + */ + fun get(caller: Injects) = fetch(caller) ?: Kapsule().apply { instances[caller] = this } + + /** + * Fetches stored instance. + * + * @param caller Injection caller instance, used as lookup key. + * @return Stored instance or null. + */ + @Suppress("UNCHECKED_CAST") + internal fun fetch(caller: Injects) = instances[caller] as? Kapsule +} diff --git a/kapsule-core/src/main/kotlin/space/traversal/kapsule/util/CallerMap.kt b/kapsule-core/src/main/kotlin/space/traversal/kapsule/util/CallerMap.kt new file mode 100644 index 0000000..6ff5433 --- /dev/null +++ b/kapsule-core/src/main/kotlin/space/traversal/kapsule/util/CallerMap.kt @@ -0,0 +1,70 @@ +/* + * Copyright 2017 Traversal Space + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package space.traversal.kapsule.util + +import java.util.* + +/** + * Utility map for storing caller instances. + * + * Implements a cache such that the most recent entry is returned at constant time. + */ +internal class CallerMap : WeakHashMap() { + + @Volatile internal var lastKey: K? = null + @Volatile internal var lastValue: V? = null + + override fun containsKey(key: K) = key == lastKey || super.containsKey(key) + + override fun containsValue(value: V): Boolean { + return super.containsValue(value) + } + + operator override fun get(key: K): V? { + return if (key == lastKey) { + lastValue + } else { + val value = super.get(key) + setLast(key, value) + return value + } + } + + operator fun set(key: K, value: V) = put(key, value) + + override fun put(key: K, value: V): V? { + setLast(key, value) + return super.put(key, value) + } + + override fun remove(key: K): V? { + if (key == lastKey) { + setLast() + } + return super.remove(key) + } + + override fun clear() { + super.clear() + setLast() + } + + /** + * Set last key-value pair cache. + * + * @param key Map key. + * @param value Map value. + */ + private fun setLast(key: K? = null, value: V? = null) { + lastKey = key + lastValue = value + } +} \ No newline at end of file diff --git a/kapsule-core/src/test/kotlin/space/traversal/kapsule/DelegateTestCase.kt b/kapsule-core/src/test/kotlin/space/traversal/kapsule/DelegateTest.kt similarity index 98% rename from kapsule-core/src/test/kotlin/space/traversal/kapsule/DelegateTestCase.kt rename to kapsule-core/src/test/kotlin/space/traversal/kapsule/DelegateTest.kt index 764b32a..ae9cfe8 100644 --- a/kapsule-core/src/test/kotlin/space/traversal/kapsule/DelegateTestCase.kt +++ b/kapsule-core/src/test/kotlin/space/traversal/kapsule/DelegateTest.kt @@ -18,7 +18,7 @@ import kotlin.reflect.KProperty /** * Test case for [Delegate]. */ -class DelegateTestCase : TestCase() { +class DelegateTest : TestCase() { @Test fun testInitialize_required() { val delegate = Delegate.Required { value } diff --git a/kapsule-core/src/test/kotlin/space/traversal/kapsule/KapsuleTestCase.kt b/kapsule-core/src/test/kotlin/space/traversal/kapsule/KapsuleTest.kt similarity index 90% rename from kapsule-core/src/test/kotlin/space/traversal/kapsule/KapsuleTestCase.kt rename to kapsule-core/src/test/kotlin/space/traversal/kapsule/KapsuleTest.kt index 97c090f..1faa23b 100644 --- a/kapsule-core/src/test/kotlin/space/traversal/kapsule/KapsuleTestCase.kt +++ b/kapsule-core/src/test/kotlin/space/traversal/kapsule/KapsuleTest.kt @@ -18,20 +18,20 @@ import kotlin.reflect.KProperty /** * Test case for [Kapsule]. */ -class KapsuleTestCase : TestCase() { +class KapsuleTest : TestCase() { @Test fun testRequired() { val kap = Kapsule() - assertTrue(kap.req { reqInt } is Delegate.Required) + assertTrue(kap.required { reqInt } is Delegate.Required) assertTrue(kap { reqInt } is Delegate.Required) - assertTrue(kap.opt { reqInt } is Delegate.Optional) + assertTrue(kap.optional { reqInt } is Delegate.Optional) } @Test fun testDelegates() { val kap = Kapsule() for (i in 0..2) { val initializer: (MultiModule.() -> Int) = { reqInt } - kap.req(initializer) + kap.required(initializer) assertEquals(i + 1, kap.delegates.count()) assertEquals(initializer, kap.delegates[i].initializer) } @@ -43,7 +43,7 @@ class KapsuleTestCase : TestCase() { val kap = Kapsule() val prop = Mockito.mock(KProperty::class.java) - val optStringDelegate = kap.opt { optString } + val optStringDelegate = kap.optional { optString } val reqIntDelegate = kap { reqInt } listOf(MultiModule("test1", 3, "abc123"), @@ -81,7 +81,7 @@ class KapsuleTestCase : TestCase() { class Target { val kap = Kapsule() - var optString by kap.opt { optString } + var optString by kap.optional { optString } val reqInt by kap { reqInt } fun inject(module: MultiModule) { diff --git a/kapsule-core/src/test/kotlin/space/traversal/kapsule/KapsulesTest.kt b/kapsule-core/src/test/kotlin/space/traversal/kapsule/KapsulesTest.kt new file mode 100644 index 0000000..aeae856 --- /dev/null +++ b/kapsule-core/src/test/kotlin/space/traversal/kapsule/KapsulesTest.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2017 Traversal Space + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package space.traversal.kapsule + +import junit.framework.TestCase +import org.junit.Test +import org.omg.CORBA.Object + +/** + * Test case for [Kapsules]. + */ +class KapsulesTest : TestCase() { + + @Test fun testFetch() { + Kapsules.instances.clear() + val caller = object : Injects {} + assertEquals(null, Kapsules.fetch(caller)) + assertEquals(0, Kapsules.instances.size) + + } + + @Test fun testGet() { + Kapsules.instances.clear() + val caller = object : Injects {} + val kap = Kapsules.get(caller) + assertEquals(kap, Kapsules.get(caller)) + assertEquals(1, Kapsules.instances.size) + } +} \ No newline at end of file diff --git a/kapsule-core/src/test/kotlin/space/traversal/kapsule/util/CallerMapTest.kt b/kapsule-core/src/test/kotlin/space/traversal/kapsule/util/CallerMapTest.kt new file mode 100644 index 0000000..465b17a --- /dev/null +++ b/kapsule-core/src/test/kotlin/space/traversal/kapsule/util/CallerMapTest.kt @@ -0,0 +1,62 @@ +/* + * Copyright 2017 Traversal Space + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package space.traversal.kapsule.util + +import junit.framework.TestCase +import org.junit.Test + +/** + * Test case for [CallerMap]. + */ +class CallerMapTest : TestCase() { + + @Test fun testPut() { + val map = CallerMap() + assertEquals(null, map.lastKey) + assertEquals(null, map.lastValue) + + map["key1"] = "value1" + assertEquals("key1", map.lastKey) + assertEquals("value1", map.lastValue) + + map["key2"] = "value2" + assertEquals("key2", map.lastKey) + assertEquals("value2", map.lastValue) + } + + @Test fun testGet() { + val map = CallerMap() + + map["key1"] = "value1" + map["key2"] = "value2" + map["key1"] + assertEquals("key1", map.lastKey) + assertEquals("value1", map.lastValue) + } + + @Test fun testRemove() { + val map = CallerMap() + + map["key1"] = "value1" + map.remove("key1") + assertEquals(null, map.lastKey) + assertEquals(null, map.lastValue) + } + + @Test fun testClear() { + val map = CallerMap() + + map["key1"] = "value1" + map.clear() + assertEquals(null, map.lastKey) + assertEquals(null, map.lastValue) + } +} diff --git a/samples/android/src/main/AndroidManifest.xml b/samples/android/src/main/AndroidManifest.xml index 751d2e3..59bbee8 100644 --- a/samples/android/src/main/AndroidManifest.xml +++ b/samples/android/src/main/AndroidManifest.xml @@ -26,6 +26,9 @@ + \ No newline at end of file diff --git a/samples/android/src/main/java/space/traversal/kapsule/demo/data/MainDao.kt b/samples/android/src/main/java/space/traversal/kapsule/demo/data/MainDao.kt index db47179..df64ca0 100644 --- a/samples/android/src/main/java/space/traversal/kapsule/demo/data/MainDao.kt +++ b/samples/android/src/main/java/space/traversal/kapsule/demo/data/MainDao.kt @@ -11,24 +11,23 @@ package space.traversal.kapsule.demo.data import android.content.Context -import space.traversal.kapsule.Kapsule +import space.traversal.kapsule.Injects import space.traversal.kapsule.demo.App import space.traversal.kapsule.demo.di.Module /** * Main implementation of [Dao]. */ -class MainDao(context: Context) : Dao { +class MainDao(context: Context) : Dao, Injects { private companion object { private val KEY_COUNT = "count" } - private val kap = Kapsule() - private val prefs by kap { sharedPreferences } + private val prefs by required { sharedPreferences } init { - kap.inject(App.module(context)) + inject(App.module(context)) } override fun fetchCount() = prefs.getInt(KEY_COUNT, 0) diff --git a/samples/android/src/main/java/space/traversal/kapsule/demo/presenter/HomePresenter.kt b/samples/android/src/main/java/space/traversal/kapsule/demo/presenter/HomePresenter.kt index d731079..89e9404 100644 --- a/samples/android/src/main/java/space/traversal/kapsule/demo/presenter/HomePresenter.kt +++ b/samples/android/src/main/java/space/traversal/kapsule/demo/presenter/HomePresenter.kt @@ -11,6 +11,7 @@ package space.traversal.kapsule.demo.presenter import android.content.Context +import space.traversal.kapsule.Injects import space.traversal.kapsule.Kapsule import space.traversal.kapsule.demo.App import space.traversal.kapsule.demo.di.Module @@ -18,13 +19,12 @@ import space.traversal.kapsule.demo.di.Module /** * Presenter for home screen. */ -class HomePresenter(context: Context) : Presenter() { +class HomePresenter(context: Context) : Presenter(), Injects { - private val kap = Kapsule() - private val dao by kap { dao } + private val dao by required { dao } init { - kap.inject(App.module(context)) + inject(App.module(context)) } /** diff --git a/samples/android/src/main/java/space/traversal/kapsule/demo/view/CounterActivity.java b/samples/android/src/main/java/space/traversal/kapsule/demo/view/CounterActivity.java new file mode 100644 index 0000000..5769718 --- /dev/null +++ b/samples/android/src/main/java/space/traversal/kapsule/demo/view/CounterActivity.java @@ -0,0 +1,65 @@ +/* + * Copyright 2017 Traversal Space + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +package space.traversal.kapsule.demo.view; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatActivity; +import android.widget.TextView; +import kotlin.jvm.functions.Function1; +import space.traversal.kapsule.Delegate; +import space.traversal.kapsule.Kapsule; +import space.traversal.kapsule.demo.App; +import space.traversal.kapsule.demo.R; +import space.traversal.kapsule.demo.data.Dao; +import space.traversal.kapsule.demo.di.Module; + +/** + * Counter activity showcasing that Kapsule still works with Java, even if it's not pretty. + */ +public class CounterActivity extends AppCompatActivity { + + private final Kapsule mKap = new Kapsule<>(); + private final Delegate mDao = mKap.required(new Function1() { + @Override + public Dao invoke(Module module) { + return module.getDao(); + } + }); + + private TextView mCounterTextView; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_counter); + mKap.inject(App.Companion.module(this)); + + mCounterTextView = (TextView) findViewById(R.id.txt_counter); + } + + @Override + protected void onStart() { + super.onStart(); + + updateCount(); + } + + /** + * Fetches count from the DAO and applies it to the text view. + */ + private void updateCount() { + if (mDao.getValue() != null) { + String text = Integer.toString(mDao.getValue().fetchCount()); + mCounterTextView.setText(text); + } + } +} diff --git a/samples/android/src/main/java/space/traversal/kapsule/demo/view/HomeActivity.kt b/samples/android/src/main/java/space/traversal/kapsule/demo/view/HomeActivity.kt index 7182198..d6baf3e 100644 --- a/samples/android/src/main/java/space/traversal/kapsule/demo/view/HomeActivity.kt +++ b/samples/android/src/main/java/space/traversal/kapsule/demo/view/HomeActivity.kt @@ -10,9 +10,13 @@ package space.traversal.kapsule.demo.view +import android.content.Intent import android.os.Bundle import android.support.v7.app.AppCompatActivity +import android.view.Menu +import android.view.MenuItem import kotlinx.android.synthetic.main.activity_main.* +import space.traversal.kapsule.Injects import space.traversal.kapsule.Kapsule import space.traversal.kapsule.demo.App import space.traversal.kapsule.demo.R @@ -23,17 +27,16 @@ import space.traversal.kapsule.demo.presenter.HomeView /** * View for [HomePresenter]. */ -class HomeActivity : AppCompatActivity(), HomeView { +class HomeActivity : AppCompatActivity(), HomeView, Injects { - private val kap = Kapsule() - private val inflater by kap { layoutInflater } + private val inflater by required { layoutInflater } private lateinit var presenter: HomePresenter override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - kap.inject(App.module(this)) + inject(App.module(this)) presenter = HomePresenter(this).also { it.attach(this) } @@ -43,6 +46,19 @@ class HomeActivity : AppCompatActivity(), HomeView { presenter.load() } + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.main, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem?) = when (item?.itemId) { + R.id.menu_counter -> { + startActivity(Intent(this, CounterActivity::class.java)) + true + } + else -> super.onOptionsItemSelected(item) + } + override fun onDestroy() { presenter.detach() super.onDestroy() diff --git a/samples/android/src/main/res/layout/activity_counter.xml b/samples/android/src/main/res/layout/activity_counter.xml new file mode 100644 index 0000000..f7e7bf5 --- /dev/null +++ b/samples/android/src/main/res/layout/activity_counter.xml @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/samples/android/src/main/res/menu/main.xml b/samples/android/src/main/res/menu/main.xml new file mode 100644 index 0000000..f1dcbeb --- /dev/null +++ b/samples/android/src/main/res/menu/main.xml @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file diff --git a/samples/android/src/main/res/values/strings.xml b/samples/android/src/main/res/values/strings.xml index e328485..b8e609a 100644 --- a/samples/android/src/main/res/values/strings.xml +++ b/samples/android/src/main/res/values/strings.xml @@ -11,6 +11,9 @@ Kapsule Demo + + Counter + Add Remove diff --git a/samples/simple/src/main/kotlin/space/traversal/kapsule/demo/Demo.kt b/samples/simple/src/main/kotlin/space/traversal/kapsule/demo/Demo.kt index fef2160..5503232 100644 --- a/samples/simple/src/main/kotlin/space/traversal/kapsule/demo/Demo.kt +++ b/samples/simple/src/main/kotlin/space/traversal/kapsule/demo/Demo.kt @@ -10,7 +10,7 @@ package space.traversal.kapsule.demo -import space.traversal.kapsule.Kapsule +import space.traversal.kapsule.Injects import space.traversal.kapsule.demo.di.Module fun main(args: Array) { @@ -23,15 +23,13 @@ fun main(args: Array) { /** * Demo app definition. */ -class Demo(context: Context) { +class Demo(context: Context) : Injects { - private val kap = Kapsule() - - var firstName by kap { firstName } - val lastName by kap.opt { lastName } - val emails by kap { emails } + var firstName by required { firstName } + val lastName by optional { lastName } + val emails by required { emails } init { - kap.inject(context.module) + inject(context.module) } }