How to update UI when data changes in data-binding android? - android

I have an activity that shows one text-view in that:
<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="Users"
type="com.example.mvvm.model.UserInfoModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.MainActivity">
<TextView
android:id="#+id/tv_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="50dp"
android:text="#{Users.name,default = hello}"/>
</LinearLayout>
</layout>
And my UserInfoModel class:
public class UserInfoModel {
private String name;
public UserInfoModel() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
And in my Activity:
public class MainActivity extends AppCompatActivity {
ActivityMainBinding binding;
UserInfoModel users;
TextView textView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
textView = binding.tvMain;
users = new UserInfoModel();
users.setName("Stack");
viewAction();
binding.setLifecycleOwner(this);
binding.setUsers(users);
}
}
My Problem is, When I Change name in viewAction method like this:
private void viewAction() {
textView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
users.setName("Change Text");
}
});
}
My UI doesn't change and text-view still showing Stack. I try to use binding.notifyPropertyChanged(id) but it doesn't affect. Where is my mistake? How can I change the data?
thanks for your attention.

You can just make your fields observable in the models.
public class UserInfoModel {
private ObservableField<String> name;
public UserInfoModel() {
this.name = new ObservableField<>();
}
public ObservableField<String> getName() {
return name;
}
public void setName(String name) {
this.name.set(name);
}
}

Actually the problem is that generated binding class doesn't know when property is changed. You should notify it. First, your viewmodel can be inherited from BaseObservable class, then you have to add #Bindable annotation to getters in your viewmodel and call notifyPropertyChanged inside setters with appropriate id from BR class(it's going to be generated for you by databinding library, just add #Bindable annotation. for instance notifyPropertyChanged(BR.name) ). Also you should pay attention how you store "binding" variable in MainActivity class.It can lead to memory leaks. You can investigate that issue. See library like databindingPropertyDelegate.
Something like this
public class UserInfoModel extends BaseObservable{
#Bindable
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name)
}
}

Related

How to make the changes made to the model effective with DataBinding after setModel?

DataBinding: how can I make sure that as a result of a modification of the data model the view is updated accordingly?
Eg:
public class MyActivity extends AppCompatActivity {
private MyActivityBinding mBinding;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.my_activity);
mBinding.setMyModel(new MyModel());
}
public void onClickAnItem(View view) {
MyModel model = mBinding.getMyModel();
model.setField1 = "Jhon";
model.setField2 = "Dho";
mBinding.executePendingBindings();
}
}
In this case the model "MyModel" has been modified but view is not updated; what did I miss?
Reading documentation I found a solution, first of all:
Any plain old Java object (POJO) may be used for data binding, but modifying a POJO will not cause the UI to update!
To give MyModel data object the ability to notify when data changes I made this modifications:
private class MyModel extends BaseObservable {
private String field1;
private String field2;
#Bindable
public String getField1() {
return this.field1;
}
#Bindable
public String getField2() {
return this.field2;
}
public void setField1(String firstName) {
this.field1 = firstName;
notifyPropertyChanged(BR.field1);
}
public void setField2(String lastName) {
this.field2 = lastName;
notifyPropertyChanged(BR.field2);
}
}
I hope this can help someone else
Documentation here

Firebase getter methods and naming convention

