Custom object two-way databinding android:text - android

I've created myself some simple class for validating fields through two-way databinding.
public class ValidatedField extends BaseObservable{
private String mValue;
#Bindable
public String getValue()
{
return mValue;
}
public void setValue(String value)
{
if(Objects.equals(value, mValue)) return;
mValue = value;
notifyPropertyChanged(BR.value);
}
...
}
In ViewModel I setup this class and bind it to view as usual in databinding
(all binding actually works so no error here).
public ValidatedField phoneNumber = new ValidatedField();
In layout, I have view with android:text property and I setup:
<EditText
android:text="#={viewModel.phoneNumber.value}"
/>
And everything works like a charm.
And my question is: is it possible to skip '.value' from layout so that it looks like this:
<EditText
android:text="#={viewModel.phoneNumber}"
/>
I could make it work if it was one way binding e.g. through binding conversion like this:
#BindingConversion
public static String convertValidatedFieldToString(ValidatedField field){
return field.getValue();
}
But I'm not able to set new value to existing ValidatedField.
I've tried to use #InverseBindingAdapter but that's without luck because it would create every time new object and not just updated value of existing one.
#InverseBindingAdapter(attribute = "android:text")
public static ValidatedField convertStringToValidatedField(TextView view)
{
return new ValidatedField(view.getText().toString());
}
Thank you!
Edit:
I should've said that I have other #Bindable fields in the class. e.g:
#Bindable
public boolean getIsError()
{
return mIsError;
}

It seems to me that you could've just extend ObservableField<String> and then override its set method.
so like:
public class ValidatedField extends ObservableField<String> {
#Override
public void set(String value) {
if (get().equals(value))
return;
super.set(value);
}
}

Databindings is lacking an InverseBindingConversion. At the moment it's not working like you intent to use it.
So you have to write #={viewModel.phoneNumber.value} anytime.

Related

ViewModel does not get updated in the layout when setters are called from method other than onCreate()

I have a class named MyInfoModel that has some instance variables in it to store data about the user and a method to notify the observers of the class.
private final List<ObserverInterface> observers = new ArrayList<>();
private int id;
private String name;
private String imageURL;
// Getters, Setters, Register and Unregister Methods for Instance Variables
// Also All Setter Methods call the notifyObservers() method after setting the values
private void notifyObservers(){
Log.d(this.getClass().getName(), "Notifying All Observers");
for (ObserverInterface observer : observers){
observer.updateViews();
}
}
I have an interface ObserverInterface that has only one method void updateViews(); which is overridden by other classes that implement this interface.
I am using Retrofit to request some data which gets updated in a static object in the Constants class.
public class Constants {
public static MyInfoModel INFO_CONSTANTS = new MyInfoModel();
}
Also, I have a View Model MainActivityViewModel that extends ViewModel class and has variables, getters and setters to support the layout of the activity.
private int id;
private String name;
private String imageURL;
// Getters and Setters
I have used DataBinding in my layout like this
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.itsyourap.app.viewModel.MainActivityViewModel" />
</data>
<TextView <!-- Layout Tags -->
android:text="#{viewModel.Name}" />
Then I have the MainActivity where I want to wait for any change in the values of variables of Constants.INFO_CONSTANTS, so I implement the ObserverInterface interface and Override the updateViews() method like this:-
public class MainActivity extends AppCompatActivity implements ObserverInterface {
private MainViewModel mainViewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding mainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
mainViewModel = new ViewModelProvider(this).get(MainActivityViewModel.class);
mainBinding.setViewModel(mainViewModel);
mainBinding.getViewModel().setName("Alpha Beta Gamma"); // Works
mainBinding.getViewModel().setImageURL("somelink.jpg"); // Works
}
public MainActivity(){
Constants.INFO_CONSTANTS.register(this);
}
#Override
public void updateViews() {
if (mainViewModel != null){
Log.d(this.getClass().getName(), "Updating Views");
mainViewModel.setName(Constants.INFO_CONSTANTS.getName()); // Does Not Work
mainViewModel.setImageURL(Constants.INFO_CONSTANTS.getImageURL()); // Does Not Work
}
}
}
The problem I am facing that the updateViews() is called successfully (according to LogCat) but the Layout isn't updated at the runtime. I have tried using LiveData and MutableLiveData but they do not seem to work as well. Also I tried using mainBinding.setLifecycleOwner(this); but it didn't work as well.
Also calling the setters from the onCreate() method works perfectly but does not work from the updateViews() method.
How should I make it work? I just want the values in the layout to change as soon as the values in the ViewModel gets changed.
Thanks in Advance
Calling setViewModel() after modifying the variables solves the issue
#Override
public void updateViews() {
if (mainViewModel != null){
Log.d(this.getClass().getName(), "Updating Views");
mainViewModel.setName(Constants.INFO_CONSTANTS.getName());
mainViewModel.setImageURL(Constants.INFO_CONSTANTS.getImageURL());
mainBinding.setViewModel(mainViewModel); // Solves The Problem
}
}

