69

I use in kotlin activity ViewPager and I want in Kotlin Fragment use the dagger injection. I have got Error: Dagger does not support injection into private fields. In Java Fragment the dagger injection work. Why can i not inject dagger in kotlin faragment ?

in my kotlin activity

mPagerAdapter = object : FragmentPagerAdapter(supportFragmentManager) {

        private val mFragments = arrayOf(KotlinFragment(), JavaFragment())
        private val mFragmentNames = arrayOf(getString(R.string.cashdocuments), getString(R.string.action_absmysql))

        override fun getItem(position: Int): Fragment {
            return mFragments[position]
        }

        override fun getCount(): Int {
            return mFragments.size
        }

        override fun getPageTitle(position: Int): CharSequence {
            return mFragmentNames[position]
        }
    }

my kotlin fragment

class KotlinFragment : Fragment()  {


@Inject
internal var mSharedPreferences: SharedPreferences? = null

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    (activity.application as SamfantozziApp).dgaeacomponent().inject(this)

}

override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?,
                          savedInstanceState: Bundle?): View? {
    super.onCreateView(inflater, container, savedInstanceState)
    val rootView = inflater!!.inflate(R.layout.activity_absserver, container, false)

    return rootView
}

}

messages gradle build

enter image description here

eurosecom
  • 2,932
  • 4
  • 24
  • 38
  • 1
    Its a shortcoming of Dagger in general- it can't access private fields. Doing so would require reflection and would be impossible to do at compile time like Dagger prefers. Doing so at run time would cost loading time to do reflective calls. So it just doesn't support it at all. – Gabe Sechan Nov 08 '17 at 19:12

12 Answers12

73

You have mistake here:

    @Inject
internal var mSharedPreferences: SharedPreferences? = null

This looks like you added @Inject annotation to the KotlinFragment class

Please change it to this and it will work:

var mSharedPreferences: SharedPreferences? = null
        @Inject set

Here is the link to the documentation: https://kotlinlang.org/docs/reference/annotations.html

Danylo Volokh
  • 4,149
  • 2
  • 33
  • 40
  • Thanks a lot. I may add that in case of DI, it may be useful to annotate the setter parameter. For example if you want to inject two `String` and you differentiate them with `@Qualifier` annotation classes, the setter needs to have that information: `@Inject set(@CustomAnnotation param) { field = param }`. – Benoit Duffez May 22 '18 at 23:45
  • 7
    You can also: `@set:Inject var mSharedPreferences: SharedPreferences? = null` – Andrzej Zabost Aug 27 '18 at 10:23
60

Accidentally I came across to my own answer and have to confess, that in fact it isn't working (at least for my use-case). Please consider Avilio's answer which worked for me also: substitute internal with lateinit.


Old answer

Remove internal modifier. Dagger needs at least package-private access in order to access annotated field. In Kotlin internal modifier is not a substitution for Java's package-private access modifier.

For detailed explanation of differences between modifiers in Java and Kotlin refer to Fragmented podcast's episode #101 - "Learning Kotlin – visibility modifiers, internal modifier, modules", as well as the official docs.

azizbekian
  • 60,783
  • 13
  • 169
  • 249
44

As simple you can do this, change this line

@Inject
internal var mSharedPreferences: SharedPreferences? = null

To

@set:Inject
internal var mSharedPreferences: SharedPreferences? = null

this work like charm in my case.

Hemant Parmar
  • 3,924
  • 7
  • 25
  • 49
37

I had the same error, even upon removing the internal keyword. So I replaced internal with lateinit and it worked.

s-hunter
  • 24,172
  • 16
  • 88
  • 130
Avilio
  • 581
  • 6
  • 5
  • 2
    Note that lateinit doesn't allow nullable type (? type). For nullable field, please make use of Provider or Lazy. The wrapped type can be nullable it. – OnJohn Jun 21 '18 at 12:01
  • 1
    This seems very odd, why does this work? Dagger's complaining about the `var` being `private` (which it isn't), and making it `lateinit` fixes that? Why? – Jonathan Aug 12 '19 at 17:18
