I have been currently studying about Android architecture and I'm trying to build a simple app using MVVM following the Google guide. I followed the tutorial until I got to the cached part (which is not important to me at the moment). The problem is that I am getting a couple of problems that I am not able to resolve.
This is my fragment:
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);
}
}
This is my Repository:
@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;
}
}
This is my 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;
}
}
These are the dependencies I have in my gradle file:
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'
Finally this is the logcat containing both of the problems:
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)
Am I missing anything? I followed the tutorial step by step.
Firstly, in order to use Dagger with ViewModel
to inject dependencies, you need to create an implementation of 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);
}
}
}
This implementation will provide instances of ViewModel
subclasses.
Well, now we need to configure the ViewModelModule
that will provide the instances. But before this, we need to create an annotation to indentify the type of ViewModel
to be provided:
@MapKey
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewModelKey {
Class<? extends ViewModel> value();
}
The @MapKey
is an annotation from Dagger that identifies the return type of a @Provides` method.
Now, this is our ViewModelModule
:
@Module
public interface ViewModelModule {
@Binds
ViewModelProvider.Factory bindViewModelFactory(ViewModelFactory factory);
@Binds
@IntoMap
@ViewModelKey(OutboundFlightsViewModel.class)
ViewModel bindOutboundFlightsViewModel(OutboundFlightsViewModel viewModel);
}
Ok, so we need to configure the component:
@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> {}
}
Note that have particular installed module called AndroidSupportInjectionModule
. This module is part of dagger.android
that contains a lot of classes to make Dagger for Android easy to use.
Finally, we need to initialize the generated class from Dagger to create our dependency graph:
public class App extends DaggerApplication {
@Override
protected AndroidInjector<? extends App> applicationInjector() {
return DaggerAppComponent.builder().create(this);
}
}
Note that the App
class extends DaggerApplication
instead of Application
. DaggerApplication
is part of dagger.android
package.
For a full example, I have a project on my Github that uses MVVM + Dagger 2.
Project: https://github.com/WellingtonCosta/android-mvvm-databinding
Additionally, I also have a tiny library to turn easy to use Dagger with Android View Model.
Project: https://github.com/WellingtonCosta/viewmodel-dagger
I hope it helps you!
It would be great if you post some of your dagger code to see what's going on. It seems you are not injecting well your repository.
On the other hand, you have some trouble in your ViewModel initialization.
If you debug your view model, you would see that your LiveData
is always null because your FlightsRepository
couldn't be injected. And your are trying to observe it in your fragment without checking it's state --> NullPointerException.
First of all, you can initialize your MutableLiveData
variable despite your repository:
// 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;
}
In this case you have an empty LiveData that can be observed in your fragment, so when you fix your repository injection and whenever it has a value to post, you can react over this changes and do whatever you want.
Regarding Dagger classes:
You need your ApplicationComponent to bind every module you would like to provide:
@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)
}
Your BuildersModule class provide every Activity that would be injected. If your activity has a Fragment that needs to be Injected, also need to be here.
@Module
abstract class BuildersModule {
@ContributesAndroidInjector(modules = [(YourViewModelModule::class)])
internal abstract fun contributeYourActivity(): YourActivity
}
In this module you would declare every repository you would like to be injected.
@Module
class RepositoryModule {
@Provides
fun yourRepository(): YourRepository {
return YourRepository()
}
}
This is your view model module, you need to create your view model factory and provide it in order to be injected.
@Module
class YourViewModelModule {
@Provides
fun providesYourViewModelFactory(yourRepository: YourRepository): YourViewModelFactory {
return YourViewModelFactory(yourRepository)
}
}
You will need view model factories in order to inject params to your view model classes.
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")
}
}
This is your activity that contains your fragment. In order to inject your fragment, you need to implement 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)
}
}
An here is your fragment, now it can receive your view model factory injected. Notice that now your ViewModelProvider take two params, this and viewModelFactory so your view model can receive the injected params.
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);
}
}
And last but not least, you need to override your onCreate method in your App class and initialize dagger with its components. Notice that here you need to implement 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
}
}
I'm sorry, I wrote this in Kotlin like every day and I forgot you were coding in Java. But don't be scared, It's similar, you will understand without problem. Let me know if you couldn't understand sth.
User contributions licensed under CC BY-SA 3.0