Inject database in a ContentProvider with dagger

前端 未结 5 2459
无人及你
无人及你 2021-02-19 22:57

I want to inject a singleton SqliteOpenHelper in a ContentProvider. However, it seems that the ContentProvider instance is being built bef

相关标签:
5条回答
  • 2021-02-19 23:15

    I faced the same issue and had to defer injection until the database was needed. You might be able to use Dagger's lazy injection to achieve the same effect.

    From the content provider's onCreate documentation:

    You should defer nontrivial initialization (such as opening, upgrading, and scanning databases) until the content provider is used

    Apparently this suggestion cannot be disregarded. Implementing the onCreate() method provides an example using an SQLiteOpenHelper with a content provider.

    0 讨论(0)
  • 2021-02-19 23:15

    Simple Solution

    There is a simple reason for this, onCreate() provider is called before the appropriate method in the Application. You can make the creation of a component in another method of the Application, for example attachBaseContext.


    1 Move your logic from onCreate to attachBaseContext in your Application.

    @Override
    public void attachBaseContext(Context base){
    
        super.attachBaseContext(base);
    
        mApplicationComponent = DaggerApplicationComponent.builder()
                .applicationModule(new ApplicationModule(this))
                .build();
        mApplicationComponent.inject(this);
    }
    

    2 You can now inject in OnCreate in your ContentProvider:

    public boolean onCreate() {
    
        YourMainApplication.get(getContext()).getComponent().inject(this);
    
        return true;
    }
    

    Disclaimer: Full Credits to @LeEnot from this russian blog: Dagger2 Inject in Content Provider. The answer is listed here for convenience as it is not available in English.

    0 讨论(0)
  • 2021-02-19 23:16

    As Alex Baker pointed out, the solution of the issue is to defer the injection when an operation (query, insert, update, delete) is called for the first time.

    to make an example:

    This won't work:

    public class YourProviderNull extends ContentProvider {
    
    
    @Inject
    YourSQLHelper yourHelper;
    
    
    @Override
    public boolean onCreate() {
        YourActivity.getComponent().inject(this); //NPE!!!
        //other logic
        return true;
    }
    
    @Override
    public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs,
                        String sortOrder) {
        SQLiteDatabase db = yourHelper.getReadableDatabase();
        //other logic
    }
    
    
    @Override
    public Uri insert(@NonNull Uri uri, ContentValues values) {
        SQLiteDatabase db = yourHelper.getWritableDatabase();
        //other logic
    }
    
    @Override
    public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
        SQLiteDatabase db = yourHelper.getWritableDatabase();
        //other logic
    }
    
    @Override
    public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        SQLiteDatabase db = yourHelper.getWritableDatabase();
        //other logicreturn db.update(resolveTableNameForUri(uri), values, selection, selectionArgs);
    }
    

    }

    but this will work correctly:

    public class YourProviderWorking extends ContentProvider {
    
    
    @Inject
    YourSQLHelper yourHelper;
    
    
    @Override
    public boolean onCreate() {
    
        //other logic
        return true;
    }
    
    @Override
    public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs,
                        String sortOrder) {
        if(yourHelper == null){
            deferInit();
        }
        SQLiteDatabase db = yourHelper.getReadableDatabase();
        //other logic
    }
    
    
    @Override
    public Uri insert(@NonNull Uri uri, ContentValues values) {
        if(yourHelper == null){
            deferInit();
        }
        SQLiteDatabase db = yourHelper.getWritableDatabase();
        //other logic
    }
    
    @Override
    public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) {
        if(yourHelper == null){
            deferInit();
        }
        SQLiteDatabase db = yourHelper.getWritableDatabase();
        //other logic
    }
    
    @Override
    public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        if(yourHelper == null){
            deferInit();
        }
        SQLiteDatabase db = yourHelper.getWritableDatabase();
        //other logicreturn db.update(resolveTableNameForUri(uri), values, selection, selectionArgs);
    }
    
    private void deferInit(){
        YourActivity.getComponent().inject(this);
    }
    

    }

    0 讨论(0)
  • 2021-02-19 23:17

    I know the question has an answer already but I post my experience hope this helps someone

    I added a module named ContentProviderBuilder :

    @Module
    abstract class ContentProviderBuilder {
    
    @ActivityScope
    @ContributesAndroidInjector
    abstract fun myCustomSuggestionProvider(): MyCustomSuggestionProvider?
    }
    

    then I added this module to my component, and extended the contentProvider from DaggerContentProvider

    and you have to add super.onCreate to your cotnentProvider and that's it

    this is my component:

    @Singleton
    @ApplicationScope
    @Component(modules = [AppModule::class, AndroidSupportInjectionModule::class, 
    RemoteModule::class, ContentProviderBuilder::class, ActivityBuilder::class, 
    FragmentBuilder::class, ViewModelModule::class])
    interface ApplicationComponent: AndroidInjector<MyApplication> {
    
    @Component.Builder
    interface Builder {
    
        @BindsInstance
        fun application(application: MyApplication): Builder
    
        fun build(): ApplicationComponent
    }
    
    override fun inject(app: MyApplication)
    }
    
    0 讨论(0)
  • 2021-02-19 23:23

    based on @Mick answer and @LeEnot article I posted an article on Medium using new Dagger 2 features.

    https://medium.com/@pedro.henrique.okawa/content-providers-dependency-injection-dagger-2-4ee3e49777b

    But basically I put the Dagger-Android dependency:

    // In my case I used 2.16 version
    implementation 'com.google.dagger:dagger-android:[DaggerVersion]'
    

    Implemented my custom Application class with HasContentProviderInjector interface and changed to inject my dependencies on attachBaseContext:

    class App : Application(), HasActivityInjector, HasContentProviderInjector {
    
        @Inject
        lateinit var androidInjector: DispatchingAndroidInjector<Activity>
    
        @Inject
        lateinit var contentProviderInjector: DispatchingAndroidInjector<ContentProvider>
    
        override fun activityInjector() = androidInjector
    
        override fun contentProviderInjector() = contentProviderInjector
    
        override fun attachBaseContext(base: Context?) {
            super.attachBaseContext(base)
            setupDependencyInjection()
        }
    
        /**
         * Injects the app component
         */
        private fun setupDependencyInjection() {
            DaggerAppComponent.builder().application(this).build().inject(this)
        }
    
    }
    

    And injected my databaseHelper instance on my ContentProvider class:

    class VoIPAppProvider: ContentProvider() {
    
        @Inject
        lateinit var databaseHelper: DatabaseHelper
    
        override fun onCreate(): Boolean {
            AndroidInjection.inject(this)
            return true
        }
    
        override fun insert(uri: Uri?, contentValues: ContentValues?): Uri? {
            ...
            return uri
        }
    
        override fun query(uri: Uri?, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor {
            ...
            return cursor
        }
    
        override fun getType(uri: Uri?): String {
            ...
            return type
        }
        override fun update(uri: Uri?, contentValues: ContentValues?, selection: String?, selectionArgs: Array<out String>?): Int {
            ...
            return rowsUpdated
        }
    
        override fun delete(uri: Uri?, selection: String?, selectionArgs: Array<out String>?): Int {
            ...
            return rowsDeleted
        }
    }
    

    PS: I decided to not use DaggerContentProvider class because most of us may already have a base class for some Android components.

    I hope it help you and thanks again to @Mick and @LeEnot

    0 讨论(0)
提交回复
热议问题