How to parse an XML file and create objects using found values?

I would like to parse an XML file using Java. I found some tutorials online but no one tells about parsing subtags and using them as objects' attributes.
I tried to use the code found here.
But it doesn't show how to treat tags that are inside other tags. Let me show you an example:
<lotto>
<cig>Z9E1CD9F58</cig>
<strutturaProponente>
<codiceFiscaleProp>00222010654</codiceFiscaleProp>
<denominazione>COMUNE DI PERDIFUMO</denominazione>
</strutturaProponente>
</lotto>
lotto is my main tag, which contains all the data I need. In my code, I created a class called in the same way. Its attributes are the same as the tags contained in the main one (cig, strutturaProponente,...).
I would like strutturaProponente to become a class. I don't know how to parse tags which are inside of tag strutturaProponente as well as the tag cig.
Thank you for your patience and consideration.
Well, let me see if I understood. Would you like the representantion in code of XML, I believe that class would be like something this.
public class Loto
{
private String cig;
public String getCig()
{
return cig;
}
public void setCig(String value){
cig = value;
}
}
public class StrutturaProponente
{
private int codiceFiscaleProp;
private string denominazione;
public int getCodiceFiscaleProp()
{
return cig;
}
public void setCodiceFiscaleProp(int value){
codiceFiscaleProp = value;
}
public String getDenominazione()
{
return denominazione;
}
public void setDenominazione(String value){
denominazione = value;
}
}
I hope have helped.

Two-way databinding(in xml), ObservableField, BaseObservable , which one I should use for the two-way databinding?

