Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to inject repository into ViewModel using Dagger 2 [duplicate]

I am following this documentation to learn about LiveData and ViewModel. In the doc, the ViewModel class has constructor as such,

public class UserModel extends ViewModel {
  private MutableLiveData<User> user;

  @Inject UserModel(MutableLiveData<User> user) {
    this.user = user;
  }

  public void init() {
    if (this.user != null) {
      return;
    }
    this.user = new MutableLiveData<>();
  }

  public MutableLiveData<User> getUser() {
    return user;
  }
}

However, when I run the code, I get exception:

final UserViewModelviewModel = ViewModelProviders.of(this).get(UserViewModel.class);

Caused by: java.lang.RuntimeException: Cannot create an instance of class UserViewModel Caused by: java.lang.InstantiationException: java.lang.Class has no zero argument constructor

like image 708
Prabin Timsina Avatar asked May 26 '17 05:05

Prabin Timsina


7 Answers

In my case as I'm using HILT, it was lacking one annotation above the Fragment that has a ViewModel: @AndroidEntryPoint

@AndroidEntryPoint
class BestFragment : Fragment() { 
....

Of course in your ViewModel class you also need to Annotate with what HILT needs: @ViewModelInject

class BestFragmentViewModel @ViewModelInject constructor(var userManager: UserManager) : ViewModel() {
....
}
like image 121
Dimitri de Jesus Avatar answered Oct 24 '22 01:10

Dimitri de Jesus


While initializing subclasses of ViewModel using ViewModelProviders, by default it expects your UserModel class to have a zero argument constructor. In your case your constructor has the argument MutableLiveData<User> user.

One way to fix this is to have a default no arg constructor for your UserModel.

Otherwise, if you want to have a non-zero argument constructor for your ViewModel class, you may have to create a custom ViewModelFactory class to initialise your ViewModel instance, which implements the ViewModelProvider.Factory interface.

I have not tried this yet, but here's a link to an excellent Google sample for this: github.com/googlesamples/android-architecture-components. Specifically, check out this class GithubViewModelFactory.java for Java code and this class GithubViewModelFactory.kt for the corresponding Kotlin code.

like image 38
Shahbaz Ahmed Avatar answered Oct 24 '22 01:10

Shahbaz Ahmed


ViewModelFactory that will provide us a right ViewModel from ViewModelModule

public class ViewModelFactory implements ViewModelProvider.Factory {
    private final Map<Class<? extends ViewModel>, Provider<ViewModel>> viewModels;

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

    @Override
    public <T extends ViewModel> T create(Class<T> modelClass) {
        Provider<ViewModel> viewModelProvider = viewModels.get(modelClass);

        if (viewModelProvider == null) {
            throw new IllegalArgumentException("model class " + modelClass + " not found");
        }

        return (T) viewModelProvider.get();
    }
}

ViewModelModule is responsible for binding all over ViewModel classes into
Map<Class<? extends ViewModel>, Provider<ViewModel>> viewModels

@Module
public abstract class ViewModelModule {

    @Binds
    abstract ViewModelProvider.Factory bindViewModelFactory(ViewModelFactory viewModelFactory); 
    //You are able to declare ViewModelProvider.Factory dependency in another module. For example in ApplicationModule.

    @Binds
    @IntoMap
    @ViewModelKey(UserViewModel.class)
    abstract ViewModel userViewModel(UserViewModel userViewModel);
    
    //Others ViewModels
}

ViewModelKey is an annotation for using as a key in the Map and looks like

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

Now you are able to create ViewModel and satisfy all necessary dependencies from the graph

public class UserViewModel extends ViewModel {
    private UserFacade userFacade;

    @Inject
    public UserViewModel(UserFacade userFacade) { // UserFacade should be defined in one of dagger modules
        this.userFacade = userFacade;
    }
} 

Instantiating ViewModel

public class MainActivity extends AppCompatActivity {

    @Inject
    ViewModelFactory viewModelFactory;
    UserViewModel userViewModel;

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

        ((App) getApplication()).getAppComponent().inject(this);

        userViewModel = ViewModelProviders.of(this, viewModelFactory).get(UserViewModel.class);

    }
}

And do not forger to add ViewModelModule into modules list

@Singleton
@Component(modules = {ApplicationModule.class, ViewModelModule.class})
public interface ApplicationComponent {
    //
}
like image 32
yoAlex5 Avatar answered Oct 24 '22 02:10

