53
votes

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

12
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

12 Answers

59
votes

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.

58
votes

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

36
votes

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.

33
votes

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

9
votes
@Inject
lateinit var mSharedPreferences: SharedPreferences

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

6
votes

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

3
votes

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

2
votes

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);
    }
}
1
votes

In My case, I changed

@Inject
var locationDBWrapper: LocationDBWrapper? = null

to

@Inject
lateinit var locationDBWrapper: LocationDBWrapper

Issue got resolved

0
votes

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")
0
votes

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
0
votes
@Inject
internal var mSharedPreferences: SharedPreferences? = null

Just change this to

@Inject
lateinit var mSharedPreferences: SharedPreferences

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