Невозможно заставить кинжал ввести модель представления - PullRequest
0 голосов
/ 04 ноября 2019

Я следовал руководству по настройке DI в приложении для Android, и, насколько я могу судить, у меня все настроено правильно. Однако я получаю следующую ошибку:

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

Ниже приведены сокращенные версии (для краткости) моих классов:

ActivityModule

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

FragmentModule

@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);
    }
}

AppComponent

@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

Похоже, что кинжал не вводится в конструктор для ProfileViewModel.

Ответы [ 2 ]

1 голос
/ 04 ноября 2019

Оказалось, проблема была в моем классе фрагментов.

Изменено с;

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

}
0 голосов
/ 04 ноября 2019

Dagger не может создать ViewModel самостоятельно. Создание экземпляра ViewModel осуществляется через экземпляр ViewModelProvider.Factory. Вы должны сообщить кинжалу, как он может создать экземпляр 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();
}

Обратитесь к этому для получения дополнительной информации. деталь -

Зачем нужна фабрика моделей в Android?

...