About creating own attributes for pre-existing Widgets like Edittext, TextView - android

Help! I want to know whether we can create own custom Attributes for pre-existing android components like EditText, TextView, AutoCompleteTextView, MultiAutoCompleteTextView etc.
Using XML I need to implement a custom attribute/property to an autocompletetextview so that it do not show autocomplete suggestions if property is set false, and vice versa.

Yes you can. There is this thing that is called binding adapters, and you could use those as new xml attributes. Well, ofcourse, you have to enable data binding on your project to make the binding adapters to work.
Read more here: https://developer.android.com/topic/libraries/data-binding/binding-adapters
Bonus: If you are using kotlin, you could instead make these binding adapters into extension functions such that you could use them as an extension function for your objects.
Update
To xml attributes for pre-existing widgets, you first need to define a custom binding adapter. Here is an example of a custom binding adapter:
// This will change the text views background color and text when it is tapped
#BindingAdapter("changeBackgroundAndTextOnTap")
public static void changeBackgroundAndTextOnTap(final TextView view, boolean shouldChange) {
// The first parameter is the type of view this xml attribute will be available to
// The second is the value you will receive from the xml attribute
if (shouldChange) {
view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
view.setBackgroundColor(Color.HSVToColor(new float[]{(int)Math.round(Math.random() * 360), 0.8f, 0.4f}));
view.setText("" + (Math.random() * 10000000000L));
}
});
}
}
But before we could use this, we should first tell android that we are using data binding, so in your app level build.gradle file, add this line:
android {
...
dataBinding {
enabled true
}
...
}
Next, to make data binding work on your xml files, you first have to wrap your layouts inside tags, like so:
<?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">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Don't Click Me!"
android:gravity="center"
android:textSize="32sp"
android:padding="24dp"
android:textColor="#dedede"
android:background="#000000"
tools:context=".MainActivity" />
</layout>
Then, on your activity, or fragment, you should set content view using databinding util:
private ActivityMainBinding mBinding; //Optional
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
}
Now that you are all set, you could now use your custom xml attribute a.k.a. data binding adapter on your layouts 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"
xmlns:tools="http://schemas.android.com/tools">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Don't Click Me!"
android:gravity="center"
android:textSize="32sp"
android:padding="24dp"
android:textColor="#dedede"
android:background="#000000"
changeBackgroundAndTextOnTap="#{true}" // Note: the #{} is necessary
tools:context=".MainActivity" />
</layout>
For an example project, here is a github repo:
https://github.com/jianastrero/Android-Data-Binding-Example-In-Java

Related

How to pass contents of other layout Views into presenter in onclick with data binding?

Basically, how do I do this:
android:onClick="#{()->presenter.login(username.getText(), password.getText())}"
where username and password are EditText views in the layout whose contents I want to pass to the presenter. Is it necessary to set up two-way data binding to do this, or is there a way to refer to the contents of those other views within the layout?
I wonder if one way to do it is to enable two-way data binding and use a view model, e.g. LoginViewModel with fields for the username and password, set this as a variable on the, pass the whole thing to the login presenter when the form is submitted, and read it out of there.
Fortunately, you can access the text values from EditText because it supports two-way. You can do this:
android:onClick="#{()->presenter.login(username.text, password.text)}"
Unfortunately there is no way to access the values ​​of the views within the layout itself using Databinding. The only way to do this is set these values inside variables in your layout file and access them using your presenter. eg:
Activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.your_layout);
String username = mBinding.editText.getText().toString();
String password = mBinding.editText.getText().toString();
mBinding.setUserName(username);
mBinding.setpassword(password);
}
Layout:
<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="name"
type="java.lang.String" />
<variable
name="password"
type="java.lang.String" />
<Button
android:onClick="#{()->presenter.login(username, password)}"/>
Not exactly you are looking for but yes there is a similar way.
You can pass whole EditText in presenter and get a text from it.
android:onClick="#{()->presenter.login(edtUsername, edtPassword)}"
and inside your presenter
public void login(EditText edtUsername, EditText edtPassword)
{
}
You can access views in the same layout by their id:
<EditText android:id="#+id/username"
... />
<EditText android:id="#+id/password"
... />
<Button android:onClick="#{v->presenter.login(username.getText(), password.getText())}"
... />

