diff --git a/app/build.gradle b/app/build.gradle
index 069caf6..3043b47 100755
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -72,21 +72,17 @@ android {
}
dependencies {
- implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.0"
+ implementation project(':library-mvp-bakery')
// Support libs
- implementation 'androidx.annotation:annotation:1.1.0'
- implementation 'androidx.appcompat:appcompat:1.2.0'
- implementation 'androidx.preference:preference:1.1.1'
+ implementation 'androidx.annotation:annotation:1.2.0'
+ implementation 'androidx.appcompat:appcompat:1.3.1'
+ implementation 'androidx.preference:preference-ktx:1.1.1'
implementation 'com.google.android.material:material:1.2.1'
- implementation 'androidx.browser:browser:1.2.0'
- implementation 'androidx.constraintlayout:constraintlayout:2.0.2'
-
- // MVP lib
- implementation 'eu.darken.mvpbakery:library:0.7.1'
+ implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
// RX
- implementation "io.reactivex.rxjava2:rxjava:2.2.17"
+ implementation "io.reactivex.rxjava2:rxjava:2.2.21"
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
//Dagger
@@ -103,27 +99,4 @@ dependencies {
implementation "com.jakewharton.timber:timber:4.7.1"
implementation 'com.github.tbruyelle:rxpermissions:0.10.2'
-
- // Testing
- testImplementation "junit:junit:4.13"
- testImplementation "org.mockito:mockito-core:3.0.0"
- testImplementation 'com.nhaarman.mockitokotlin2:mockito-kotlin:2.1.0'
- testImplementation 'com.github.tmurakami:dexopener:1.0.2'
-
- androidTestImplementation "org.mockito:mockito-core:3.0.0"
- androidTestImplementation 'org.mockito:mockito-android:2.23.0'
- androidTestImplementation 'com.github.tmurakami:dexopener:1.0.2'
-
- androidTestImplementation 'androidx.test:runner:1.2.0'
- androidTestImplementation 'androidx.test:rules:1.2.0'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
- androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.2.0'
- androidTestImplementation 'androidx.test.espresso:espresso-intents:3.2.0'
- androidTestImplementation 'androidx.test.espresso.idling:idling-concurrent:3.2.0'
-}
-
-kotlin {
- experimental {
-
- }
}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 5e1b117..7ce6de2 100755
--- a/build.gradle
+++ b/build.gradle
@@ -3,7 +3,9 @@ buildscript {
'compileSdk': 30,
'minSdk' : 16,
'targetSdk' : 30,
-
+ 'deps' : [
+ 'dagger': '2.21',
+ ],
'version' : [
'major': 3,
'minor': 3,
@@ -16,21 +18,21 @@ buildscript {
ext.buildConfig.version['fullName'] = "${buildConfig.version.name}.${buildConfig.version.build}"
ext.buildConfig.version['code'] = buildConfig.version.major * 1000000 + buildConfig.version.minor * 10000 + buildConfig.version.patch * 100 + buildConfig.version.build
+
repositories {
google()
- jcenter()
+ mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:4.1.0'
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.0"
+ classpath 'com.android.tools.build:gradle:7.0.2'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31"
}
}
allprojects {
repositories {
google()
- jcenter()
-
+ mavenCentral()
maven { url 'https://jitpack.io' }
}
}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index f0bd82c..c08b24f 100755
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip
diff --git a/library-mvp-bakery/.gitignore b/library-mvp-bakery/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/library-mvp-bakery/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/library-mvp-bakery/build.gradle b/library-mvp-bakery/build.gradle
new file mode 100644
index 0000000..874cadd
--- /dev/null
+++ b/library-mvp-bakery/build.gradle
@@ -0,0 +1,45 @@
+plugins {
+ id 'com.android.library'
+ id 'kotlin-android-extensions'
+ id 'kotlin-android'
+}
+
+android {
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+ compileSdkVersion buildConfig.compileSdk
+
+ defaultConfig {
+ minSdkVersion buildConfig.minSdk
+ targetSdkVersion buildConfig.targetSdk
+
+ consumerProguardFiles 'proguard-rules.pro'
+ }
+ buildTypes {
+ debug {
+ }
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+dependencies {
+ implementation "androidx.lifecycle:lifecycle-common:2.3.1"
+ implementation "androidx.lifecycle:lifecycle-common-java8:2.3.1"
+ implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
+
+ implementation "androidx.annotation:annotation:1.2.0"
+ implementation "androidx.appcompat:appcompat:1.3.1"
+ implementation "androidx.preference:preference-ktx:1.1.1"
+
+ implementation "com.google.dagger:dagger:${buildConfig.deps.dagger}"
+ implementation "com.google.dagger:dagger-android:${buildConfig.deps.dagger}"
+ implementation "com.google.dagger:dagger-android-support:${buildConfig.deps.dagger}"
+
+ annotationProcessor "com.google.dagger:dagger-compiler:${buildConfig.deps.dagger}"
+ annotationProcessor "com.google.dagger:dagger-android-processor:${buildConfig.deps.dagger}"
+}
\ No newline at end of file
diff --git a/library-mvp-bakery/proguard-rules.pro b/library-mvp-bakery/proguard-rules.pro
new file mode 100644
index 0000000..218482b
--- /dev/null
+++ b/library-mvp-bakery/proguard-rules.pro
@@ -0,0 +1,18 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/darken/android-sdks/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+-keep class androidx.lifecycle.** { *; }
diff --git a/library-mvp-bakery/src/main/AndroidManifest.xml b/library-mvp-bakery/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..22f13d9
--- /dev/null
+++ b/library-mvp-bakery/src/main/AndroidManifest.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/base/MVPBakery.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/base/MVPBakery.kt
new file mode 100644
index 0000000..e5aedec
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/base/MVPBakery.kt
@@ -0,0 +1,93 @@
+package eu.darken.mvpbakery.base
+
+import android.app.Activity
+import android.app.Fragment
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.LifecycleOwner
+import eu.darken.mvpbakery.injection.InjectedPresenter
+import java.util.*
+
+class MVPBakery> internal constructor(builder: Builder) {
+ companion object {
+ @JvmStatic
+ fun > builder(): Builder {
+ return Builder()
+ }
+ }
+
+ private val presenterRetainer: PresenterRetainer
+ private val stateForwarder: StateForwarder?
+ private val presenterFactory: PresenterFactory
+ private val presenterCallbacks: List>
+
+ val presenter: PresenterT?
+ get() = presenterRetainer.presenter
+
+ init {
+ this.presenterRetainer = builder.presenterRetainer
+ this.stateForwarder = builder.stateForwarder
+ this.presenterCallbacks = builder.presenterCallbacks
+ this.presenterFactory = builder.presenterFactory
+ }
+
+ fun attach(lifecycleOwner: LifecycleOwner) {
+ if (stateForwarder != null) this.presenterRetainer.stateForwarder = stateForwarder
+ this.presenterRetainer.presenterFactory = presenterFactory
+ this.presenterRetainer.attach(lifecycleOwner, object : PresenterRetainer.Callback {
+ override fun onPresenterAvailable(presenter: PresenterT) {
+ presenterCallbacks.forEach { it.onPresenterAvailable(presenter) }
+ }
+ })
+ }
+
+ class Builder> internal constructor() where ViewT : Presenter.View, ViewT : LifecycleOwner {
+ internal lateinit var presenterFactory: PresenterFactory
+ internal lateinit var presenterRetainer: PresenterRetainer
+ internal var stateForwarder: StateForwarder? = null
+ internal val presenterCallbacks: MutableList> = ArrayList()
+
+ /**
+ * If you want the presenter to be able to store data via [Activity.onSaveInstanceState] then you need to call this.
+ *
+ * @param stateForwarder pass a [object][StateForwarder] that you have to call on in onCreate()/onSaveInstance()
+ */
+ fun stateForwarder(stateForwarder: StateForwarder): Builder {
+ this.stateForwarder = stateForwarder
+ return this
+ }
+
+ fun addPresenterCallback(callback: PresenterRetainer.Callback): Builder {
+ presenterCallbacks.add(callback)
+ return this
+ }
+
+ /**
+ * For injection you probably want to pass a [eu.darken.mvpbakery.injection.PresenterInjectionCallback]
+ */
+ fun presenterRetainer(presenterRetainer: PresenterRetainer): Builder {
+ this.presenterRetainer = presenterRetainer
+ return this
+ }
+
+ /**
+ * For injection pass an [InjectedPresenter]
+ */
+ fun presenterFactory(presenterFactory: PresenterFactory): Builder {
+ this.presenterFactory = presenterFactory
+ return this
+ }
+
+ fun build(): MVPBakery {
+ return MVPBakery(this)
+ }
+
+ /**
+ * @param lifecycleOwner Your [AppCompatActivity], [Fragment] or [Fragment]
+ */
+ fun attach(lifecycleOwner: ViewT): MVPBakery {
+ val lib = build()
+ lib.attach(lifecycleOwner)
+ return lib
+ }
+ }
+}
\ No newline at end of file
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/base/Presenter.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/base/Presenter.kt
new file mode 100644
index 0000000..0cb04b0
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/base/Presenter.kt
@@ -0,0 +1,12 @@
+package eu.darken.mvpbakery.base
+
+
+import androidx.lifecycle.LifecycleOwner
+
+interface Presenter {
+ fun onBindChange(view: ViewT?)
+
+ fun onDestroy()
+
+ interface View : LifecycleOwner
+}
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/base/PresenterFactory.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/base/PresenterFactory.kt
new file mode 100644
index 0000000..77c24ef
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/base/PresenterFactory.kt
@@ -0,0 +1,22 @@
+package eu.darken.mvpbakery.base
+
+interface PresenterFactory> {
+ fun createPresenter(): FactoryResult
+
+ class FactoryResult> internal constructor(
+ internal val presenter: PresenterT?,
+ internal val retry: Boolean,
+ internal val retryException: Throwable? = null
+ ) {
+ companion object {
+
+ fun > retry(retryException: Throwable? = null): FactoryResult {
+ return FactoryResult(null, true, retryException)
+ }
+
+ fun > forPresenter(presenter: PresenterT): FactoryResult {
+ return FactoryResult(presenter, false)
+ }
+ }
+ }
+}
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/base/PresenterRetainer.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/base/PresenterRetainer.kt
new file mode 100644
index 0000000..442d8b6
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/base/PresenterRetainer.kt
@@ -0,0 +1,34 @@
+package eu.darken.mvpbakery.base
+
+
+import android.os.Bundle
+import androidx.lifecycle.LifecycleOwner
+
+interface PresenterRetainer> {
+ val presenter: PresenterT?
+ var stateForwarder: StateForwarder?
+ var presenterFactory: PresenterFactory
+
+ fun attach(lifecycleOwner: LifecycleOwner, callback: Callback)
+
+ interface Callback> {
+ fun onPresenterAvailable(presenter: PresenterT)
+ }
+
+ class DefaultStateListener(private val retainer: PresenterRetainer<*, *>) : StateForwarder.Listener {
+
+ override fun onCreate(savedInstanceState: Bundle?): Boolean {
+ if (retainer.presenter is StateListener) {
+ (retainer.presenter as StateListener).onRestoreState(savedInstanceState)
+ return true
+ }
+ return false
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ if (retainer.presenter is StateListener) {
+ (retainer.presenter as StateListener).onSaveState(outState)
+ }
+ }
+ }
+}
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/base/StateForwarder.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/base/StateForwarder.kt
new file mode 100644
index 0000000..3df9cf3
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/base/StateForwarder.kt
@@ -0,0 +1,52 @@
+package eu.darken.mvpbakery.base
+
+import android.os.Bundle
+
+class StateForwarder {
+ private var internalCallback: Listener? = null
+ internal var inState: Bundle? = null
+ private var outState: Bundle? = null
+ private var isRestoreConsumed = false
+ val hasRestoreEvent: Boolean
+ get() = !isRestoreConsumed
+
+ fun setListener(internalCallback: Listener?) {
+ this.internalCallback = internalCallback
+ if (internalCallback == null) return
+ if (inState != null) {
+ isRestoreConsumed = internalCallback.onCreate(inState)
+ if (isRestoreConsumed) inState = null
+ }
+ outState?.let {
+ internalCallback.onSaveInstanceState(it)
+ outState = null
+ }
+ }
+
+ fun onCreate(savedInstanceState: Bundle?) {
+ isRestoreConsumed = internalCallback != null && internalCallback!!.onCreate(savedInstanceState)
+ if (!isRestoreConsumed) this.inState = savedInstanceState
+ }
+
+ fun onSaveInstanceState(outState: Bundle) {
+ if (internalCallback != null) {
+ internalCallback!!.onSaveInstanceState(outState)
+ } else {
+ this.outState = outState
+ }
+ }
+
+ interface Listener {
+ /**
+ * Call directly after [android.app.Activity.onCreate] or [android.app.Fragment.onCreate]
+ *
+ * @return true if the instance state was delivered or false if it should be persisted
+ */
+ fun onCreate(savedInstanceState: Bundle?): Boolean
+
+ /**
+ * Call before [android.app.Activity.onSaveInstanceState] or [android.app.Fragment.onSaveInstanceState]
+ */
+ fun onSaveInstanceState(outState: Bundle)
+ }
+}
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/base/StateListener.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/base/StateListener.kt
new file mode 100644
index 0000000..6cf44a5
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/base/StateListener.kt
@@ -0,0 +1,8 @@
+package eu.darken.mvpbakery.base
+
+import android.os.Bundle
+
+interface StateListener {
+ fun onRestoreState(inState: Bundle?)
+ fun onSaveState(outState: Bundle)
+}
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/base/ViewModelRetainer.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/base/ViewModelRetainer.kt
new file mode 100644
index 0000000..0c65426
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/base/ViewModelRetainer.kt
@@ -0,0 +1,134 @@
+package eu.darken.mvpbakery.base
+
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.Fragment
+import androidx.lifecycle.*
+import java.util.*
+
+class ViewModelRetainer> : PresenterRetainer {
+ companion object {
+ private fun getKey(owner: LifecycleOwner): String {
+ return (owner.javaClass.canonicalName ?: owner.javaClass.name) + ".MVPBakery.Container." + "Default"
+ }
+ }
+
+ override val presenter: PresenterT?
+ get() = container?.presenter
+ internal val repo: ContainerRepo
+ internal var container: Container? = null
+ override var stateForwarder: StateForwarder? = null
+ set(value) {
+ field = value
+ stateForwarder?.setListener(PresenterRetainer.DefaultStateListener(this))
+ }
+ override lateinit var presenterFactory: PresenterFactory
+
+ constructor(appCompatActivity: AppCompatActivity) {
+ repo = ViewModelProviders.of(appCompatActivity, ContainerRepo.FACTORY).get(ContainerRepo::class.java)
+ }
+
+ constructor(supportFragment: Fragment) {
+ repo = ViewModelProviders.of(supportFragment, ContainerRepo.FACTORY).get(ContainerRepo::class.java)
+ }
+
+ internal class ContainerRepo : ViewModel() {
+ private val containerMap = HashMap>()
+
+ operator fun get(key: Any): T {
+ @Suppress("UNCHECKED_CAST")
+ return containerMap[key] as T
+ }
+
+ fun put(key: Any, item: Container<*, *>?) {
+ containerMap[key] = item!!
+ }
+
+ override fun onCleared() {
+ for ((_, value) in containerMap) {
+ value.destroy()
+ }
+ containerMap.clear()
+ super.onCleared()
+ }
+
+ companion object {
+ val FACTORY: ViewModelProvider.Factory = object : ViewModelProvider.Factory {
+ override fun create(modelClass: Class): T {
+ try {
+ return modelClass.newInstance()
+ } catch (e: Exception) {
+ throw RuntimeException(e)
+ }
+ }
+ }
+ }
+ }
+
+ override fun attach(lifecycleOwner: LifecycleOwner, callback: PresenterRetainer.Callback) {
+ val key = getKey(lifecycleOwner)
+ container = repo.get>(key)
+
+ if (container == null || container!!.observer == null) {
+ val observer = object : DefaultLifecycleObserver {
+ var delayedInit = false
+
+ fun tryInitialization(isDelayedInit: Boolean) {
+ if (container == null) {
+ val result = presenterFactory.createPresenter()
+ if (result.retry) {
+ if (isDelayedInit) {
+ throw IllegalStateException("No presenter after final init attempt.", result.retryException)
+ } else {
+ delayedInit = true
+ return
+ }
+ }
+
+ container = Container(result.presenter)
+ repo.put(key, container)
+ }
+
+ container!!.observer = this
+
+ stateForwarder?.let {
+ if (it.hasRestoreEvent && presenter is StateListener) {
+ (presenter as StateListener).onRestoreState(it.inState)
+ }
+ }
+
+ callback.onPresenterAvailable(presenter!!)
+ }
+
+ override fun onCreate(owner: LifecycleOwner) = tryInitialization(false)
+
+ override fun onStart(owner: LifecycleOwner) {
+ if (delayedInit) {
+ delayedInit = false
+ tryInitialization(true)
+ }
+ @Suppress("UNCHECKED_CAST")
+ presenter?.onBindChange(owner as ViewT)
+ }
+
+ override fun onResume(owner: LifecycleOwner) = Unit
+
+ override fun onPause(owner: LifecycleOwner) = Unit
+
+ override fun onStop(owner: LifecycleOwner) = presenter!!.onBindChange(null)
+
+ override fun onDestroy(owner: LifecycleOwner) {
+ stateForwarder?.setListener(null)
+ container?.observer = null
+ owner.lifecycle.removeObserver(this)
+ }
+ }
+ lifecycleOwner.lifecycle.addObserver(observer)
+ }
+ }
+
+ internal class Container>(val presenter: PresenterT?) {
+ var observer: LifecycleObserver? = null
+
+ fun destroy() = presenter?.onDestroy()
+ }
+}
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/ComponentPresenter.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/ComponentPresenter.kt
new file mode 100644
index 0000000..a68766b
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/ComponentPresenter.kt
@@ -0,0 +1,35 @@
+package eu.darken.mvpbakery.injection
+
+
+import androidx.annotation.CallSuper
+import eu.darken.mvpbakery.base.Presenter
+
+abstract class ComponentPresenter> : Presenter {
+ lateinit var component: ComponentT
+
+ var view: ViewT? = null
+ private set
+
+ @CallSuper
+ override fun onBindChange(view: ViewT?) {
+ this.view = view
+ }
+
+ @CallSuper
+ override fun onDestroy() {
+
+ }
+
+ @FunctionalInterface
+ interface ViewAction {
+ fun runOnView(v: T)
+ }
+
+ fun onView(action: ViewAction) {
+ view?.let { action.runOnView(it) }
+ }
+
+ fun withView(action: (v: ViewT) -> Unit) {
+ view?.let { action.invoke(it) }
+ }
+}
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/ComponentSource.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/ComponentSource.kt
new file mode 100644
index 0000000..56400f8
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/ComponentSource.kt
@@ -0,0 +1,24 @@
+package eu.darken.mvpbakery.injection
+
+import dagger.android.AndroidInjector
+import dagger.internal.Preconditions.checkNotNull
+import javax.inject.Inject
+import javax.inject.Provider
+
+
+class ComponentSource @Inject constructor(private val injectorFactories: Map, @JvmSuppressWildcards Provider>>)
+ : ManualInjector {
+
+ override fun inject(instance: T) {
+ get(instance).inject(instance)
+ }
+
+ override fun get(instance: T): AndroidInjector {
+ val factoryProvider = injectorFactories[instance.javaClass]
+ ?: throw ClassNotFoundException("No injector available for $instance")
+
+ val factory = factoryProvider.get() as AndroidInjector.Factory
+
+ return checkNotNull(factory.create(instance), "%s.create(I) should not return null.", factory.javaClass.canonicalName)
+ }
+}
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/InjectedPresenter.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/InjectedPresenter.kt
new file mode 100644
index 0000000..690e913
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/InjectedPresenter.kt
@@ -0,0 +1,57 @@
+package eu.darken.mvpbakery.injection
+
+import android.app.Activity
+
+import androidx.fragment.app.Fragment
+import eu.darken.mvpbakery.base.Presenter
+import eu.darken.mvpbakery.base.PresenterFactory
+import eu.darken.mvpbakery.injection.activity.HasManualActivityInjector
+import eu.darken.mvpbakery.injection.fragment.HasManualFragmentInjector
+
+
+class InjectedPresenter, ComponentT : PresenterComponent> : PresenterFactory {
+ private val activity: Activity?
+ private val supportFragment: Fragment?
+
+ constructor(source: Activity) {
+ this.activity = source
+ this.supportFragment = null
+ }
+
+ constructor(source: Fragment) {
+ this.supportFragment = source
+ this.activity = null
+ }
+
+ override fun createPresenter(): PresenterFactory.FactoryResult {
+ val component: ComponentT
+ when {
+ activity != null -> {
+ val injectorSource = activity.application as HasManualActivityInjector
+ @Suppress("UNCHECKED_CAST")
+ component = injectorSource.activityInjector()[activity] as ComponentT
+ }
+ supportFragment != null -> {
+ val injectorSource = supportFragment.activity as HasManualFragmentInjector
+ try {
+ val injector = injectorSource.supportFragmentInjector()!!
+ @Suppress("UNCHECKED_CAST")
+ component = injector[supportFragment] as ComponentT
+ } catch (e: Throwable) {
+ when (e) {
+ is NullPointerException, is UninitializedPropertyAccessException -> {
+ return PresenterFactory.FactoryResult.retry(retryException = e)
+ }
+ else -> throw e
+ }
+ }
+ }
+ else -> throw RuntimeException("No injection source.")
+ }
+
+ val presenter = component.presenter
+ presenter.component = component
+
+ return PresenterFactory.FactoryResult.forPresenter(presenter)
+ }
+}
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/ManualInjector.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/ManualInjector.kt
new file mode 100644
index 0000000..c798f5e
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/ManualInjector.kt
@@ -0,0 +1,7 @@
+package eu.darken.mvpbakery.injection
+
+import dagger.android.AndroidInjector
+
+interface ManualInjector : AndroidInjector {
+ operator fun get(instance: T): AndroidInjector
+}
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/PresenterComponent.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/PresenterComponent.kt
new file mode 100644
index 0000000..3f075fd
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/PresenterComponent.kt
@@ -0,0 +1,7 @@
+package eu.darken.mvpbakery.injection
+
+import eu.darken.mvpbakery.base.Presenter
+
+interface PresenterComponent, Self : PresenterComponent> {
+ val presenter: PresenterT
+}
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/PresenterInjectionCallback.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/PresenterInjectionCallback.kt
new file mode 100644
index 0000000..7e9dcf6
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/PresenterInjectionCallback.kt
@@ -0,0 +1,14 @@
+package eu.darken.mvpbakery.injection
+
+import dagger.android.AndroidInjector
+import eu.darken.mvpbakery.base.Presenter
+import eu.darken.mvpbakery.base.PresenterRetainer
+
+class PresenterInjectionCallback, ComponentT>(private val injectionTarget: TargetT)
+ : PresenterRetainer.Callback where ComponentT : AndroidInjector, ComponentT : PresenterComponent {
+
+ override fun onPresenterAvailable(presenter: PresenterT) {
+ val component = presenter.component
+ component.inject(injectionTarget)
+ }
+}
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/activity/ActivityComponent.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/activity/ActivityComponent.kt
new file mode 100644
index 0000000..f9b3c62
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/activity/ActivityComponent.kt
@@ -0,0 +1,11 @@
+package eu.darken.mvpbakery.injection.activity
+
+import android.app.Activity
+
+import dagger.android.AndroidInjector
+
+interface ActivityComponent : AndroidInjector {
+ abstract class Builder> : AndroidInjector.Builder() {
+ abstract override fun build(): ComponentT
+ }
+}
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/activity/ActivityKey.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/activity/ActivityKey.kt
new file mode 100644
index 0000000..f77012a
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/activity/ActivityKey.kt
@@ -0,0 +1,11 @@
+package eu.darken.mvpbakery.injection.activity
+
+import android.app.Activity
+import dagger.MapKey
+import kotlin.reflect.KClass
+
+@MustBeDocumented
+@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
+@Retention(AnnotationRetention.RUNTIME)
+@MapKey
+annotation class ActivityKey(val value: KClass)
\ No newline at end of file
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/activity/HasManualActivityInjector.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/activity/HasManualActivityInjector.kt
new file mode 100644
index 0000000..77094f8
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/activity/HasManualActivityInjector.kt
@@ -0,0 +1,11 @@
+package eu.darken.mvpbakery.injection.activity
+
+import android.app.Activity
+
+import dagger.android.HasActivityInjector
+import eu.darken.mvpbakery.injection.ManualInjector
+
+interface HasManualActivityInjector : HasActivityInjector {
+
+ override fun activityInjector(): ManualInjector
+}
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/broadcastreceiver/BroadcastReceiverComponent.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/broadcastreceiver/BroadcastReceiverComponent.kt
new file mode 100644
index 0000000..e026daf
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/broadcastreceiver/BroadcastReceiverComponent.kt
@@ -0,0 +1,11 @@
+package eu.darken.mvpbakery.injection.broadcastreceiver
+
+import android.content.BroadcastReceiver
+
+import dagger.android.AndroidInjector
+
+interface BroadcastReceiverComponent : AndroidInjector {
+ abstract class Builder> : AndroidInjector.Builder() {
+ abstract override fun build(): ComponentT
+ }
+}
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/broadcastreceiver/BroadcastReceiverKey.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/broadcastreceiver/BroadcastReceiverKey.kt
new file mode 100644
index 0000000..697d884
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/broadcastreceiver/BroadcastReceiverKey.kt
@@ -0,0 +1,11 @@
+package eu.darken.mvpbakery.injection.broadcastreceiver
+
+import android.content.BroadcastReceiver
+import dagger.MapKey
+import kotlin.reflect.KClass
+
+@MustBeDocumented
+@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
+@Retention(AnnotationRetention.RUNTIME)
+@MapKey
+annotation class BroadcastReceiverKey(val value: KClass)
\ No newline at end of file
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/broadcastreceiver/HasManualBroadcastReceiverInjector.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/broadcastreceiver/HasManualBroadcastReceiverInjector.kt
new file mode 100644
index 0000000..c269a69
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/broadcastreceiver/HasManualBroadcastReceiverInjector.kt
@@ -0,0 +1,11 @@
+package eu.darken.mvpbakery.injection.broadcastreceiver
+
+
+import android.content.BroadcastReceiver
+
+import dagger.android.HasBroadcastReceiverInjector
+import eu.darken.mvpbakery.injection.ManualInjector
+
+interface HasManualBroadcastReceiverInjector : HasBroadcastReceiverInjector {
+ override fun broadcastReceiverInjector(): ManualInjector
+}
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/fragment/FragmentComponent.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/fragment/FragmentComponent.kt
new file mode 100644
index 0000000..125d153
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/fragment/FragmentComponent.kt
@@ -0,0 +1,10 @@
+package eu.darken.mvpbakery.injection.fragment
+
+import androidx.fragment.app.Fragment
+import dagger.android.AndroidInjector
+
+interface FragmentComponent : AndroidInjector {
+ abstract class Builder> : AndroidInjector.Builder() {
+ abstract override fun build(): ComponentT
+ }
+}
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/fragment/FragmentKey.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/fragment/FragmentKey.kt
new file mode 100644
index 0000000..18bfd85
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/fragment/FragmentKey.kt
@@ -0,0 +1,11 @@
+package eu.darken.mvpbakery.injection.fragment
+
+import androidx.fragment.app.Fragment
+import dagger.MapKey
+import kotlin.reflect.KClass
+
+@MustBeDocumented
+@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
+@Retention(AnnotationRetention.RUNTIME)
+@MapKey
+annotation class FragmentKey(val value: KClass)
\ No newline at end of file
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/fragment/HasManualFragmentInjector.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/fragment/HasManualFragmentInjector.kt
new file mode 100644
index 0000000..ea8350d
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/fragment/HasManualFragmentInjector.kt
@@ -0,0 +1,9 @@
+package eu.darken.mvpbakery.injection.fragment
+
+import androidx.fragment.app.Fragment
+import dagger.android.support.HasSupportFragmentInjector
+import eu.darken.mvpbakery.injection.ManualInjector
+
+interface HasManualFragmentInjector : HasSupportFragmentInjector {
+ override fun supportFragmentInjector(): ManualInjector?
+}
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/service/HasManualServiceInjector.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/service/HasManualServiceInjector.kt
new file mode 100644
index 0000000..f3c4983
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/service/HasManualServiceInjector.kt
@@ -0,0 +1,11 @@
+package eu.darken.mvpbakery.injection.service
+
+
+import android.app.Service
+
+import dagger.android.HasServiceInjector
+import eu.darken.mvpbakery.injection.ManualInjector
+
+interface HasManualServiceInjector : HasServiceInjector {
+ override fun serviceInjector(): ManualInjector
+}
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/service/ServiceComponent.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/service/ServiceComponent.kt
new file mode 100644
index 0000000..9740141
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/service/ServiceComponent.kt
@@ -0,0 +1,11 @@
+package eu.darken.mvpbakery.injection.service
+
+import android.app.Service
+
+import dagger.android.AndroidInjector
+
+interface ServiceComponent : AndroidInjector {
+ abstract class Builder> : AndroidInjector.Builder() {
+ abstract override fun build(): ComponentT
+ }
+}
diff --git a/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/service/ServiceKey.kt b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/service/ServiceKey.kt
new file mode 100644
index 0000000..c5344d5
--- /dev/null
+++ b/library-mvp-bakery/src/main/java/eu/darken/mvpbakery/injection/service/ServiceKey.kt
@@ -0,0 +1,11 @@
+package eu.darken.mvpbakery.injection.service
+
+import android.app.Service
+import dagger.MapKey
+import kotlin.reflect.KClass
+
+@MustBeDocumented
+@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
+@Retention(AnnotationRetention.RUNTIME)
+@MapKey
+annotation class ServiceKey(val value: KClass)
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index e7b4def..d0e1069 100755
--- a/settings.gradle
+++ b/settings.gradle
@@ -1 +1,2 @@
include ':app'
+include ':library-mvp-bakery'
\ No newline at end of file