I am trying to instantiate UserViewModel in my activity however it keeps giving me a java.lang.RuntimeException: Cannot create an instance of viewmodel class kindly assist.
This is how my ViewModel looks like
public class UserViewModel extends AndroidViewModel {
private NodeAuthService api;
private SharedPreferences pref;
private static MutableLiveData<List<User>> userDetails = new MutableLiveData<>();
public UserViewModel(#NonNull Application application) {
super(application);
api = AuthRetrofitClient.getInstance().create(NodeAuthService.class);
}
private String email = pref.getString("email", "");
public void loadUser(){
Call<List<User>> call;
call = api.getUser(email);
call.enqueue(new Callback<List<User>>() {
#Override
public void onResponse(Call<List<User>> call, Response<List<User>> response) {
userDetails.postValue(response.body());
}
#Override
public void onFailure(Call<List<User>> call, Throwable t) {
Log.d("USER",t.getMessage());
}
});
}
public MutableLiveData<List<User>>getUserDetails(){
return userDetails;
}
}
This is how my activity is setup
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.navigation_drawer);
String nameVm;
userViewModel = ViewModelProviders.of(this).get(UserViewModel.class);
userViewModel.loadUser();
userViewModel.getUserDetails().observe(this, new Observer<List<User>>() {
#Override
public void onChanged(List<User> users) {
if (users != null){
for (int i = 0; i<users.size(); i++){
nameVm = String.valueOf(users.get(0));
}
}
}
});
}
Create ViewModelFactory class
public class MyViewModelFactory implements ViewModelProvider.Factory {
private Application mApplication;
public MyViewModelFactory(Application application) {
mApplication = application;
}
#Override
public <T extends ViewModel> T create(Class<T> modelClass) {
// Replace UserViewModel → with whatever or however you create your ViewModel
return (T) new UserViewModel(mApplication);
}
}
and init ViewModel like
UserViewModel myViewModel = ViewModelProviders.of(this, new MyViewModelFactory(this.getApplication())).get(UserViewModel.class);
Related
I created an abstract GlobalActivity extending AppCompatActivity and a GlobalViewModel extending ViewModel, in order to have some LiveData always ready to show Dialog messages and Toast messages, as well as displaying and hiding a ProgressBar. Problem is that the LoginActivity is not observing the LiveData object I mentioned above, so is not reacting to changes nor calls. Here is my code:
GlobalActivity:
public abstract class GlobalActivity extends AppCompatActivity {
protected GlobalViewModel mGlobalViewModel = new GlobalViewModel();
private Consumer<Throwable> errorHandler = throwable -> {
Timber.e(throwable);
DialogUtils.showOneButtonDialog(this, R.string.unexpected_error, null);
};
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
RxJavaPlugins.setErrorHandler(this.errorHandler);
setUpBasicViewModel();
mGlobalViewModel.getDialogMessage().observe(this, mssg -> DialogUtils.showOneButtonDialog(GlobalActivity.this, mssg, null));
mGlobalViewModel.getToastMessage().observe(this, mssg -> DialogUtils.showMessage(mssg));
mGlobalViewModel.getIsLoading().observe(this, bool -> setLoadingState(bool));
}
public abstract void setLoadingState(boolean bool);
public abstract void setUpBasicViewModel();
}
GlobalViewModel:
public class GlobalViewModel extends ViewModel {
protected MutableLiveData<String> dialogMessage = new MutableLiveData<>();
protected MutableLiveData<String> toastMessage = new MutableLiveData<>();
protected SingleLiveEvent<Boolean> isLoading = new SingleLiveEvent<>();
public GlobalViewModel(){}
public MutableLiveData<String> getDialogMessage() {
return dialogMessage;
}
public MutableLiveData<String> getToastMessage() {
return toastMessage;
}
public SingleLiveEvent<Boolean> getIsLoading() {
return isLoading;
}
}
LoginActivity:
public class LoginActivity extends GlobalActivity {
private LoginViewModel mLoginViewModel;
private ActivityLoginBinding mDataBinding;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLoginViewModel = new ViewModelProvider(this, new LoginViewModelFactory()).get(LoginViewModel.class);
mDataBinding = DataBindingUtil.setContentView(this, R.layout.activity_login);
mDataBinding.setLifecycleOwner(this);
mDataBinding.setViewModel(mLoginViewModel);
}
#Override
public void setLoadingState(boolean bool) {
mDataBinding.progressBar.setVisibility(mDataBinding.progressBar.isShown() ? View.GONE : View.VISIBLE);
}
#Override
public void setUpBasicViewModel() {
mGlobalViewModel = ViewModelProviders.of(this).get(GlobalViewModel.class);
}
...
}
I am able to make a network request and get back a response inside my data repository but not able to get that inside my view model.
Data repository:
public class DataRepository {
private APIService apiService;
private static DataRepository INSTANCE = null;
public MutableLiveData<ResponseEntity> loginUser(UserEntity userEntity){
final MutableLiveData<ResponseEntity> responseEntity = new MutableLiveData<>();
apiService.loginUser(userEntity)
.enqueue(new Callback<ResponseEntity>() {
#Override
public void onResponse(Call<ResponseEntity> call, Response<ResponseEntity> response) {
Log.d(Constants.LOGGER, "from data repository " + response.body());
responseEntity.setValue(response.body());
}
#Override
public void onFailure(Call<ResponseEntity> call, Throwable t) {
Log.d(Constants.LOGGER, "from data repository: there was an error");
responseEntity.setValue(null);
}
});
return responseEntity;
}
}
View model:
public class LoginViewModel extends AndroidViewModel {
private MutableLiveData<ResponseEntity> networkResponse;
public void sendLoginNetworkRequest(UserEntity userEntity){
networkResponse = mRepository.loginUser(userEntity);
}
public MutableLiveData<ResponseEntity> getResponse(){
return networkResponse;
}
Activity:
public class LoginActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
loginViewModel = ViewModelProviders.of(this).get(LoginViewModel.class);
loginViewModel.getResponse()
.observe(this, new Observer<ResponseEntity>() {
#Override
public void onChanged(#Nullable ResponseEntity responseEntity) {
Log.d(Constants.LOGGER, "response entity changed " + responseEntity);
}
});
}
public void loginClicked(View view) {
loginViewModel.sendLoginNetworkRequest(userEntity);
}
}
The log from the data repository shows up but the one from the activity doesn't. What am I doing wrong?
I found the answer!
I had to make the responseEntity MutableLiveData variable in my DataRepository class into a class variable and create a function which returns that and now it works!
Repository:
public class DataRepository {
private APIService apiService;
private static DataRepository INSTANCE = null;
final MutableLiveData<ResponseEntity> responseEntity = new MutableLiveData<>();
public void loginUser(UserEntity userEntity){
apiService.loginUser(userEntity)
.enqueue(new Callback<ResponseEntity>() {
#Override
public void onResponse(Call<ResponseEntity> call, Response<ResponseEntity> response) {
Log.d(Constants.LOGGER, "from data repository " + response.body());
responseEntity.setValue(response.body());
}
#Override
public void onFailure(Call<ResponseEntity> call, Throwable t) {
Log.d(Constants.LOGGER, "from data repository: there was an error");
responseEntity.setValue(null);
}
});
}
public MutableLiveData<ResponseEntity> getLiveResponses(){
return responseEntity;
}
}
Viewmodel:
public class LoginViewModel extends AndroidViewModel {
public void sendLoginNetworkRequest(UserEntity userEntity){
mRepository.loginUser(userEntity);
}
public MutableLiveData<ResponseEntity> getResponse(){
return mRepository.getLiveResponse;
}
}
I want to re-use ViewModel and LiveData for reading nodes from Firebase. This is my code in Fragment
FirebaseDatabaseViewModel test = ViewModelProviders.of(this, new FirebaseDatabaseViewModel.Factory(getActivity().getApplication(),"node1")).get(FirebaseDatabaseViewModel.class);
LiveData<DataSnapshot> ldTest = test.getDataSnapshotLiveData();
ldTest.observe(this, new Observer<DataSnapshot>() {
#Override
public void onChanged(#Nullable DataSnapshot dataSnapshot) {
Log.d("MyTag", "liveData.observe TEST dataSnapshot = " + dataSnapshot);
}
});
FirebaseDatabaseViewModel test2 = ViewModelProviders.of(this, new FirebaseDatabaseViewModel.Factory(getActivity().getApplication(),"node2")).get(FirebaseDatabaseViewModel.class);
LiveData<DataSnapshot> ldTest2 = test2.getDataSnapshotLiveData();
ldTest2.observe(this, new Observer<DataSnapshot>() {
#Override
public void onChanged(#Nullable DataSnapshot dataSnapshot) {
Log.d("MyTag", "liveData.observe TEST2 dataSnapshot = " + dataSnapshot);
}
});
}
Here is ViewModel
public class FirebaseDatabaseViewModel extends AndroidViewModel {
private final String mRef;
private final FirebaseQueryLiveData liveData;
public FirebaseDatabaseViewModel(Application application, String ref) {
super(application);
this.mRef = ref;
this.liveData = new FirebaseQueryLiveData(FirebaseDatabase.getInstance().getReference(mRef));
}
#NonNull
public LiveData<DataSnapshot> getDataSnapshotLiveData() {
return liveData;
}
public static class Factory extends ViewModelProvider.NewInstanceFactory {
#NonNull
private final Application mApplication;
private final String mRef;
public Factory(#NonNull Application application, String ref) {
mApplication = application;
this.mRef = ref;
}
#NonNull
#Override
public <T extends ViewModel> T create(#NonNull Class<T> modelClass) {
return (T) new FirebaseDatabaseViewModel(mApplication, mRef);
}
}
}
Here is LiveData
public class FirebaseQueryLiveData extends LiveData<DataSnapshot> {
private final Query query;
private final MyValueEventListener listener = new MyValueEventListener();
public FirebaseQueryLiveData(Query query) {
this.query = query;
}
public FirebaseQueryLiveData(DatabaseReference ref) {
this.query = ref;
}
#Override
protected void onActive() {
Log.d("MyTag", "onActive");
query.addValueEventListener(listener);
}
#Override
protected void onInactive() {
Log.d("MyTag", "onInactive");
query.removeEventListener(listener);
}
private class MyValueEventListener implements ValueEventListener{
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
setValue(dataSnapshot);
}
#Override
public void onCancelled(DatabaseError databaseError) {
Log.e("MyTag", "Can't listen to query " + query, databaseError.toException());
}
}
}
Problem is reading same node from FirebaseDatabase
D/MyTag: liveData.observe TEST dataSnapshot = DataSnapshot { key = node1, value = {.....
D/MyTag: liveData.observe TEST2 dataSnapshot = DataSnapshot { key = node1, value = {....
Second time I expected node2
The default ViewModelProvider only keeps a single ViewModel for a given class name. The only time your Factory is invoked is when there's no already existing ViewModel - in your case, you're using the same class name for both calls, so your second factory is never used.
Generally, you should consider only having a single ViewModel and have it return multiple different LiveData instances based on the key passed into it, keeping a HashMap<String,LiveData> to avoid recreating LiveData objects it already has:
public class FirebaseDatabaseViewModel extends AndroidViewModel {
private HashMap<String, LiveData<DataSnapshot>> mLiveDataMap = new HashMap<>();
public FirebaseDatabaseViewModel(#NonNull final Application application) {
super(application);
}
public LiveData<DataSnapshot> getDataSnapshotLiveData(String ref) {
if (!mLiveDataMap.containsKey(ref)) {
// We don't have an existing LiveData for this ref
// so create a new one
mLiveDataMap.put(ref, new FirebaseQueryLiveData(
FirebaseDatabase.getInstance().getReference(ref)));
}
return mLiveDataMap.get(ref);
}
}
I want to unit test a repository which depends on LocationLiveData class:
public class LocationLiveData extends LiveData<LocationData> {
private Context mContext;
private LocationCallback locationCallback = new LocationCallback(){
#Override
public void onLocationResult(LocationResult locationResult) {
...
setValue(locationData);
}
};
#Inject
public LocationLiveData(Context context) {
mContext = context;
...
}
...
}
How can I make the mock act like liveData which emits a LocationData object after I called setValue(someLocationDataInstance)?
#RunWith(JUnit4.class)
public class LocationRepoImplTest {
#Rule
public InstantTaskExecutorRule instantExecutorRule = new InstantTaskExecutorRule();
private LocationRepo mLocationRepo;
private LocationLiveData mlocationLiveData;
#Before
public void setUp() throws Exception {
mlocationLiveData = mock(LocationLiveData.class);//(LocationLiveData) new MutableLiveData<LocationData>();
mLocationRepo = new LocationRepoImpl(mlocationLiveData);
}
#Test
public void getUserPosition() throws Exception {
LiveData<LatLng> result = mLocationRepo.getUserPosition();
Observer observer = mock(Observer.class);
result.observeForever(observer);
//how can I setValue for mLocationLiveData here?
//e.g this way: mLocationLiveData.setValue(new LocationData(TestUtil.posUser, (float) 10.0));
assertThat(result.getValue(), is(TestUtil.posUser));
}
}
Update 1: I want to test following repository:
public class LocationRepoImpl implements LocationRepo {
private LocationLiveData mLocationLiveData;
#Inject
public LocationRepoImpl(LocationLiveData locationLiveData) {
mLocationLiveData = locationLiveData;
}
#Override
public LiveData<LatLng> getUserPosition() {
return Transformations.map(mLocationLiveData, LocationData::getLatLng);
}
}
I can make DI from some class to use in application such as Retrofit, Picasso.
they can work fine when i use them on Activity but when i try to use some DI on other class i get NULL, for exmple this code work fine
public class ActivityRegister extends BaseActivities {
#Inject
GithubService githubService;
#Inject
JobManager jobManager;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
...
repositoryCall = githubService.getAllRepositories();
...
}
private void getRepositories() {
repositoryCall.enqueue(new Callback<List<GithubRepo>>() {
#Override
public void onResponse(Call<List<GithubRepo>> call, Response<List<GithubRepo>> response) {
List<GithubRepo> repoList = new ArrayList<>();
repoList.addAll(response.body());
Log.e("JOB ", "OK");
}
#Override
public void onFailure(Call<List<GithubRepo>> call, Throwable t) {
Log.e("JOB ", "NO!!");
}
});
}
for GithubService i get created instance successfull, not i'm trying to use that into GetLatestRepositories, but i get NULL, how can i define correctly that to injecting into class?
public class GetLatestRepositories extends Job {
#Inject
GithubService githubService;
private Call<List<GithubRepo>> repositoryCall;
public GetLatestRepositories() {
super(new Params(Priority.MID).requireNetwork().persist());
repositoryCall = githubService.getAllRepositories();
}
#Override
public void onAdded() {
}
#Override
public void onRun() throws Throwable {
repositoryCall.enqueue(new Callback<List<GithubRepo>>() {
#Override
public void onResponse(Call<List<GithubRepo>> call, Response<List<GithubRepo>> response) {
List<GithubRepo> repoList = new ArrayList<>();
repoList.addAll(response.body());
Log.e("JOB ", "OK");
}
#Override
public void onFailure(Call<List<GithubRepo>> call, Throwable t) {
Log.e("JOB ", "NO!!");
}
});
}
#Override
protected void onCancel(int cancelReason, #Nullable Throwable throwable) {
}
#Override
protected RetryConstraint shouldReRunOnThrowable(#NonNull Throwable throwable, int runCount, int maxRunCount) {
return null;
}
}
ActivityRegisterComponent component class:
#ActivityRegisterScope
#Component(modules = ActivityRegisterModule.class, dependencies = GithubApplicationComponent.class)
public interface ActivityRegisterComponent {
void injectActivityRegister(ActivityRegister homeActivity);
}
GithubApplicationComponent:
#GithubApplicationScope
#Component(modules = {GithubServiceModule.class, PicassoModule.class, JobManagerModule.class, ActivityModule.class})
public interface GithubApplicationComponent {
Picasso getPicasso();
GithubService getGithubService();
JobManager getJobManager();
}
Application class:
public class Alachiq extends Application {
...
public static Alachiq alachiq;
public static String packageName;
public static Resources resources;
private static Context context;
private GithubService githubService;
private Picasso picasso;
private GithubApplicationComponent component;
private JobManager jobManager;
private static Alachiq instance;
#Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
}
#Override
public void onCreate() {
super.onCreate();
//#formatter:off
resources = this.getResources();
context = getApplicationContext();
packageName = getPackageName();
//#formatter:on
Timber.plant(new Timber.DebugTree());
component = DaggerGithubApplicationComponent.builder()
.contextModule(new ContextModule(this))
.build();
githubService = component.getGithubService();
picasso = component.getPicasso();
jobManager = component.getJobManager();
}
public GithubApplicationComponent component() {
return component;
}
public static Alachiq get(Activity activity) {
return (Alachiq) activity.getApplication();
}
public static Alachiq getInstance() {
return instance;
}
public static Context getContext() {
return context;
}
}
and ActivityRegister onCreate:
ApplicationComponent component = DaggerApplicationComponent.builder()
.githubApplicationComponent(Alachiq.get(this).component())
.build();
GithubService class:
public interface GithubService {
#GET("users/{username}/repos")
Call<List<GithubRepo>> getReposForUser(#Path("username") String username);
#GET("repositories")
Call<List<GithubRepo>> getAllRepositories();
#GET("users/{username}")
Call<GithubUser> getUser(#Path("username") String username);
}
Before you can even use the object from GithubService class you need to do the following:
myComponent= DaggerMyComponent.builder().computerModule(new ComputerModule()).build();//replace accordingly.
In your case you will have to do something like:
githubService = DaggerGithubApplicationComponent.builder().nameofYourModule(new NameOfTheClass()).build);
Now you can use the githubService object:
repositoryCall = githubService.getAllRepositories();
This is a type of design pattern called creational.