I'm trying to use Android Databinding in my Android app. I've an Object Outlet
if I initialize the object without any data. Everything works fine. And all data are well collected. But, if I initialize the Object
with data and passing it to the layout, it crashes the app with complains of Invoking Interface method on Null Views
. This is my Layout file below:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="java.util.List"/>
<import type="android.view.View" />
<variable
name="outlet"
type="co.deliveryscience.dangote.Network.model.Outlet.Create.Outlet"/>
</data>
<android.support.v4.widget.NestedScrollView
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:layout_marginLeft="64dp"
android:layout_marginRight="64dp"
android:layout_marginTop="32dp"
android:background="#FFF"
android:animateLayoutChanges="true"
app:cardCornerRadius="4dp"
app:elevation="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="16dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="16dp"
android:animateLayoutChanges="true">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/HeaderText"
android:text="@string/personal_information"
android:layout_marginBottom="8dp"
android:layout_marginTop="16dp"/>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@color/gray_semi_transparent"
android:layout_marginBottom="32dp"/>
<LinearLayout
android:id="@+id/layoutCreate"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_marginBottom="32dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/name_of_outlet"
android:layout_marginBottom="8dp"/>
<EditText
android:id="@+id/nameOfOutlet"
android:layout_width="match_parent"
android:layout_height="55dp"
android:inputType="textCapWords"
android:singleLine="true"
android:imeOptions="actionNext"
android:text='@{outlet.name ?? ""}'
android:addTextChangedListener="@{outlet.onNameChanged}"
android:nextFocusDown="@+id/address"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:baselineAligned="false">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginRight="8dp"
android:layout_marginEnd="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ownerfirstname"
android:layout_marginBottom="8dp"/>
<EditText
android:id="@+id/fName"
android:layout_width="match_parent"
android:layout_height="55dp"
android:layout_marginBottom="16dp"
android:inputType="textCapWords"
android:singleLine="true"
android:imeOptions="actionNext"
android:text="@{outlet.firstName}"
android:addTextChangedListener="@{outlet.onFirstNameChanged}"
android:nextFocusRight="@+id/lName"/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/ownerlastname"
android:layout_marginBottom="8dp"/>
<EditText
android:id="@+id/lName"
android:layout_width="match_parent"
android:layout_height="55dp"
android:layout_marginBottom="16dp"
android:inputType="textCapWords"
android:text='@{outlet.lastName == null ? "" : outlet.lastName}'
android:addTextChangedListener="@{outlet.onLastNameChanged}"
android:singleLine="true"
android:imeOptions="actionNext"
android:nextFocusDown="@+id/nameOfOutlet"/>
</LinearLayout>
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/HeaderText"
android:text="@string/contact_information"
android:layout_marginBottom="8dp"
android:layout_marginTop="16dp"/>
<View
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:background="@color/gray_semi_transparent"
android:layout_marginBottom="32dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/address"
android:layout_marginBottom="8dp"/>
<EditText
android:id="@+id/address"
android:layout_width="match_parent"
android:layout_height="55dp"
android:layout_marginBottom="16dp"
android:inputType="textPostalAddress"
android:text='@{outlet.address == null ? "" : outlet.address}'
android:addTextChangedListener="@{outlet.onAddresChanged}"
android:imeOptions="actionNext"
android:nextFocusDown="@+id/lga"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="16dp"
android:baselineAligned="false">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_weight="1"
android:layout_marginRight="8dp"
android:layout_marginEnd="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/lga"
android:layout_marginBottom="8dp"/>
<AutoCompleteTextView
android:id="@+id/lga"
android:layout_width="match_parent"
android:layout_height="55dp"
android:imeOptions="actionNext"
android:inputType="textCapWords"
android:text="@{outlet.lga}"
android:singleLine="true"
android:nextFocusRight="@+id/state"/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_weight="1"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/state"
android:layout_marginBottom="8dp"/>
<AutoCompleteTextView
android:id="@+id/state"
android:layout_width="match_parent"
android:layout_height="55dp"
android:imeOptions="actionNext"
android:inputType="textCapWords"
android:singleLine="true"
android:nextFocusDown="@+id/phoneNumber1" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginBottom="16dp"
android:baselineAligned="true">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginRight="8dp"
android:layout_marginEnd="8dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/phone_number_1"
android:layout_marginBottom="8dp"/>
<EditText
android:id="@+id/phoneNumber1"
android:layout_width="match_parent"
android:layout_height="55dp"
android:layout_marginBottom="16dp"
android:inputType="phone"
android:imeOptions="actionNext"
android:addTextChangedListener="@{outlet.onPhoneNumberChanged}"
android:text="@{outlet.phoneNumber}"
android:singleLine="true"
android:nextFocusDown="@+id/phoneNumber2"/>
</LinearLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="8dp"
android:layout_marginStart="8dp"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/phone_number_2"
android:layout_marginBottom="8dp"/>
<EditText
android:id="@+id/phoneNumber2"
android:layout_width="match_parent"
android:layout_height="55dp"
android:layout_marginBottom="16dp"
android:inputType="phone"
android:imeOptions="actionNext"
android:addTextChangedListener="@{outlet.onOtherPhoneNumberChanged}"
android:text="@{outlet.phoneNumber}"
android:singleLine="true"
android:nextFocusDown="@+id/outletOwnerEmail"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
</layout>
And my Observable Object is this
public class Outlet extends BaseObservable {
private static final String TAG = "Outlet";
@SerializedName("name")
@Expose
private String name;
@SerializedName("firstName")
@Expose
private String firstName;
@SerializedName("lastName")
@Expose
private String lastName;
@SerializedName("phoneNumber")
@Expose
private String phoneNumber;
@SerializedName("otherNumber")
@Expose
private String otherNumber;
@SerializedName("address")
@Expose
private String address;
/**
*
* @return
* The name
*/
@Bindable
public String getName() {
return name;
}
/**
*
* @param name
* The name
*/
public void setName(String name) {
setAtomicName(name);
notifyPropertyChanged(BR.name);
}
public void setAtomicName(String basic) {
this.name = basic;
}
public TextWatcher onNameChanged = new SimpleTextWatcher() {
@Override
public void onTextChanged(String newValue) {
Log.e(TAG+"Name", newValue);
setAtomicName(newValue);
}
};
/**
*
* @return
* The firstName
*/
@Bindable
public String getFirstName() {
return firstName;
}
/**
*
* @param firstName
* The firstName
*/
public void setFirstName(String firstName) {
setAtomicFirstName(firstName);
notifyPropertyChanged(BR.firstName);
}
public void setAtomicFirstName(String basic) {
this.firstName = basic;
}
public TextWatcher onFirstNameChanged = new SimpleTextWatcher() {
@Override
public void onTextChanged(String newValue) {
Log.e(TAG+"FirstName", newValue);
setAtomicFirstName(newValue);
}
};
/**
*
* @return
* The lastName
*/
@Bindable
public String getLastName() {
return lastName;
}
/**
*
* @param lastName
* The lastName
*/
public void setLastName(String lastName) {
setAtomicLastName(lastName);
notifyPropertyChanged(BR.lastName);
}
public void setAtomicLastName(String basic) {
this.lastName = basic;
}
public TextWatcher onLastNameChanged = new SimpleTextWatcher() {
@Override
public void onTextChanged(String newValue) {
Log.e(TAG+"lstname", newValue);
setAtomicLastName(newValue);
}
};
/**
*
* @return
* The phoneNumber
*/
@Bindable
public String getPhoneNumber() {
return phoneNumber;
}
/**
*
* @param phoneNumber
* The phoneNumber
*/
public void setPhoneNumber(String phoneNumber) {
setAtomicPhoneNumber(phoneNumber);
notifyPropertyChanged(BR.phoneNumber);
}
public void setAtomicPhoneNumber(String basic) {
this.phoneNumber = basic;
}
public TextWatcher onPhoneNumberChanged = new SimpleTextWatcher() {
@Override
public void onTextChanged(String newValue) {
Log.e(TAG+"phonenumber", newValue);
setAtomicPhoneNumber(newValue);
}
};
/**
*
* @return
* The otherNumber
*/
public String getOtherNumber() {
return otherNumber;
}
/**
*
* @param otherNumber
* The otherNumber
*/
public void setOtherNumber(String otherNumber) {
setAtomicOtherPhoneNumber(otherNumber);
notifyPropertyChanged(BR.phoneNumber);
}
public void setAtomicOtherPhoneNumber(String basic) {
this.otherNumber = basic;
}
public TextWatcher onOtherPhoneNumberChanged = new SimpleTextWatcher() {
@Override
public void onTextChanged(String newValue) {
Log.e(TAG+"otherphone", newValue);
setAtomicOtherPhoneNumber(newValue);
}
};
/**
*
* @return
* The address
*/
@Bindable
public String getAddress() {
return address;
}
/**
*
* @param address
* The address
*/
public void setAddress(String address) {
setAtomicAddress(address);
notifyPropertyChanged(BR.phoneNumber);
}
public void setAtomicAddress(String basic) {
this.address = basic;
}
public TextWatcher onAddresChanged = new SimpleTextWatcher() {
@Override
public void onTextChanged(String newValue) {
Log.e(TAG+"address", newValue);
setAtomicAddress(newValue);
}
};
@BindingAdapter({"bind:email"})
public static void checkEmail(EditText editText, String email) {
if (editText != null) {
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
if (editText.length() > 0) {
if (!Utils.emailMatchPattern(editText.getText().toString())) editText.setError("Email Requried");
}
}
});
}
}
}
When I do this. Outlet outlet = new Outlet();
and assign it to my binding data like this binding.setOutlet(outlet);
I wouldn't get any error. But, if I do this:
Outlet outlet = gson.fromJson(realmOutlet.getPayload(), Outlet.class);
binding.setOutlet(outlet);
I get an NPE Error
and based on the NPE, it's not because the data is not there. It's like as if the adapters
hasn't been initialized. I know it keeps pointing to the addressField
because, it's the first item. And this is the adapter
for Address. It's an abstract class. So, I'm confused why aren't they initializede:
public TextWatcher onAddresChanged = new SimpleTextWatcher() {
@Override
public void onTextChanged(String newValue) {
Log.e(TAG+"address", newValue);
setAtomicAddress(newValue);
}
};
Below is my Log
I can't trace the log to know what exactly to debug. But, for it to be saying the view attaching itself to the Interface method
is null. It's like, the Binding Layouts
has not been initialized yet. So, why is it working when I initialize it with empty data?
And my onCreate
code
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_create);
binding = DataBindingUtil.setContentView(this, R.layout.activity_create);
realm = RealmUtils.getRealmInstance(getApplicationContext());
setSupportActionBar(binding.createToolbar);
action = getIntent().getStringExtra("action");
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
initAutoCompletedTexts();
if (action != null) {
if (action.equals("create")) {
getSupportActionBar().setTitle(R.string.create);
outlet = new Outlet();
outlet.setShopkeeper(shopkeeper);
binding.setOutlet(outlet);
} else {
getSupportActionBar().setTitle(R.string.update);
binding.saveBtn.setText("UPDATE");
initUpdateSpcific();
}
}
}
//createBinding.setCustomer(outlet);
binding.saveBtn.setOnClickListener(view -> {
save();
});
}
There are few things to take note:
1) Initialisation works the following way:
Outlet outlet = new Outlet();
2) Assigning a value:
Outlet outlet = gson.fromJson(realmOutlet.getPayload(), Outlet.class);
In order to keep the databinded you MUST initialise and then update the data accordingly.
So you have to do both but in a structured way, i.e
And the subsequent updates: assign the values and setOutlet
UPDATE:
This is just answer to you question and the followup of the chat we had in comments so that other can take reference from.