I am using data binding for setting data to recyclerview item and I am using #BindingAdapter for custom attrinbutes.
Now i want to update the text of a textview in each second. The textview is displaying a text(time ago) with related to a timestamp, i have created a #BindingAdapter function by passing the timestamp, which works perfectly for setting the text single time, but i want to update it in each second.
<TextView
android:id="#+id/tvTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textStyle="italic"
app:timeago="#{order.DateStamp}"
app:layout_constraintBottom_toTopOf="#id/tvOrderId"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Just now" />
Following is my binding adapter
#BindingAdapter("timeago")
public static void setTimeAgo(TextView view, long time) {
view.setText(getTimeAgo(time));
}
this is my layout file
You can use EchoBind class, BindingAdapter for this.
The binding should be in two way from XML to model/adapter or model/adapter to XML.
To get detailed documentation refer these link1 & link2.
Related
I have a TextView that I want to change the position by other view.
For example,
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/tv"
...
app:layout_constraintBottom_toTopOf="#id/btn_to_follow"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
I have a button btn_to_follow, btn_to_follow2, btn_to_follow3.
So, I'd like to change app:layout_constraintBottom_toTopOf attribute dynamically with those three values.
I have a data class for state.
data class ButtonState(type: String){
val followPosition = R.id.btn_to_follow
}
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/tv"
...
app:layout_constraintBottom_toTopOf="#{vm.followPosition}"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
I tried to change followPosition with 'R.id.xxx' values which are int values. It didn't work.
So, How can I approach what I expect??
I think what you are missing is "Live" portion of it.
app:layout_constraintBottom_toTopOf="#{vm.followPosition}"
It works, it does get the value from the followPosition in the ViewModel where followPosition is an Int.
But there isn't any implicit way how it gets updated. View was inflated and it no longer cares what is in followPosition.
You need followPosition to be LiveData<Int> or the parent data class to be stored in LiveData.
Surely a simple question to ask but I've tried for hours and I can't seem to get the problem !
I have a DialogFragment which contains a
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/interval_input_layout"
style="#style/Widget.MaterialComponents.TextInputLayout.FilledBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:boxStrokeWidth="0dp">
<AutoCompleteTextView
android:id="#+id/time_interval_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"
tools:ignore="LabelFor" />
</com.google.android.material.textfield.TextInputLayout>
This is the listener set on this AutoCompleteView
binding.untilInput.onItemClickListener =
AdapterView.OnItemClickListener { parent, view, position, id ->
when (position) {
0 -> {
binding.numberLayout.visibility = View.GONE
}
1 -> {
binding.numberLayout.visibility = View.GONE
}
2 -> {
binding.numberLayout.visibility = View.VISIBLE
}
}
}
While number layout is just a linear layout like this
<androidx.appcompat.widget.LinearLayoutCompat
android:id="#+id/number_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="16dp">
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="For number of events"
android:textColor="?android:textColorSecondary" />
</androidx.appcompat.widget.LinearLayoutCompat>
At first the listener wasn't being called so I set it like this instead of directly calling the function setOnItemClickListener (don't know why that wasn't working) , Now the listener is being called , I even put a breakpoint and debugged it and its setting visibility but its not taking any effect and it does not cause any error so I can't seem to get the problem here
Using a DialogFragment I inflated the view layout with the use of databinding in the method OnCreateView & then in OnViewCreated I worked with my view and caused changes to UI and set listeners to it and again cause changes to UI
in OnCreateDialog I called both of these methods onCreateView (to get the view and set it to dialog) and onViewCreated to setup the view
I tried to find the view using findViewById just to ensure if the databinding was working fine and it was working fine but visibility still won't change and the view won't update !
I still have't gotten to the root of the problem but it seems you have to store the view inside a variable so you don't lose a reference to it when onClickListener is called and in my opinion just when the onClickListener is called getting the view from the binding becomes invalid and it should but the small problem is that I am not calling onDestroy to make the binding null because the binding was invalid at that time !
Now I am just storing a refrence to binding like this
val myvar = binding.monthLayout
and I am capturing the value of myvar inside the onClickListener rather than using the binding
I've successfully made my first bind adapter and I wish to know a bit more about it.
I want to know how to make an attribute that can get only specific strings for a different state for my view.
For example every view has the visibility attribute that it can be"gone", "visible", "invisible"
<TextView
android:id="#+id/loading_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="#id/inventory_items_recycler"
app:layout_constraintEnd_toEndOf="#+id/inventory_items_recycler"
app:layout_constraintStart_toStartOf="#id/inventory_items_recycler"
app:layout_constraintBottom_toBottomOf="#id/inventory_items_recycler"
android:textSize="18sp"
android:visibility="gone"
app:item_id="#{ItemID.BLACK_GLOVES.ordinal()}"
/>
I've made a custom attribute called item_id that get a number that represent enum value. And in my binding utils I have this code:
#BindingAdapter("item_id")
public static void setItemName(TextView tv, int itemId) {
tv.setText(ItemData.get(ItemID.values()[itemId]).getName());
}
I prefer to have something similar to the visibility attribute that it value can be either "visible", "invisible" or "gone"
Bonus::
I wish android studio can auto-complete me for the possibilities that I can use.
You could pass directly the enum to your binding adapter, instead of converting it first to an int and than back to enum.
#BindingAdapter("item_id")
public static void setItemName(TextView tv, ItemID itemId) {
..
}
Then you could pass directly the enum in your xml:
app:item_id="#{ItemID.BLACK_GLOVES}"
This way you'll have a limited number of possibilities to enter and will be less likely to accidentally enter a meaningless integer.
However, binding adapters and custom attibutes are different. With a binding adapter, you still need to use the syntax of binding expression, ie: "#{ }".
android:visibility , on the other hand, is an attribute. You can also define custom attributes for your custom views and get something similar (have a limited number of input options and IDE shows you your options etc). But you shouldn't confuse that with binding adapters. These are two different concepts.
Try this:
#BindingAdapter("isGone")
#JvmStatic
fun View.setVisibility(isGone: Boolean) {
if (isGone) this.visibility = View.GONE else View.VISIBLE
}
Inside your xml:
<com.google.android.material.checkbox.MaterialCheckBox
android:id="#+id/cb_class"
style="#style/TextStyleNormal.White"
android:layout_marginStart="#dimen/margin_large"
isGone="#{isSharedDailyActivity}"// it take boolean value
app:buttonTint="#color/white"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:useMaterialThemeColors="true" />
Consider the following code:
binding adapter:
#BindingAdapter("visibility")
fun setVisibility(view: View, shouldBeVisible: Boolean) {
view.visibility = if (shouldBeVisible) View.VISIBLE else View.GONE
}
what's the difference between using bind namespace like this:
<TextView
android:id="#+id/text_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
bind:visibility="#{mainViewModel.showTextView}"/>
and using app namespace like this:
<TextView
android:id="#+id/text_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:visibility="#{mainViewModel.showTextView}"/>
they both work in my code.
App Name-Space
The app namespace is not specific to a library, but it is used for all attributes defined in your app, whether by your code or by libraries you import, effectively making a single global namespace for custom attributes - i.e., attributes not defined by the android system.
In this case, the appcompat-v7 library uses custom attributes mirroring the android: namespace ones to support prior versions of android (for example: android:showAsAction was only added in API11, but app:showAsAction (being provided as part of your application) works on all API levels your app does) - obviously using the android:showAsAction wouldn't work on API levels where that attribute is not defined.
Bind
Bind is use for custom setter in android data binding. For details check below
You simply need to annotate a static method with the BindingAdapter annotation. This Annotation takes a string as a parameter. The string is the custom attribute, this static method will be bound to. Avoid to add the namespace in the annotation parameter as it will make the binding flaky. The first parameter of the method is the View object to
apply the function to, and the second parameters is the value retrieved from layout XML.
#BindingAdapter("progressColor")
public static void setProgressBarColor(ProgressBar loader, int color) {
if (loader != null) {
loader.getIndeterminateDrawable()
.setColorFilter(color, android.graphics.PorterDuff.Mode.SRC_IN);
}
}
Fore more details
<ProgressBar
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminate="true"
bind:progressColor="#{#android:color/holo_green_dark}"
/>
Details about Binding Adapter check below:
https://developer.android.com/topic/libraries/data-binding/binding-adapters
They are the exact same, you just gave them different names in your XML file. Your bind namespace probably looks like this:
xmlns:bind="http://schemas.android.com/apk/res-auto"
You could also call this bla or whatever. Using bind just makes more clear what it does.
I am using MVVM Cross in Xamarin Studio:
I have a text view, and I want to do something like this:
<TextView
android:id="#+id/title1"
android:layout_toRightOf="#+id/thumb1"
local:MvxBind="FormattedText Item.Description;"
/>
Where Item.Description is set at runtime, and is equal to something like:
"<b>The header</b>\\n\\nThe sub text"
or another formatted string.
I am aware I can do this if the text string is static by using a resource file, but my text is not static.
Bonus points if you give me a solution that would work in Android and iOS XML!
This sounds like a great time to learn about MvvmCross Value Converters.
From the Wiki:
Value Converters in MvvmCross are used to provide mappings to/from
logical values in the view models and presented values in the user
interface.
In this case you can make a new class inherited from MvxValueConverter and override the Convert method. You will do the string formatting inside the Convert method. Then in your binding you can reference the Value Converter and MvvmCross will automatically call the Value Converter before it displays the bound data.
Here is an example Value Converter which takes a float? as input and outputs a formatted currency string:
public class CurrencyValueConverter : MvxValueConverter<float?, string>
{
protected override string Convert(float? value, Type targetType, object parameter, CultureInfo culture)
{
return !value.HasValue ? null : string.Format(culture, "{0:C}", value.Value);
}
}
Then inside the AXML you can reference the Value Converter using the following syntax:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="8dp"
android:text="[CashOnDeliveryCharges]"
local:MvxBind="Text Currency(CashOnDeliveryCharges), FallbackValue='N/A'" />
For those of you who require simple formatting such as adding dashes to phone numbers etc, see Trevor's excellent and informative answer above.
If you need things like bold, underline etc, and you're working cross platform in MVVM Cross but without the aid of Xamarin forms, you'll need to use raw XML and android:layout_toRightOf="#+id/the_id_of_the_previous_element" in combination with things like android:textStyle="bold"
E.g
<TextView
android:id="#+id/id1"
android:textStyle="bold"
local:MvxBind="Text YourPropertyToBind;"
/>
<TextView
android:id="#+id/id2"
android:layout_toRightOf="#+id/id1"
android:text="the_second_bit_of_text_this_is_static_not_bound_if_you_want"
/>
<TextView
android:id="#+id/id3"
android:layout_toRightOf="#+id/id2"
android:textStyle="bold"
local:MvxBind="Text The_Next_Bit_Of_Text;"
/>