Fragments
Tangle performs Fragment injection using constructor injection, just like the rest of a typical Dagger/Anvil graph. There are several steps to configuration, with two different paths at the end.
1. Set up Gradle
- Kotlin Plugin
- Groovy Plugin
- Kotlin dependencies block
- Groovy dependencies block
// any Android module's build.gradle.kts
plugins {
id("android-library") // or application, etc.
kotlin("android")
id("com.rickbusarow.tangle")
}
tangle {
fragmentsEnabled = true // default is null
}
// any Android module's build.gradle
plugins {
id 'android-library' // or application, etc.
kotlin("android")
id 'com.rickbusarow.tangle'
}
// optional
tangle {
fragmentsEnabled true // default is null
}
// any Android module's build.gradle.kts
plugins {
id("android-library") // or application, etc.
kotlin("android")
id("com.squareup.anvil")
}
dependencies {
api("com.rickbusarow.tangle:tangle-fragment-api:0.14.0")
anvil("com.rickbusarow.tangle:tangle-fragment-compiler:0.14.0")
}
// any Android module's build.gradle
plugins {
id 'android-library' // or application, etc.
kotlin("android")
id 'com.squareup.anvil'
}
dependencies {
api 'com.rickbusarow.tangle:tangle-fragment-api:0.14.0'
anvil 'com.rickbusarow.tangle:tangle-fragment-compiler:0.14.0'
}
2. Use Anvil for the app-scoped Component
Tangle uses the MergeComponent annotation from Anvil to identify the application's Component and add its own dependencies to the Dagger graph.
For anyone already using Anvil, there's probably nothing to be done here.
Anvil uses KClass<T>
references to define scopes. A common pattern is to define an AppScope
class specifically for this purpose in a low-level shared (Gradle) module:
package myApp.core.anvil
abstract class AppScope private constructor()
Then at your application Component, use MergeComponent
with this scope:
package myApp.app
import com.squareup.anvil.annotations.MergeComponent
import myApp.core.anvil.AppScope
@MergeComponent(AppScope::class)
interface MyAppComponent
3. Set the custom FragmentFactory
New Fragment instances are provided by TangleFragmentFactory. This custom factory
is automatically added to any Dagger graph for any @MergeComponent
-annotated Component.
If a requested Fragment is not contained within Tangle's bindings, TangleFragmentFactory
will
fall back to using the default initialization with an empty constructor. This means that large
projects can be migrated gradually.
If a project was already doing Fragment constructor injection using vanilla Dagger, they were
probably already binding into a
Map<Class<out Fragment>, Provider<@JvmSuppressWildcards Fragment>>
. That is what Tangle uses,
so existing multi-bound graphs will often support gradual migrations as well.
Any FragmentManager used within the application will need to have its fragmentFactory
property set to a TangleFragmentFactory
instance. This means the
AppCompatActivity.supportFragmentManager
, and possibly Fragment.childFragmentManager
as well.
This is easiest if your application uses an abstract base class.
abstract class BaseActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
supportFragmentManager.fragmentFactory = Components.get<BaseActivityComponent>()
.tangleFragmentFactory
super.onCreate(savedInstanceState)
}
}
Click to see how Components
works
In a core module, define this singleton.
package myApp.core.anvil
object Components {
@PublishedApi
internal val _components = mutableSetOf<Any>()
/** Set by Application class after creating app component */
fun add(component: Any) {
_components.add(component)
}
inline fun <reified T> get(): T = _components
.filterIsInstance<T>()
.single()
}
In your application, save off the AppComponent instance.
package myApp.core.anvil
class MyApplication : Application() {
override fun onCreate() {
val component = DaggerMyAppComponent.factory()
.create(/*...*/)
Components.add(component)
super.onCreate()
}
}
Anywhere you need to, create a "component" interface with whatever dependency properties you need,
and annotate it with @ContributesTo(<some scope definition>)
. Your AppComponent will
automatically implement this interface,
which means that an implementation of it will be stored in Components
.
import com.squareup.anvil.annotations.ContributesTo
@ContributesTo(AppScope::class)
interface BaseActivityComponent {
val tangleFragmentFactory: TangleFragmentFactory
}
Now, Components.get<BaseActivityComponent>()
will return MyAppComponent
safely cast to BaseActivityComponent
, and you can access its properties.
val fragmentFactory = Components.get<BaseActivityComponent>()
.tangleFragmentFactory
4. Contribute Fragments to the graph
Finally, add the Fragments themselves. For basic injection, the only difference from any other constructor-injected class is that you must add the ContributesFragment annotation. This will ensure that the Fragment is included in the TangleFragmentFactory.
import tangle.fragment.ContributesFragment
@ContributesFragment(AppScope::class)
class MyFragment @Inject constructor(
val myRepository: MyRepository
) : Fragment() {
// ...
}
5. Create Fragments with the FragmentManager
All the pieces are now in place, and your FragmentManagers are able to create Fragments with Dagger dependencies.
class MyActivity: BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportFragmentManager.beginTransaction()
.add<MyFragment>(R.id.fragmentContainer)
.commit()
}
}
Next step -- "Assisted" Bundle injection
Tangle is able to generate type-safe factories for Bundle arguments, similar to AssistedInject. Read about this more in bundle injection.
@ContributesFragment(AppScope::class)
class MyFragment @FragmentInject constructor() : Fragment() {
val name by arg<String>("name")
@FragmentInjectFactory
interface Factory {
fun create(@TangleParam("name") name: String): MyFragment
}
}