I have a Integer value: 10000, which I want to databind it to the interface as 100.00.
android:text="#{BigDecimal(viewModel.transactionDetail.transaction.amount).toString()}"
Data binding is not for implementing logics inside view, it is
provided to attach view to data models.
If you use controllers / handler, you can put your converting logic there. See data binding documentation, you will always see Handlers in their examples.
Way 1
Using handler you can write your converting logic inside handler.
<variable
name="handler"
type="com.package.MainHandler"/>
android:text="#{handler.getAmountInDollar(transaction.amount)}"
In your Activity/Fragment
binding.setHandler(new Handler());
In Handler
public String getAmountInDollar(int amount) {
return new DecimalFormat("##.##").format(amount);
}
Way 2
If you don't use presenter still, you can do this.
public class Transaction {
private int amount;
public String getAmountInDollar() {
return new DecimalFormat("##.##").format(amount);
}
}
Now you can do this
android:text="#{transaction.amountInDollar}"
That's the way you should do this.
Related
I have a two queries which return two long values. I am setting these two long values to be displayed in individual text views. Finally I have a third text view which displays the combined value of both longs. I am having a problem getting the combined total to show as its setting the value before the livedata is returned.
Below is a snippet of the code
private void getData() {
mViewModelReframing.totalWorkouts(pkUserId).observe(getViewLifecycleOwner(), new Observer<List<ModelStatsTotalWorkouts>>() {
#Override
public void onChanged(List<ModelStatsTotalWorkouts> modelStatsTotalWorkouts) {
for (ModelStatsTotalWorkouts list : modelStatsTotalWorkouts) {
totalReframeWorkouts = list.getTotalWorkouts();
}
if (totalReframeWorkouts == 0) {
tvTotalReframes.setText(0 + getString(R.string.workouts_empty));
} else {
tvTotalReframes.setText("" + totalReframeWorkouts);
}
}
});
mViewModelCheckIn.totalWorkouts(pkUserId).observe(getViewLifecycleOwner(), new Observer<List<ModelStatsTotalWorkouts>>() {
#Override
public void onChanged(List<ModelStatsTotalWorkouts> tableCheckIns) {
for (ModelStatsTotalWorkouts list : tableCheckIns) {
totalCheckInWorkouts = list.getTotalWorkouts();
}
tvTotalCheckIns.setText("" + totalCheckInWorkouts);
// Combine both longs together for a combined total.
totalWorkouts = totalReframeWorkouts + totalCheckInWorkouts;
tvTotalWorkouts.setText("" + totalWorkouts);
}
});
}
Is there a better way to write the logic to achieve the desired result without the issue of the livedata not being returned fast enough?
Whenever you use independent Reactive streams like this (LiveData, RxJava, etc) you are going to have race conditions. You need to make explicit the dependencies for an action to happen - in this case your ability to update the UI in the way that you want had dependencies on BOTH APIs returning. This is the RxJava equivalent of zip. A few tips:
Consider using only a single Viewmodel for a view. The viewmodel should really be preparing data for your view specificially. In this case, it should really be that singular ViewModel that handles combining this data before passing it to your vew at all.
Barring that, since you've chosen LiveData here, you can do what you want by using a MediatorLiveData. Essentially, it acts as a composite stream source that depends on whichever other LiveData streams you add to it as described by that article. In this way, you can explicitly wait for all the needed values to arrive before you try to update the UI.
I solved the question by using this method:
public LiveData<List<ModelStatsTotalWorkouts>> totalWorkoutsCombined(long userId) {
LiveData liveData1 = database.getUsersDao().totalReframeWorkouts(userId);
LiveData liveData2 = database.getUsersDao().totalCheckInWorkouts(userId);
MediatorLiveData liveDataMerger = new MediatorLiveData<>();
liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));
return liveDataMerger;
}
In an Android app using Architecture Components I have the following view model:
public class MainViewModel extends AndroidViewModel {
private final MutableLiveData<List<String>> mUnchecked = new MutableLiveData<>();
private LiveData<List<String>> mChecked;
public void setUnchecked(List<String> list) {
mUnchecked.setValue(list);
}
public LiveData<List<String>> getChecked() { // OBSERVED BY A FRAGMENT
return mChecked;
}
public MainViewModel(Application app) {
super(app);
mChecked = Transformations.switchMap(mUnchecked,
list-> myDao().checkWords(list));
}
The purpose of the above switchMap is to check, which of the words passed as a list of strings, do exist in a Room table:
#Dao
public interface MyDao {
#Query("SELECT word FROM dictionary WHERE word IN (:words)")
LiveData<List<String>> checkWords(List<String> words);
The above code works well for me!
However I am stuck with wanting something slightly different -
Instead of the list of strings, I would prefer to pass a map of strings (words) -> integers (scores):
public void setUnchecked(Map<String,Integer> map) {
mUnchecked.setValue(map);
}
The integers would be word scores in my game. And once the checkWords() has returned the results, I would like to set the scores to null for the words not found in the Room table and leave the other scores as they are.
The programming code would be easy (iterate through mChecked.getValue() and set to null for the words not found in the list returned by the DAO method) - but how to "marry" it with my LiveData members?
TL;DR
I would like to change my view model to hold maps instead of the lists:
public class MainViewModel extends AndroidViewModel {
private final MutableLiveData<Map<String,Integer>> mUnchecked = new MutableLiveData<>();
private final MutableLiveData<Map<String,Integer>> mChecked = new MutableLiveData<>();
public void setUnchecked(Map<String,Integer> map) {
mUnchecked.setValue(map);
}
public LiveData<Map<String,Integer>> getChecked() { // OBSERVED BY A FRAGMENT
return mChecked;
}
public MainViewModel(Application app) {
super(app);
// HOW TO OBSERVE mUnchecked
// AND RUN myDao().checkWords(new ArrayList<>(mUnchecked.getValue().keys()))
// WRAPPED IN Executors.newSingleThreadScheduledExecutor().execute( ... )
// AND THEN CALL mChecked.postValue() ?
}
How to achieve that please? Should I extend MutableLiveData or maybe use MediatorLiveData or maybe use Transformations.switchMap()?
UPDATE:
I will try the following tomorrow (today is too late in the evening) -
The Dao method I will change to return a list instead of LiveData:
#Query("SELECT word FROM dictionary WHERE word IN (:words)")
List<String> checkWords(List<String> words);
And then I will try to extend the MutableLiveData:
private final MutableLiveData<Map<String,Integer>> mChecked = new MutableLiveData<>();
private final MutableLiveData<Map<String,Integer>> mUnchecked = new MutableLiveData<Map<String,Integer>>() {
#Override
public void setValue(Map<String,Integer> uncheckedMap) {
super.setValue(uncheckedMap);
Executors.newSingleThreadScheduledExecutor().execute(() -> {
List<String> uncheckedList = new ArrayList<>(uncheckedMap.keySet());
List<String> checkedList = WordsDatabase.getInstance(mApp).wordsDao().checkWords(uncheckedList);
Map<String,Integer> checkedMap = new HashMap<>();
for (String word: uncheckedList) {
Integer score = (checkedList.contains(word) ? uncheckedMap.get(word) : null);
checkedMap.put(word, score);
}
mChecked.postValue(checkedMap);
});
}
};
Well, what you have there in the update probably works, though I wouldn't create a new Executor for every setValue() call — create just one and hold onto it in your MutableLiveData subclass. Also, depending on your minSdkVersion, you might use some of the Java 8 stuff on HashMap (e.g., replaceAll()) to simplify the code a bit.
You could use MediatorLiveData, though in the end I think it would result in more code, not less. So, while from a purity standpoint MediatorLiveData is a better answer, that may not be a good reason for you to use it.
Frankly, this sort of thing isn't what LiveData is really set up for, IMHO. If this were my code that I were working on right now, I'd be using RxJava for the bulk of it, converting to LiveData in the end. And, I'd have as much of this as possible in a repository, rather than in a viewmodel. While your unchecked-to-checked stuff would be a tricky RxJava chain to work out, I'd still prefer it to the MutableLiveData subclass.
What EpicPandaForce suggests is an ideal sort of LiveData-only approach, though I don't think he is implementing your algorithm quite correctly, and I am skeptical that it can be adapted easily to your desired algorithm.
In the end, though, the decision kinda comes down to: who is going to see this code?
If this code is for your eyes only, or will live in a dusty GitHub repo that few are likely to look at, then if you feel that you can maintain the MutableLiveData subclass, we can't really complain.
If this code is going to be reviewed by co-workers, ask your co-workers what they think.
If this code is going to be reviewed by prospective employers... consider RxJava. Yes, it has a learning curve, but for the purposes of getting interest from employers, they will be more impressed by you knowing how to use RxJava than by you knowing how to hack LiveData to get what you want.
Tricky question!
If we check the source code for Transformations.switchMap, we see that:
1.) it wraps the provided live data with a MediatorLiveData
2.) if the wrapped live data emits an event, then it invokes a function that receives the new value of wrapped live data, and returns a "new" live data of a different type
3.) if the "new" live data of a different type differs from the previous one, then the observer of the previous one is removed, and it's added to the new one instead (so that you only observe the newest LiveData and don't accidentally end up observing an old one)
With that in mind, I think we can chain your switchMap calls and create a new LiveData whenever myDao().checkWords(words) changes.
LiveData<List<String>> foundInDb = Transformations.switchMap(mWords, words -> myDao().checkWords(words));
LiveData<Map<String, Integer>> found = Transformations.switchMap(foundInDb, (words) -> {
MutableLiveData<Map<String, Integer>> scoreMap = new MutableLiveData<>();
// calculate the score map from `words` list
scoreMap.setValue(map);
return scoreMap;
});
this.mFound = found;
Please verify if what I'm telling you is correct, though.
Also if there are a bunch of words, consider using some async mechanism and scoreMap.postValue(map).
How to use sharedPreferences inside observable, as I know take value outside observable is a bad practise.
I have something like this:
class City {
private String tempCelsius;
private String tempKelvin;
// other fields
public getTempCelsius() {
return tempCelsius;
}
public getTempKelvin() {
return tempKelvin;
}
}
In Presenter I have:
public Single<City> getData() {
return dataManager.loadCitiesFromDb().
map(city -> dataManager.makeApiCall(city)
//getting data, error handling
)
.subscribe(city -> view.showCityData(city));
}
By the way my DataManager contains instance of PreferenceHelper.
How can I showCityData with temperature based on value from SharedPreferences.
If Kelvin use City's method getTempKelvin(), else if Celsius use City's method getTempCelsius().
From Api I received both, but need to show only once according to saved preferences.
I'm developing application wherein I want to use MVVM pattern. Currently, all events from xml are handled by the activity which pass them next to ViewModel. E.g. user clicks login button, the event is handled by activity; now the activity call view model's method, inside this method I'm calling RxFirebase (Rx wrapper on Firebase) method which returns Observable, subscribe to them and return it; in view I'm again subscribe to this observable for doing UI update. This situation is presented below.
My question is if this approach is correct? In my opinion, the better solution is to handle the error in ViewModel, but how then I can update UI? One of the solutions is to create interface, e.g. ShowMessageListener, next pass it to ViewModel and use to show message, but I prefer harness RxJava to this.
View method:
public void onLoginClick(View view) {
mBinding.clProgress.setVisibility(View.VISIBLE);
mViewModel.onLoginClick().subscribe(authResult -> {
mBinding.clProgress.setVisibility(View.GONE);
startAnotherActivity();
}, throwable -> {
mBinding.clProgress.setVisibility(View.GONE);
if (throwable instanceof FirebaseApiNotAvailableException) {
Snackbar.make(mBinding.getRoot(), R.string.google_play_services_unavilable, Snackbar.LENGTH_LONG).show();
} else {
Snackbar.make(mBinding.getRoot(), throwable.getMessage(), Snackbar.LENGTH_LONG).show();
}
});
}
ViewModel method:
public Observable<AuthResult> onLoginClick() {
Observable<AuthResult> observable = RxFirebaseAuth.signInWithEmailAndPassword(mAuth, mEmail.get(), mPassword.get());
observable.subscribe(authResult -> {
//save user
}, throwable -> {
//handle error
});
return observable;
}
Your answer is almost correct except that you should really seperate View and (Business)-Logic. This would be the attempt if you use databinding which is highly recommend when using Architecture Components.
That means that everything which updates the UI should be in your View, everything which is not relevant for the view should be in the ViewModel.
That means that you can pass your ViewModel to your Layout, which has a onClick and call the Method in the ViewModel. Example:
<?xml version="1.0" encoding="utf-8"?>
<layout ..>
<data><variable name="viewModel" type="YourVm" /></data>
<Button onClick="#{viewModel::onButtonClick}
</layout>
Now you can handle the onClick inside your ViewModel like
public void onClick(View view) {
Log.d("Click", "My Button was clicked");
}
If you "really" want to observe for errors from your View you could either Create an ObservableBoolean which is set to True onec there's an error and subscribe for changes. You can put it inside the ViewModel like:
public final ObservableBoolean observableError = new ObservableBoolean();
public void onClick(...) { observableError.set(true); }
Now you can observe the Boolean inside your View
yourViewModel.obserableError.observe(this, result -> {
// do your error stuff
});
If you don't use Databinding it's almost the same except that you pass a ClickListener to the Button.
Means that you listen for the OnClick in your View, call the "processing"-method in your ViewModel and update the ObservableBoolean if an error occured. Since your a Listening for changes you can process the SnackBar stuff inside your View.
Snackbar and everything which involves the view should really be seperated from the ViewModel except a navigator. In this case you should create WeakReferences to avoid leaks.
Take care that the ObservableBoolean is NOT part of RxJava. It's part of Architecture Components.
If you want to solve it using RxJava you could create a PublishSubject in your ViewModel like:
Viewmodel.java
public final PublishSubject<String> ps = PublishSubject.create<>()
public void onClick(...) { ps.next("my evil error string"); }
And finally Observe it in your view
myViewModel.ps.subscribe( data -> {...}, error -> { ... } )
Take care that you dispose your RxJava Subscriptions in onCleared() which is in your ViewModel interface.
Edit: I haven't tested the code since i have only Kotlin Projects at the moment but should work in java.
Found an issue in your code that you didnt validate if mBinding is null. This may be null since you subscribe for changes and try to create the SnackBar in the View which may be disposed already. Always use if (mBinding != null) Snackbar.snackysnacky(..)
I am using RXJava on Android for asynchronously access the database.
I want to save an object in my database.
In this way, I created a method which take a final parameter (the object I want to save) and returns an Observable.
At this point I don't care to emit anything so I will call subscriber.onComplete() at the end.
Here is my code:
public Observable saveEventLog(#NonNull final EventLog eventLog) {
return Observable.create(new Observable.OnSubscribe<Object>() {
#Override
public void call(Subscriber<? super Object> subscriber) {
DBEventLog log = new DBEventLog(eventLog);
log.save();
subscriber.onCompleted();
}
});
}
The thing is, I saw many answer using the final keyword for the parameter, but I would like to do this without it.
The reason is I don't really like the approach of declare a final variable in order to use it in another thread.
Is there any alternative? Thanks.
We usually suggest avoiding the use of create because it may seem simple to use it but they usually violate the advanced requirements of RxJava. Instead, you should use one of the factory methods of Observable. In your case, the just factory method will get what you wanted: no final parameter:
public Observable<?> saveEventLog(#NonNull EventLog eventLog) {
return Observable
.just(eventLog)
.doOnNext(e -> {
DBEventLog log = new DBEventLog(e);
log.save();
})
.ignoreElements();
}