I am trying to set the layout visibility with data binding. While the data is being loaded from the database, the default visibility which I set in XML is not working. Here is the layout file
<RelativeLayout
android:id="#+id/error_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="#{homeViewModel.comfortErrorVisibility, default=invisible}"/>
The view model is like this
public class HomeViewModel extends BaseObservable {
private ObservableField<String> comfortErrorMessage = new ObservableField<>();
public HomeViewModel(){
validateSpace();
}
#Bindable
public int getComfortErrorVisibility(){
// change the visibility based on error message
return TextUtils.isEmpty(comfortErrorMessage.get()) ? View.VISIBLE : View.INVISIBLE;
}
private void validateSpace(){
//some business logic to set the comfrotErrorMessage
}
}
Am I missing anything here? By default, I want to set the visibility as invisible for error layout. But its shown by default.
Afaik, default is just for the preview on Android studio and won't do anything in runtime. Can't find the official documentation anymore, but there are quite a few SO Posts about it.
From what I can tell, when you set the binding the data binding framework will call getComfortErrorVisibility to get the visibility of error message. Your condition is set so that when the error message is empty or null the visibility is visible:
TextUtils.isEmpty(comfortErrorMessage.get()) ? View.VISIBLE : View.INVISIBLE;
Because your comfortErrorMessage is initialized like ObservableField(), its initial value will be null and hence the first thing you see is a visible error field.
Maybe you should change the condition for the visibility?
this is because you make a mistake in getComfortErrorVisibility method. At start your comfortErrorMessage is empty so your method return visible a and text view will be shown, try change your method to:
#Bindable
public int getComfortErrorVisibility(){
return TextUtils.isEmpty(comfortErrorMessage.get()) ? View.INVISIBLE: View.VISIBLE;
}
public class HomeViewModel extends BaseObservable {
private ObservableField<String> comfortErrorMessage = new ObservableField<>();
public HomeViewModel(){
validateSpace();
}
}
import text utils in your <data> binding tag and ...
<RelativeLayout
android:id="#+id/error_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="#{TextUtils.isEmpty(viewmodel.comfortErrorMessage) ? View.VISIBLE : View.INVISIBLE"/>
use visibility = invisible or gone:
<RelativeLayout
android:id="#+id/error_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="invisible"/>
and then change the visibilty programmatically where you need it
if(TextUtils.isEmpty(some_error))
{
findViewbyId(R.id.error_layout).setVisibilty(View.VISIBILE)}
}
Add your comfortErrorVisibility , init it as View.INVISIBLE
private ObservableField<String> comfortErrorMessage = new ObservableField<>();
private ObservableField<Integer> comfortErrorVisibility = new ObservableField<>(View.INVISIBLE);
In your XML, just put the field name:
<RelativeLayout
android:id="#+id/error_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="#{homeViewModel.comfortErrorVisibility}"/>
If your comfortErrorMessage can be changed dynamically on runtime i.e. user is able to edit it from an EditText, then u can instead add property changed callback
import android.text.TextUtils;
import android.view.View;
import androidx.databinding.BaseObservable;
import androidx.databinding.Observable;
import androidx.databinding.ObservableField;
public class HomeViewModel extends BaseObservable {
private ObservableField<String> comfortErrorMessage = new ObservableField<>();
private ObservableField<Integer> comfortErrorVisibility = new ObservableField<>(View.INVISIBLE);
public HomeViewModel() {
validateSpace();
comfortErrorMessage.addOnPropertyChangedCallback(
new Observable.OnPropertyChangedCallback() {
#Override
public void onPropertyChanged(Observable sender, int propertyId) {
if (TextUtils.isEmpty(comfortErrorMessage.get())) {
comfortErrorVisibility.set(View.VISIBLE);
} else {
comfortErrorVisibility.set(View.INVISIBLE);
}
}
}
);
}
private void validateSpace() {
//some business logic to set the comfrotErrorMessage
}
}
and your comfortErrorMessage should assigned with two-way binding with = sign "#={homeViewModel.comfortErrorMessage}"
Related
I have a custom control with buttons (image) that should update an activity (fragment) control (big 0 string).
May be it is too simple but I don't know how to do it because onClickListener buttons are inside the custom control class. Which is the best approach?
Listener inside custom control class is:
binding.counterSelectorViewPrevious.setOnClickListener(OnClickListener {
decreaseValue()}
fun decreaseValue() {
if (mSelectedIndex > 0) {
val newSelectedIndex = mSelectedIndex - 1
setSelectedIndex(newSelectedIndex)
}
}
Activity control is just a TextView outside of custom control class.
<TextView
android:id="#+id/workout_length"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="0"
android:textAlignment="textEnd"
android:textAppearance="#style/TextAppearance.AppCompat.Display2"
tools:text="99:99:99" />
Thanks in advance.
You need to accept a listener in your custom view what get's some callback from your activity/fragment
something like this
private OnValueChangeListener onValueChangeListener;
public void addOnValueChangeListener(OnValueChangeListener onValueChangeListener)
{
this.onValueChangeListener = onValueChangeListener;
}
public void decreaseValue() {
onValueChangeListener.onValueChange(newValue);
}
public interface OnValueChangeListener {
void onValueChange(int newValue);
}
then you need to add invoke this somewhere in your activity/fragment (like in onCreate/onCreateView)
void setupListener() {
addOnValueChangeListener(newValue -> {
findViewById(R.id.workout_length).setText("" + newValue);
});
}
My goal is to 2-way databind material.Slider view to MutableLiveData from my viewmodel:
<com.google.android.material.slider.Slider
...
android:value="#={viewmodel.fps}"
...
/>
Of course, it's not working because there is no databinding adapter for Slider in androidx.databinding library
[databinding] Cannot find a getter for <com.google.android.material.slider.Slider android:value> that accepts parameter type <java.lang.Integer>. If a binding adapter provides the getter, check that the adapter is annotated correctly and that the parameter type matches.
But, they have one for SeekBar: /androidx/databinding/adapters/SeekBarBindingAdapter.java
As I understand, 2-way databinding should work only with "progress" attribute, and 1-way databinding requires two attributes: "onChanged" and "progress"
I made a try to adapt SeekBarBindingAdapter for Slider:
#InverseBindingMethods({
#InverseBindingMethod(type = Slider.class, attribute = "android:value"),
})
public class SliderBindingAdapter {
#BindingAdapter("android:value")
public static void setValue(Slider view, int value) {
if (value != view.getValue()) {
view.setValue(value);
}
}
#BindingAdapter(value = {"android:valueAttrChanged", "android:onValueChange"}, requireAll = false)
public static void setOnSliderChangeListener(Slider view, final Slider.OnChangeListener valChanged, final InverseBindingListener attrChanged) {
if (valChanged == null)
view.addOnChangeListener(null);
else
view.addOnChangeListener((slider, value, fromUser) -> {
if (valChanged != null)
valChanged.onValueChange(slider, value, fromUser);
});
if (attrChanged != null) {
attrChanged.onChange();
}
}
#Override
public void onValueChange(#NonNull Slider slider, float value, boolean fromUser) {
}
It's not building:
Could not find event android:valueAttrChanged on View type Slider
but why it looks for valueAttrChanged if I only use
android:value="#={viewmodel.fps}"
?
How do I find the right attribute to add to BindingAdapter, if I don't see valueAttrChanged in Slider class?
Let's look at SeekBarBindingAdapter's setOnSeekBarChangeListener() method. It adds four different attributes: {"android:onStartTrackingTouch", "android:onStopTrackingTouch", "android:onProgressChanged", "android:progressAttrChanged"} but only the last one is used by two-way databinding.
But why there are four attributes? If you look at SeekBar class, it has setOnSeekBarChangeListener() method which allows you to set and remove a listener. The problem is that SeekBar can only have one listener, and that listener provides different callbacks: onProgressChanged, onStartTrackingTouch and onStopTrackingTouch.
SeekBarBindingAdapter registers its own listener which means that no one can register another listener without removing the existing one. It's why SeekBarBindingAdapter provides onStartTrackingTouch, onStopTrackingTouch and onProgressChanged attributes, so you can listen to these events without registering your own OnSeekBarChangeListener.
Actually the Slider adapter can be much simpler than SeekBarBindingAdapter, because the Slider allows you to add and remove listeners using addOnChangeListener() and removeOnChangeListener(). So a two-way databinding adapter can register its own listener and anyone else can register other listeners without removing previous ones.
It allows us to define a pretty concise adapter. I created a kotlin example, hope you can translate it to java:
#InverseBindingAdapter(attribute = "android:value")
fun getSliderValue(slider: Slider) = slider.value
#BindingAdapter("android:valueAttrChanged")
fun setSliderListeners(slider: Slider, attrChange: InverseBindingListener) {
slider.addOnChangeListener { _, _, _ ->
attrChange.onChange()
}
}
And the layout:
...
<com.google.android.material.slider.Slider
...
android:value="#={model.count}" />
...
You can find the full sources here.
Update Android Java
Data binding
<variable
name="device"
type=".....Device" />
Trong file binding
#InverseBindingAdapter(attribute = "android:value")
public Float getSlider(Slider slider) {
return slider.getValue();
}
#BindingAdapter("app:valuesAttrChanged")
public void setSliderListeners(Slider slider, InverseBindingListener attrChange) {
slider.addOnChangeListener(new Slider.OnChangeListener() {
#Override
public void onValueChange(#NonNull Slider slider, float value, boolean fromUser) {
attrChange.onChange();
}
});
}
Trong file xml thêm dòng
android:value="#{device.data}"
data is value change
I'm having trouble making two Edit Text views that update when one is changed. To provide some context, see the following image:
Also the view in action (can't have it embedded apparently.):
https://i.imgur.com/an6Kodx.mp4
Here, we add targets (T1, T2, T3 etc.), then draw an arc and user may set start and finish points of the camera (gray and red icons respectively.) Then, we get the total move value (in degrees). This value will determine amount the motor will rotate (The app is basically a controller for users to have automated photo-shoots).
What I try to achieve is that, when user enters a photo number, right edittext divides total move degrees to that count and show angle per photo and vice-versa.
However, I'm a bit lost among all the online content demonstrating various examples (like password strength etc.)
I've included DataBinding on gradle.
I've created a custom class (RotaryPhotoShoot) to have a model of three main parameters (angle per shoot, number of photos and total move).
I've moved my cosntraint layout to layout root.
I've created data as seen on following code blocks.
RotaryPhotoShoow.java (my model)
package com.example.macrorecapp.models;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
public class RotaryPhotoShoot extends BaseObservable {
private static final String TAG = "Rotary Photo Shoot";
private float anglePerPhotos;
private int numberOfPhotos;
private int totalMoveDegrees;
public RotaryPhotoShoot(float anglePerPhotos,int numberOfPhotos, int totalMoveDegrees) {
this.anglePerPhotos = anglePerPhotos;
this.numberOfPhotos = numberOfPhotos;
this.totalMoveDegrees = totalMoveDegrees;
}
#Bindable
public float getAnglePerPhotos() {
return anglePerPhotos;
}
#Bindable
public int getNumberOfPhotos() {
return numberOfPhotos;
}
#Bindable
public int getTotalMoveDegrees() {
return totalMoveDegrees;
}
#Bindable
public void setAnglePerPhotos(float anglePerPhotos) {
this.anglePerPhotos = anglePerPhotos;
}
#Bindable
public void setNumberOfPhotos(int numberOfPhotos) {
this.numberOfPhotos = numberOfPhotos;
}
#Bindable
public void setTotalMoveDegrees(int totalMoveDegrees) {
this.totalMoveDegrees = totalMoveDegrees;
}
}
activity_rotary_photo_settings.xml
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="photoShoot"
type="com.example.macrorecapp.models.RotaryPhotoShoot" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/appMainBackground"
tools:context=".features.rotary.RotaryPhotoSettings">
...
<com.example.macrorecapp.features.shared.views.RotaryView
android:id="#+id/rotaryPhotoView"
android:layout_width="360dp"
android:layout_height="360dp"
app:isClockwise="true"
app:targetList="#array/targets"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#id/h_guideline1"
app:layout_constraintBottom_toTopOf="#id/h_guideline2" />
<EditText
android:id="#+id/numberOfPhotosEdittext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{``+photoShoot.numberOfPhotos}"
android:textAlignment="center"
android:ems="4"
android:textSize="15sp"
android:textColor="#FFFFFFFF"
app:layout_constraintBottom_toTopOf="#+id/numberOfPhotosSubtext"
app:layout_constraintEnd_toStartOf="#id/v_guideline"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/numberOfPhotosBG"
android:importantForAutofill="no"
android:inputType="number" />
...
<EditText
android:id="#+id/anglePerPhotosEdittext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{``+photoShoot.anglePerPhotos+(char) 0x00B0}"
android:textAlignment="center"
android:ems="4"
android:textSize="15sp"
android:textColor="#FFFFFFFF"
app:layout_constraintBottom_toTopOf="#+id/anglePerPhotosSubtext"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="#id/v_guideline"
app:layout_constraintTop_toTopOf="#+id/anglePerPhotosBG"
android:importantForAutofill="no"
android:inputType="numberDecimal" />
...
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
And finally RotaryPhotoSettings.java
package com.example.macrorecapp.features.rotary;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import android.os.Bundle;
import android.view.View;
import android.view.animation.AlphaAnimation;
import com.example.macrorecapp.R;
import com.example.macrorecapp.databinding.ActivityRotaryPhotoSettingsBinding;
//import com.example.macrorecapp.features.shared.views.RotaryView;
import com.example.macrorecapp.models.RotaryPhotoShoot;
public class RotaryPhotoSettings extends AppCompatActivity {
private AlphaAnimation buttonClick = new AlphaAnimation(1F, 0.2F);
//RotaryView mPhotoRotaryView;
//private int mTotalMoveInDegrees;
ActivityRotaryPhotoSettingsBinding mBinding;
RotaryPhotoShoot mRotaryPhotoShoot;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_rotary_photo_settings);
mRotaryPhotoShoot = new RotaryPhotoShoot(6.88f, 25, 178);
mBinding.setPhotoShoot(mRotaryPhotoShoot);
//mPhotoRotaryView = findViewById(R.id.rotaryPhotoView);
//mPhotoRotaryView.addTarget(300);
//mTotalMoveInDegrees = mPhotoRotaryView.getTotalMoveInDegrees();
}
public void goBack(View view) {
view.startAnimation(buttonClick);
finish();
}
public void openThreeSixtyPhotoRotary(View view) {
}
}
Currently I have no errors whatsoever and I'm sure I'll be able change views one way when I programmatically set them in activity. What I feel like I should do is, first use #={} syntax in xmls to begin with. Then I may need to have custom adapters or binders. I've also seen that people use ObservableInt etc. which I got a bit lost. I needed to set my getTotalMove function to set static to get it from RotaryView.java but from then on I couldn't progress.
I'd like to have some pointers what to do onward. I think I can easily handle rounding up numbers where I implement the custom binder/adapter. I know for example the angle may be decimal while the photo count needs to be integer. I will be rounding up photo count and change the angle itself to closest possible value once it is done being edited. I will also need to determine whether start and end points will be included in the interval. Like, for 100 degrees, with 20 degrees per shoot, it'd be like this:
0: S__S__S__S__S__S :100 Thus 6 photos etc.
Before I implement any listeners etc., I figured I could ask here first, because obviously point of using the Data Binding library is to get rid of bunch of listeners and so on. I would appreciate some sort of example where two EditText views change eachother.
Once I figure out how to set non-edited EditText, I'll be dealing with extra considerations I mentioned above, but first I need to get done with two way binding part. I suppose this "two way" is between view and view model, not directly between views, obviously. So I don't know if I can have a trick like #={``+photoShoot.totalMove/photoShoot.anglePerPhoto} etc. in xml.
Anyways, the post is much longer than it is supposed to be, my apologies.
This looked pretty straight-forward at first glance, but the more I look into it, the more complicated it gets. Maybe I'm just confusing myself.
I'd like to add some partial-answer to my own question. I tried to adjust info that I had from following link in my own use case:
https://www.bignerdranch.com/blog/two-way-data-binding-on-android-observing-your-view-with-xml/
I managed to change angle box (one on the right) with following changes:
I deleted some unnecessary variables in my custom view you see above and added a public "Total Move" getter. I use this in my model class RotaryPhotoShoot.
I also added #={} in my xml as you can see updated code below. This combined with notifyPropertyChanged(com.example.macrorecapp.BR.numberOfPhotos); made it possible to update angle box.
Before adding another wall of text, I'll just add the relevant parts of my code for further reference to other people.
RotaryPhotoSettings.java (The activity class that utilizes binding.)
package com.example.macrorecapp.features.rotary;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import android.os.Bundle;
import android.view.View;
import android.view.animation.AlphaAnimation;
import com.example.macrorecapp.R;
import com.example.macrorecapp.databinding.ActivityRotaryPhotoSettingsBinding;
import com.example.macrorecapp.models.RotaryPhotoShoot;
public class RotaryPhotoSettings extends AppCompatActivity {
private AlphaAnimation buttonClick = new AlphaAnimation(1F, 0.2F);
//RotaryView mPhotoRotaryView;
//private int mTotalMoveInDegrees;
ActivityRotaryPhotoSettingsBinding mBinding;
RotaryPhotoShoot mRotaryPhotoShoot;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_rotary_photo_settings);
mRotaryPhotoShoot = new RotaryPhotoShoot(6.88f, 25);
mBinding.setPhotoShoot(mRotaryPhotoShoot);
//mPhotoRotaryView = findViewById(R.id.rotaryPhotoView);
//mPhotoRotaryView.addTarget(300);
//mTotalMoveInDegrees = mPhotoRotaryView.getTotalMoveInDegrees();
}
public void goBack(View view) {
view.startAnimation(buttonClick);
finish();
}
public void openThreeSixtyPhotoRotary(View view) {
}
}
My model class, RotaryPhotoShoot.java
package com.example.macrorecapp.models;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
import com.example.macrorecapp.features.shared.views.RotaryView;
public class RotaryPhotoShoot extends BaseObservable {
private static final String TAG = "Rotary Photo Shoot";
private float anglePerPhotos;
private int numberOfPhotos;
public RotaryPhotoShoot(float anglePerPhotos, int numberOfPhotos) {
this.anglePerPhotos = anglePerPhotos;
this.numberOfPhotos = numberOfPhotos;
}
#Bindable
public float getAnglePerPhotos() {
return RotaryView.getTotalMoveInDegrees()/(float) numberOfPhotos;
}
#Bindable
public int getNumberOfPhotos() {
return numberOfPhotos;
}
#Bindable
public void setAnglePerPhotos(float anglePerPhotos) {
this.anglePerPhotos = RotaryView.getTotalMoveInDegrees()/numberOfPhotos;
}
#Bindable
public void setNumberOfPhotos(int numberOfPhotos) {
this.numberOfPhotos = numberOfPhotos;
notifyPropertyChanged(com.example.macrorecapp.BR.numberOfPhotos);
notifyPropertyChanged(com.example.macrorecapp.BR.anglePerPhotos);
}
}
The activity layout file that have views in it, activity_rotary_photo_settings.xml
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="photoShoot"
type="com.example.macrorecapp.models.RotaryPhotoShoot" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
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"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/appMainBackground"
tools:context=".features.rotary.RotaryPhotoSettings"
android:focusable="true"
android:focusableInTouchMode="true">
<com.example.macrorecapp.features.shared.views.RotaryView
android:id="#+id/rotaryPhotoView"
android:layout_width="360dp"
android:layout_height="360dp"
app:isClockwise="true"
app:targetList="#array/targets"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#id/h_guideline1"
app:layout_constraintBottom_toTopOf="#id/h_guideline2" />
<EditText
android:id="#+id/numberOfPhotosEdittext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#={``+photoShoot.numberOfPhotos}"
android:textAlignment="center"
android:ems="4"
android:textSize="15sp"
android:textColor="#FFFFFFFF"
app:layout_constraintBottom_toTopOf="#+id/numberOfPhotosSubtext"
app:layout_constraintEnd_toStartOf="#id/v_guideline"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/numberOfPhotosBG"
android:importantForAutofill="no"
android:inputType="number" />
<EditText
android:id="#+id/anglePerPhotosEdittext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{``+String.format(`%.2f`, photoShoot.anglePerPhotos)+(char) 0x00B0}"
android:textAlignment="center"
android:ems="4"
android:textSize="15sp"
android:textColor="#FFFFFFFF"
app:layout_constraintBottom_toTopOf="#+id/anglePerPhotosSubtext"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="#id/v_guideline"
app:layout_constraintTop_toTopOf="#+id/anglePerPhotosBG"
android:importantForAutofill="no"
android:inputType="numberDecimal" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Current problems that I could use some markers:
I need cross changes, currently I don't know how to tell whether a change is coming from the EditText being changed by typing or not. When the change is coming from other box, I will format/round the value properly and update the EditText view.
I could just use a bool value that I would toggle depending on whether the change is coming from manual editing or value changing progromatically. This would help me prevent infinite loop. However, as I said above, I am not sure what to listen to in order to achieve that.
Another behavior I would like to have is that, when camera start-finish icons are moved and TotalMove (in degrees) changed, I want to have numberOfPhotos fixed and update anglePerPhotos only. I may need to add binding in RotaryView.java for that. If this is an overkill, I may just add a trigger/listener on RotaryPhotoShoot. Currently. when I make a change in numberOfPhotos after I change the camera positions, angle is calculated properly as expected.
One little bug(?) I have is that, I cannot delete the last digit in numberOfPhotos field. See the following webm video below:
https://gfycat.com/distortedyoungdairycow
One thing I've realized is that, getter and setters in model class alone achieves what I need to do. This indeed removes the need to mess around with listeners and custom adapters. Since I'm using two EditTexts interchangeably, I may end up using them still.
Note that you can use any built-in Java functions (see string formatting I used in anglePerPhotos field). If necessary, I know how to import a class in <data></data> block.
I'll add one more link before I finish this update-answer for those who may be lost how to set if Data Binding in their project for the first time:
https://www.youtube.com/watch?v=v4XO_y3RErI
This solved my problem:
android:text="#{String.valueOf(product.quantityInventory + product.quantityShop)}"
I'm still new to all this MVVM and Android Architecture Components. I have a few screens (Login and Register) that have inputs for email, password, name etc and a 'continue' button that's only enabled when the required fields are filled out correctly. There's also text views for messages such as "password must be..." and "not a valid email...". I'm trying to use a binding adapter and viewmodel to pull some of this validation logic out of my MainActivity and away from my business logic, but I'm struggling. I'm not even sure if this is the best way to do it. My thought with the ViewModel is that it would hold it's state on rotation/activity changes.
RegisterActivity is an Activity that simply holds a ViewPager with 3 fragments (email/password, first/last name, validation code).
Binding Adapter
#BindingAdapter("app:onTextChanged")
public static void onTextChanged(TextInputEditText view, TextViewBindingAdapter.OnTextChanged listener) {
}
Layout
<TextView
android:id="#+id/tv_error_message"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="#string/register_email_requirement"
android:textColor="#color/moenPrimaryError"
android:textSize="18sp"
android:visibility="#{viewModel.emailValidationVisible ? View.VISIBLE: View.GONE}"
app:layout_constraintBottom_toTopOf="#id/input_layout_password"
app:layout_constraintEnd_toEndOf="#+id/input_layout_email_address"
app:layout_constraintStart_toStartOf="#+id/input_layout_email_address"
app:layout_constraintTop_toBottomOf="#+id/input_layout_email_address"
app:layout_goneMarginTop="16dp" />
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/input_email_address"
style="#style/MoenTextInputEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
app:onTextChanged="#{viewModel.onTextChanged}"
app:onFocusChange="#{inputLayoutEmailAddress}">
</com.google.android.material.textfield.TextInputEditText>
ViewModel
public class RegisterFragmentViewModel extends BaseObservable {
private boolean emailValidationVisible = false;
#Bindable
public boolean getEmailValidationVisible() {
return this.emailValidationVisible;
}
public void toggle() {
this.emailValidationVisible = !this.emailValidationVisible;
notifyPropertyChanged(BR.viewModel);
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
Log.w("tag", "onTextChanged " + s);
this.toggle();
}
}
This is just a test I have. My thought was that I could bind the TextView visibility to a boolean that I can toggle/manipulate with a onTextChanged listener, but I don't know how to wire up the Binding Adapter. Am I on the right path? Is there a better/easier way to do this?
Am I on the right path? Is there a better/easier way to do this?
it is a way of doing it. I would remove the ternary operator from this line
android:visibility="#{viewModel.emailValidationVisible ? View.VISIBLE: View.GONE}"
and create a simple function in your VM that returns it. EG:
#Bindable
public int getVisibility() {
return emailValidationVisible ? View.VISIBLE: View.GONE
}
and in toggle(), you will have something like
public void toggle() {
this.emailValidationVisible = !this.emailValidationVisible;
notifyPropertyChanged(BR.visibility);
}
which will call the getter for you. Your xml will have to be changed like follow
android:visibility="#{viewModel.getVisibility()}"
Alternately you could create a BindingAdapter that takes a Boolean and change the visibility accordingly to it
I'm having a Floating Action Button listener with AlertDiaolog inside. And I want to use my buttons from XML. And if I want to write an onClickListener() for them.
So in Java I have to initialize it like:
butAdd = (Button)dialog.findViewById(R.id.btn_add)
butAdd.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//Some code
}
But when I trying to use:
var butAdd = dialog?.findViewById(R.id.btn_add) as Button;
in Kotlin it's incorrect
So any suggestions how to fix it? What'is wrong with listeners?
Here is my code of Floating Action Button:
fab?.setOnClickListener {
diaolg = AlertDialog.Builder(this#Cards)
val linearlayout = getLayoutInflater().inflate(R.layout.add_password, null)
diaolg?.setView(linearlayout)
?.setTitle("Add a new password")
?.setCancelable(true)
var login = findViewById(R.id.login) as EditText
var password = findViewById(R.id.password) as EditText
var title = findViewById(R.id.title) as EditText
var butAdd = diaolg?.findViewById(R.id.btn_add) as Button
var butCancel = diaolg?.findViewById(R.id.btn_cancel) as Button
butAdd.setOnClickListener(View.OnClickListener {
fun onClick(v:View){
}
})
butCancel.setOnClickListener(View.OnClickListener {
fun onClick(v:View){
}
})
diaolg?.create()
diaolg?.show()
}
please find id by using
var butAdd = linearlayout.findViewById<Button>(R.id.btn_add) as Button;
Whatever class you're in has no findViewById method.
You have to findViewById on the inflated layout, so:
var login = linearLayout.findViewById(R.id.login) as EditText
Also, I dont think you need to have the ? on the dialog, it cannot be null.
Also, I would make your views vals instead of vars.
import this line in your Activity
<Button
android:id="#+id/btn_submit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="submit" />
import this line in your Activity
// Using R.layout.activity_main from the main source set
//activity_main is your layout file name
import kotlinx.android.synthetic.main.activity_main.*
btn_submit.setOnClickListener {
Toast.makeText(this, "hello", Toast.LENGTH_LONG).show();
}
Its work for me.