12
@Inject
lateinit var mSharedPreferences: SharedPreferences

Also, this worked for me as well for late initialization variable

0xAliHn
  • 18,390
  • 23
  • 91
  • 111
8

I followed the above advise of not making the field internal. But, that was not enough. Kapt still converting the var to private during compilation.

I had to add the @JvmField annotation to make it behave better.

The answer I found was here: https://discuss.kotlinlang.org/t/kapt-converting-public-fields-to-private-during-compilation/11757

vikash singh
  • 1,479
  • 1
  • 19
  • 29
G K
  • 81
  • 2
  • 1
5
@Inject
internal var mSharedPreferences: SharedPreferences? = null

Just change this to

@Inject
lateinit var mSharedPreferences: SharedPreferences

P.S. You can't use lateinit with nullables.

shivang
  • 266
  • 3
  • 7
4

Remove the internal modifier. And I also had to declare lateinit and remove the Optional.

Paul Roub
  • 36,322
  • 27
  • 84
  • 93
4

In My case, I changed

@Inject
var locationDBWrapper: LocationDBWrapper? = null

to

@Inject
lateinit var locationDBWrapper: LocationDBWrapper

Issue got resolved

Tarun Anchala
  • 2,232
  • 16
  • 15
  • While this resolves the issue and thus the +1, if any still wants to be able to define the field as nullable, do this (summary: add @JvmField annotation with nullable declaration): https://stackoverflow.com/questions/44310196/kotlin-and-dagger-can-i-use-inject-to-an-object-still-make-it-nullable-optiona – nayakasu Jul 14 '21 at 07:16
2

Have you defined fun inject(fragment: KotlinFragment) in your ApplicationComponent? Because it looks like your error message is saying exactly that.

EDIT: maybe you haven't provided SharedPreferences in your Module like this:

@Module
public class AndroidModule {
    private final TimrApplication application;

    public AndroidModule(TimrApplication application) {
        this.application = application;
    }

    @Provides
    SharedPreferences provideSharedPreferences(){
        return PreferenceManager.getDefaultSharedPreferences(application);
    }
}
finki
  • 2,063
  • 1
  • 20
  • 23
  • Yes I did. I have defined KotlinFragment in my DgAeaComponent.java Problem is with definition of var mSharedPreferences in my KotlinFragment.kt – eurosecom Nov 09 '17 at 19:59
  • I added the provide method for the `SharedPreferences` like you should have defined in any of your modules which is used in a Component like this: `@Component(modules = AndroidModule.class)` – finki Nov 10 '17 at 08:41
  • I provide method for the SharedPreferences in my Module. The problem was with declaration of var mSharedPreferences in my KotlinFragment.kt. It has to be @Inject lateinit var mSharedPreferences: SharedPreferences Edit your answer i would like to check. – eurosecom Nov 10 '17 at 10:10
1

After updating to Android Studio 3.5, most of the answers don't work. The consolidated solution is :

1. If you want to use lateinit:

@Inject lateinit var mSharedPreferences: SharedPreferences

2. If you don't want to use lateinit:

var mSharedPreferences: SharedPreferences? = null
    @Inject set(value) {
       field = value
}

OR

@Inject
@JvmField
var mSharedPreferences: SharedPreferences

NOTE: These methods don't work anymore. You get the error not a valid name: <set-?>Provide

@set:Inject
var mSharedPreferences: SharedPreferences? = null
var mSharedPreferences: SharedPreferences? = null 
    @Inject set
ankuranurag2
  • 2,300
  • 15
  • 30
0

You can't make injected vars private, but you can make them protected, which is effectively the same in a final class (and in Kotlin classes are final by default unless you use the "open" modifier). Also, use lateinit modifier like it has been said already.

@Inject
protected lateinit var mSharedPreferences: SharedPreferences

If your class is not open, this might produce a warning in Android Studio saying "'protected' visibility is effectively 'private' in a final class" - you can just suppress that with:

@Suppress("ProtectedInFinal")
Serge
  • 359
  • 2
  • 13