I am just getting started with RxJava/RxAndroid and I was wondering if I can use it to solve the following problem. Basically, given a Field, say a textview, and a value, a string, I am looking for a way to automatically update the textview whenever the value of the string changes. I am not sure exactly how I would implement this as an Observable. Let me demonstrate;
String str = "Test"; //the string value
TextView textView = (TextView) findViewById(R.id.textView); //the textview
Observable o = //looking for this part. Want to observe the String str
o.subscribe(new Observer<String>() { //subscribe here looking for string changes
#Override
public void onCompleted() {
System.out.println("Completed");
}
#Override
public void onError(Throwable e) {
}
#Override
public void onNext(String s) {
textView.setText(s); //update the textview here
}
});
//here is where the string changes, it could be hardcoded, user input, or
//anything else really, I just want the textview to be updated automatically
//without another setText
str = "Different String";
Is what I am looking for possible with RxAndroid/RxJava?
The easiest way to accomplish that would be to use any kind of of Subject, maybe either a BehaviorSubject or a PublishSubject. A Subject is both a Subscriber (so you can put values into it with onNext) and an Observable (so you can subscribe to it). Look here for an explanation of the differences: http://reactivex.io/documentation/subject.html
So, instead of
String str = "Test";
you would have
BehaviorSubject<String> stringSubject = BehaviorSubject.<String>create("Test");
You can then directly subscribe to stringObservable.
And instead of assigning a new value to your variable like this:
str = "Hello World!";
you would do
stringSubject.onNext("Hello World!");
Oh, and never leave onError empty - doing so will quietly swallow any exceptions that may have occured earlier, and you will sit and wonder why nothing is happening. At least write e.printStacktrace().
Related
I am working on an app in which users have to select a country code, i was successful in creating a spinner for the said purpose as shown in this link:
Creating a spinner for choosing country code
But i am getting problem in reading the value selected in the spinner.
{
String abc = onCountryPickerClick();//abc is always null
}
public String onCountryPickerClick (){
ccp.setOnCountryChangeListener(new CountryCodePicker.OnCountryChangeListener() {
#Override
public void onCountrySelected() {
selected_country_code = ccp.getSelectedCountryCodeWithPlus();
}
});
return selected_country_code;
}
When String abc = onCountryPickerClick(); is being invoked, the selected_country_code value will be assigned to abc.
When your CountryCodePicker.OnCountryChangeListener's onCountrySelected() method is being invoked, the ccp.getSelectedCountryCodeWithPlus();'s value gets assigned to selected_country_code. Since String is immutable, changing selected_country_code's value won't change the value of abc, nor the return selected_country_code; will be invoked.
One of possible solutions would be to change your CountryCodePicker.OnCountryChangeListener anonymous implementation to assign the selected country value to abc e.g.
#Override
public void onCountrySelected() {
selected_country_code = ccp.getSelectedCountryCodeWithPlus();
abc = selected_country_code
}
Callbacks are not synchronous. Unfortunately, you cannot simply do String abc = onCountryPickerClick(); because what you are returning is something that is not yet set. Let's go through your code:
ccp.setOnCountryChangeListener(
new CountryCodePicker.OnCountryChangeListener() {
#Override
public void onCountrySelected() {
selected_country_code = ccp.getSelectedCountryCodeWithPlus();
}
});
The code seems to say that when the country is selected in the spinner, you assign the value of selected_country_code. Assuming this is an action triggered by the user, when you call String abc = onCountryPickerClick();, how can you be sure the user has selected anything? This is the issue. You cannot be sure that the user has already selected the option and returning the value is not enough.
You can solve this in many ways. You can for example keep propagating the callback:
public void onCountryPickerClick(OnCountryChangeListener listener){
ccp.setOnCountryChangeListener(listener);
}
// Anywhere you call this
onCountryPickerClick(new CountryCodePicker.OnCountryChangeListener() {
#Override
public void onCountrySelected() {
// Here do whatever you want with the selected country
}
});
The above approach is not very different than what you have now. There are other options. You could use java observables i.e.:
class CountryCodeObservable extends Observable {
private String value;
public CountryCodeObservable(String value) {
this.value = value;
}
public void setCountryCode(String countryCode) {
value = countryCode;
setChanged();
notifyObservers(value);
}
}
public CountryCodeObservable onCountryPickerClick(){
CountryCodeObservable retValue = new CountryCodeObservable("");
ccp.setOnCountryChangeListener(
new CountryCodePicker.OnCountryChangeListener() {
#Override
public void onCountrySelected() {
retValue.setCountryCode(ccp.getSelectedCountryCodeWithPlus());
}
});
return retValue;
}
// Then when calling this method you can do something like:
CountryCodeObservable observable = onCountryPickerClick();
observable.addObserver((obj, arg) -> {
// arg is the value that changed. You'll probably need to cast it to
// a string
});
The above example lets you add more than one observable. It might be too much for your use case, I just thought it illustrates another approach and also the asynchronicity of this situation.
Again, there are even more ways to solve this, the key is that you can't simply return a string and hope it changes when the user selects anything.
I want to receive a string from addValueEventListener() method I use to resell the data from the database Firebase. The data arrive correctly.
But when certain to get the string out of that method to use it in another, it returns nothing.
You have tips?
I already tried putExtras and also create a method on purpose but it did not work.
final DatabaseReference mPostReference = FirebaseDatabase.getInstance().getReference().child("user-daily").child(getUid()).child("2017-Year");
mPostReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
final ArrayList<String> labels = new ArrayList<String>();
for (DataSnapshot data : dataSnapshot.getChildren()){
final DailyItem dailyItem = data.getValue(DailyItem.class);
labels.add(dailyItem.mese);
}
title.setText(labels.get(position));
a = title.getText().toString();
}
#Override
public void onCancelled(DatabaseError databaseError) {
Toast.makeText(view.getContext(),"database error",
Toast.LENGTH_SHORT).show();
}
});
//this return null... why?
String title = a;
The data is loaded from Firebase asynchronously. By the time you run title = a, the onDataChange method hasn't been called yet. Set some breakpoints in a debugger to verify this, it's key to understanding how asynchronous loading works.
The solution is to reframe your problem from "first get the object, then do blabla with the title" to "start getting the object; once the object is available, do blabla with the title".
In code this translates to:
final DatabaseReference mPostReference = FirebaseDatabase.getInstance().getReference().child("user-daily").child(getUid()).child("2017-Year");
mPostReference.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
final ArrayList<String> labels = new ArrayList<String>();
for (DataSnapshot data : dataSnapshot.getChildren()){
final DailyItem dailyItem = data.getValue(DailyItem.class);
labels.add(dailyItem.mese);
}
title.setText(labels.get(position));
// Do blabla with the title
String title = title.getText().toString();
}
#Override
public void onCancelled(DatabaseError databaseError) {
Toast.makeText(view.getContext(),"database error",
Toast.LENGTH_SHORT).show();
}
});
Many developers new to Firebase (and other modern web APIs, as they all work this way) struggle with this problem. So I recommend you also check out their questions and answers:
Cannot access firebaseObjectObservable outside of set
Android Firebase get value of child without DataChange
Value of a global variable is reset after it is initialised in ValueEventListener
can't get values out of ondatachange method
ArrayList not updating inside onChildAdded function
Setting Singleton property value in Firebase Listener
and most others in this list of search results
In order to retrieve the string from method addValueEventListener in viewmodel or any other network call, it is recommended to use the either MutableLiveData<T> or LiveData<T> and observe the same in your activity. Observer will observe the changes, and as soon as string got filled up, the observer method will automatically give you string which you are looking.
You need to create reference variable for the LiveData<T> reference_variable wherever your addValueEventLister is located and set its value in your addValueEventListener.
And then in your viewmodel create the returning value function like below...
Observe this function in your activity and you will have your string.
public MutableLiveData<TotalRunsWicketsAndData> getDisplayableDetails() {
return observableLiveData;
}
I am using MutableLiveData here.
This is a trick which does it. It would be easy to do so if you have less data to retrieve from ValueEventListener.
Inside the onDataChange(), use a setText to set the required value in it. Keep the visibility of this text view as "Gone". Then retrieve using getText outside the ValueEventListener.
You can retrieve the whole list by using GenericTypeIndicator. Follow the official guide on here
I want when a user types the email, the moment they press# , it auto fills the rest to them as username#coretec.co.ke e.g but the problem is that its crashing
#Override
public void afterTextChanged(Editable s) {
if(email.getText().toString().contains("#")){
String e = email.getText().toString();
email.setText(e+"coretec.co.ke");
}
}
Error logcat:
java.lang.StackOverflowError
at java.lang.System.arraycopy(System.java:216)
at android.text.SpannableStringBuilder.getChars(SpannableStringBuilder.java:926)
at android.text.TextUtils.getChars(TextUtils.java:81)
at android.text.method.ReplacementTransformationMethod$ReplacementCharSequence.getChars(ReplacementTransformationMethod.java:151)
at android.text.TextUtils.getChars(TextUtils.java:81)
at android.text.TextUtils.indexOf(TextUtils.java:114)
at android.text.StaticLayout.generate(StaticLayout.java:191)
at android.text.DynamicLayout.reflow(DynamicLayout.java:288)
at android.text.DynamicLayout.<init>(DynamicLayout.java:174)
at android.widget.TextView.makeSingleLayout(TextView.java:6209)
at android.widget.TextView.makeNewLayout(TextView.java:6107)
at android.widget.TextView.checkForRelayout(TextView.java:6820)
at android.widget.TextView.setText(TextView.java:3850)
at android.widget.TextView.setText(TextView.java:3708)
at android.widget.EditText.setText(EditText.java:81)
at android.widget.TextView.setText(TextView.java:3683)
at com.coretec.coretec.activity.Login$1.afterTextChanged(Login.java:79)
The afterTextChanged's documentation states,
This method is called to notify you that, somewhere within s, the text
has been changed. It is legitimate to make further changes to s from
this callback, but be careful not to get yourself into an infinite
loop, because any changes you make will cause this method to be called
again recursively
One way to update the EditText from TextWatcher is to unregister the watcher from EditText first, set new values to EditText and finally register the watcher again at the EditText to handle further change.
Here is a good example of it.
remove text change listener before adding your text else it will go in infinite loop. update your code like this:
#Override
public void afterTextChanged(Editable s) {
if(email.getText().toString().contains("#")){
email.removeTextChangedListener(this); // this line
String e = email.getText().toString();
email.setText(e+"coretec.co.ke");
}
}
Try this
String text = email.getText().toString();
if(text.contains("#") && !text.contains("#coretec.co.ke")){
String e = email.getText().toString();
email.setText(e+"coretec.co.ke");
}
I'm build the Fun Facts app on the Android Development Track. I decided to take a exploratory detour and try to create a very basic introductory message to the user. I changed the factTextView text to "You can click the button below to see a new fact!" and changed the showFactButton text to "Try it out!"
From there, I changed the final line onClick object (is that an object?) to the following:
public void onClick(View view) {
String fact = mFactBook.getFact();
// Update the label with our dynamic fact
factLabel.setText(fact);
// Set button text to new fact prompt
showFactButton.setText("Show another fun fact.");
This seems to work fine. However, I feel like "updating" the button text to the same new string on every press isn't always the best practice, even if it is easy and readable. I tried to add a boolean that will check the text of the button, and update it only if it has not already been updated. This is what I've come up with so far:
View.OnClickListener listener = new View.OnClickListener() {
#Override
public String launchText = getResources().getString(R.string.start_text);
public String nextText = getResources().getString(R.string.next_text);
public String buttonText = (String) showFactButton.getText();
public boolean updateLaunchText() {
if (buttonText.equals(launchText)) {
buttonText.replaceAll(launchText, nextText);
return true;
}
else {
return true;
}
}
public void onClick(View view) {
String fact = mFactBook.getFact();
// Update the label with our dynamic fact
factLabel.setText(fact);
}
};
With the following added to strings.xml:
<string name="start_text">Try it out!</string>
<string name="next_text">Show another Fun Fact!</string>
No errors, but the button text stays on "Try it out!" I'm sure that all the extra objects are totally unnecessary compared to the first, working method for the scope of this app, but I'd still like to figure it out since I don't really have any idea what I'm doing with the boolean.
Questions: 1) What am I missing in the longer boolean approach? 2) What's the actual most efficient approach to accomplish this task?
Did you connect the listener to the button object?Without that connection no logic is applied to a button click.It goes like this:
buttonName.setOnClickListener(...)
You'd have to initialize the button object first though :)
Where r u call to method updateLaunchText() ?
you should change the objects to global object (not to create the into the listener):
private String launchText = getResources().getString(R.string.start_text);
private String nextText = getResources().getString(R.string.next_text);
private String buttonText = (String) showFactButton.getText();
and take the method updateLaunchText() out of the listener too.
and then into the onClick(View view) call to updateLaunchText() like this:
public void onClick(View view) {
updateLaunchText();
String fact = mFactBook.getFact();
// Update the label with our dynamic fact
factLabel.setText(fact);
}
I'm writing custom EditTextPreference.
Using this code inside my CustomEditTextPreference:
#Override
protected void onDialogClosed(boolean shouldSave) {
if (shouldSave) {
String sValue = getText();
value = Float.parseFloat(sValue);
peristValue();
}
}
sValue is null. How do I acquire the value from edit then?
You should probably use
getEditText().getText().toString();
Since getText() by itself gets the current SharedPreference value, which may or may not exist.