Skip to content

Commit

Permalink
Review whetstone packaging structure (#48)
Browse files Browse the repository at this point in the history
* Restructure whetstone packaging to favor independent use-cases rather than whetstone layers

* Rename InternalInjectApi to InternalWhetstoneApi and remove explicit targets

* Merge all app modules into a single ApplicationModule

* Change component functions to properties where it makes sense

* Improve documentation of Whetstone methods
  • Loading branch information
kingsleyadio authored Sep 8, 2022
1 parent 062cc05 commit cb72932
Show file tree
Hide file tree
Showing 35 changed files with 174 additions and 196 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.deliveryhero.whetstone.sample

import com.deliveryhero.whetstone.SingleIn
import com.deliveryhero.whetstone.component.ApplicationComponent
import com.deliveryhero.whetstone.scope.ApplicationScope
import com.deliveryhero.whetstone.app.ApplicationComponent
import com.deliveryhero.whetstone.app.ApplicationScope
import com.squareup.anvil.annotations.MergeComponent
import dagger.Component
import javax.inject.Singleton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import android.content.Context
import android.os.Build
import android.util.Log
import com.deliveryhero.whetstone.Whetstone
import com.deliveryhero.whetstone.component.ApplicationComponent
import com.deliveryhero.whetstone.component.ApplicationComponentOwner
import com.deliveryhero.whetstone.app.ApplicationComponent
import com.deliveryhero.whetstone.app.ApplicationComponentOwner
import com.deliveryhero.whetstone.app.ApplicationScope
import com.deliveryhero.whetstone.injector.ContributesInjector
import com.deliveryhero.whetstone.sample.MainService.Companion.NOTIFICATION_CHANNEL_ID
import com.deliveryhero.whetstone.scope.ApplicationScope
import javax.inject.Inject

@ContributesInjector(ApplicationScope::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import android.os.IBinder
import androidx.core.app.NotificationCompat
import com.deliveryhero.whetstone.Whetstone
import com.deliveryhero.whetstone.injector.ContributesInjector
import com.deliveryhero.whetstone.scope.ServiceScope
import com.deliveryhero.whetstone.service.ServiceScope
import javax.inject.Inject

@ContributesInjector(ServiceScope::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView
import com.deliveryhero.whetstone.Whetstone
import com.deliveryhero.whetstone.injector.ContributesInjector
import com.deliveryhero.whetstone.scope.ViewScope
import com.deliveryhero.whetstone.view.ViewScope
import javax.inject.Inject

@ContributesInjector(ViewScope::class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.deliveryhero.whetstone

import kotlin.reflect.KClass

@InternalInjectApi
@InternalWhetstoneApi
@Target(AnnotationTarget.ANNOTATION_CLASS)
@Retention(AnnotationRetention.RUNTIME)
public annotation class AutoScopedBinding(val base: KClass<*>, val scope: KClass<*>)
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
package com.deliveryhero.whetstone

@Retention(value = AnnotationRetention.BINARY)
@Target(
AnnotationTarget.CLASS,
AnnotationTarget.FUNCTION,
AnnotationTarget.TYPEALIAS,
AnnotationTarget.PROPERTY
)
@RequiresOptIn(
level = RequiresOptIn.Level.ERROR,
message = "This is an internal API that should not be used directly. No compatibility guarantees " +
"are provided. It is recommended to report your use-case of internal API so stable API" +
" could be provided instead."
)
public annotation class InternalInjectApi
public annotation class InternalWhetstoneApi
80 changes: 42 additions & 38 deletions whetstone/src/main/java/com/deliveryhero/whetstone/Whetstone.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import androidx.annotation.IdRes
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentFactory
import androidx.lifecycle.Lifecycle
import com.deliveryhero.whetstone.component.ActivityComponent
import com.deliveryhero.whetstone.component.ServiceComponent
import com.deliveryhero.whetstone.component.ApplicationComponent
import com.deliveryhero.whetstone.component.ApplicationComponentOwner
import com.deliveryhero.whetstone.component.ViewComponent
import com.deliveryhero.whetstone.activity.ActivityComponent
import com.deliveryhero.whetstone.app.ApplicationComponent
import com.deliveryhero.whetstone.app.ApplicationComponentOwner
import com.deliveryhero.whetstone.fragment.ContributesFragment
import com.deliveryhero.whetstone.injector.ContributesInjector
import com.deliveryhero.whetstone.service.ServiceComponent
import com.deliveryhero.whetstone.view.ViewComponent
import dagger.MembersInjector
import java.util.concurrent.atomic.AtomicReference

Expand All @@ -28,11 +29,14 @@ public object Whetstone {
private val root = AtomicReference<ApplicationComponent>()

@SuppressLint("NewApi")
@InternalInjectApi // This method path is not used yet
@InternalWhetstoneApi // This method path is not used yet
public fun initialize(initializer: () -> ApplicationComponent) {
root.updateAndGet { component -> component ?: initializer() }
}

/**
* Retrieves the component interface from an [application].
*/
@Suppress("MemberVisibilityCanBePrivate")
public fun <T : Any> fromApplication(application: Application): T {
require(application is ApplicationComponentOwner) {
Expand All @@ -42,7 +46,9 @@ public object Whetstone {
}

/**
* Returns the component interface from an [activity].
* Retrieves the component interface from an [activity].
*
* If one is not already existing for this activity, a new one will be created and returned
*/
@Suppress("MemberVisibilityCanBePrivate")
public fun <T : Any> fromActivity(activity: Activity): T {
Expand All @@ -55,31 +61,22 @@ public object Whetstone {
}

/**
* Returns the component interface from a [service].
*/

private fun <T : Any> fromService(service: Service): T {
return fromApplication<ServiceComponent.ParentComponent>(service.application)
.getServiceComponentFactory()
.create(service) as T
}

/**
* A helper that let you inject dependencies into the fields and methods of an [Application].
* Injects dependencies into the fields and methods of an [Application].
*
* Applications that use this method must have the [ContributesInjector] annotation,
* and they must have at least 1 `@Inject` field or method. Otherwise, calling this method
* will result in an [IllegalArgumentException]
* When injecting an application, the injected fields and methods must be annotated with `@Inject`
* and the application itself must be annotated with `@ContributesInjector(ApplicationScope::class)`
* Otherwise, calling this method will result in an [IllegalArgumentException]
* @see [ContributesInjector]
*/
public fun inject(application: Application) {
val injector = fromApplication<ApplicationComponent>(application)
.getMembersInjectorMap()[application.javaClass] as? MembersInjector<Application>
.membersInjectorMap[application.javaClass] as? MembersInjector<Application>

requireNotNull(injector).injectMembers(application)
}

/**
* A helper that let you inject default dependencies into the fields and methods of an [Activity].
* Injects dependencies into the fields and methods of the given [activity].
*
* For example:
* ```
Expand All @@ -95,24 +92,27 @@ public object Whetstone {
* }
* ```
*
* It also installs a default [FragmentFactory] if the [activity] is a [FragmentActivity].
* @see [installFragmentFactory]
* When injecting an activity, the injected fields and methods must be annotated with `@Inject`
* and the activity itself must be annotated with `@ContributesInjector(ActivityScope::class)`
* Otherwise, those fields will be ignored, which may lead to runtime exception.
* @see [ContributesInjector]
*
* Activities that use this method must have the [ContributesInjector] annotation,
* and they must have at least 1 `@Inject` field or method. Otherwise, calling this method
* will result in an [IllegalStateException]
* This method also installs Whetstone's [FragmentFactory] into the activity's fragment manager.
* As a result, such fragments can take advantage of Whetstone's Fragment injection feature.
* @see [ContributesFragment]
* @see [installFragmentFactory]
*/
public fun inject(activity: FragmentActivity) {
installFragmentFactory(activity)

val injector = fromActivity<ActivityComponent>(activity)
.getMembersInjectorMap()[activity.javaClass] as? MembersInjector<Activity>
.membersInjectorMap[activity.javaClass] as? MembersInjector<Activity>

injector?.injectMembers(activity)
}

/**
* A helper that let you inject default dependencies into the fields and methods of a [Service].
* Injects dependencies into the fields and methods of the given [service].
*
* For example:
* ```
Expand All @@ -128,13 +128,17 @@ public object Whetstone {
* }
* ```
*
* Services that use this method must have the [ContributesInjector] annotation,
* and they must have at least 1 `@Inject` field or method. Otherwise, calling this method
* will result in an [IllegalStateException]
* When injecting a service, the injected fields and methods must be annotated with `@Inject`
* and the service itself must be annotated with `@ContributesInjector(ServiceScope::class)`
* Otherwise, calling this method will result in an [IllegalArgumentException]
* @see [ContributesInjector]
*/
public fun inject(service: Service) {
val injector = fromService<ServiceComponent>(service)
.getMembersInjectorMap()[service.javaClass] as? MembersInjector<Service>
val app = service.application
val injector = fromApplication<ServiceComponent.ParentComponent>(app)
.getServiceComponentFactory()
.create(service)
.membersInjectorMap[service.javaClass] as? MembersInjector<Service>

requireNotNull(injector).injectMembers(service)
}
Expand All @@ -144,13 +148,13 @@ public object Whetstone {
val injector = fromActivity<ViewComponent.ParentComponent>(activity)
.getViewComponentFactory()
.create(view)
.getMembersInjectorMap()[view.javaClass] as? MembersInjector<View>
.membersInjectorMap[view.javaClass] as? MembersInjector<View>

requireNotNull(injector).injectMembers(view)
}

/**
* Installs a default multi-binding [FragmentFactory] into the [activity]'s [FragmentFactory].
* Installs Whetstone's multi-binding [FragmentFactory] into the [activity]'s fragment manager.
*
* Once called, the [FragmentFactory] will be used to create new instances from this point onward.
*
Expand All @@ -162,7 +166,7 @@ public object Whetstone {
"installFragmentFactory must be called before activity's super.onCreate."
}
val activityComponent = fromActivity<ActivityComponent>(activity)
activity.supportFragmentManager.fragmentFactory = activityComponent.getFragmentFactory()
activity.supportFragmentManager.fragmentFactory = activityComponent.fragmentFactory
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
package com.deliveryhero.whetstone.component
package com.deliveryhero.whetstone.activity

import android.app.Activity
import androidx.fragment.app.FragmentFactory
import com.deliveryhero.whetstone.SingleIn
import com.deliveryhero.whetstone.app.ApplicationScope
import com.deliveryhero.whetstone.injector.MembersInjectorMap
import com.deliveryhero.whetstone.scope.ActivityScope
import com.deliveryhero.whetstone.scope.ApplicationScope
import com.squareup.anvil.annotations.ContributesSubcomponent
import com.squareup.anvil.annotations.ContributesTo
import dagger.BindsInstance
import dagger.Module
import dagger.multibindings.Multibinds

/**
* A Dagger component that has the lifetime of the [android.app.Activity].
*/
@ContributesSubcomponent(scope = ActivityScope::class, parentScope = ApplicationScope::class)
@SingleIn(ActivityScope::class)
public interface ActivityComponent {
public fun getFragmentFactory(): FragmentFactory
public fun getMembersInjectorMap(): MembersInjectorMap
public val fragmentFactory: FragmentFactory
public val membersInjectorMap: MembersInjectorMap

/**
* Interface for creating an [ActivityComponent].
Expand All @@ -34,11 +31,3 @@ public interface ActivityComponent {
public fun getActivityComponentFactory(): Factory
}
}

@Module
@ContributesTo(ActivityScope::class)
public interface ActivityModule {

@Multibinds
public fun membersInjectors(): MembersInjectorMap
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.deliveryhero.whetstone.activity

import com.deliveryhero.whetstone.injector.MembersInjectorMap
import com.squareup.anvil.annotations.ContributesTo
import dagger.Module
import dagger.multibindings.Multibinds

@Module
@ContributesTo(ActivityScope::class)
public interface ActivityModule {

@Multibinds
public fun membersInjectors(): MembersInjectorMap
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.deliveryhero.whetstone.scope
package com.deliveryhero.whetstone.activity

/**
* Scope marker class for bindings that should exist for the life of an [android.app.Activity].
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package com.deliveryhero.whetstone.component
package com.deliveryhero.whetstone.app

import android.app.Application
import androidx.lifecycle.ViewModelProvider
import com.deliveryhero.whetstone.SingleIn
import com.deliveryhero.whetstone.injector.MembersInjectorMap
import com.deliveryhero.whetstone.scope.ApplicationScope
import com.squareup.anvil.annotations.ContributesTo
import dagger.BindsInstance
import javax.inject.Singleton
Expand All @@ -16,8 +15,8 @@ import javax.inject.Singleton
@SingleIn(ApplicationScope::class)
@Singleton
public interface ApplicationComponent {
public fun getViewModelFactory(): ViewModelProvider.Factory
public fun getMembersInjectorMap(): MembersInjectorMap
public val viewModelFactory: ViewModelProvider.Factory
public val membersInjectorMap: MembersInjectorMap

/**
* Interface for creating an [ApplicationComponent].
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.deliveryhero.whetstone.component
package com.deliveryhero.whetstone.app

/**
* Interface that should be implemented by the [android.app.Application] to supply
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.deliveryhero.whetstone.app

import android.app.Application
import android.content.Context
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
import com.deliveryhero.whetstone.ForScope
import com.squareup.anvil.annotations.ContributesTo
import dagger.Binds
import dagger.Module
import dagger.Provides
import kotlinx.coroutines.CoroutineScope

@Module
@ContributesTo(ApplicationScope::class)
public interface ApplicationModule {

@Binds
@ForScope(ApplicationScope::class)
public fun Application.bindContext(): Context

public companion object {

@Provides
@ForScope(ApplicationScope::class)
public fun provideLifecycleOwner(): LifecycleOwner {
return ProcessLifecycleOwner.get()
}

@Provides
@ForScope(ApplicationScope::class)
public fun provideCoroutineScope(
@ForScope(ApplicationScope::class) lifecycleOwner: LifecycleOwner
): CoroutineScope {
return lifecycleOwner.lifecycleScope
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.deliveryhero.whetstone.scope
package com.deliveryhero.whetstone.app

/**
* Scope marker class for bindings that should exist for the life of an [android.app.Application].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ package com.deliveryhero.whetstone.fragment

import androidx.fragment.app.Fragment
import com.deliveryhero.whetstone.AutoScopedBinding
import com.deliveryhero.whetstone.InternalInjectApi
import com.deliveryhero.whetstone.scope.FragmentScope
import com.deliveryhero.whetstone.InternalWhetstoneApi

/**
* Marker annotation signalling that the compiler should generate necessary instance
Expand All @@ -27,6 +26,6 @@ import com.deliveryhero.whetstone.scope.FragmentScope
* }
* ```
*/
@OptIn(InternalInjectApi::class)
@OptIn(InternalWhetstoneApi::class)
@AutoScopedBinding(base = Fragment::class, scope = FragmentScope::class)
public annotation class ContributesFragment
Loading

0 comments on commit cb72932

Please sign in to comment.