I am working on Google's new Android Architecture Component AAC
My problem
method inside ViewModel class gets called multiple time , causing error
1 getLoggedInUser() gets called multiple times
2 Before retrofit's onNext() or onError() , the onChanged() gets called
MyActivity
LoginPojo loginPojo=new LoginPojo();
loginPojo.setEmailId(viewFunctions.getText(etLoginEmailId));
loginPojo.setPassword(viewFunctions.getText(etLoginPwd));
viewModel.loginUser(loginPojo);
viewModel.getLoggedInUser().observe(this, new Observer<LoginPojo>() {
#Override
public void onChanged(#Nullable LoginPojo pojo) {
viewFunctions.hideCustomProgress();
if (pojo.isError()) {
if (pojo.isNetworkError()) {
} else {
}
} else {
if (pojo.getStatus().equalsIgnoreCase(constants.ERROR)) {
} else {
}
}
}
});
ViewModel
private MutableLiveData<LoginPojo> mutableLogin = new MutableLiveData<>();
public final LiveData<LoginPojo> loginData = Transformations.switchMap(mutableLogin, new Function<LoginPojo, LiveData<LoginPojo>>() {
#Override
public LiveData<LoginPojo> apply(LoginPojo input) {
return repository.loginUser(input.getEmailId(), input.getPassword());
}
});
public void loginUser(LoginPojo loginPojo) {
mutableLogin.setValue(loginPojo);
}
public LiveData<LoginPojo> getLoggedInUser() {
return loginData;
}
Related
Even though I am using ViewModel, whenever the device is rotated, the data in the Recyclerview disappears. I had to put the makeSearch() method inside the onClick() method because I need to get the text that the button grabs and use it as the search parameter. Is there a better way I can handle this to avoid this problem? My code is right here:
SearchActivity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_search);
// What happens when the search button is clicked
materialButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (Objects.requireNonNull(textInputEditText.getText()).toString().isEmpty()) {
textInputEditText.setError("Type a search query");
} else {
mSearchInput = Objects.requireNonNull(textInputEditText.getText()).toString();
textInputEditText.setText("");
makeSearch();
}
}
});
}
// Gets the ViewModel, Observes the Question LiveData and delivers it to the Recyclerview
private void makeSearch() {
final SearchAdapter searchAdapter = new SearchAdapter();
SearchViewModel mSearchViewModel = new ViewModelProvider(this,
new CustomSearchViewModelFactory(new SearchRepository())).get(SearchViewModel.class);
mSearchViewModel.setQuery(mSearchInput);
mSearchViewModel.getQuestionLiveData().observe(this, new Observer<List<Question>>() {
#Override
public void onChanged(List<Question> questions) {
mQuestions = questions;
searchAdapter.setQuestions(questions);
}
});
mRecyclerView.setAdapter(searchAdapter);
searchAdapter.setOnClickListener(mOnClickListener);
}
SearchViewModel:
public class SearchViewModel extends ViewModel {
private SearchRepository mSearchRepository;
private MutableLiveData<String> mSearchLiveData = new MutableLiveData<>();
private LiveData<List<Question>> mQuestionLiveData = Transformations.switchMap(mSearchLiveData, (query) -> {
return mSearchRepository.getQuestions(query);
});
SearchViewModel(SearchRepository searchRepository) {
this.mSearchRepository = searchRepository;
}
public LiveData<List<Question>> getQuestionLiveData() {
return mQuestionLiveData;
}
public void setQuery(String query) {
mSearchLiveData.setValue(query);
}
}
SearchRepository:
public class SearchRepository {
//private String inTitle;
private MutableLiveData<List<Question>> mQuestions = new MutableLiveData<>();
public SearchRepository() {
//getQuestionsWithTextInTitle();
}
private void getQuestionsWithTextInTitle(String inTitle) {
ApiService apiService = RestApiClient.getApiService(ApiService.class);
Call<QuestionsResponse> call = apiService.getQuestionsWithTextInTitle(inTitle);
call.enqueue(new Callback<QuestionsResponse>() {
#Override
public void onResponse(Call<QuestionsResponse> call, Response<QuestionsResponse> response) {
QuestionsResponse questionsResponse = response.body();
if (questionsResponse != null) {
mQuestions.postValue(questionsResponse.getItems());
//shouldShowData = true;
} else {
Log.d("SearchRepository", "No matching question");
//shouldShowData = false;
}
}
#Override
public void onFailure(Call<QuestionsResponse> call, Throwable t) {
//shouldShowData = false;
t.printStackTrace();
}
});
}
public LiveData<List<Question>> getQuestions(String inTitle) {
getQuestionsWithTextInTitle(inTitle);
return mQuestions;
}
}
Your approach of passing the search input in through your CustomSearchViewModelFactory and into the constructor for the ViewModel and into the constructor for your SearchRepository isn't going to work in any case. While the first time you search your CustomSearchViewModelFactory creates the ViewModel, the second time you hit search, your SearchViewModel is already created and your factory is not invoked a second time, meaning you never get the second query.
Instead, you should file the ViewModel Overview documentation, and use Transformations.switchMap() to convert your input (the search string) into a new LiveData<List<Question>> for that given query.
This means that your ViewModel would look something like
public class SearchViewModel extends ViewModel {
private SearchRepository mSearchRepository;
private MutableLiveData<String> mSearchLiveData = new MutableLiveData<String>();
private LiveData<List<Question>> mQuestionLiveData =
Transformations.switchMap(mSearchLiveData, (query) -> {
return mSearchRepository.getQuestions(query);
});
public SearchViewModel() {
mSearchRepository = new SearchRepository();
}
public void setQuery(String query) {
mSearchLiveData.setValue(query);
}
public LiveData<List<Question>> getQuestionLiveData() {
return mQuestionLiveData;
}
}
You'd then update your Activity to:
Always observe the getQuestionLiveData() (note that you won't get a callback to your Observer until you actually set the first query)
Call setQuery() on your SearchViewModel in your makeSearch()
Remove your CustomSearchViewModelFactory entirely (it would no longer be needed).
hello i have two problems regarding live data with view model and navigation component first one is when i go from fragment A with live data to fragment B and then from B to A the data in my list gets duplicated,
the other problem is one i re call viewModel.loadList() in my fragment after making and event to filter the data also gets duplicated
here is my view model
public class HomeViewModel extends ViewModel {
MutableLiveData<ArrayList<HomeResponseModel>> homeLiveData = new MutableLiveData<>();
ArrayList<HomeResponseModel> homeList = new ArrayList<>();
public MutableLiveData<ArrayList<HomeResponseModel>> geHomeList(HomeRequestModel homeRequestModel, Context context, ApiInterface apiInterface, LottieAnimationView lottieAnimationView) {
if (homeLiveData == null) {
homeLiveData = new MutableLiveData<ArrayList<HomeResponseModel>>();
loadHomeList(homeRequestModel);
}
return homeLiveData;
}
public void loadHomeList(HomeRequestModel homeRequestModel) {
Call<List<HomeResponseModel>> call = apiInterface.getHomeList(homeRequestModel, );
call.enqueue(new Callback<List<HomeResponseModel>>() {
#Override
public void onResponse(Call<List<HomeResponseModel>> call, Response<List<HomeResponseModel>> response) {
if (response.isSuccessful()) {
homeList.addAll(response.body());
homeLiveData.setValue(homeList);
}
}
#Override
public void onFailure(Call<List<HomeResponseModel>> call, Throwable t) {
}
});
}
my observer in onCreateView
viewModel.geHomeList(homeRequestModel).observe(getViewLifecycleOwner(), new Observer<ArrayList<HomeResponseModel>>() {
#Override
public void onChanged(ArrayList<HomeResponseModel> homeResponse) {
homeResponseModels.addAll(homeResponse);
homeAdapter.notifyDataSetChanged();
}
});
}
how i recall load method after a filter event
viewModel.loadHomeList(homeRequestModel);
Clear the list before adding the new models:
viewModel.geHomeList(homeRequestModel).observe(getViewLifecycleOwner(), new Observer<ArrayList<HomeResponseModel>>() {
#Override
public void onChanged(ArrayList<HomeResponseModel> homeResponse) {
homeResponseModels.clear();
homeResponseModels.addAll(homeResponse);
homeAdapter.notifyDataSetChanged();
}
});
}
Or, even better:
In case your adapter holds a List or HomeResponseModel you could create a method to update it:
public update(List<HomeResponseModel> homeResponse) {
this.homeResponseModels = homeResponse;
notifydatasetchanged();
}
and then change the observe method to call it:
viewModel.geHomeList(homeRequestModel).observe(getViewLifecycleOwner(), new Observer<ArrayList<HomeResponseModel>>() {
#Override
public void onChanged(ArrayList<HomeResponseModel> homeResponse) {
homeAdapter.update(homeResponse);
}
});
}
Besides that, in your ViewModel in the loadHomeList method in the onResponse callback you could assign the data received to the liveData:
if (response.isSuccessful()) {
homeLiveData.setValue(response.body());
}
no need to save it in the homeList var, you can get rid of that var. Otherwise, perform homeList.clear(); before adding to it all the received data to avoid duplicates.
My ViewModel class looks like this:
public class ViewModelMainActivity extends AndroidViewModel {
private LocalRepository localRepository;
private LiveData<List<Task>> allJob;
private LiveData<List<Task>> allShopping;
private LiveData<List<Task>> allOther;
public ViewModelMainActivity(#NonNull Application application) {
super(application);
localRepository = new LocalRepository(application);
allJob = localRepository.getAllJob();
allShopping = localRepository.getAllShopping();
allOther = localRepository.getAllOther();
}
public void insert(Task task) {
localRepository.insert(task);
}
public void delete(Task task) {
localRepository.delete(task);
}
public LiveData<List<Task>> getAllJob() {
return allJob;
}
public LiveData<List<Task>> getAllShopping() {
return allShopping;
}
public LiveData<List<Task>> getAllOther() {
return allOther;
}
}
Then in MainActivity calls two methods:
private void getAllJob() {
viewModelMainActivity.getAllJob().observe(this, new Observer<List<Task>>() {
#Override
public void onChanged(List<Task> tasks) {
if(tasks.size() == 0) {
linearLayoutActivityMain.setVisibility(View.VISIBLE);
} else {
linearLayoutActivityMain.setVisibility(View.INVISIBLE);
}
taskAdapter.setAllJobTasks(tasks);
}
});
}
private void getAllShopping() {
viewModelMainActivity.getAllShopping().observe(this, new Observer<List<Task>>() {
#Override
public void onChanged(List<Task> tasks) {
Log.i("Size", "Shopping: " + String.valueOf(tasks.size()));
if(tasks.size() == 0) {
linearLayoutActivityMain.setVisibility(View.VISIBLE);
} else {
linearLayoutActivityMain.setVisibility(View.INVISIBLE);
}
taskAdapter.setCurrentTasks(tasks);
}
});
}
Why when I save a task:
viewModelMainActivity.insert(task);
e.g. to the job category, both onChanged methods are called, not just the onChanged method in getAllJob.
How could I separate it? That only the onChanged method would be called for values that have changed. Should I create separate ViewModels objects? But what about saving the task then? I would have to call the insert method three times for each object?
I am new to ReactiveX and I have a case where I want my observable to emit data to a late subscriber(whenever the observer subscribes, observable should emit the same data that it emitted previously). I made this Observable class that provide ReplaySubject's same instance to all observers (it is singleton class).
public class AccountsObservable {
private static ConnectableObservable<String> hotObservable;
private static AccountsObservable accountsObservable;
public static AccountsObservable getObject() {
if (accountsObservable == null) {
accountsObservable = new AccountsObservable();
}
return accountsObservable;
}
public ConnectableObservable<String> getObservable() {
if (hotObservable == null) {
Observable<String> observable = ReplaySubject.create(new ObservableOnSubscribe<String>() {
#Override
public void subscribe(ObservableEmitter<String> emitter) throws Exception {
emitter.onNext("XYZ");
emitter.onComplete();
}
});
hotObservable = observable.replay();//publish
}
return hotObservable;
}
}
Similarly, this is the observer class that creates new observer instance.
public class AccountsObserver {
AccountsFetchListener listener;
public AccountsObserver(AccountsFetchListener listener) {
this.listener = listener;
}
public Observer<String> getObserver() {
return new Observer<String>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onNext(String accounts) {
listener.onSuccess(accounts);
}
#Override
public void onError(Throwable e) {
listener.onFailure();
}
#Override
public void onComplete() {
}
};
}
public interface AccountsFetchListener {
void onSuccess(String accounts);
void onFailure();
}
}
Here is the function where I test these observables
private void testObs() {
ConnectableObservable<String> observable = AccountsObservable.getObject().getObservable();
Observer<String> observer = new AccountsObserver(new AccountsObserver.AccountsFetchListener() {
#Override
public void onSuccess(String accounts) {
Log.e("DATA -> ", accounts);
}
#Override
public void onFailure() {
}
}).getObserver();
observable.subscribe(observer);
observable.connect();
}
I called this function "testObs()" 5 times but it emitted data only 2 times. The problem seems to be in AccountsObservable class where I provide ReplaySUbject's instance. Thanks
Your code runs fine as it is, your logs are being suppressed in logcat as per this:
We declared an application as too chatty once it logs more than 5 lines a second. Please file a bug against the application's owner that is producing this developer-verbose-debug-level class logging spam. The logs are 256KB, that means the application is creating a DOS attack and shortening the logs timepan to 6 seconds(!) making it useless for all others.
You can avoid this behaviour by whitelisting your app for logcat:
adb logcat -P '<pid or uid of your app>'
Here is the code:
public class HomeDetails extends Model {
public Home mHomeData;
public AD mAdData;
public HomeDetails(Api api, String url) {
api.getHome(url, createHome(), this);
api.getAd(url, createAD(), this);
}
private NetworkResponse.Listener<Home> createHome() {
return new NetworkResponse.Listener<Home>() {
#Override
public void onResponse(Home home) {
mHomeData = home;
}
};
}
private NetworkResponse.Listener<AD> createAD() {
return new NetworkResponse.Listener<AD>() {
#Override
public void onResponse(AD ad) {
mAdData = ad;
}
};
}
}
I'd like to use RxJava to help me to know when the two requests are all done. if all is done, then execute another method.
You can use Observable.create() to create the two observable for the two network calls, then you can concat() or zip() them and execute whatever you want in the onNext().