I have used data-binding for a while, even now it is not available for JDK 8 and API 24 now. I still find a way to use the data binding in a easier way. But when I use the following way to do the exact two-way data binding(In my mind, the two-way data binding is the thing like here(What is two way binding?), somethings strange is happened.
1. Two-way databinding(in xml)
android:text="#={testStr}"
This is not mentioned in the official documentation(https://developer.android.com/topic/libraries/data-binding/index.html, this page is usually updated, may be it is changed now). But it is available to bind the variable to the xml.
2. ObservableField for the attributes
Example from here (https://developer.android.com/topic/libraries/data-binding/index.html#observablefields)
private static class User {
public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public final ObservableInt age = new ObservableInt();
}
3. Extend the model class to the BaseObservable
private static class User extends BaseObservable {
private String firstName;
private String lastName;
#Bindable
public String getFirstName() {
return this.firstName;
}
#Bindable
public String getLastName() {
return this.lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
public void setLastName(String lastName) {
this.lastName = lastName;
notifyPropertyChanged(BR.lastName);
}
}
The model class must be extended to the BaseObservable class, and also the getter method must be annotated with "#Bindable" and the setter method need to call the method notifyPropertyChange() with corresponding naming in the binding xml.
My question is, I would like to know the drawback and the advantages for three binding methods. Of course, I know the first one will be easier. But some moment I found in the documentation and in some website. And it disappeared in the next moment. The official documentation is changed without any clear announcement. I still wonder should I use the first method so I have to prepare to change the the method 2 or 3.
Student_XML2WAY.java
public class Student_XML2WAY {
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int pAge) {
age = pAge;
}
public String getName() {
return name;
}
public void setName(String pName) {
name = pName;
}
}
Student_ObserField.java
public class Student_ObserField {
private ObservableInt age;
private ObservableField<String> name;
public Student_ObserField() {
age = new ObservableInt();
name = new ObservableField<>();
}
public ObservableInt getAge() {
return age;
}
public ObservableField<String> getName() {
return name;
}
}
Student_Extend.java
public class Student_Extend extends BaseObservable{
private int age;
private String name;
#Bindable
public int getAge() {
return age;
}
public void setAge(int pAge) {
age = pAge;
notifyPropertyChanged(BR.student3);
}
#Bindable
public String getName() {
return name;
}
public void setName(String pName) {
name = pName;
notifyPropertyChanged(BR.student3);
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="student1"
type="example.com.testerapplication.sp.bean.Student_XML2WAY"/>
<variable
name="student2"
type="example.com.testerapplication.sp.bean.Student_ObserField"/>
<variable
name="student3"
type="example.com.testerapplication.sp.bean.Student_Extend"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#={student1.name}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#{student2.name}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#{student3.name}"/>
<Button
android:id="#+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="update"/>
</LinearLayout>
</layout>
Activity class
public class MainActivity extends AppCompatActivity {
private Student_XML2WAY mStudent1;
private Student_ObserField mStudent2;
private Student_Extend mStudent3;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = DataBindingUtil.inflate(LayoutInflater.from(this), R.layout.activity_main, null, false);
mStudent1 = new Student_XML2WAY();
mStudent1.setName("XML First");
mStudent2 = new Student_ObserField();
mStudent2.getName().set("ObserField Second");
mStudent3 = new Student_Extend();
mStudent3.setName("Extend Third");
binding.setStudent1(mStudent1);
binding.setStudent2(mStudent2);
binding.setStudent3(mStudent3);
setContentView(binding.getRoot());
binding.btn1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mStudent1.setName("Student1");
mStudent2.getName().set("Student2");
mStudent3.setName("Student3");
}
});
}
}
Your Student_XML2WAY.java won't work with 2-way Binding, since it does not fulfill the requirements to do so (BaseObservable, Bindable or something like that).
I would use BaseObservable if I will directly access the model, just like in your Student_Extend. I will have an instance of Student_Extend in my Activity and I will set the variable in onCreate:
Student mStudent = new Student("John Doe", 42); //
binding.setStudent(mStudent);
//later:
mStudent.setAge(37);
If implemented correctly, this will also change the Age in your UI (as well as in your model).
If you do not want to access your model directly and want to use a ViewModel, I work with ObervableFields:
public class Student {
private String name;
private int age;
//Corresponding setters and getters
}
public class StudentViewModel {
private ObservableField<Student> mStudentField = new ObservableField<>();
//if I have a large model class, and only want to use some fields,
//I create some getters (and setters, for the two way attributes)
//Something like this:
public int getAge() {
return mStudentField.get().getAge();
}
public void setAge(int newAge) {
return mStudentField.get().setAge(newAge);
}
}
So, I create an instance of StudentViewModel in my Activity and set it to the binding. Pseudo-xml would look like this:
<layout>
<data>
<variable name="studentViewModel"
type="locaction.of.StudentViewModel"> <!-- or do an import -->
</data>
<EditText
android:text="#={studentViewModel.age}"/>
</layout>
So, the ViewModel approach is "clearer" since you outsource almost everything that has to do with views. Put your BindingAdapter, click methods, converter methods there and keep your Activity clean. Also, you do not directly change your model.
This approach can be an overkill for simple classes and projects. ;)
If you want to see a full, example that uses DataBinding and MVVM, check out Droids on roids approach on this.
I feel that ObservableField approach is the way to go as there's no need to write getters/setters OR invoke notifyPropertyChanged.
Also, if you have a custom object ObservableField<Student> studentField, and you use android:text="#{viewModel.studentField.name}, the text does get updated when you invoke studentField.set(newStudent).
I find RxJava very useful. ObservableField can be easily converted to rx.Observable and vice versa. This allows use of Rx operators. In case you are interested, you can check the implementation here: FieldUtils.java

how to pass, from a class to another, the name of a variable (NOT the value)

