尝试使用Dagger

时间:2019-02-20 16:24:18

标签: java android mvvm dagger-2 dagger

我目前一直在研究Android体系结构,并试图在Google guide之后使用MVVM构建一个简单的应用程序。我按照本教程进行操作,直到进入缓存部分(此刻对我而言这并不重要)。问题是我遇到了一些无法解决的问题。

这是我的片段:

public class OutboundFragment extends Fragment {

    private OutboundFlightsViewModel viewModel;

    public OutboundFragment() {
        // Required empty public constructor
    }

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

        viewModel = ViewModelProviders.of(this).get(OutboundFlightsViewModel.class);
        viewModel.init();
        viewModel.getFlights().observe(this, flights -> {
            // Update UI.
        });
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_outbound, container, false);
    }

}

这是我的存储库:

@Singleton
public class FlightsRepository {

    public LiveData<Flights> getFlights() {

        final MutableLiveData<Flights> data = new MutableLiveData<>();

        ApiInterface apiService =
                ApiClient.getClient().create(ApiInterface.class);

        Call<Flights> call = apiService.getFlights();
        call.enqueue(new Callback<Flights>() {
            @Override
            public void onResponse(Call<Flights>call, Response<Flights> response) {
                data.setValue(response.body());
            }

            @Override
            public void onFailure(Call<Flights>call, Throwable t) {
                // Log error here since request failed
            }
        });

        return data;
    }
}

这是我的ViewModel:

public class OutboundFlightsViewModel extends ViewModel {

    private LiveData<Flights> flights;
    private FlightsRepository flightsRepo;

    @Inject
    public OutboundFlightsViewModel(FlightsRepository flightsRepo) {
        this.flightsRepo = flightsRepo;
    }

    public OutboundFlightsViewModel(){}

    public void init() {
        if (this.flights != null) {
            return;
        }
        if (flightsRepo != null) {
            flights = flightsRepo.getFlights();
        }
    }

    public LiveData<Flights> getFlights() {
        return this.flights;
    }
}

这些是我在gradle文件中具有的依赖项:

    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support:design:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.3'
    implementation "android.arch.lifecycle:extensions:1.1.1"
    implementation "android.arch.lifecycle:viewmodel:1.1.1"
    // Dagger
    implementation 'com.google.dagger:dagger:2.20'
    implementation 'com.google.dagger:dagger-android-support:2.20'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.20'
    // Retrofit, gson
    implementation 'com.google.code.gson:gson:2.8.2'
    implementation 'com.squareup.retrofit2:retrofit:2.0.2'
    implementation 'com.squareup.retrofit2:converter-gson:2.0.2'
    // RecyclerView
    implementation 'com.android.support:recyclerview-v7:28.0.0'
    // butter knife
    implementation 'com.jakewharton:butterknife:8.8.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'

最后,这是包含两个问题的logcat:

2019-02-20 13:19:12.864 3864-27637/? E/ExternalAccountType: Unsupported attribute viewStreamItemActivity
2019-02-20 13:19:13.069 1181-1181/? E/LoadedApk: Unable to instantiate appComponentFactory
    java.lang.ClassNotFoundException: Didn't find class "android.support.v4.app.CoreComponentFactory" on path: DexPathList[[],nativeLibraryDirectories=[/system/app/OPBackup/lib/arm64, /system/app/OPBackup/OPBackup.apk!/lib/arm64-v8a, /system/lib64, /system/lib64]]
        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:169)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
        at android.app.LoadedApk.createAppFactory(LoadedApk.java:226)
        at android.app.LoadedApk.updateApplicationInfo(LoadedApk.java:346)
        at android.app.ActivityThread.handleDispatchPackageBroadcast(ActivityThread.java:5524)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1831)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at com.android.server.SystemServer.run(SystemServer.java:482)
        at com.android.server.SystemServer.main(SystemServer.java:322)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:537)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:838)
