Skip to content

Commit 93f5c99

Browse files
authored
Fragment factory PoC (#1)
* Added PoC with FragmentFactory from AndroidX fragment 1.1.0 alpha * Added Dagger 2 to the project FragmentFactory and Fragments (OfferFragment is the only with a non-default constructor for now) are created with the help of Dagger now. Based on the article: https://www.captechconsulting.com/blogs/using-androidxs-fragmentfactory-with-dagger-for-fragment-dependency-injection
1 parent e17ffb8 commit 93f5c99

15 files changed

Lines changed: 222 additions & 17 deletions

File tree

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ This is a sample project where I experimented with different aspects of the [And
1111
- starting Fragments for result and waiting for that result (similar to Activity's `startActivityForResult` and `onActivityResult`)
1212
- transitions between fragments with shared Toolbar
1313
- a simple Toolbar navigation icon morphing ("<-" -> "X")
14+
- base Dagger setup with a [FragmentFactory](https://developer.android.com/reference/androidx/fragment/app/FragmentFactory) used to create Fragments with non-default constructors
1415

15-
There's no MVP/MVVM/MVI here, no dependency injection - just the Navigation Component to make it simple.
16+
There's no MVP/MVVM/MVI here - just the Navigation Component to make the sample simple.
1617

1718
## How does it look like?
1819

app/build.gradle.kts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
plugins {
22
id("com.android.application")
33
kotlin("android")
4+
kotlin("kapt")
45
kotlin("android.extensions")
56
id("androidx.navigation.safeargs.kotlin")
67
}
@@ -29,9 +30,12 @@ dependencies {
2930
implementation(Dependencies.AndroidX.constraintlayout)
3031
implementation(Dependencies.AndroidX.legacySupport)
3132
implementation(Dependencies.Google.material)
32-
implementation(Dependencies.AndroidArchitecture.navigation)
3333
implementation(Dependencies.AndroidArchitecture.navigationKtx)
34-
implementation(Dependencies.AndroidArchitecture.navigationUi)
3534
implementation(Dependencies.AndroidArchitecture.navigationUiKtx)
3635
implementation(Dependencies.Util.timber)
36+
implementation(Dependencies.Injection.dagger)
37+
implementation(Dependencies.Injection.daggerAndroid)
38+
39+
kapt(Dependencies.Injection.daggerCompiler)
40+
kapt(Dependencies.Injection.daggerAnnotationProcessor)
3741
}

app/src/main/java/com/github/zawadz88/navigationcomponentplayground/CustomApplication.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
package com.github.zawadz88.navigationcomponentplayground
22

3-
import android.app.Application
3+
import com.github.zawadz88.navigationcomponentplayground.di.DaggerApplicationComponent
4+
import dagger.android.AndroidInjector
5+
import dagger.android.support.DaggerApplication
46
import timber.log.Timber
57

6-
class CustomApplication : Application() {
8+
class CustomApplication : DaggerApplication() {
9+
10+
override fun applicationInjector(): AndroidInjector<out CustomApplication> =
11+
DaggerApplicationComponent.builder().create(this)
712

813
override fun onCreate() {
914
super.onCreate()
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.github.zawadz88.navigationcomponentplayground
2+
3+
import android.content.Context
4+
import android.os.Bundle
5+
import androidx.navigation.fragment.NavHostFragment
6+
import com.github.zawadz88.navigationcomponentplayground.di.factory.InjectingFragmentFactory
7+
import dagger.android.support.AndroidSupportInjection
8+
import javax.inject.Inject
9+
10+
@Suppress("ProtectedInFinal")
11+
class InjectingNavHostFragment : NavHostFragment() {
12+
13+
@Inject
14+
protected lateinit var daggerFragmentInjectionFactory: InjectingFragmentFactory
15+
16+
override fun onAttach(context: Context) {
17+
AndroidSupportInjection.inject(this)
18+
super.onAttach(context)
19+
}
20+
21+
override fun onCreate(savedInstanceState: Bundle?) {
22+
childFragmentManager.fragmentFactory = daggerFragmentInjectionFactory
23+
super.onCreate(savedInstanceState)
24+
}
25+
}

app/src/main/java/com/github/zawadz88/navigationcomponentplayground/MainActivity.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11
package com.github.zawadz88.navigationcomponentplayground
22

33
import android.content.Intent
4+
import android.content.SharedPreferences
45
import android.os.Bundle
56
import android.view.Menu
67
import android.view.MenuItem
7-
import androidx.appcompat.app.AppCompatActivity
88
import androidx.navigation.findNavController
9+
import dagger.android.support.DaggerAppCompatActivity
910
import timber.log.Timber
11+
import javax.inject.Inject
1012

11-
class MainActivity : AppCompatActivity() {
13+
class MainActivity : DaggerAppCompatActivity() {
14+
15+
@Inject
16+
protected lateinit var sharedPreferences: SharedPreferences
1217

1318
override fun onCreate(savedInstanceState: Bundle?) {
1419
super.onCreate(savedInstanceState)
1520
setContentView(R.layout.activity_main)
21+
Timber.d("sharedPreferences: $sharedPreferences")
1622
}
1723

1824
override fun onNewIntent(intent: Intent?) {

app/src/main/java/com/github/zawadz88/navigationcomponentplayground/OfferFragment.kt

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.github.zawadz88.navigationcomponentplayground
22

3+
import android.content.SharedPreferences
34
import android.os.Bundle
45
import android.view.LayoutInflater
56
import android.view.View
@@ -9,19 +10,30 @@ import androidx.navigation.fragment.navArgs
910
import com.github.zawadz88.navigationcomponentplayground.login.NAVIGATION_RESULT_LOGGED_IN
1011
import com.github.zawadz88.navigationcomponentplayground.navigation.BackNavigationListener
1112
import com.github.zawadz88.navigationcomponentplayground.navigation.BackNavigationResult
13+
import com.github.zawadz88.navigationcomponentplayground.util.TextProducer
1214
import kotlinx.android.synthetic.main.fragment_offer.fragmentApplyButton
1315
import kotlinx.android.synthetic.main.fragment_offer.fragmentLoginButton
1416
import kotlinx.android.synthetic.main.fragment_offer.fragmentLoginWithPasswordButton
1517
import timber.log.Timber
18+
import javax.inject.Inject
1619

1720
private const val REQUEST_CODE_LOGIN = 1
1821

19-
class OfferFragment : BackNavigationListener, BaseFragment() {
22+
class OfferFragment
23+
@Inject constructor(
24+
private val sharedPreferences: SharedPreferences,
25+
private val textProducer: TextProducer
26+
) : BackNavigationListener, BaseFragment() {
2027

2128
private val args: OfferFragmentArgs by navArgs()
2229

2330
private val myId: Int by lazy { args.myId }
2431

32+
override fun onCreate(savedInstanceState: Bundle?) {
33+
super.onCreate(savedInstanceState)
34+
Timber.d("Constructor sharedPreferences: $sharedPreferences")
35+
}
36+
2537
override fun onCreateView(
2638
inflater: LayoutInflater, container: ViewGroup?,
2739
savedInstanceState: Bundle?
@@ -32,7 +44,7 @@ class OfferFragment : BackNavigationListener, BaseFragment() {
3244

3345
override fun onActivityCreated(savedInstanceState: Bundle?) {
3446
super.onActivityCreated(savedInstanceState)
35-
Toast.makeText(requireContext(), "myId: $myId", Toast.LENGTH_SHORT).show()
47+
Toast.makeText(requireContext(), textProducer.produceOfferMessage(myId), Toast.LENGTH_SHORT).show()
3648
fragmentApplyButton.setOnClickListener { goToApply() }
3749
fragmentLoginWithPasswordButton.setOnClickListener {
3850
navigateForResultWithAnimation(REQUEST_CODE_LOGIN, OfferFragmentDirections.actionOfferFragmentToLoginWithPasswordFragment())
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.github.zawadz88.navigationcomponentplayground.di
2+
3+
import com.github.zawadz88.navigationcomponentplayground.CustomApplication
4+
import com.github.zawadz88.navigationcomponentplayground.di.module.AppModule
5+
import dagger.Component
6+
import dagger.android.AndroidInjector
7+
import dagger.android.support.AndroidSupportInjectionModule
8+
import javax.inject.Singleton
9+
10+
@Singleton
11+
@Component(
12+
modules = [
13+
AndroidSupportInjectionModule::class,
14+
AppModule::class
15+
]
16+
)
17+
interface ApplicationComponent : AndroidInjector<CustomApplication> {
18+
19+
@Component.Builder
20+
abstract class Builder : AndroidInjector.Builder<CustomApplication>() {
21+
22+
abstract override fun build(): ApplicationComponent
23+
}
24+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.github.zawadz88.navigationcomponentplayground.di
2+
3+
import androidx.fragment.app.Fragment
4+
import dagger.MapKey
5+
import kotlin.annotation.AnnotationRetention.RUNTIME
6+
import kotlin.reflect.KClass
7+
8+
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
9+
@Retention(value = RUNTIME)
10+
@MapKey
11+
internal annotation class FragmentKey(val value: KClass<out Fragment>)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.github.zawadz88.navigationcomponentplayground.di.factory
2+
3+
import androidx.fragment.app.Fragment
4+
import androidx.fragment.app.FragmentFactory
5+
import timber.log.Timber
6+
import javax.inject.Inject
7+
import javax.inject.Provider
8+
9+
/**
10+
* Based on https://github.com/alex-townsend/FragmentFactoryDaggerSample/blob/0f89cb3b38983d5868f9aa183f02e47ca44869ef/app/src/main/java/com/atownsend/fragmentfactorysample/di/factory/InjectingFragmentFactory.kt
11+
*/
12+
class InjectingFragmentFactory
13+
@Inject constructor(
14+
private val creators: Map<Class<out Fragment>, @JvmSuppressWildcards Provider<Fragment>>
15+
) : FragmentFactory() {
16+
17+
init {
18+
Timber.d("Creating injection factory")
19+
}
20+
21+
override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
22+
val fragmentClass = loadFragmentClass(classLoader, className)
23+
val creator = creators[fragmentClass]
24+
?: return createFragmentAsFallback(classLoader, className)
25+
26+
try {
27+
return creator.get()
28+
} catch (e: Exception) {
29+
throw RuntimeException(e)
30+
}
31+
}
32+
33+
private fun createFragmentAsFallback(classLoader: ClassLoader, className: String): Fragment {
34+
Timber.w("No creator found for class: $className. Using default constructor")
35+
return super.instantiate(classLoader, className)
36+
}
37+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.github.zawadz88.navigationcomponentplayground.di.module
2+
3+
import android.app.Application
4+
import android.content.SharedPreferences
5+
import android.preference.PreferenceManager
6+
import com.github.zawadz88.navigationcomponentplayground.CustomApplication
7+
import com.github.zawadz88.navigationcomponentplayground.MainActivity
8+
import dagger.Binds
9+
import dagger.Module
10+
import dagger.Provides
11+
import dagger.android.ContributesAndroidInjector
12+
13+
@Module
14+
abstract class AppModule {
15+
16+
@Binds
17+
abstract fun bindApplication(app: CustomApplication): Application
18+
19+
@ContributesAndroidInjector(modules = [NavHostModule::class])
20+
abstract fun mainActivityInjector(): MainActivity
21+
22+
@Module
23+
companion object {
24+
@Provides
25+
@JvmStatic
26+
fun provideSharedPreferences(
27+
app: Application
28+
): SharedPreferences = PreferenceManager.getDefaultSharedPreferences(app)
29+
}
30+
}

0 commit comments

Comments
 (0)