I need to pass name of a variable created in Class A to the Class B, so I can put a value in that variable (in Class B).
But, in Class B I do not know the name of that variable.
The code is something like this:
Class A
public class A {
int valore; // this is the variable, in Class b, I don't know this name!
public void callClassB(){
ClassB.Method(what shoudld i put here?)
}
}
This is the Class B
public class B {
public void Method(the_Name_Of_TheVariable_I_get){
the_Name_Of_TheVariable_I_get = 5; // i need to do this
}
}
Why do you need the variable name? Simply pass the variable itself. In class B create a method
public int getValore(){
return valore;
}
Then in Class A use modify the code as
public void callClassB(){
ClassB.Method(getValore())
}
I do not really understand what you are trying to achieve here?
You can also use the following appraoch:
interface ValueSetter {
void setValue(int value);
}
Class A
public class A implements ValueSetter{
int valore;
public void callClassB(){
ClassB.Method(this)
}
void setValue(int value){
valore = value;
}
}
This is the class B
public class B{
public void Method(ValueSetter valueSetter){
ValueSetter.setValue(5);
}
}
This is more inline with OOPS..
You will need to use reflection for this.
Here is a tutorial from Oracle: http://docs.oracle.com/javase/tutorial/reflect/index.html
You cant get the name of variable at runtime though. But assuming you have the name of the field the code would look something like this:
this.getClass().getDeclaredField(the_Name_Of_TheVariable_I_get).set(this, 5);
you can pass the name of the variable "valore", then you need reflection to assign it in your method :
a = new A();
Field f = a.getClass().getDeclaredField(varName);
f.set(a, 5);
a can be a parameter too. (it is necessary to give the instance that possesses the member).
However, this is not a recommended way of treating your issue, as it is unreliable in the sense that the compiler will not be able to check you are accessing items that actually exist.
It would be better to use an interface, for instance :
public interface Settable {
public void set(int value);
}
and then:
public class A implements Settable {
private int valore;
public void set(int value) {
valore = value;
}
public void callClassB(){
ClassB.Method(this);
}
}
and in B:
public class B{
public void Method(Settable settable){
settable.set(5);
}
}

Android - Global variables?

I need to stock some datas in my application.
I know that i can do it like this:
class:
public class MyApplication extends Application {
private String someVariable;
public String getSomeVariable() {
return someVariable;
}
public void setSomeVariable(String someVariable) {
this.someVariable = someVariable;
}
}
Implementation:
MyApp appState = ((MyApp)getApplicationContext());
String state = appState.getSomeVariable();
This is working if i'm in an activity.
But if i'm in a class not extended from Activity, how can I access at my datas?
thanks in advance for your help!
You can use a Singleton design pattern. You can then use it anywhere, because it has static access.
public class SingletonClass {
private static SingletonClass _instance = null;
private int _value = 0;
private SingletonClass() {
}
public static SingletonClass getInstance() {
if (_instance == null)
_instance = new SingletonClass();
return _instance;
}
public int getValue() {
return _value;
}
public void setValue(int value) {
_value = value;
}
}
and then access it like this:
SingletonClass.getInstance().getValue();
Note: This is a good and easy workaround for some programming problems, but use it very wisely.. it comes with it's problems
Use SharedPrefrences
http://developer.android.com/guide/topics/data/data-storage.html
Perhaps by injecting all the required for a class data via constructor or special setter, I would suggest former one. (Constructor Injection vs. Setter Injection)
There are more solutions like static fields but personally I do not like this approach since statics sometimes makes unit testing a bit messy.
BTW, what kind of variables you want to share?
I use, it may be gruesome to some, a class with static variables, that you can retrieve from every class in the app.
Just create a class with all the field as static, and you can use them throughout your app. It doesn't get erased, only when stopping the app.
You could also just add static variables to your application class.
You can use static methods (or variables if they are public). It's really a little messy, but if you group them (methods) in the right way you'll earn happinnes and satisfaction )
static public int getSomeInt(){
//something
}
And then anywhere in your app use
int x=MyApplication.getSomeInt();
By the way, using this style, you don't need to extend Application class. It's better to create an abstract class for such purposes.
Pass the context of your activity as a param to the method or class:
// ...
public void doStuff(Context context) {
// for example, to retrieve and EditText
EditText et = context.findViewById(R.id.editText1);
}
then, on your activity, you would do:
// ...
MyClass myClass = new MyClass();
// ...
myClass.doStuff(this);
// ...

Categories

Resources