Android DataBinding & MVVM - Using same name for layout files in different layout folders

I've been developing an app with data binding & MVVM.
I'm trying to use an alternative layout for my app on landscape mode. I have:
layout/fragment_content.xml
layout-land/fragment_content.xml
Both layouts have same views with different look, and get feeds from same view models, like this:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data class="MyBinding">
<variable
name="viewModel"
type="com.myapp.package.viewModel.VMFirst"/>
<variable
name="controlModel"
type="com.myapp.package.viewModel.VMSecond"/>
</data>
<DIFFERENT CONTENT HERE>
All the views and id's exist in both layouts.
Well, problem is, it doesn't compile, error is simply "cannot find symbol method getViewModel" and getter for the other variable.
What I tried so far:
Using layout and layout-land folders ( Failed, error is explained above )
Using layout aliases Use Layout Aliases which I found here Issue 199344: Data binding does not work with layout aliases. I didn't change anything in xml files while trying this approach. This also failed, error is Could not write to com.myapp.package.databinding.MyBinding
Is it not possible to use data binding data tag in multiple layout files ? What should I use to use different layouts for different states while using data binding ? Thanks !
Edit: deleting class="MyBinding" did not change errors.
If anyone searches for this question, after 2 years I tried to do the same, and I saw it's working all fine now.
I created a layout file activity_main under layout and layout_sw600dp. Here's the layout under layout resources:
<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">
<variable
name="small_variable"
type="Integer"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/myRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<View
android:id="#+id/small_square"
android:layout_width="60dp"
android:layout_height="60dp"
android:background="#android:color/holo_blue_bright"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
This one is the layout under layout_sw600dp folder:
<?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">
<variable
name="big_variable"
type="Long"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/myRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<View
android:id="#+id/big_square"
android:layout_width="60dp"
android:layout_height="60dp"
android:background="#android:color/holo_blue_bright"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Both has a view but it has different id in each: small_square and big_square.
I run the project on phone & tablet. Here are my findings:
DataBinding creates an implementation that contains ALL views and variables under all layout files of same name in different layout folders.
Views that exists in all layouts are not nullable, all others are nullable. In above XML's, myRoot is not a nullable view when using binding from Kotlin, while big_square and small_square are nullable views. Variables are nullable whether or not they exists in all layouts ( which is expected behaviour ).
You cannot name binding classes different in each file. It has to be same ( MainBinding in above examples, or if you don't define it LayoutResourceName + Binding by default ).
Names for views and variables on binding implementation are camel case. So my small_variable & small_square was binding.smallVariable and binding.smallSquare on code side.
With Kotlin, you can just use views like binding.bigSquare?.operation, which is great that you don't need to check if it's tablet or phone or view is null or not beforehand.
Just a tip, you can assign binding fields even if layout that they are in won't be used. You can still say binding.smallVariable = 3 on code and it'll do the assignment and save the value. I think it's good to be careful.
I heavily use MVVM in my apps and am also building a library around it.
I follow the convention that there is a single ViewModel in every XML. Also, the name of the viewmodel variable is same in all XMLs.
So, in your case, you can create another ViewModel class that contains VMFirst and VMSecond.
public class ParentVM {
VMFirst first;
VMSecond second;
}
Both the XMLs (portrait and landscape) will have same names, say activity_main.xml.
<layout>
<data>
<variable
type="ParentViewModel"
name="vm"/>
</data>
Then no check is required in MainActivity code.
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewDataBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setVariable(BR.vm, new ParentViewModel());
}
This works.
Advantages of single ViewModel
In fact, because I follow same variable name throughout all xmls, I am able to include the binding logic in a base class MvvmActivity itself. So, all my activities look like:
public class MainActivity extends MvvmActivity {
#NonNull
#Override
protected ViewModel createViewModel() {
return new MainViewModel();
}
#Override
protected int getLayoutId() {
return R.layout.activity_main;
}
}
MvvmActivity implementation: MvvmActivity.java
Another advantage of keeping a constant data binding variable is that you can setup RecyclerView or ViewPager adapters in XML itself. See Setup RecyclerView from XML for more details.
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 main_activity.xml so the generate class was MainActivityBinding. --Binding Data
and generated at compile time.
so, select different layout by java code.
layout/
R.layout.activity_main
R.layout.activity_main_tablet
values/
<bool name="is_mobile">true</bool>
<bool name="is_tablet">false</bool>
values-w820dp/
<bool name="is_mobile">false</bool>
<bool name="is_tablet">true</bool>
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(getResources().getBoolean(R.bool.is_mobile)) {
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
} else {
ActivityMainTabletBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main_tablet);
}
}

