Use data binding to set View visibility - android

Trying to set visibility of View using custom variable, but error occurs: Identifiers must have user defined types from the XML file. visible is missing it. Is it possible to set view visibility using data binding? Thanks.
<data>
<variable
name="sale"
type="java.lang.Boolean"/>
</data>
<FrameLayout android:visibility="#{sale ? visible : gone}"/>

As stated in the Android Developer Guide, you need to do it like this:
<data>
<import type="android.view.View"/>
<variable
name="sale"
type="java.lang.Boolean"/>
</data>
<FrameLayout android:visibility="#{sale ? View.GONE : View.VISIBLE}"/>

In your layout:
<data>
<variable
name="viewModel"
type="...."/>
</data>
<View
android:layout_width="10dp"
android:layout_height="10dp"
android:visibility="#{viewModel.saleVisibility, default=gone}"/>
In your ViewModel java code:
#Bindable
public int getSaleVisibility(){
return mSaleIndecator ? VISIBLE : GONE;
}

The problem is that visibility is an Integer on the View class, this means you have two ways to make this work:
Use the View.VISIBLE and View.GONE constants. https://developer.android.com/topic/libraries/data-binding/index.html#imports
Define a custom setter for visibility that takes a Boolean. https://developer.android.com/topic/libraries/data-binding/index.html#custom_setters
Possible implementation:
#BindingAdapter("android:visibility")
public static void setVisibility(View view, Boolean value) {
view.setVisibility(value ? View.VISIBLE : View.GONE);
}
Which will make <FrameLayout android:visibility="#{sale}"/> work.

Similar to Kiskae solution. Put this method in a separate file, for instance, Bindings.kt:
#BindingAdapter("android:visibility")
fun View.bindVisibility(visible: Boolean?) {
isVisible = visible == true
// visibility = if (visible == true) View.VISIBLE else View.GONE
}
Then in layout XML:
<data>
<variable
name="viewModel"
type="SomeViewModel" />
</data>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="#{viewModel.number == 1}" />

Related

Progressbar visibility property not working with data binding

