I have done like docs here but Live data'a value is not changing. Please tell me what am i doing wrong.
MainActivity
public class MainActivity extends AppCompatActivity {
private NameViewModel mModel;
private ActivityMainBinding binding;
int index = 0;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.button.setOnClickListener((v) -> {
mModel.getCurrentName().setValue("Test");
});
mModel = ViewModelProviders.of(this).get(NameViewModel.class);
final Observer<String> nameObserver = (text) -> {
binding.textInputLayout.getEditText().setText(text);
};
mModel.getCurrentName().observe(this, nameObserver);
}
}
NameViewModel.java
public class NameViewModel extends ViewModel {
private MutableLiveData<String> mCurrentName;
public MutableLiveData<String> getCurrentName() {
if (mCurrentName == null) {
return new MutableLiveData<>();
}
return mCurrentName;
}
}
This is because, your logic returns new instance of mCurrentName each time. Please use the following function.
public class NameViewModel extends ViewModel {
private MutableLiveData<String> mCurrentName;
public MutableLiveData<String> getCurrentName() {
// Ensure there is only 1 instance of mCurrentName
if (mCurrentName == null) {
mCurrentName = new MutableLiveData<>();
}
return mCurrentName;
}
}
A much better and safer way (reduce chance of making such mistake), is to initialize mCurrentName in constructor, and mark it as final.
public class NameViewModel extends ViewModel {
private final MutableLiveData<String> mCurrentName;
public NameViewModel() {
mCurrentName = new MutableLiveData<>();
}
public MutableLiveData<String> getCurrentName() {
return mCurrentName;
}
}
Related
I have updated to Android 31 and Android Studio Dolphin and now my tests are failing because of TextUtils.isEmpty() returns wrong result.
I have this method to mock TextUtils.isEmpty().
protected void mockTextUtilsIsEmpty() {
PowerMockito.mockStatic(TextUtils.class);
PowerMockito.when(TextUtils.isEmpty(any(CharSequence.class))).thenAnswer(invocation -> {
String val = (String) invocation.getArguments()[0];
return val == null || val.length() == 0;
});
}
This is my test class.
#RunWith(PowerMockRunner.class) #PrepareForTest(TextUtils.class)
public class CustomerDetailsPresenterTest extends BaseTest {
#Rule TrampolineSchedulerRule trampolineSchedulerRule = new TrampolineSchedulerRule();
#Mock GetCustomerUseCase getCustomerUseCase;
#Mock GetMenuItemsUseCase getMenuItemsUseCase;
#Mock RolesManager rolesManager;
#Mock CustomerDetailsPresenter.View view;
private CustomerDetailsPresenter presenter;
private final int customerId = 1;
#Before public void setUp() {
mockTextUtilsIsEmpty();
presenter = new CustomerDetailsPresenter(getCustomerUseCase, getMenuItemsUseCase, rolesManager);
presenter.setView(view);
}
#Test public void shouldDisplayCustomerWithEmptyData() {
// Given
CustomerDetails customerDetails = CustomerDetails.newBuilder()
.build();
// When
Mockito.when(getCustomerUseCase.execute(customerId)).thenReturn(Single.just(customerDetails));
presenter.getCustomerDetails(customerId);
//Then
Mockito.verify(view).showRefreshing();
Mockito.verify(view).hideRefreshing();
Mockito.verify(view).displayCustomerEmailUnknown();
Mockito.verify(view).displayCustomerNoteUnknown();
}
}
This is my actual class that I want to test.
public class CustomerDetailsPresenter implements Presenter{
private final GetCustomerUseCase getCustomerUseCase;
private final GetMenuItemsUseCase getMenuItemsUseCase;
private final RolesManager rolesManager;
private CompositeDisposable disposables;
private View view;
#Inject public CustomerDetailsPresenter(
GetCustomerUseCase getCustomerUseCase,
GetMenuItemsUseCase getMenuItemsUseCase,
RolesManager rolesManager
) {
this.getCustomerUseCase = getCustomerUseCase;
this.getMenuItemsUseCase = getMenuItemsUseCase;
this.rolesManager = rolesManager;
}
public void setView(View view) {
this.view = view;
}
public void getCustomerDetails(int id) {
disposables = RxUtil.initDisposables(disposables);
if (rolesManager.isUserReadOnly()) {
view.showScreenAsReadOnly();
}
view.showRefreshing();
Disposable disposable = getCustomerUseCase.execute(id)
.doOnSuccess(customerDetails -> view.hideRefreshing())
.subscribe(customerDetails -> {
if (customerDetails != null) {
if (TextUtils.isEmpty(customerDetails.getInvoiceEmail())) {
view.displayCustomerEmailUnknown();
} else {
view.displayCustomerEmail(customerDetails.getInvoiceEmail());
}
if (TextUtils.isEmpty(customerDetails.getBillingNote())) {
view.displayCustomerNoteUnknown();
} else {
view.displayCustomerNote(customerDetails.getBillingNote());
}
view.displayCustomerAddress(customerDetails);
view.displayLastModified(customerDetails);
} else {
view.hideCustomerSecondAndThirdPhones();
}
} else {
view.hideCustomerDetails();
}
},
view::handleError
);
disposables.add(disposable);
What could be the problem?
So, in this part, it always goes to the else part of the statement, and as you can in my unit test that should not happen since I am providing null to invoice email field.
if (TextUtils.isEmpty(customerDetails.getInvoiceEmail())) {
view.displayCustomerEmailUnknown();
} else {
view.displayCustomerEmail(customerDetails.getInvoiceEmail());
}
Any ideas?
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 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);
I'm planning to have a model class and provide an instance of this model through an Android ViewModel. The instance in the ViewModel can change through the application lifecycle.
My current idea is like this:
public class Book {
private MutableLiveData<String> mName = new MutableLiveData<>();
public Book(...) {
...
}
public LiveData<String> getName() {
return mName;
}
public void setName(String name) {
mName.setValue(name);
}
}
public class MyViewModel extends ViewModel {
private MutableLiveData<Book> mCurrentBook = new MutableLiveData<>();
private MutableLiveData<Book> mRecommendedBook = new MutableLiveData<>();
public LiveData<Book> getCurrentBook() {
return mCurrentBook;
}
public void setCurrentBook(Book book) {
mCurrentBook.setValue(book);
}
}
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
model.getCurrentBook().observe(this, book -> {
book.getName().observe(this, name -> {
// update UI
});
});
...
model.setCurrentBook(someOtherBook);
}
}
Is this a good approach? I'm not sure if it's a good idea to have the LiveData nested in another class.
Also could it be a problem that I'm creating a new observer for the book name, each time the book changes?
I answered a similar question here
You should use Transformation to carry data between your observer's.
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);
}
}