【问题标题】:Unable to get Dagger to Inject Viewmodel无法让 Dagger 注入 Viewmodel
【发布时间】:2020-02-29 16:12:57
【问题描述】:

我一直在遵循在 Android 应用程序中设置 DI 的指南,据我所知,我已经正确设置了所有内容。但是,我收到以下错误:

java.lang.RuntimeException: Cannot create an instance of class com.topper.topper.ui.viewmodel.ProfileViewModel

以下是我的课程的精简版(为简洁起见):

活动模块

@Module
public abstract class ActivityModule {
    @ContributesAndroidInjector(modules = FragmentModule.class)
    abstract MainActivity contributeMainActivity();
}

片段模块

@Module
public abstract class FragmentModule {
    @ContributesAndroidInjector
    abstract ProfileFragment contributeProfileFragment();
}

ViewModelModule

@Module
public abstract class ViewModelModule {
    @Binds
    @IntoMap
    @ViewModelKey(ProfileViewModel.class)
    abstract ViewModel bindProfileViewModel(ProfileViewModel profileViewModel);

    @Binds
    abstract ViewModelProvider.Factory bindViewModelFactory(ViewModelFactory factory);
}

AppModule

@Module(includes = ViewModelModule.class)
public class AppModule {

    @Provides
    @Singleton
    TopperDB provideDatabase(Application application) {
        return Room.databaseBuilder(application,
                TopperDB.class, "TopperDB.db")
                .build();
    }

    @Provides
    @Singleton
    CachedImageDao provideCachedImageDao(TopperDB database) {
        return database.cachedImageDao();
    }

    @Provides
    @Singleton
    Executor provideExecutor() {
        return Executors.newSingleThreadExecutor();
    }

    @Provides
    @Singleton
    CachedImageRepository provideCachedImageRepository(CachedImageDao cachedImageDao, Executor executor) {
        return new CachedImageRepository(cachedImageDao, executor);
    }
}

应用组件

@Singleton
@Component(modules = {AndroidSupportInjectionModule.class, AndroidInjectionModule.class, ActivityModule.class, FragmentModule.class, AppModule.class})
public interface AppComponent {

    void inject(TopperApp app);

    @Component.Builder
    interface Builder {
        @BindsInstance
        Builder application(Application application);

        AppComponent build();
    }
}

ViewModelKey

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@MapKey
public @interface ViewModelKey {
    Class<? extends ViewModel> value();
}

ViewModelFactory

@Singleton
public class ViewModelFactory implements ViewModelProvider.Factory {

    private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;

    @Inject
    ViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
        this.creators = creators;
    }

    @NotNull
    @SuppressWarnings("unchecked")
    @Override
    public <T extends ViewModel> T create(@NotNull Class<T> modelClass) {
        Provider<? extends ViewModel> creator = creators.get(modelClass);
        if (creator == null) {
            for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entrySet()) {
                if (modelClass.isAssignableFrom(entry.getKey())) {
                    creator = entry.getValue();
                    break;
                }
            }
        }
        if (creator == null) {
            throw new IllegalArgumentException("unknown model class " + modelClass);
        }
        try {
            return (T) creator.get();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

CachedImageRepository

@Singleton
public class CachedImageRepository extends BaseRepository {

    private final CachedImageDao cachedImageDao;
    private final Executor executor;

    @Inject
    public CachedImageRepository(CachedImageDao cachedImageDao, Executor executor) {
        this.cachedImageDao = cachedImageDao;
        this.executor = executor;
    }
}

ProfileViewModel

public class ProfileViewModel extends ViewModel {

    private CachedImageRepository cachedImageRepo;

    @Inject
    public ProfileViewModel(CachedImageRepository cachedImageRepo) {
        this.cachedImageRepo = cachedImageRepo;
    }
}

ProfileFragment

public class ProfileFragment extends BaseFragment {

    @Inject
    ViewModelProvider.Factory viewModelFactory;

    private ProfileViewModel mViewModel;

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        configureDagger();
    }

    public View onCreateView(@NonNull LayoutInflater inflater,
                             ViewGroup container, Bundle savedInstanceState) {

        FragmentProfileBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_profile, container, false);

        mViewModel = ViewModelProviders.of(this.requireActivity(), viewModelFactory).get(ProfileViewModel.class);
        mViewModel.init();
        binding.setProfileViewModel(mViewModel);

        return binding.getRoot();
    }

    private void configureDagger() {
        AndroidSupportInjection.inject(this);
    }

}