2019-02-20 13:19:13.092 10596-10622/com.example.mguimaraes.maxmilhas E/libc: Access denied finding property "vendor.debug.egl.profiler"
2019-02-20 13:19:13.093 10596-10622/com.example.mguimaraes.maxmilhas E/libc: Access denied finding property "vendor.debug.prerotation.disable"
2019-02-20 13:19:13.073 1181-1181/? E/LoadedApk: Unable to instantiate appComponentFactory
    java.lang.ClassNotFoundException: Didn't find class "android.support.v4.app.CoreComponentFactory" on path: DexPathList[[],nativeLibraryDirectories=[/system/app/OPBackup/lib/arm64, /system/app/OPBackup/OPBackup.apk!/lib/arm64-v8a, /system/lib64, /system/lib64]]
        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:169)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:379)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:312)
        at android.app.LoadedApk.createAppFactory(LoadedApk.java:226)
        at android.app.LoadedApk.updateApplicationInfo(LoadedApk.java:346)
        at android.app.ActivityThread.handleDispatchPackageBroadcast(ActivityThread.java:5524)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1831)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:193)
        at com.android.server.SystemServer.run(SystemServer.java:482)
        at com.android.server.SystemServer.main(SystemServer.java:322)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:537)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:838)
2019-02-20 13:19:13.149 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.153 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.172 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.172 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.184 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.184 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.192 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.192 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.192 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.197 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.198 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.198 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.201 12490-12516/? E/neplus.launche: Invalid ID 0x00000000.
2019-02-20 13:19:13.343 10596-10596/com.example.mguimaraes.maxmilhas E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.mguimaraes.maxmilhas, PID: 10596
    java.lang.NullPointerException: Attempt to invoke virtual method 'void android.arch.lifecycle.LiveData.observe(android.arch.lifecycle.LifecycleOwner, android.arch.lifecycle.Observer)' on a null object reference
        at com.example.mguimaraes.maxmilhas.Fragments.OutboundFragment.onActivityCreated(OutboundFragment.java:30)
        at android.support.v4.app.Fragment.performActivityCreated(Fragment.java:2460)
        at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1483)
        at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManager.java:1784)
        at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1852)
        at android.support.v4.app.BackStackRecord.executeOps(BackStackRecord.java:802)
        at android.support.v4.app.FragmentManagerImpl.executeOps(FragmentManager.java:2625)
        at android.support.v4.app.FragmentManagerImpl.executeOpsTogether(FragmentManager.java:2411)
        at android.support.v4.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManager.java:2366)
        at android.support.v4.app.FragmentManagerImpl.execSingleAction(FragmentManager.java:2243)
        at android.support.v4.app.BackStackRecord.commitNowAllowingStateLoss(BackStackRecord.java:654)
        at android.support.v4.app.FragmentPagerAdapter.finishUpdate(FragmentPagerAdapter.java:146)
        at android.support.v4.view.ViewPager.populate(ViewPager.java:1244)
        at android.support.v4.view.ViewPager.populate(ViewPager.java:1092)
        at android.support.v4.view.ViewPager.onMeasure(ViewPager.java:1622)
        at android.view.View.measure(View.java:23355)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6758)
        at android.support.design.widget.CoordinatorLayout.onMeasureChild(CoordinatorLayout.java:733)
        at android.support.design.widget.HeaderScrollingViewBehavior.onMeasureChild(HeaderScrollingViewBehavior.java:95)
        at android.support.design.widget.AppBarLayout$ScrollingViewBehavior.onMeasureChild(AppBarLayout.java:1556)
        at android.support.design.widget.CoordinatorLayout.onMeasure(CoordinatorLayout.java:803)
        at android.view.View.measure(View.java:23355)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6758)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
        at android.support.v7.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:143)
        at android.view.View.measure(View.java:23355)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6758)
        at android.support.v7.widget.ActionBarOverlayLayout.onMeasure(ActionBarOverlayLayout.java:401)
        at android.view.View.measure(View.java:23355)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6758)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
        at android.view.View.measure(View.java:23355)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6758)
        at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1535)
        at android.widget.LinearLayout.measureVertical(LinearLayout.java:825)
        at android.widget.LinearLayout.onMeasure(LinearLayout.java:704)
        at android.view.View.measure(View.java:23355)
        at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6758)
        at android.widget.FrameLayout.onMeasure(FrameLayout.java:185)
        at com.android.internal.policy.DecorView.onMeasure(DecorView.java:717)
        at android.view.View.measure(View.java:23355)
        at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:2917)
        at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1747)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2040)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1635)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7795)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1172)
        at android.view.Choreographer.doCallbacks(Choreographer.java:984)
        at android.view.Choreographer.doFrame(Choreographer.java:809)