I am trying my hands on Firebase for the first time and I ran into kind of a problem.
Getting data out of my Firebase storage/database only works if the getter method fits the variable name or the member variables are public. But my naming convention for member variables is mVariableName and i leave that "m" out of my getter methods name. Now I have multiple questions:
Is making the model member variables public a viable option or is that bad practice?
What is the best approach here for naming? Should i name the getter methods getmName or should i leave the "m" out of the member variable names? Should I then change it for the whole project or just for this class?
I just want to know what the best practices are here.
This is the class that reads the entries:
public class ImagesActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private ImageAdapter mAdapter;
private FirebaseStorage mFirebaseStorage;
private DatabaseReference mDatabase;
private List<Upload> mUploads;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_show_images);
mRecyclerView = findViewById(R.id.recycler_view);
mRecyclerView.setHasFixedSize(true);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mUploads = new ArrayList<>();
mFirebaseStorage = FirebaseStorage.getInstance();
mDatabase = FirebaseDatabase.getInstance().getReference(Constants.DATABASE_PATH_UPLOADS);
mDatabase.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
for (DataSnapshot postSnapshot : dataSnapshot.getChildren()) {
Upload upload = postSnapshot.getValue(Upload.class);
Log.i("UPLOAD", "Upload : " + upload.getName());
mUploads.add(upload);
}
mAdapter = new ImageAdapter(getApplicationContext(), mUploads);
mRecyclerView.setAdapter(mAdapter);
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
}
}
And these are the rules:
Database:
{
"rules": {
".read": true,
".write": true
}
}
Storage:
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if true;
}
}
}
And the Upload.class (only works if either fields are public or getter method names fit m-convention, which is ugly):
public class Upload {
public String mName;
public String mImageUrl;
public Upload() {
}
public Upload(String name, String imageUrl) {
if (name.trim().equals("")) {
name = "No Name";
}
mName = name;
mImageUrl = imageUrl;
}
public String getName() {
return mName;
}
public String getImageUrl() {
return mImageUrl;
}
}
The best practice is to use a standard POJO, or Plain Old Java Object. If you do that, Firebase will get out of your way:
public final class User {
// These names don't matter since they are private, you could call it `glubustufo` šŸ˜
// They should always be private
private String mName;
private String mEmail;
// ...
// Constructors
// Methods should be public and use the get/set convention where the following
// words are in CamelCase and will be translated to lowerCamelCase in the db.
public String getName() { return mName; }
public void setName(String name) { mName = name; }
public String getEmail() { return mEmail; }
public void setEmail(String email) { mEmail = email; }
// equals, hashCode, toString
}
Edit, use this class:
public final class Upload {
private String mName;
private String mImageUrl;
public Upload() {
// Needed for Firebase reflection
}
public Upload(String name, String imageUrl) {
if (name.trim().equals("")) {
name = "No Name";
}
mName = name;
mImageUrl = imageUrl;
}
public String getName() {
return mName;
}
public void setName(String name) {
mName = name;
}
public String getImageUrl() {
return mImageUrl;
}
public void setImageUrl(String url) {
mImageUrl = url;
}
}
As an alternative to Supercilex' excellent answer, you can also use a class with only public fields (and not getters/setters):
public final class Upload {
public String name;
public String imageUrl;
}
In this situation Firebase will look for (or create) a JSON property that exactly matches the field name, so make sure you capitalize it correctly.
The Firebase client creates an instance of this class by looking for a parameterless constructor. In this trivial class there is no constructor, so the Java/Android compiler will generate a default, parameterless constructor for you. If you add you own constructor, be sure to also add a parameterless one (as in Supercilex' answer).
See also:
How to Convert Firebase data to Java Object...? for an overview of the options when reading/writing the database from Java.

It's possible to addOnPropertyChangedCallback on a #Bindable?

I would like to observe a #Bindable via Java, is it possible?
I read that I can observe a ObservableField on this answer:
https://stackoverflow.com/a/31885802/858257
But sometimes you need the primitive field and the best approach is using a #Bindable.
Sure you can. If you have a field marked with #Bindable and implement Observable, you can listen for changes to that field. Any bindable field must notify when changed. For example:
public class Item extends BaseObservable {
private String name;
private int stockCount;
#Bindable
public String getName() { return name; }
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
#Bindable
public int getStockCount() { return stockCount; }
public void setStockCount(int stockCount) {
this.stockCount = stockCount;
notifyPropertyChanged(BR.stockCount);
}
}
You can then listen for changes on this object. I used BaseObservable as the base class for this data class because it implements the observability for me.
public void listenForStockChange(Item item) {
item.addOnPropertyChangedCallback(new OnPropertyChangedCallback() {
#Override
public void onPropertyChanged(Observable sender, int propertyId) {
if (propertyId == BR.stockCount) {
Item item = (Item) sender;
// Do whatever you want when the stock changes
}
}
});
}

No setter/field for found Android Firebase

I used FirebaseRecyclerAdapter to get all the childs of "Pro" using a Model class named " Spacecraft" and now I want to retrieve all the candidates into a child of Pro like "1"
I created a public static "candidat" into "Spacecraft" and I used the setters and getters but still the same error
This is my database:
this is the Model Class
public class Spacecraft{
private String name;
private String desc;
private String last;
private candidat candidat;
public Spacecraft.candidat getCandidat() {
return candidat;
}
public void setCandidat(Spacecraft.candidat candidat) {
this.candidat = candidat;
}
public Spacecraft() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public String getLast() {
return last;
}
public void setLast(String last) {
this.last = last;
}
public static class candidat{
private String info;
private String namecandid;
public candidat(){}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
public String getNamecandid() {
return namecandid;
}
public void setNamecandid(String namecandid) {
this.namecandid = namecandid;
}
}
}
This is my code for FirebaseRecyclerAdapter
#Override
protected void onStart() {
super.onStart();
FirebaseRecyclerAdapter<Spacecraft, candidatviewholder> firebaseRecyclerAdapter = new FirebaseRecyclerAdapter<Spacecraft, candidatviewholder>(
Spacecraft.class,
R.layout.candidat,
candidatviewholder.class,
query){
#Override
protected void populateViewHolder(candidatviewholder viewHolder, Spacecraft model, int position) {
viewHolder.setName1(model.getCandidat().getNamecandid());
viewHolder.setInfo1(model.getCandidat().getInfo());
}
};
rv.setAdapter(firebaseRecyclerAdapter);
}
The error:
No setter/field for key1 found on class com.example.ilyas.evotingapplication.Spacecraft$candidat
I had this error but the above solutions didn't fix it. Hopefully, this alternate solution will help others. If you have that error occur for almost every variable, chances are that you have Proguard enabled and it is removing the un-used getter and setter methods. To fix this, add a line similar to this to your proguard-rules.pro file:
-keep class com.example.yourapp.ObjectClass
where ObjectClass is the name of your java object class that is stored to Firebase.
I think it's just that your data models on Firebase and in Java differ.
In your java class, the Spacecraft class has a candidat field of type Candidat. But, in the database, the candidat field is really a nested object (map), containing one key Key1, which value is a Candidat structure.
So, depending on what did you want to achieve:
if you wanted each spacecraft to have exactly one candidat: save the database object properly, so {info: "info 1", namecandid: "name 1"} is saved directly under candidat field, not one level deeper, so the field has type Candidat in the code.
if you wanted each spacecraft to have a few candidats: instead of private Candidat candidat field, it should be typed Map<String, Candidat>, because that's the type it has in your database screenshot.
Work for me:
-keepclassmembers class com.myPackageName.MyClassName { *; }

Create two-way binding with Android Data Binding

I have implemented the new Android data-binding, and after implementing realised that it does not support two-way binding. I have tried to solve this manually but I am struggling to find a good solution to use when binding to an EditText.
In my layout I have this view:
<EditText
android:id="#+id/firstname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textCapWords|textNoSuggestions"
android:text="#{statement.firstName}"/>
Another view is also showing the results:
<TextView
style="#style/Text.Large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{statement.firstName}"/>
In my fragment I create the binding like this:
FragmentStatementPersonaliaBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_statement_personalia, container, false);
binding.setStatement(mCurrentStatement);
This works and puts the current value of firstName in the EditText. The problem is how to update the model when the text changes. I tried putting an OnTextChanged-listener on the editText and updating the model. This created a loop killing my app (model-update updates the GUI, which calls textChanged times infinity). Next I tried to only notify when real changes occured like this:
#Bindable
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
boolean changed = !TextUtils.equals(this.firstName, firstName);
this.firstName = firstName;
if(changed) {
notifyPropertyChanged(BR.firstName);
}
}
This worked better, but everytime I write a letter, the GUI is updated and for som reason the edit-cursor is moved to the front.
Any suggestions would be welcome
EDIT 04.05.16:
Android Data binding now supports two way-binding automatically!
Simply replace:
android:text="#{viewModel.address}"
with:
android:text="#={viewModel.address}"
in an EditText for instance and you get two-way binding. Make sure you update to the latest version of Android Studio/gradle/build-tools to enable this.
(PREVIOUS ANSWER):
I tried Bhavdip Pathar's solution, but this failed to update other views I had bound to the same variable. I solved this a different way, by creating my own EditText:
public class BindableEditText extends EditText{
public BindableEditText(Context context) {
super(context);
}
public BindableEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BindableEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private boolean isInititalized = false;
#Override
public void setText(CharSequence text, BufferType type) {
//Initialization
if(!isInititalized){
super.setText(text, type);
if(type == BufferType.EDITABLE){
isInititalized = true;
}
return;
}
//No change
if(TextUtils.equals(getText(), text)){
return;
}
//Change
int prevCaretPosition = getSelectionEnd();
super.setText(text, type);
setSelection(prevCaretPosition);
}}
With this solution you can update the model any way you want (TextWatcher, OnTextChangedListener etc), and it takes care of the infinite update loop for you. With this solution the model-setter can be implemented simply as:
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
This puts less code in the model-class (you can keep the listeners in your Fragment).
I would appreciate any comments, improvements or other/better solutions to my problem
This is now supported in Android Studio 2.1+ when using the gradle plugin 2.1+
Simply change the EditText's text attribute from #{} to #={} like this:
<EditText
android:id="#+id/firstname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textCapWords|textNoSuggestions"
android:text="#={statement.firstName}"/>
for more info, see: https://halfthought.wordpress.com/2016/03/23/2-way-data-binding-on-android/
#Gober The android data-binding support the two way binding. Therefore you do not need to make it manually. As you tried by putting the OnTextChanged-listener on the editText. It should update the model.
I tried putting an OnTextChanged-listener on the editText and updating
the model. This created a loop killing my app (model-update updates
the GUI, which calls textChanged times infinity).
Itā€™s worth noting that binding frameworks that implement two-way binding would normally do this check for youā€¦
Hereā€™s the example of modified view model, which does not raise a data binding notification if the change originated in the watcher:
Letā€™s create a SimpleTextWatcher that only requires only one method to be overridden:
public abstract class SimpleTextWatcher implements TextWatcher {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void afterTextChanged(Editable s) {
onTextChanged(s.toString());
}
public abstract void onTextChanged(String newValue);
}
Next, in the view model we can create a method that exposes the watcher. The watcher will be configured to pass the changed value of the control to the view model:
#Bindable
public TextWatcher getOnUsernameChanged() {
return new SimpleTextWatcher() {
#Override
public void onTextChanged(String newValue) {
setUsername(newValue);
}
};
}
Finally, in the view we can bind the watcher to the EditText using addTextChangeListener:
<!-- most attributes removed -->
<EditText
android:id="#+id/input_username"
android:addTextChangedListener="#{viewModel.onUsernameChanged}"/>
Here is the implementation of the view Model that resolve the notification infinity.
public class LoginViewModel extends BaseObservable {
private String username;
private String password;
private boolean isInNotification = false;
private Command loginCommand;
public LoginViewModel(){
loginCommand = new Command() {
#Override
public void onExecute() {
Log.d("db", String.format("username=%s;password=%s", username, password));
}
};
}
#Bindable
public String getUsername() {
return this.username;
}
#Bindable
public String getPassword() {
return this.password;
}
public Command getLoginCommand() { return loginCommand; }
public void setUsername(String username) {
this.username = username;
if (!isInNotification)
notifyPropertyChanged(com.petermajor.databinding.BR.username);
}
public void setPassword(String password) {
this.password = password;
if (!isInNotification)
notifyPropertyChanged(com.petermajor.databinding.BR.password);
}
#Bindable
public TextWatcher getOnUsernameChanged() {
return new SimpleTextWatcher() {
#Override
public void onTextChanged(String newValue) {
isInNotification = true;
setUsername(newValue);
isInNotification = false;
}
};
}
#Bindable
public TextWatcher getOnPasswordChanged() {
return new SimpleTextWatcher() {
#Override
public void onTextChanged(String newValue) {
isInNotification = true;
setPassword(newValue);
isInNotification = false;
}
};
}
}
I hope this is what you are looking and sure can help you. Thanks
There is a simpler solution. Just avoid updating field if it hadn't really changed.
#Bindable
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
if(this.firstName.equals(firstName))
return;
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
POJO:
public class User {
public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public User(String firstName, String lastName) {
this.firstName.set(firstName);
this.lastName.set(lastName);
}
public TextWatcherAdapter firstNameWatcher = new TextWatcherAdapter(firstName);
public TextWatcherAdapter lastNameWatcher = new TextWatcherAdapter(lastName);
}
Layout:
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{user.firstName, default=First_NAME}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{user.lastName, default=LAST_NAME}"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/editFirstName"
android:text="#{user.firstNameWatcher.value}"
android:addTextChangedListener="#{user.firstNameWatcher}"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/editLastName"
android:text="#{user.lastNameWatcher.value}"
android:addTextChangedListener="#{user.lastNameWatcher}"/>
Watcher:
public class TextWatcherAdapter implements TextWatcher {
public final ObservableField<String> value =
new ObservableField<>();
private final ObservableField<String> field;
private boolean isInEditMode = false;
public TextWatcherAdapter(ObservableField<String> f) {
this.field = f;
field.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback(){
#Override
public void onPropertyChanged(Observable sender, int propertyId) {
if (isInEditMode){
return;
}
value.set(field.get());
}
});
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
//
}
#Override public void afterTextChanged(Editable s) {
if (!Objects.equals(field.get(), s.toString())) {
isInEditMode = true;
field.set(s.toString());
isInEditMode = false;
}
}
}
I struggled to find a full example of 2-way databinding. I hope this helps.
The full documentation is here:
https://developer.android.com/topic/libraries/data-binding/index.html
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="item"
type="com.example.abc.twowaydatabinding.Item" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#={item.name}"
android:textSize="20sp" />
<Switch
android:id="#+id/switch_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="#={item.checked}" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="change"
android:onClick="button_onClick"/>
</LinearLayout>
</layout>
Item.java:
import android.databinding.BaseObservable;
import android.databinding.Bindable;
public class Item extends BaseObservable {
private String name;
private Boolean checked;
#Bindable
public String getName() {
return this.name;
}
#Bindable
public Boolean getChecked() {
return this.checked;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
public void setChecked(Boolean checked) {
this.checked = checked;
notifyPropertyChanged(BR.checked);
}
}
MainActivity.java:
public class MainActivity extends AppCompatActivity {
public Item item;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
item = new Item();
item.setChecked(true);
item.setName("a");
/* By default, a Binding class will be generated based on the name of the layout file,
converting it to Pascal case and suffixing ā€œBindingā€ to it.
The above layout file was activity_main.xml so the generate class was ActivityMainBinding */
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setItem(item);
}
public void button_onClick(View v) {
item.setChecked(!item.getChecked());
item.setName(item.getName() + "a");
}
}
build.gradle:
android {
...
dataBinding{
enabled=true
}
}

Categories

Resources