MainActivity

public class MainActivity extends AppCompatActivity implements ProgressDisplay, HasSupportFragmentInjector {

    @Inject
    DispatchingAndroidInjector<Fragment> dispatchingAndroidInjector;

    @Inject
    ViewModelProvider.Factory viewModelFactory;

    private AppBarLayout appBarLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        this.configureDagger();
    }

    private void configureDagger() {
        AndroidInjection.inject(this);
    }

}

TopperApp

public class TopperApp extends Application implements HasActivityInjector {

    public Context ctx;
    @Inject
    DispatchingAndroidInjector<Activity> dispatchingAndroidInjector;

    @Override
    public void onCreate() {
        super.onCreate();
        this.initDagger();
        ctx = getApplicationContext();
    }

    public Context getAppContext() {
        return ctx;
    }

    @Override
    public DispatchingAndroidInjector<Activity> activityInjector() {
        return dispatchingAndroidInjector;
    }

我想我已经包含了上面所有的相关细节,但如果我遗漏了什么,请告诉我。

任何帮助将不胜感激,这几天我一直在用头撞墙。

谢谢。

编辑:如果有帮助,我尝试向 ProfileViewModel 添加一个空构造函数,这会导致以下错误:

java.lang.NullPointerException: Attempt to invoke virtual method 'void com.topper.topper.data.repo.CachedImageRepository.cacheImage(android.content.Context, java.lang.String, int)' on a null object reference

所以看来 dagger 没有注入到 ProfileViewModel 的构造函数中。

【问题讨论】:

    标签: android viewmodel dagger-2


    【解决方案1】:

    Dagger 无法自行创建 ViewModel。 ViewModel 实例化是通过 ViewModelProvider.Factory 的实例完成的。 您需要告诉 dagger 它如何创建ProfileViewModel. 的实例

    所以@Binds 在这种情况下对你不起作用。您需要定义一个返回ProfileViewModel 实例的方法并用@Provides 对其进行注释。

    例如-

    @Module
    public class ViewModelModule {
    
    @Provides
    @IntoMap
    @ViewModelKey(ProfileViewModel.class)
    public ProfileViewModel bindProfileViewModel(ViewModelFactory factory) {
        return factory.create();
    }
    
    @Provides
    public ViewModelProvider.Factory bindViewModelFactory(){
        return new ViewModelFactory();
    }
    

    更多细节请参考这里 -

    Why a viewmodel factory is needed in Android?

    【讨论】:

    • 嗨,请参阅我发布的答案 - 我已经使用 @Binds。
    【解决方案2】:

    原来问题出在我的 Fragment 课上。

    改自;

    public class ProfileFragment extends BaseFragment {
    
        @Inject
        ViewModelProvider.Factory viewModelFactory;
    
        private ProfileViewModel mViewModel;
    
        @Override
        public void onActivityCreated(@Nullable Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            configureDagger();
        }
    
        public View onCreateView(@NonNull LayoutInflater inflater,
                                 ViewGroup container, Bundle savedInstanceState) {
    
            FragmentProfileBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_profile, container, false);
    
            mViewModel = ViewModelProviders.of(this.requireActivity(), viewModelFactory).get(ProfileViewModel.class);
            mViewModel.init();
            binding.setProfileViewModel(mViewModel);
    
            return binding.getRoot();
        }
    
        private void configureDagger() {
            AndroidSupportInjection.inject(this);
        }
    
    }
    

    public class ProfileFragment extends BaseFragment {
    
        @Inject
        ViewModelProvider.Factory viewModelFactory;
    
        private ProfileViewModel mViewModel;
    
        public View onCreateView(@NonNull LayoutInflater inflater,
                                 ViewGroup container, Bundle savedInstanceState) {
    
            configureDagger();
    
            FragmentProfileBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_profile, container, false);
    
            mViewModel = ViewModelProviders.of(this.requireActivity(), viewModelFactory).get(ProfileViewModel.class);
            mViewModel.init();
            binding.setProfileViewModel(mViewModel);
    
            return binding.getRoot();
        }
    
        private void configureDagger() {
            AndroidSupportInjection.inject(this);
        }
    
    }
    

    【讨论】:

    • 其实你应该把configureDagger()移到onCreate()或者onAttach(Context
    • @EpicPandaForce 谢谢,一旦我发现了问题,我确实将它移到了 OnAttach()。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-02-13
    • 2018-03-08
    • 2021-02-23
    • 1970-01-01
    相关资源
    最近更新 更多