0

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.

Marcos Guimaraes
  • 1,243
  • 4
  • 27
  • 50

2 Answers2

0

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!

  • Hey, thanks for the great help! I am trying to implement your suggestion but I get 'cannot resolve symbol DaggerAppComponent'. Am I missing any import? – Marcos Guimaraes Feb 20 '19 at 19:46
  • Nevermind, I rebuilt the project and it resolved the problem. Do I have to change anything on my ViewModel class or my fragment? Because I am getting the same problem: 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 – Marcos Guimaraes Feb 20 '19 at 19:53
0

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.

  • Hi, thanks for the reply. I am new to this whole Dagger thing so I assumed I only needed to use what the guide told me too. But I tried the solution suggested below Wellington but I'm getting the same problem. Also, I tried to use the piece of code you added but it seems that postValue has a proteced access. – Marcos Guimaraes Feb 20 '19 at 20:08
  • In order to help you out with Dagger, you should post some of your module's code. Yes, postValue has protected access in LiveData, you should change the type to MutableLiveData. Let me edit the snippet. – Federico Heiland Feb 21 '19 at 13:14
  • Thanks for the reply. Regarding the module classes, I am using the exact same code suggested by Wellington Costa. But Dagger is not generating the binding classes, like "ActivityMainBinding", etc. Do you happen to know why? – Marcos Guimaraes Feb 21 '19 at 13:18
  • I edited the answer, and I added some Dagger code to get your injections working. Sorry for the language, I used Kotlin because I love it. Let me know if you have any doubts. And keep reading about Dagger, it's a huge framework that you need to understand it in order to avoid common mistakes. – Federico Heiland Feb 21 '19 at 16:34