yoAlex5


For Hilt:

Simple add @AndroidEntryPoint for main acticity and fragments, and @HiltViewModel for viewModels

Example after:

@HiltViewModel
class SplashViewModel @Inject constructor(

@AndroidEntryPoint
class SplashFragment : Fragment() {
    private lateinit var b: SplashFragmentBinding
    private val vm: SplashViewModel by viewModels()

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding
like image 28
Fortran Avatar answered Oct 24 '22 00:10

Fortran


I had some issues with @ViewModelInject since it has been deprecated using HILT. To solve the problem change this code:

class MainViewModel @ViewModelInject constructor(
    val mainRepository: MainRepository
): ViewModel()

with:

@HiltViewModel
class MainViewModel @Inject constructor(
    val mainRepository: MainRepository
): ViewModel()

Of course, remember to add the @AndroidEntryPoint annotation above your fragment or activity (wherever you are instantiating your ViewModel) like this:

@AndroidEntryPoint
class UsageFragment : Fragment(R.layout.fragment_usage) {
   .
   .
   .
}

Ultimate tip:

You can immediately see if HILT is working looking if there are the icons on the left in your ViewModel.

Here it does not work:

enter image description here

Here it does work:

enter image description here

If you don't see them after updating the code click on Build -> Rebuild Project

like image 11
Mattia Ferigutti Avatar answered Oct 24 '22 02:10

Mattia Ferigutti


Early in 2020, Google have deprecated the ViewModelProviders class, in version 2.2.0 of the androidx lifecycle library.

It's no longer necessary to use ViewModelProviders to create an instance of a ViewModel, you can pass your Fragment or Activity instance to the ViewModelProvider constructor instead.

If you use the code like:

val viewModel = ViewModelProviders.of(this).get(CalculatorViewModel::class.java)

you'll get a warning that ViewModelProviders has been deprecated.

You can instead do:

val viewModel = ViewModelProvider(this).get(CalculatorViewModel::class.java)

Or alternatively, to use a delegate, make the following changes.

  1. In the build.gradle (Module: app) file, use version 2.2.0 of the lifecycle components: implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'

    Also add implementation "androidx.activity:activity-ktx:1.1.0"

    If you want to use the ViewModel from a Fragment instead, use

    implementation "androidx.fragment:fragment-ktx:1.2.2"

    fragment-ktx automatically includes activity-ktx, so you don't need to specify both in the dependencies.

  2. You need to specify Java 8 in the android section :

android {
  compileSdkVersion 28
  defaultConfig {
    applicationId "com.kgandroid.calculator"
    minSdkVersion 17
    targetSdkVersion 28
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
  }
  
  buildTypes {
    release {
      minifyEnabled false
      proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
  }
         
  kotlinOptions {
    jvmTarget = "1.8"
  }
}
  1. In your Fragment or Activity, change the import to:

    import androidx.activity.viewModels

  2. The code to create a ViewModel then becomes:

    val viewModel: CalculatorViewModel by viewModels()

    instead of

    val viewModel = ViewModelProviders.of(this).get(CalculatorViewModel::class.java)

    Use the viewModel object as :

    val viewModel: CalculatorViewModel by viewModels()

    viewModel.newNumber.observe(this, Observer { stringResult -> newNumber.setText(stringResult) })

where newNumer is a LiveData object

In a Fragment that you want to share the Activity's ViewModel, you'd use

`val viewModel: CalculatorViewModel by activityViewModels()`

**That's the equivalent of passing the Activity instance in the (deprecated) 
ViewModelProviders.of() function.**
like image 7
kgandroid Avatar answered Oct 24 '22 02:10

kgandroid


2020-07-29 10:13:25

For lifecycle_version = '2.2.0' ViewProviders.of API is deprecated . It`s my situation :

class MainActivityViewModel(application: Application) : AndroidViewModel(application) {

    private var repository: UserRepository

    val allUsers: LiveData<List<User>>
......


error:
val userViewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)

success:
val factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application)
userViewModel = ViewModelProvider(this,factory).get(MainActivityViewModel::class.java)

Pass application by api ViewModelProvider.AndroidViewModelFactory.getInstance


like image 5
javakam Avatar answered Oct 24 '22 01:10

javakam