2019-02-20 13:19:13.343 10596-10596/com.example.mguimaraes.maxmilhas E/AndroidRuntime:     at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1158)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6863)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:537)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

我错过了什么吗?我一步一步地遵循了教程。

2 个答案:

答案 0 :(得分:0)

首先,为了将Dagger与ViewModel配合使用来注入依赖关系,您需要创建ViewModelProvider.Factory的实现:

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

    @SuppressWarnings("unchecked")
    @Override
    public <T extends ViewModel> T create(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 viewmodel class " + modelClass);
        }
        try {
            return (T) creator.get();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

此实现将提供ViewModel子类的实例。

好,现在我们需要配置将提供实例的ViewModelModule。但是在此之前,我们需要创建一个注释来标识要提供的ViewModel的类型:

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

@MapKey是Dagger的注解,用于标识@ Provides`方法的返回类型。

现在,这是我们的ViewModelModule

@Module
public interface ViewModelModule {

    @Binds
    ViewModelProvider.Factory bindViewModelFactory(ViewModelFactory factory);

    @Binds
    @IntoMap
    @ViewModelKey(OutboundFlightsViewModel.class)
    ViewModel bindOutboundFlightsViewModel(OutboundFlightsViewModel viewModel);

}

好,所以我们需要配置组件:

@Singleton
@Component(modules = {
    AndroidSupportInjectionModule.class,
    ViewModelModule.class
    // other modules goes here
})
interface AppComponent extends AndroidInjector<App> {

    @Component.Builder
    abstract class Builder extends AndroidInjector.Builder<App> {}

}

请注意,已经安装了名为AndroidSupportInjectionModule的特定模块。该模块是dagger.android的一部分,其中包含许多类,这些类使Dagger for Android易于使用。

最后,我们需要初始化Dagger生成的类以创建依赖图:

public class App extends DaggerApplication {

    @Override
    protected AndroidInjector<? extends App> applicationInjector() {
        return DaggerAppComponent.builder().create(this);
    }

}

请注意,App类扩展了DaggerApplication而不是ApplicationDaggerApplicationdagger.android软件包的一部分。

举一个完整的例子,我在Github上有一个使用MVVM + Dagger 2的项目。

项目:https://github.com/WellingtonCosta/android-mvvm-databinding

此外,我还有一个很小的库,可以轻松将Dagger与Android View Model一起使用。

项目:https://github.com/WellingtonCosta/viewmodel-dagger

希望对您有帮助!

答案 1 :(得分:0)

如果您发布一些匕首代码以查看发生了什么,那将是很好的。看来您没有很好地注入您的存储库。

另一方面,您在ViewModel初始化中遇到了一些麻烦。 如果调试视图模型,则会看到LiveData始终为空,因为无法注入FlightsRepository。您正在尝试在片段中观察它,而不检查它的状态-> NullPointerException。

首先,尽管您有存储库,也可以初始化MutableLiveData变量:

    // View Model snippet
    private MutableLiveData<Flights> flights;
    private FlightsRepository flightsRepo;

    @Inject
    public OutboundFlightsViewModel(FlightsRepository flightsRepo) {
        this.flightsRepo = flightsRepo;
    }

    public OutboundFlightsViewModel(){}

    public void init() {
        flights = new MutableLiveData<Flights>;

        if (flightsRepo != null) {
            flights.postValue(flightsRepo.getFlights());
        }
    }

    public LiveData<Flights> getFlights() {
        return this.flights;
    }

在这种情况下,您可以在片段中观察到一个空的LiveData,因此,当您修复存储库注入时,只要有值要发布,就可以对这些更改做出反应并做任何您想做的事。

关于Dagger类:

您需要您的ApplicationComponent绑定您想提供的每个模块:

@Singleton
@Component(modules = [(AndroidInjectionModule::class),  (BuildersModule::class), (RepositoryModule::class)])
interface ApplicationComponent {

    @Component.Builder
    interface Builder {

        @BindsInstance
        fun application(application: AppController): Builder

        fun build(): ApplicationComponent
    }

    fun inject(app: AppController)
}

您的BuildersModule类提供了将要注入的每个Activity。 如果您的活动中有需要注入的片段,也需要在此处。

@Module
abstract class BuildersModule {

    @ContributesAndroidInjector(modules = [(YourViewModelModule::class)])
    internal abstract fun contributeYourActivity(): YourActivity
}

在此模块中,您将声明要注入的每个存储库。

@Module
class RepositoryModule {
    @Provides
    fun yourRepository(): YourRepository {
        return YourRepository()
    }
}

这是您的视图模型模块,您需要创建视图模型工厂并将其提供以便注入。

@Module
class YourViewModelModule {
    @Provides
    fun providesYourViewModelFactory(yourRepository: YourRepository): YourViewModelFactory {
        return YourViewModelFactory(yourRepository)
    }
}

您将需要视图模型工厂,以便将参数注入到视图模型类中。

class YourViewModelFactory(private val repository: YourRepository) : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(YourViewModel::class.java)) {
            return YourViewModel(repository) as T
        }

        throw IllegalArgumentException("unknown view model class")
    }
}

这是包含片段的活动。为了注入您的片段,您需要实现HasSupportFragmentInjection。

class OutboundActivity : AppCompatActivity, HasSupportFragmentInjector {

   @Inject
   lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Fragment>

   override fun supportFragmentInjector(): AndroidInjector<Fragment> {
           return dispatchingAndroidInjector
    }

    override fun onCreate(savedInstanceState: Bundle?) {
           super.onCreate(savedInstanceState)
           AndroidInjection.inject(this)
    }


}

这是您的片段,现在它可以接收注入的视图模型工厂了。请注意,现在您的ViewModelProvider接受两个参数,分别为 this viewModelFactory ,以便您的视图模型可以接收注入的参数。


class OutboundFragment: Fragment {

    @Inject
    lateinit var viewModelFactory: YourViewModelFactory

    private var viewModel: OutboundFlightsViewModel

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        AndroidSupportInjection.inject(this)

        viewModel = ViewModelProviders.of(this, viewModelFactory).get(OutboundFlightsViewModel.class);
        viewModel.init();
        viewModel.getFlights().observe(this, flights -> {
            // Update UI.
        });
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_outbound, container, false);
    }

}

最后但并非最不重要的一点,您需要在App类中重写onCreate方法,并使用其组件初始化匕首。注意,这里需要实现HasActivityInjection。

class AppController : Application(), HasActivityInjector {

    @Inject
    lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector<Activity>


    override fun onCreate() {
        super.onCreate()

        // Registered a global instance of AppController
        appControllerInstance = this

        // Init Dagger
        DaggerApplicationComponent.builder()
                .application(this)
                .build()
                .inject(this)
     }

      override fun activityInjector(): AndroidInjector<Activity>? {
        return dispatchingAndroidInjector
    }
}

对不起,我每天都在Kotlin中写这篇文章,而我却忘记了您使用Java进行编码。但请不要害怕,类似,您会毫无问题地理解。 如果您听不懂,请告诉我。