Android dataBinding - #BindingAdapter custom app namespace being ignored

I have created a custom bindingAdapter in android and when i pass in the color i want the color to change, this is actually for a test im working on just to make sure it works. Here is the code:
here is my view Model for the data binding:
public class User {
public ObservableInt visible;
public User(int visible) {
this.visible=new ObservableInt(visible);
}
#BindingAdapter({"app:bindColor"}) //notice the bindColor custom attribute
public static void setTextColor(TextView view,String color) {
if("green".equals(color))
view.setTextColor(Color.parseColor("#63f421"));
}
}
Now in my xml file which is binded to this model im expected to pass in a color so that the setTextColor method can use it:
<?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 class="MainActivityBinder">
<variable name="user" type="com.example.android.floatingactionbuttonbasic.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/tv_one"
android:text="my first textview"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/tv_two"
android:text="my second textview"
android:visibility="#{user.visible}"
app:bindColor="#{'green'}" //see im passing in the green string here
android:textColor="#android:color/holo_green_dark"/>
</LinearLayout>
</layout>
I am getting the error at runtime time of:
Error:(27, 65) error: package com.example.android.floatingactionbuttonbasic.databinding does not exist
Warning:Application namespace for attribute app:bindColor will be ignored.
Error:(24, 33) Identifiers must have user defined types from the XML file. een is missing it
Error:Execution failed for task ':Application:compileDebugJavaWithJavac'.
> java.lang.RuntimeException: Found data binding errors.
if i take out the bindingAdapter stuff it works perfectly with the other databinding stuff. Its just this custom binding thats not working. My project is titled floatingactionbuttonbasic btw.
I was able to get this to work. The issue was how i was passing in the string. It should have the `` around it.
app:bindColor="#{`green`}"
you can also do this :
app:bindColor='#{"green"}'
But what seems to not be allowed is this notation:
app:bindColor="#{'green'}"
i wrote a blog about it to help others if interested.

Android data binding not working with View 'android:tag' property