Visibility property not working
<data>
<import type="android.view.View"/>
<variable
name="viewmodel"
type="com.wiesoftware.spine.ui.auth.LoginViewModel" />
</data>
This is my progressbar code
<ProgressBar
android:id="#+id/pbLogin"
android:visibility="#{viewmodel.isVisibles ? View.VISIBLE : View.GONE, default=gone}"
style="?android:attr/progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="#+id/editTextTextPersonName2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/editTextTextPersonName" />
I am changing the value of isVisibles in viewModel but it doesn't reflect please give a solution. This is the class where I change isVisibles value.
class LoginViewModel(
private val authRepositry: AuthRepositry
):ViewModel() {
var email:String?=null
var password:String?=null
var loginEventListener:LoginEventListener?=null
var isVisibles:Boolean=false
fun loginButtonClicked(view: View){
isVisibles=true
if (email.isNullOrEmpty() || password.isNullOrEmpty()){
loginEventListener?.onLoginFailed("Please enter credentials.")
isVisibles=false
return
}
}
}```
> I have updated viewmodel class please check and let me know where is the issue I am using Kotlin Please provide me solution thanks
try add this
binding.lifecycleOwner = this // activity

How to pass observable fields through include tag?

I have a layout for the toolbar spinner:
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="selectedItem"
type="int" />
</data>
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="#style/Theme.Paper.Toolbar.Popup"
app:theme="#style/Theme.Paper.Toolbar">
<Spinner
android:id="#+id/toolbar_spinner"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:theme="#style/Theme.Paper.Toolbar.Spinner"
app:popupTheme="#style/Theme.Paper.Toolbar.Spinner.Popup"
app:selectedItem="#{selectedItem}"/>
</androidx.appcompat.widget.Toolbar>
</layout>
I include that layout in fragment layouts like below:
<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="model"
type="com.contedevel.timetable.models.WeekViewModel" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.contedevel.timetable.activities.MainActivity">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include
android:id="#+id/toolbar_layout"
layout="#layout/toolbar_spinner"
app:selectedItem="#{model.selectedWeekPosition}"/>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>
Here is my ViewModel:
class WeekViewModel(application: Application) : AndroidViewModel(application) {
val selectedWeekPosition = ObservableInt(0)
}
But selectedItem changes once on the start of activity only. When I try to change it in onOptionsItemSelected a nothing happens:
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
if (R.id.action_now == item?.itemId) {
week = WeekUtils.getCurrentWeek(timetable.entity)
viewModel.selectedWeekPosition.set(week - 1)
return true
}
return super.onOptionsItemSelected(item)
}
I suppose the value doesn't change due to the first layout (layout data binder) unwraps it to Int and the second layout binder gets Int instead of ObservableInt. But I still don't know how to fix it not to pass ObservableInt in the toolbar spinner binder directly.
Does anyone know how to fix it? Spinner binding adapter are below:
object SpinnerBindings {
#BindingAdapter("selectedItem")
#JvmStatic fun Spinner.setSelectedItem(position: Int) {
setSelection(position)
}
}
Pass your viewmodel to toolbar spinner layout.
Step 1: Add bind attribute to layout tag that contains include(fragment layouts) :
xmlns:bind="http://schemas.android.com/apk/res-auto"
Step 2: Change your data item in toolbar spinner layout:
<data>
<variable
name="vmodel"
type="com.contedevel.timetable.models.WeekViewModel" />
</data>
Step 3: Change your include tag like below:
<include
android:id="#+id/toolbar_layout"
layout="#layout/toolbar_spinner"
bind:vmodel="#{model}"/>
Step 4 : You have access to your viewModel in toolbar spinner layout, So:
<Spinner
android:id="#+id/toolbar_spinner"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:theme="#style/Theme.Paper.Toolbar.Spinner"
app:popupTheme="#style/Theme.Paper.Toolbar.Spinner.Popup"
app:selectedItem="#{vmodel.selectedWeekPosition}"/>

MVVM BindingAdapters not showing ProgressBar

I'm trying to create a simple example with databinding and BindingAdapters in order to show/hide a ProgressBar depending on TextView if it's empty or not. Below you can see my code. What I'm doing wrong?
loading_state.xml
<?xml version="1.0" encoding="utf-8"?>
<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="textString"
type="String"/>
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:visibleGone="#{textString==null}">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{textString}"/>
</android.support.constraint.ConstraintLayout>
</layout>
My BindingAdapter
object BindingAdapters {
#JvmStatic
#BindingAdapter("visibleGone")
fun showHide(view: View, visible: Boolean){
view.visibility = if (visible) View.VISIBLE else View.GONE
}
}
I include the layout in my second fragment in order to check the textview text
<include layout="#layout/loading_state"
app:textString="#{textView2.text.toString()}"/>
and also in my SecondFramgent class I take the value from MainFragment class (I'm using the new Navigation component)
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val txtFromMain = SecondFragmentArgs.fromBundle(arguments)
textView2.text = txtFromMain.txtFromMain
}
What am I missing?
Thank you very much.
For those facing the same issue you can find my solutions matches in my case below:
I had to change my BindingAdapter.
#BindingAdapter("visibleGone")
fun showHide(view: View, visible: String){
view.visibility = if (visible.isEmpty()) View.VISIBLE else View.GONE
}
You did not set two-way databinding for TV, thus string is not getting updated inside databinding
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{textString}"/>
Change android:text="#{textString}" to android:text="#={textString}"
This is first look of the problem, does it help?

How to use method of object in Android data binding?

Suppose I have this object:
public class Field
{
List<String> list;
public Field()
{
list = new ArrayList();
}
public boolean isOnTheList(String someText)
{
return list.contains(someText);
}
}
Now I want to use this function on a xml with binding like this.
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View"/>
<variable
name="field"
type="com.greendao.db.Field"/>
<variable
name="user"
type="com.package.User"/>
</data>
<LinearLayout
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="#{field.isOnTheList(user.name)?View.VISIBLE:View.GONE}"
tools:context=".advisor.new_visit_report.activities.FieldsSelectionActivity"> ...
The problem is that is not working. Anyone has already tried this?
If everything else is set correctly, I would suggest to edit your method to return View.VISIBLE or View.GONE directly:
public int isOnTheList(String someText) {
return list.contains(someText) ? View.VISIBLE : View.GONE;
}
Used in xml:
android:visibility="#{field.isOnTheList(user.name)}

ViewDataBinding getVariable?

The 2 main questions are :
Why ViewDataBinding doesn't have a method like getVariable("variableName") that will look up for a variable and returns it or null if no variable with this name exists.
Is their any way/workaround to achieve this kind of behavior?
So to be more explicit : if I don't know the type of my ViewDataBinding, is their a way to get its variable or I must know its type?
Here is how it is working actually :
I havea layout called my_layout.xml :
<layout>
<data>
<variable
android:name="myVaribale"
android:type="String"/>
</variable>
</data>
<RelativeLayout
android:id="#+id/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="#{myVariable}"/>
</RelativeLayout>
<layout>
I inflate an instance of its ViewDataBinding :
MyLayoutBinding binding = (MyLayoutBinding) DataBindingUtil.inflate(inflater, R.layout.my_layout, root, false);
I can get its variable by calling the appropriate method :
String myVariable = binding.getMyVariable();
So this was how to get a variable when we know the type of the ViewDataBinding.
My problem is here :
Let's imagine I have 3 layouts called my_layout_1, my_layout_2, my_layout_3 and those 3 layouts are surrounded by a <layout> tag.
Those 3 layouts has also the same variable (myVariable).
So here are the layouts :
my_layout_1
<layout>
<data>
<variable
android:name="myVariable"
android:type="String"/>
</variable>
</data>
<RelativeLayout
android:id="#+id/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="#{myVariable}"
style="#style/my_style_1"/>
</RelativeLayout>
<layout>
my_layout_2
<layout>
<data>
<variable
android:name="myVariable"
android:type="String"/>
</variable>
</data>
<RelativeLayout
android:id="#+id/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="#{myVariable}"
style="#style/my_style_2"/>
</RelativeLayout>
<layout>
my_layout_3
<layout>
<data>
<variable
android:name="myVariable"
android:type="String"/>
</variable>
</data>
<RelativeLayout
android:id="#+id/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="#{myVariable}"
style="#style/my_style_3"/>
</RelativeLayout>
<layout>
So the different layout represent a text with a different style (I know it can be achieved by others way but the goal is just to explain why I need this function getVariable(variableName).
Imagine now that I got a layout called my_text_container which will randomly contain one of the 3 layouts.
my_text_container
<layout>
<FrameLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<layout>
So I'll add a text by doing something like this (again, it's just for demonstration purpose) :
MyTextContainerBinding textContainerBinding = (MyTextContainerBinding) DataBindingUtil.inflate(inflater, R.layout.my_text_container, root, false);
int textLayoutRes;
int randomNumber = generateNumberBetween0and2();
switch(randomNumber) {
case 0: textLayoutRes = R.layout.my_layout_1;
break;
case 1: textLayoutRes = R.layout.my_layout_2;
break;
case 2: textLayoutRes = R.layout.my_layout_3;
break;
}
textContainerBinding.getRoot().addView(DataBindingUtil.inflate(inflater, textLayoutRes, root, false));
And finally I want to retrieve myVariable but I don't know if it's a layout 1, 2 or 3. Basically what could be done now is to use instanceof operator to check whether its a type MyLayoutBinding1, MyLayoutBinding2 or MylayoutBinding3. But as I said this use-case is only for demonstration purpose and I could have 999 different layouts.
So what I would like to do is :
ViewDataBinding myLayout = DataBindingUtil.getBinding(textContainerBinding.getRoot().getChildAt(0));
String myVariable = myLayout.getVariable("myVariable");
if(myVariable != null)
Log.d(TAG, "myLayout was MylayoutBinding1 or myLayoutBinding2 or myLayoutBinding3");
else
Log.d(TAG, "mylayout was not one of the 3 layouts");
Sorry for this (very) long post but thank's for reading it and for any future answers !
I am not sure if I get it but what I do is I declare a variable in the layout like this:
<data>
<variable
name="variableName"
type="bla.bla.VariableType"/>
</data>
Then first you get your layout binding in onCreate:
binding = DataBindingUtil.inflate(inflater, R.layout.your_layout, container, false);
Then you should send a variable to the layout in code like:
binding.setVariableName(yourVariable);
Then you can retrieve it like:
binding.getVariableName();
why ViewDataBinding Doesn't have getMethodVariable?
consider the normal scenario without binding. you have a text container layout. how will you include another layout inside it in a normal activity.
my_text_container.xml:(Normal activity)
You inflate this layout to your java file using R.layout.my_text_container, you won't inflate every layout to a different view to access textview inside it. just one inflate and go with it.
So the same procedure is followed in DataBinding as well, when you use include tag ,the respective fields will be inside MyTextContainerLayoutBinding.
you don't have to inflate it again and again with activity name like MyLayout1Binding, MyLayout2Binding. that's why getting variable name become unnecessary over here.
I think adding variability to Binding , will be more helpful in a place where you just want to display data. Data from one model across multiple included layouts,more helpful with Listviews,Recyclerviews(where layout use data from models).
you can't provide different type for different layouts ,if you are going include one layout to another.(Remember that this binds both layout to single name (parent_activity in your case container)).
Eg:
UserProfileModel:
public class UserProfileModel {
String firstname;
String lastname;
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
}
my_layout_1.xml;
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable name="user"
type="com.hourglass.lingaraj.bindingusingvariables.UserProfileModel" />
</data>
<LinearLayout
android:orientation="vertical"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:visibility="visible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{user.firstname}"
style="#style/mystyle_1"/>
<include layout ="#layout/my_layout_2"
app:user="#{user}" />
</LinearLayout>
</layout>
my_layout_2.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="user"
type="com.hourglass.lingaraj.bindingusingvariables.UserProfileModel"/>
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:visibility="visible"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{user.lastname}"
style="#style/mystyle_2"/>
</RelativeLayout>
</layout>
MainActivity.java:
public class MainActivity extends AppCompatActivity {
MyLayout1Binding layout_binding;
UserProfileModel data_model;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_layout_1);
data_model = new UserProfileModel();
data_model.setFirstname("Lingaraj");
data_model.setLastname("Sankaravelu");
layout_binding = DataBindingUtil.inflate(getLayoutInflater(),R.layout.my_layout_1,null,false);
layout_binding = DataBindingUtil.setContentView(this,R.layout.my_layout_1);
layout_binding.setUser(data_model);
}
}
As you see we are using only one model to populate data across included layout. So there is no need get Variable name to verify which model is loaded as it's Binded to single layout.
There is no workaround to achieve this, us of I have tried.
So my answer will be No you can't get a variable name from binding
References.
No More findViewById
Android Data Binding: That Thing
Android Data Binding: Adding some variability
Android Data Binding: Express Yourself

Categories

Resources