I want to create change password option for my app which will update the current password with new pasword and Im using rxjava and retrofit to send a update request to server. Sorry if Im having issues with the correct terminologies. Im new to android. Issue im having is Validations I have added to viewmodel does not work properly. I think its because of the fragment class not configured properly. im having trouble with setting it to to show error messages(such as "Old Password is required" and "New Password is required") which should be validated by the viewmodel and change password according to that.
Im currently getting a "cannot resolve method maketext" error from the Toast I have made in the fragment class.
Any help with this matter is highly appreciated.Please find my code here. Also please let me know if my approach is correct or how it can be improved.
UpdatePasswordFragment.java
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mViewModel = ViewModelProviders.of(this).get(UpdatePasswordViewModel.class);
binding.setViewModel(mViewModel);
//mViewModel.setUser(new Gson().fromJson(getIntent().getStringExtra(Constants.INTENT_USER), User.class));
mViewModel.setUser(new Gson().fromJson(getArguments().getString("user"), User.class));
binding.setLifecycleOwner(this);
mViewModel.getMessage().observe(this, s -> {
Toast.makeText(this,s, Toast.LENGTH_LONG).show();
});
}
UpdatePassowrdViewModel.java
public class UpdatePasswordViewModel extends ViewModel {
private Repository Repository;
Application application;
public void init(Application application) {
this.application = application;
showSpinner.setValue(false);
Repository = new Repository(application);
updatePasswordMutableLiveData.setValue(new UpdatePassword());
}
private MutableLiveData<UpdatePassword> updatePasswordMutableLiveData = new MutableLiveData<>();
private MutableLiveData<Boolean> showSpinner = new MutableLiveData<>();
private final String SUCCESS_MESSAGE = "Password Successfully Changed";
private User mUser;
public MutableLiveData<String> getOldPassword() {
return oldPassword;
}
public void setOldPassword(MutableLiveData<String> oldPassword) {
this.oldPassword = oldPassword;
}
public MutableLiveData<String> getNewPassword() {
return newPassword;
}
public void setNewPassword(MutableLiveData<String> newPassword) {
this.newPassword = newPassword;
}
public MutableLiveData<String> getConfirmNewPassword() {
return confirmNewPassword;
}
public void setConfirmNewPassword(MutableLiveData<String> confirmNewPassword) {
this.confirmNewPassword = confirmNewPassword;
}
private MutableLiveData<String> oldPassword = new MutableLiveData<>();
private MutableLiveData<String> newPassword = new MutableLiveData<>();
private MutableLiveData<String> confirmNewPassword = new MutableLiveData<>();
private MutableLiveData<Boolean> showLoader = new MutableLiveData<>();
public void setUser(User user) {
this.mUser = user;
}
public MutableLiveData<String> getMessage() {
return message;
}
private MutableLiveData<String> message = new MutableLiveData<>();
public MutableLiveData<Boolean> getShowLoader() {
return showLoader;
}
#SuppressLint("CheckResult")
public void changePassword() {
showSpinner.setValue(true);
Repository.changePassword(mUser.getUserName(), oldPassword.getValue(),newPassword.getValue())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(s -> {
if(SUCCESS_MESSAGE.equals(s)) {
oldPassword.setValue("");
newPassword.setValue("");
confirmNewPassword.setValue("");
}
showSpinner.setValue(false);
message.setValue(s.toString());
}, throwable -> {
showSpinner.setValue(false);
message.setValue(throwable.getLocalizedMessage());
});
}
public void savePasswordClicked(View view) {
if(oldPassword.getValue().trim().length() == 0) {
message.setValue("Old Password is required");
return;
}
if(newPassword.getValue().trim().length() == 0) {
message.setValue("New Password is required");
return;
}
if(!newPassword.getValue().equals(confirmNewPassword.getValue())) {
message.setValue("New Password and Confirm Password doesn't match");
return;
}
changePassword();
}
Repository.Java
public Observable<ApiResponse<User>> changePassword(String userId, String oldPassword, String newPassword) {
// return mApi.updatePassword(UpdatePassword);
return mApi.updatePassword(userId,oldPassword, newPassword );
}
THis is the retrofit call I have made in the APi
#PUT("user/updatepassword")
Observable<ApiResponse<User>> updatePassword(
#Field("currentPassword") String oldPassword,
#Field("newPassword") String newPassword,
#Field("userId") String userId
);
First of all, you are using not only ViewModel here, but data binding too. First thing you need to do to be able to use data binding is to add to your build.gradle the following:
// enable data binding for app here
android {
...
dataBinding {
enabled = true
}
}
Second mistake is that you are making setters and getters for MutableLiveData, you should change the value of the data by calling .setValue(newValue), the reference of the object should be immutable if you want your observers to be notified of change.
The last thing you need to do is to make sure the required fields are binded correctly in you layout, in your case you need a two-way binding, example:
<CheckBox
android:id="#+id/rememberMeCheckBox"
android:checked="#={viewmodel.rememberMe}"
/>
You can read more about two-way data binding here.
Related
I'm trying to send params from my ViewModel to my ViewRepository but I don't understan how can I send some params.
For example this is my observer in my fragment:
apoyaLoginViewModel.getPostLoginApoya(tokenApoya, usuario, password).observe(getActivity(), new Observer<PostLoginApoya>() {
#Override
public void onChanged(PostLoginApoya postLoginApoya) {
loginApoyaModel = postLoginApoya;
}
});
I'm sending some params in this line:
getPostLoginApoya(tokenApoya, usuario, password)
And this is my ViewModel:
public class ApoyaLoginViewModel extends AndroidViewModel {
private ApoyaLoginViewRepositori apoyaLoginViewRepositori;
private LiveData<PostLoginApoya> postLoginApoya;
public ApoyaLoginViewModel(Application aplication){
super(aplication);
apoyaLoginViewRepositori = new ApoyaLoginViewRepositori();
postLoginApoya = apoyaLoginViewRepositori.loginApoyaUser;
}
public LiveData<PostLoginApoya> getPostLoginApoya(String tokenApoya, String usuario, String password){return postLoginApoya;}
}
And this is a fragment of my ViewRepository:
ApoyaLoginViewRepositori(){
seccion15ServerClient = Seccion15ServerClient.getInstance();
seccionApiService = seccion15ServerClient.getSeccionApiService();
loginApoyaUser = getLoginUser();
}
public MutableLiveData<PostLoginApoya> getLoginUser(String tokenApoya, String usuario, String password){
if(loginApoyaUser == null){
loginApoyaUser = new MutableLiveData<>();
}
But I'm getting an error in this line:
loginApoyaUser = getLoginUser();
This is because my method getLoginUser has 3 parameters but my constructor no. maybe this is not the correct way to send information between ViewModel and ViewRepository.
How can I send this params to my constructor in my ViewRepository
You don't have to pass any argument in getPostLoginApoya, create a separate method for that: loginApoyaUser(token, usuario, password). and call this method whenever you want to login the user, you will automatically receive an event with the logged in user.
fragment:
viewModel.getPostLoginApoya().observe(getActivity(), new Observer<PostLoginApoya>() {
#Override
public void onChanged(PostLoginApoya postLoginApoya) {
// do something with your user here
}
});
//you have to call this method somewhere, when you click on a button for example.
viewModel.loginApoyaUser(token, usuario, password);
ViewModel:
public class ApoyaLoginViewModel extends AndroidViewModel {
private ApoyaLoginViewRepositori apoyaLoginViewRepositori;
private LiveData<PostLoginApoya> postLoginApoya;
public ApoyaLoginViewModel(#NonNull Application application) {
super(application);
apoyaLoginViewRepositori = new ApoyaLoginViewRepositori();
postLoginApoya = apoyaLoginViewRepositori.getPostLoginApoya();
}
public LiveData<PostLoginApoya> getPostLoginApoya(){
return postLoginApoya;
}
public void loginApoyaUser(String tokenApoya, String usuario, String password) {
apoyaLoginViewRepositori.loginApoyaUser(tokenApoya, usuario, password);
}
}
Repo:
public class ApoyaLoginViewRepositori {
private MutableLiveData<PostLoginApoya> postLoginApoyaLiveData;
private PostLoginApoya postLoginApoya;
public ApoyaLoginViewRepositori() {
postLoginApoyaLiveData = new MutableLiveData<>();
}
public LiveData<PostLoginApoya> getPostLoginApoya() {
return postLoginApoyaLiveData;
}
public void loginApoyaUser(String tokenApoya, String usuario, String password) {
postLoginApoya = //login user here
//notify observers data has been changed
postLoginApoyaLiveData.postValue(postLoginApoya);
}
}
i have a fragment in my app that i show two list of saparate data in it.i'm using from android architecture components to load my data.
Once the data is fetched from the network, I store it locally using Room DB and then display it on the UI using ViewModel that observes on the LiveData object (this works fine). However, I want to be able to have a refreshLayout which When Refreshing Occurs a refresh action and perform a network request to get new data from the API if and only if there is a network connection.The issue is when Refreshing Occurs data load from locate database and network together .
my question is :How do I manage to get data only from Network when refreshing data?
How do I manage to get data only from Network when refreshing data?
I've seen this question and it didn't help me...
my codes:
repository:
public NetworkResult<LiveData<HomeHealthModel>> getHomeHealth(String query) {
MutableLiveData<String> _liveError = new MutableLiveData<>();
MutableLiveData<HomeHealthModel> data = new MutableLiveData<>();
LiveData<List<GeneralItemModel>> liveClinics = App.getDatabase().getGeneralItemDAO().getTops(GeneralItemType.Clinics, GeneralItemType.TOP);
LiveData<List<GeneralItemModel>> liveDoctors = App.getDatabase().getGeneralItemDAO().getTops(GeneralItemType.Doctors, GeneralItemType.TOP);
setupService(_liveError); //request data from network
data.postValue(new HomeHealthModel(liveClinics, liveDoctors));
_liveError.postValue(String.valueOf(NetworkResponseType.LocaleData));
return new NetworkResult<>(_liveError, data);
}
my viewModel
public class HomeHealthVM extends ViewModel {
private MutableLiveData<String> queryLiveData;
private LiveData<String> networkErrors;
private LiveData<List<GeneralItemModel>> Clinics;
private LiveData<List<GeneralItemModel>> Doctors;
public HomeHealthVM(HealthRepository repository) {
queryLiveData = new MutableLiveData<>();
LiveData<NetworkResult<LiveData<HomeHealthModel>>> repoResult;
repoResult = Transformations.map(queryLiveData, repository::getHomeHealth);
LiveData<HomeHealthModel> model = Transformations.switchMap(repoResult, input -> input.data);
Doctors = Transformations.switchMap(model, HomeHealthModel::getDoctors);
Clinics = Transformations.switchMap(model, HomeHealthModel::getClinics);
networkErrors = Transformations.switchMap(repoResult, input -> input.error);
}
public void search(String queryString) {
queryLiveData.postValue(queryString);
}
public String lastQueryValue() {
return queryLiveData.getValue();
}
public LiveData<String> getNetworkErrors() {
return networkErrors;
}
public LiveData<List<GeneralItemModel>> getClinics() {
return Clinics;
}
public LiveData<List<GeneralItemModel>> getDoctors() {
return Doctors;
}
}
my fragment code:
private void setupViewModel() {
ViewModelFactory<HealthRepository> factory = new ViewModelFactory<>(new HealthRepository());
healthVM = ViewModelProviders.of(this, factory).get(HomeHealthVM.class);
healthVM.getNetworkErrors().observe(this, states -> {
try {
if (Integer.parseInt(states) != WarningDialogType.Success &&
Integer.parseInt(states) != WarningDialogType.Locale) {
stopLoading();
linerNoInternet.setVisibility(View.VISIBLE);
linerContent.setVisibility(View.GONE);
}
} catch (Exception e) {
stopLoading();
linerNoInternet.setVisibility(View.VISIBLE);
linerContent.setVisibility(View.GONE);
}
});
healthVM.getDoctors().observe(this, doctors -> {
if (doctors.size() > 0) {
doctorsAdapter.submitList(doctors);
stopLoading();
} else {
}
});
healthVM.getClinics().observe(this, clinics -> {
if (clinics.size() > 0) {
clinicsAdapter.submitList(clinics);
stopLoading();
} else {
conesClinics.setVisibility(View.GONE);
}
});
healthVM.search("");
}
I checked this article but observe the response changes in MainActivity.
Here is my code for LoginRepo
public MutableLiveData<LoginResponseModel> checkLogin(LoginRequestModel loginRequestModel) {
final MutableLiveData<LoginResponseModel> data = new MutableLiveData<>();
Map<String, String> params = new HashMap<>();
params.put("email", loginRequestModel.getEmail());
params.put("password", loginRequestModel.getPassword());
apiService.checkLogin(params)
.enqueue(new Callback<LoginResponseModel>() {
#Override
public void onResponse(Call<LoginResponseModel> call, Response<LoginResponseModel> response) {
if (response.isSuccessful()) {
data.setValue(response.body());
Log.i("Response ", response.body().getMessage());
}
}
#Override
public void onFailure(Call<LoginResponseModel> call, Throwable t) {
data.setValue(null);
}
});
return data;
}
Here is my Code LoginViewModel
public class LoginViewModel extends ViewModel {
public MutableLiveData<String> emailAddress = new MutableLiveData<>();
public MutableLiveData<String> password = new MutableLiveData<>();
Map<String, String> params = new HashMap<>();
LoginRepo loginRepo;
private MutableLiveData<LoginResponseModel> loginResponseModelMutableLiveData;
public LiveData<LoginResponseModel> getUser() {
if (loginResponseModelMutableLiveData == null) {
loginResponseModelMutableLiveData = new MutableLiveData<>();
loginRepo = LoginRepo.getInstance();
}
return loginResponseModelMutableLiveData;
}
//This method is using Retrofit to get the JSON data from URL
private void checkLogin(LoginRequestModel loginRequestModel) {
loginResponseModelMutableLiveData = loginRepo.checkLogin(loginRequestModel);
}
public void onLoginClick(View view) {
LoginRequestModel loginRequestModel = new LoginRequestModel();
loginRequestModel.setEmail(emailAddress.getValue());
loginRequestModel.setPassword(password.getValue());
params.put("email", loginRequestModel.getEmail());
params.put("password", loginRequestModel.getPassword());
checkLogin(loginRequestModel);
}
}
Here is my code for LoginActivity
private LoginViewModel loginViewModel;
private ActivityMainBinding binding;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
loginViewModel = ViewModelProviders.of(this).get(LoginViewModel.class);
binding = DataBindingUtil.setContentView(LoginActivity.this, R.layout.activity_main);
binding.setLifecycleOwner(this);
binding.setLoginViewModel(loginViewModel);
loginViewModel.getUser().observe(this, new Observer<LoginResponseModel>() {
#Override
public void onChanged(#Nullable LoginResponseModel loginUser) {
if (loginUser != null) {
binding.lblEmailAnswer.setText(loginUser.getUser().getId());
Toast.makeText(getApplicationContext(), loginUser.getUser().getId(), Toast.LENGTH_SHORT).show();
}
}
});
}
onLoginClick method used in LoginViewModel is using LiveData.
The Response coming from api is okay. But onchange() it is not shown, how to use LiveData using MVVM pattern in simple Login Example. Please help!
Here is what i have tried using your classes just altering retrofit to background thread to wait 5 seconds and then setting the data (you need to confirm the response being successful as you don't change the data if it's failing and hence if the loginResponseModel is null then it will enter the onChanged Method but it won't do anything as you don't have a condition if it is equals to null) here is what i did
in Main Activity -> onCreate() i just created the viewmodel and observed on the mutableLiveData
myViewModel.onLoginClick(null);
myViewModel.simpleModelMutableLiveData.observe(this, new Observer<String>() {
#Override
public void onChanged(#Nullable String s) {
if(s==null)
Log.v("testinggg","test - onChanged --- Null " );
else
Log.v("testinggg","test - onChanged --- s -> "+s );
}
});
Then here is the ViewModel -> in which you will have the MutableLiveData itself named simpleModelMutableLiveData
MutableLiveData<String> simpleModelMutableLiveData;
public LiveData<String> getUser() {
if (simpleModelMutableLiveData == null) {
simpleModelMutableLiveData = new MutableLiveData<>();
}
return simpleModelMutableLiveData;
}
// this method will return Object of MutableLiveData<String> and let the simpleModelMutableLiveData be the returned object
private void checkLogin(String placeholder) {
simpleModelMutableLiveData = MyRepo.checkLogin(placeholder);
}
public void onLoginClick(View view) {
checkLogin("test");
}
and at last the Repo method in which i will return the MutableLiveData and let the simpleModelMutableLiveData to be the return and initiate a background thread using runnable that will wait 5 seconds before it sets the value using a handler (in your case you will need to set the value of the data after enqueue inside the Overridden Methods onResponse and onFailure)
as follows
public static MutableLiveData<String> checkLogin(String test) {
final MutableLiveData<String> data = new MutableLiveData<>();
Runnable r = new Runnable() {
public void run() {
runYourBackgroundTaskHere(data);
}
};
new Thread(r).start();
return data;
}
private static void runYourBackgroundTaskHere(final MutableLiveData<String> data) {
try {
Thread.sleep(5000);
// Handler handler = new Handler();
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
// things to do on the main thread
/* Here i set the data to sss and then null and when
you check the logcat and type the keyword used for
logging which is "testinggg"
you will find it show sss and then null which means
it has entered the onChanged and showed you the log */
data.setValue("sss");
data.setValue(null);
}
});
} catch (InterruptedException e) {
e.printStackTrace();
}
}
I am creating very simple android app using mvvm and repository pattern. It fetch data from network (using retrofit2/RxJava2) if app is online and saves to DB (using room) and post to observe. If app is offline, app gets the data from DB and post to observe. From activity app updates the textviews after getting observed from viewmodel class.
Everything is working very fine when app has active internet connection. When internet is not available it does not load data from DB. And that's the problem am facing with no clue.
Activity class
viewModel.loadHomeData();
viewModel.homeDataEntityResult().observe(this, this::updateTextViews);
private void updateTextViews(HomeDataEntity data) {
if (data != null) {
tv1.setText(data.todayDate);
tv2.setText(data.bnDate);
tv3.setText(data.location);
}
}
Viewmodel class
private RamadanRepository repository;
private DisposableObserver<HomeDataEntity> disposableObserver;
private MutableLiveData<HomeDataEntity> homeDataEntityResult = new MutableLiveData<>();
public LiveData<HomeDataEntity> homeDataEntityResult() {
return homeDataEntityResult;
}
public void loadHomeData() {
disposableObserver = new DisposableObserver<HomeDataEntity>() {
#Override
public void onNext(HomeDataEntity homeDataEntity) {
homeDataEntityResult.postValue(homeDataEntity);
}
#Override
public void onError(Throwable e) {
}
#Override
public void onComplete() {
}
};
repository.getHomeData()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.debounce(400, MILLISECONDS)
.subscribe(disposableObserver);
}
Repository class
public Observable<HomeDataEntity> getHomeData() {
boolean hasConnection = appUtils.isOnline();
Observable<HomeDataEntity> observableFromApi = null;
if (hasConnection) {
observableFromApi = getHomeDataFromApi();
}
Observable<HomeDataEntity> observableFromDb = getHomeDataFromDb();
if (hasConnection)
return Observable.concatArrayEager(observableFromApi, observableFromDb);
else return observableFromDb;
}
private Observable<HomeDataEntity> getHomeDataFromApi() {
return apiService.getDemoHomeData()
.map(HomeDataEntity::copyFromResponse)
.doOnNext(homeDataDao::saveData);
}
private Observable<HomeDataEntity> getHomeDataFromDb() {
return homeDataDao.getHomeData()
.toObservable()
.doOnNext(homeDataEntity -> {
Timber.d("db data %s", homeDataEntity.toString());
});
}
When app is online it also prints the roomDB inserted data after fetching. What actually am missing when app is offline?
I have two collections: Users and Books. I need to get the results of both of them whether Users OR Books is updated and then merge the results together into a LinkedHashMap to use as a listView menu.
I thought a MediatorLiveData would be the way to go, but if I put the query of Users and the Query of Books in then I get null from one of the two LiveData objects because only one or the other fires. I thought maybe if one of them fires, then perhaps I have a query run inside each addSource() in the MediatorLiveData, but I'm not sure if that's the way to go.
My post regarding the MediatorLiveData is here:
Using MediatorLiveData to merge to LiveData (Firestore) QuerySnapshot streams is producing weird results
My two queries and LiveData objects are as follows:
//getUsers query using FirebaseQueryLiveData class
private Query getUsersQuery() {
FirebaseAuth mAuth = FirebaseAuth.getInstance();
adminID = mAuth.getUid();
query = FirebaseFirestore.getInstance().collection("admins")
.document(adminID)
.collection("users")
return query;
}
private FirebaseQueryLiveData usersLiveData = new FirebaseQueryLiveData(getUsersQuery());
//getBooks query using FirebaseQueryLiveData class
private Query getBooksQuery () {
FirebaseGroupID firebaseGroupID = new FirebaseGroupID(getApplication());
groupID = firebaseGroupID.getGroupID();
query = FirebaseFirestore.getInstance().collection("books")
.whereEqualTo("groupID", groupID)
return query;
}
private FirebaseQueryLiveData booksLiveData = new FirebaseQueryLiveData(getBooksQuery());
Somehow when Users updates, I need to get the data of Books as well and then merge them, but I also need this to happen if Books updates and then get the data of Users and merge them.
Any ideas would be greatly appreciated.
Additional Note/Observation
Okay, so I'm not completely ruling out a MediatorLiveData object. Certainly it allows me the listening of two different LiveData objects within the same method, however, I don't want to merge the two of them directly because I need to act on each liveData object individually. So as an example: usersLiveData fires because we create or modify a user, then I need to query books, get the results and merge users and books etc.
Below is my MediatorLiveData as it currently stands:
//MediatorLiveData merge two LiveData QuerySnapshot streams
private MediatorLiveData<QuerySnapshot> usersBooksLiveDataMerger() {
final MediatorLiveData<QuerySnapshot> mediatorLiveData = new MediatorLiveData<>();
mediatorLiveData.addSource(usersLiveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable QuerySnapshot querySnapshot) {
mediatorLiveData.setValue(querySnapshot);
}
});
mediatorLiveData.addSource(booksLiveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable QuerySnapshot querySnapshot) {
mediatorLiveData.setValue(querySnapshot);
}
});
return mediatorLiveData;
}
Right now it's returning null results of the other LiveData source. Instead I need to query then merge. Any ideas on how to do this? There isn't much out there on this very thing.
I tried putting a query inside a Function that is called using a Transformations.map() but because of it be an asynchronous call, the return statement is being called before the query finishes.
Here's my attempt at the Function:
private class ListenUsersGetBooks implements Function<QuerySnapshot, LinkedHashMap<User, List<Book>>> {
#Override
public LinkedHashMap<User, List<Book>> apply(final QuerySnapshot input) {
userBookList = new LinkedHashMap<>();
getBooksQuery().get().addOnCompleteListener(new OnCompleteListener<QuerySnapshot>() {
#Override
public void onComplete(#NonNull Task<QuerySnapshot> task) {
List<User> users = input.toObjects(User.class);
List<Book> books = task.getResult().toObjects(Book.class);
Log.d(TAG, "USERLIST! " + users);
Log.d(TAG, "BOOKLIST! " + books);
for (User user : users) {
bookList = new ArrayList<>();
for (Book book : books) {
if (user.getUserID().equals(book.getUserID())
&& book.getBookAssigned()) {
bookList.add(book);
}
else if (user.getAllBookID().equals(book.getBookID())) {
bookList.add(book);
}
}
userBookList.put(user, bookList);
}
Log.d(TAG,"OBSERVE userBookList: " + userBookList);
}
});
return userBookList;
}
}
Here's a simple version of what you could do, I hope it makes sense.
You're close with the MediatorLiveData. Instead of MediatorLiveData<QuerySnapshot> you probably want to use a custom object like this:
class MyResult {
public QuerySnapshot usersSnapshot;
public QuerySnapshot booksSnapshot;
public MyResult() {}
boolean isComplete() {
return (usersSnapshot != null && booksSnapshot != null);
}
}
Then in your observers, do something like this:
private MediatorLiveData<MyResult> usersBooksLiveDataMerger() {
final MediatorLiveData<MyResult> mediatorLiveData = new MediatorLiveData<>();
mediatorLiveData.addSource(usersLiveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable QuerySnapshot querySnapshot) {
MyResult current = mediatorLiveData.getValue();
current.usersSnapshot = querySnapshot;
mediatorLiveData.setValue(current);
}
});
mediatorLiveData.addSource(booksLiveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable QuerySnapshot querySnapshot) {
MyResult current = mediatorLiveData.getValue();
current.booksSnapshot = querySnapshot;
mediatorLiveData.setValue(current);
}
});
return mediatorLiveData;
}
Then when you observe the combined live data:
usersBooksLiveDataMerger().observe(new Observer<MyResult>() {
#Override
public void onChanged(#Nullable MyResult result) {
if (result == null || !result.isComplete()) {
// Ignore, this means only one of the queries has fininshed
return;
}
// If you get to here, you know all the queries are ready!
// ...
}
});
I think I solved it. We were declaring a new MyResult object in each mediatorLiveData.addSource() method. Which meant that we were getting a new object for each QuerySnapshot so we would never get them to merge with each other.
Here's the update to MediatorLiveData:
private MediatorLiveData<MyResult> usersBooksLiveDataMerger() {
final MediatorLiveData<MyResult> mediatorLiveData = new MediatorLiveData<>();
final MyResult current = new MyResult();
mediatorLiveData.addSource(usersLiveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable QuerySnapshot querySnapshot) {
current.setUsersSnapshot(querySnapshot);
mediatorLiveData.setValue(current);
}
});
mediatorLiveData.addSource(booksLiveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable QuerySnapshot querySnapshot) {
current.setBooksSnapshot(querySnapshot);
mediatorLiveData.setValue(current);
}
});
return mediatorLiveData;
}
Now I'm getting users and books in the observer in Activity! Now the only thing I need to do is transform (merge the data) into a LinkedHashMap, but I think I got that figured out. Thanks Sam!
So this is where I am with your suggestions Sam.
I added getter and setter methods to the MyResult class as it wasn't giving me access to the member variables in the observer otherwise:
public class MyResult {
QuerySnapshot usersSnapshot;
QuerySnapshot booksSnapshot;
//default constructor
public MyResult() {
}
public QuerySnapshot getUsersSnapshot() {
return usersSnapshot;
}
public void setUsersSnapshot(QuerySnapshot usersSnapshot) {
this.usersSnapshot = usersSnapshot;
}
public QuerySnapshot getBooksSnapshot() {
return booksSnapshot;
}
public void setBooksSnapshot(QuerySnapshot booksSnapshot) {
this.booksSnapshot = booksSnapshot;
}
public boolean isComplete() {
return (usersSnapshot != null && booksSnapshot != null);
}
}
Here's the MediatorLiveData and get method. I changed the MyResult class initialization to = new MyResult(); thinking there was an issue with using mediatorLiveData.getValue(); as the initialization and get method.
private MediatorLiveData<MyResult> usersBooksLiveDataMerger() {
final MediatorLiveData<MyResult> mediatorLiveData = new MediatorLiveData<>();
mediatorLiveData.addSource(usersLiveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable QuerySnapshot querySnapshot) {
MyResult current = new MyResult();
current.setUsersSnapshot(querySnapshot);
mediatorLiveData.setValue(current);
}
});
mediatorLiveData.addSource(booksLiveData, new Observer<QuerySnapshot>() {
#Override
public void onChanged(#Nullable QuerySnapshot querySnapshot) {
MyResult current = new MyResult();
current.setBooksSnapshot(querySnapshot);
mediatorLiveData.setValue(current);
}
});
return mediatorLiveData;
}
public MediatorLiveData<MyResult> getUsersBooksLiveDataMerger() {
return usersBooksLiveDataMerger();
}
And finally the observer:
mainViewModel.getUsersBooksLiveDataMerger().observe(this, new Observer<MainViewModel.MyResult>() {
#Override
public void onChanged(#Nullable MainViewModel.MyResult myResult) {
if (myResult == null || !myResult.isComplete()) {
// Ignore, this means only one of the queries has fininshed
Log.d(TAG, "OBSERVE BLAH!!!!");
return;
}
// If you get to here, you know all the queries are ready!
// ...
List<Book> books;
List<User> users;
books = myResult.getBooksSnapshot().toObjects(Book.class);
users = myResult.getUsersSnapshot().toObjects(User.class);
Log.d(TAG, "OBSERVE MERGE users: " + users);
Log.d(TAG, "OBSERVE MERGE books: " + books);
}
});
Please note: I did do a null check in the mediatorLiveData, just took it out for testing purposes.
Somehow I need to trigger my books query if just my users is triggered AND I need to trigger my users query if just my books is triggered...I feel like there is a step before the MediatorLiveData that needs to happen so we can make sure one liveData triggers the other query. Does that make sense?
You can greatly simplify the usage by using my LiveDataZipExtensions https://gist.github.com/Benjiko99/d2e5406aab0a4a775ea747956ae16624
With them, you don't have to create an object to hold your combined result.
Example usage
val firstNameLD = MutableLiveData<String>().apply { value = "John" }
val lastNameLD = MutableLiveData<String>().apply { value = "Smith" }
// The map function will get called once all zipped LiveData are present
val fullNameLD = zip(firstNameLD, lastNameLD).map { (firstName, lastName) ->
"$firstName $lastName"
}