Trying to use the new Android Data Binding in my project, but I'm getting an error when trying to set the 'android:tag' property to some custom variable.
My menu_item.xml file:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="menuItem"
type="com.example.MenuItem" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:tag="#{menuItem}"
tools:ignore="UseCompoundDrawables">
<!--suppress AndroidUnknownAttribute -->
<ImageView
android:id="#+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:imageResource="#{menuItem.itemType.drawableId}" />
<TextView
android:id="#+id/displayName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{menuItem.itemType.displayNameId}" />
</LinearLayout>
</layout>
My MenuItem class:
public class MenuItem {
public final ItemType itemType;
public MenuItem(ItemType itemType) {
this.itemType = itemType;
}
}
Part of the genetated MenyItemBinding.java:
public MenuItemBinding(View root) {
super(root, 0);
final Object[] bindings = mapBindings(root, 3, sIncludes, sViewsWithIds);
this.displayName = (android.widget.TextView) bindings[2];
this.displayName.setTag(null);
this.icon = (android.widget.ImageView) bindings[1];
this.icon.setTag(null);
this.mboundView0 = (android.widget.LinearLayout) bindings[0];
this.mboundView0.setTag(root.getResources().getString(com.myApp.R.string.#{menuItem}));
setRootTag(root);
invalidateAll();
}
And the error is in the generated class, when trying to set the Tag of the bound view.
Any ideas how to get around this? Preferably, not to use a custom LinearLayout to support this.
That is a bug. We haven't tried data binding tags, mostly because tags are special.
When targeting devices pre-ICS, Android data binding takes over the tag of the outermost element of the layout. This tag is used for mostly for binding lifecycle and is used by DataBindingUtil.findBinding() and DataBindingUtil.getBinding().
So, since data binding isn't working on tags, the only work-around is to not supply a tag to your LinearLayout or supply a fixed tag or resource string. If you are targeting ICS and above, it is valid to reassign the tag after binding the layout:
MenuItemBinding binding = MenuItemBinding.inflate(layoutInflater);
binding.getRoot().setTag(menuItem);
You can also create a BindingAdapter for a new attribute:
#BindingAdapter("specialTag")
public static void setSpecialTag(View view, Object value) {
view.setTag(value);
}
and then use it in your layout:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:specialTag="#{menuItem}"
tools:ignore="UseCompoundDrawables"/>
This will allow you to use findViewByTag() and all of the other things you expect.
However, this will NOT work if you target Honeycomb and earlier devices. There is no getting around that. You may be tempted to do something like this:
#BindingAdapter("specialTag")
public static void setSpecialTag(View view, Object value) {
view.setTag(R.id.app_tag, value);
}
You won't be able to use findViewByTag with that approach, but it will store whatever value you want when you use your view. But the reason we don't use ID'd tags with Honeycomb and earlier is that there is a memory leak when using ID'd tags, so don't do it.
I hope this helps. I'll file a bug internally to support data bound android:tags.

Possibility to add parameters in button xml?

I currently have an activity with some buttons.
In my xml, buttons are defined like this:
<ImageButton (...) android:onClick="GoToPageX"/>
and I have in my activity:
public void GotoPageX() {
startActivity(new Intent(this, PageX.class));
finish();
}
The problem is that I have hundreds of buttons and do not want to write
<ImageButton (...) android:onClick="GoToPage1"/>
<ImageButton (...) android:onClick="GoToPage2"/>
<ImageButton (...) android:onClick="GoToPage3"/>
...
<ImageButton (...) android:onClick="GoToPage100"/>
and all the scripts.
I am now using
public void GotoPage( int i) {
startActivity(new Intent(getBaseContext(), activities.get(i)));
finish();
}
and would like to give the parameter i from the xml, is that possible?
Thank a lot for any help.
It is not directly possible. However, maybe you could use android:tag to get your parameter.
<ImageButton (...) android:onClick="goToPage" android:tag="25"/>
public void goToPage(View v) {
String pageNumber = v.getTag().toString();
/* ... */
}
You could also do this by enabling data binding and using a lambda expression for the onClick value. This way is especially useful if you plan to use multiple inputs of different types. Here's an example of a simple MainActivity.xml in which this strategy is used.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="main" type="com.example.android.myapp.MainActivity" />
</data>
<LinearLayout android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageButton (...) android:onClick='#{() -> main.GotoPage(1,"one")}'/>
<ImageButton (...) android:onClick='#{() -> main.GotoPage(2,"two")}'/>
<ImageButton (...) android:onClick='#{() -> main.GotoPage(3,"three")}'/>
...
<ImageButton (...) android:onClick='#{() -> main.GotoPage(100,"one hundred")}'/>
</LinearLayout>
</layout>
and in MainActivity.java
public void GotoPage(int i, String otherVariable) {
/** code using i and otherVariable **/
}
UPDATE: For those who don't know how to set up data binding, I will explain it here so you don't have to google around for it. First, enable dataBinding in the build.gradle file:
android {
...
dataBinding {
enabled = true
}
...
}
Also, make sure jcenter() is in your repositories.
Then, go to the XML of the layout where onClick will be used and wrap its layout in a layout tag with a data section like this:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="main" type="com.example.android.yourapp.MainActivity" />
</data>
<YourLayout>
...
</YourLayout>
</layout>
For the variable tag's type parameter, you need to put the class that will contain the function which onClick points to. In this example, I will use the main activity class, which is named MainActivity in my test project.
After you have your layout wrapped in a layout tag like in the example above, clean the project in Android Studio. You may also need to invalidate cache/restart or close and reopen Android Studio.
Next, if the the layout with onClick you are trying to set up data binding for is the same layout set by setContentView in your main activity class, open the file that contains your main activity class. If the layout with onClick you are trying to set up data binding for is inflated programmatically in a different file, open the file in which the layout is inflated instead.
Add these imports to that file:
import com.example.android.yourapp.databinding.YourLayoutBinding;
import android.databinding.DataBindingUtil;
That first class you are importing is generated when you clean the project (and possibly have to invalidate cache/restart) and is automatically named after the XML file you added the layout wrapper to. If the layout file is named your_layout.xml, the import class will be named YourLayoutBinding. The exact import path will depend on your app name and structure, but it will always be within a databinding parent class.
The next step depends on whether the layout you are adding data binding to is set with setContentView or is inflated with inflate. Both versions of the following step make use of the method setMain. The setMain method is automatically generated and named using the value of the name parameter in the layout wrapper we added. Since we put name="main", the method is called setMain.
If the layout you are adding data binding to is the same layout set by setContentView find the line in your main activity class that looks like setContentView(R.layout.your_layout); and change it to use DataBindingUtil.setContentView instead of setContentView, adding this as its first argument. Use binding.setMain to point the layout's main variable to the current activity.
YourLayoutBinding binding = DataBindingUtil.setContentView(this, R.layout.your_layout);
binding.setMain(this);
If the layout you are adding data binding to is not set by setContentView but rather inflated go to where it is inflated in your code. It should look something like this:
return inflater.inflate(R.layout.your_layout, container, false);
Modify it to use DataBindingUtil.inflate, adding the previous inflater as its first argument. Use binding.setMain to point the layout's main variable to the main activity, and use binding.getRoot() to get the view. It should end up like this:
YourLayoutBinding binding = DataBindingUtil.inflate(inflater, R.layout.your_layout, container, false);
binding.setMain((MainActivity) getActivity());
return binding.getRoot();
Now the data binding is ready to use. Add a function for onClick to point to within your main activity class.
public void exampleFunction(int number, String text) {
System.out.println("Number: " + number + ", Text: " + text);
}
You can call it from the layout you added data binding to using a lambda expression. This example function doesn't require a View, so it can be used like this:
<Button android:id="#+id/buttonID"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="26sp"
android:text="Test"
android:onClick='#{() -> main.exampleFunction(123, "test")}'/>
Make sure to use single quotes around the value for onClick if you plan on using a String input.
If you do need to pass the button's view to your function, simply add a View parameter to your function's required arguments and use a lambda expression like this instead:
<Button android:id="#+id/buttonID"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="26sp"
android:text="Test"
android:onClick='#{(view) -> main.exampleFunction(view, 123, "test")}'/>
If you will create some layout element in xml you can use there
<ImageButton
android:id="#+id/some_id_value" />
where some_id_value is kind of unique string which will be translate into id which is kept in R.java (better for you- don't change anything there) than in code you can get that id by using
R.id.some_id_value
read a little bit there that's really basics.
You can set Tags for a view. Tags are basically a way for views to have memories.
xml:
<ImageButton
...Other Parameters...
android:id="#+id/Button2"
android:tag="2"
android:onClick="GoToPageX"/>
<ImageButton
...Other Parameters...
android:id="#+id/Button3"
android:tag="3"
android:onClick="GoToPageX"/>
The line android:tag="2" set a tag value of 2(string data type) to Button2
Java file:
General Case:
Inside GoToPageX(View v) function,
use v.getTag() to get the tag value of corresponding view(From which ever view the method was called).
Your case:
Add the method as follows
public void GoToPageX(View v){
int i = Integer.parseInt(v.getTag()); //parseInt converts string to integer
startActivity(new Intent(getBaseContext(), activities.get(i)));
finish();
}

Categories

Resources