I am getting started for using DataBinding and something is wrong with my onClick.
GameViewModel.java
public void onClickItem(int row, int col){
Log.d("click","row: "+row+" col: "+col);
}
#BindingAdapter("load_image")
public static void loadImage(ImageView view,int imageId) {
view.setImageResource(getDrawable(imageId));
}
GameFragment.java
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//View view=inflater.inflate(R.layout.fragment_game, container, false);
FragmentGameBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_game, container, false);
View view = binding.getRoot();
ButterKnife.bind(this,view);
binding.setGameViewModel(gameViewModel);
gameViewModel= ViewModelProviders.of(getActivity()).get(GameViewModel.class);
gameViewModel.init();
return view;
}
fragment_game.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"
tools:context=".view.GameFragment">
<data>
<import type="android.support.v4.app.Fragment"/>
<import type="android.view.View"/>
<variable
name="gameViewModel"
type="harkor.addus.viewmodel.GameViewModel" />
</data>
<android.support.constraint.ConstraintLayout
(...)>
<TextView
(...)>
<android.support.constraint.ConstraintLayout
(...)>
<ImageView
android:id="#+id/image_puzzle11"
android:src="#android:color/holo_green_dark"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginEnd="1dp"
android:layout_marginRight="1dp"
android:onClick="#{() -> gameViewModel.onClickItem(1,1)}"
app:load_image="#{0}"
app:layout_constraintBottom_toTopOf="#+id/image_puzzle21"
app:layout_constraintDimensionRatio="w,1:1"
app:layout_constraintEnd_toStartOf="#+id/image_puzzle12"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
(...)
load_image is working, but onClick do nothing...
No error in compilation, no crash when button is clicking on device, no result in console...
Please check with below code:
You have written as to call on Click of image as :
android:onClick="#{() -> gameViewModel.onClickItem(1,1)}"
Try to write as below and check again :
android:onClick="#{(v) -> gameViewModel.onClickItem(1,1)}"
As per the Guidance This is not the way to achieve the Architecture Principles we can work as below as per the MVVM Architecture:
1. Create an Interface
2. Define Interface as handler inside the Layout File as :
<variable
name="handler"
type="com.cityguide.interfaces.MustVisitItemListener"></variable>
3.Now we are using this handler to define onclick as :
android:onClick="#{(v) ->handler.onGalleryItemClick(v,currentPosition,photo)}"
Implement the Handler with our java Class or Activity class before bind the Handler with View as below:
private MustVisitItemListener mItemListener;
mItemListener = new MustVisitItemListener() { };
5.Set the Interface handler with bind object as below:
mbinding.setHandler(mItemListener);
The easiest way is to set the view model and calling the proper method in the View's onClick from the layout:
Your xml:
<data>
<variable
name="viewModel"
type="co.package.MyViewModel" />
</data>
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/my_id"
android:layout_width="#dimen/full_width"
android:layout_height="wrap_content"
android:onClick="#{() -> viewModel.doSomething()}" />
But if for any reason you need to call a method from your fragment or activity, the best suggestion is to create an interface to handle the method, implement the method and set it to the layout as follows:
Your xml
<data>
<variable
name="myHandlers"
type="co.package.MyHandlersListener" />
</data>
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/my_id"
android:layout_width="#dimen/full_width"
android:layout_height="wrap_content"
android:onClick="#{() -> myHandlers.doSomething()}" />
And within your Fragment or Activity you create the interface and then implement it:
Your activity/fragment:
/* My Handler Methods */
interface MyHandlersListener {
fun doSomething()
}
Then implement the listener, taking into consideration that the method something is defined and implemented within your activity/fragment class:
private val myHandlersListener: MyHandlersListener = object : MyHandlersListener {
override fun doSomething() {
something()
}
}
And using databinding, you can set the handler into your layout class (this can be done within the onCreate or onCreateView method depending if you are using and activity or fragment respectively):
myBinding.myHandlers = myHandlersListener
In this way it works perfectly and you follow the guide given by Android's team:
https://developer.android.com/topic/libraries/data-binding/expressions#method_references
Related
I got question for implementing same data binding layout in 2 fragment.
Example :
fragment_example.xml it will use by ExampleBasicToolbarFragment and ExampleToolbarImageFragment. in each fragment have onclick function.
How to achieve that?
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="toolbarFragment"
type="example.toolbar.ExampleBasicToolbarFragment" />
<variable
name="imageToolbarfragment"
type="example.toolbar.ExampleToolbarImageFragment" />
</data>
<id.co.cicil.libs.core.view.viewstate.ViewState
style="#style/Layout.Match"
android:id="#+id/viewState">
<LinearLayout
android:id="#+id/linear"
style="#style/Layout.Match"
android:orientation="vertical"
android:gravity="center">
<androidx.appcompat.widget.AppCompatTextView
style="#style/TextContent.Normal"
android:text="INI FRAGMENT"/>
<com.google.android.material.button.MaterialButton
android:id="#+id/btnSnackbar"
style="#style/Button.Orange"
android:text="coba snackbar"
app:safeOnClick="#{**HOW I PASS MY FUNCTION TO THIS??**}"/>
</LinearLayout>
</id.co.cicil.libs.core.view.viewstate.ViewState>
</layout>
Use abstraction principle if you need the ability to generalize several classes by extending an interface or abstract class.
<data>
<variable
name="customInterface"
type="example.toolbar.CustomInterface" />
</data>
...
<com.google.android.material.button.MaterialButton
android:id="#+id/btnSnackbar"
style="#style/Button.Orange"
android:text="coba snackbar"
app:safeOnClick="#{() -> customInterface.performClick()}"/>
This interface will force extending classes to implement certain behaviour (certain methods). An example:
package example.toolbar
interface CustomInterface {
void performOnClick();
}
Both of your fragments now can implement this interface. You will be able to pass the interface instance into view binding instance. An example:
class ExampleBasicToolbarFragment extends Fragment, CustomInterface {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// create view binding instance
viewBinding.customInterface = this
}
#Override
void performOnClick() {
...
}
}
class ExampleToolbarImageFragment extends Fragment, CustomInterface {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// create view binding instance
viewBinding.customInterface = this
}
#Override
void performOnClick() {
...
}
}
I want to handle clicks in my recyclerview rows and currently pass my presenter to my viewholder bindings.
Presenter interface:
interface IMainPresenter{
public void showDetail(Pojo pojo);
}
Viewholder:
class ViewHolder(itemView: View, val parent: ViewGroup?, private val binding: ListMainBinding) : RecyclerView.ViewHolder(itemView) {
fun bind(pojo: Pojo, mainPresenter: IMainPresenter) {
binding.pojo = pojo
binding.mainPresenter = mainPresenter
binding.executePendingBindings()
}
}
and in my layout I call a method of my presenter on the onClick attribute
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<import type="android.view.View" />
<variable
name="mainPresenter"
type="com.noisyninja.androidlistpoc.views.main.IMainPresenter" />
<variable
name="pojo"
type="com.noisyninja.androidlistpoc.model.Pojo" />
</data>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/black"
android:onClick="#{(view) -> mainPresenter.showDetail(pojo)}"
...
...
...
I think passing the presenter is not a good design, but what is a better approach to handle clicks from rows which trigger a method in my presenter?
IMHO a better approach would be to add OnItemClickListener to RecyclerView like here, and call
ItemClickSupport.addTo(recyclerView).setOnItemClickListener{
recycler, position, v ->
mainPresenter.showDetail(recycler.getAdapter().getItem(position))
}
during the setup of RecyclerView.
I want to call onButtonclick(View v) using android binding in the layout xml file.
How to achieve button click using android binding in this case?
I followed as below but it didn't work. Any suggestions ? Thanks in Advance.
Layout1.xml
<data>
<variable
name="myFrag"
type="com.myapp.Fragment1" />
</data>
...
<Button
android:id="#+id/step_button"
style="#style/button_style"
android:onClick="#{myFrag :: onButtonclick}"/>
Fragment1.java
public class Fragment1 extends Fragment {
.....
public void onButtonclick(View v)
{
myStdent.setId("No ID");
}
.....
}
Layout1Binding myBinding = DataBindingUtil.inflate(inflater, R.layout.layout1.xml, container, false);
myBinding. setMyFrag(this);
Above lines of code has solved the problem. I have added these lines in onCreateView.
I think you are missing
http://prntscr.com/fm6yih (init variable for databinding object)
In your fragment layout xml, define a fragment variable. Then in your fragment class, map the xml variable to the fragment using binding.setVariable. Now you should be able to use all fragment variables and methods in xml.
In fragment layout xml:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.example.MyFragment">
<data>
<variable
name="frag"
type="com.example.MyFragment" />
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="#{() -> frag.yourMethod()}" />
</RelativeLayout>
</layout>
In fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val inflater = context?.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val binding = DataBindingUtil.inflate<ViewDataBinding>(inflater, R.layout.fragment_my, myFragmentContainer, true)
binding.setVariable(BR.frag, this#MyFragment)
}
}
I am trying to implement MVVM in my app with DataBinding library. For simple tasks that I have accomplish I can find the way out, but problem is that I cannot finish activity after some action.
Problem:
After receiving specific broadcast I have to close activity from ViewModel class. Since VM class doesn't have reference of the View, how can I finish the activity?
To be precise, I have splash screen and corresponding VM class for it that starts IntentService for downloading a data. After the data has been downloaded I have to finish the splash screen and start MainActivity. I have found the way to start new Activity from VM, but to finish the previous one is the mystery.
Can you please help me?
Thanks!
Create a SplashStatus model with a ObservableBoolean:
private static class SplashStatus {
public final ObservableBoolean isFinished = new ObservableBooelan();
}
Here is your Splash layout:
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="status" type="com.example.SplashStatus"/>
</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:text="Splash Screen"
android:onFinish="#{status.isFinished}"/>
</LinearLayout>
</layout>
And binding adapter method:
#BindingAdapter("android:onFinish")
public static void finishSplash(View view, boolean isFinished) {
if(isFinished){
((Activity)(view.getContext())).startActivity(new Intent(view.getContext(), MainActivity.class))
((Activity)(view.getContext())).finish();
}
}
In the SplashActivity.java init your data binding on onCreate. Whenever you assign isFinished.set(true) onFinished method will start your MainActivity and finish current.
if you want just to finish() the activity from layout with databinding:
android:onClick="#{(view)->((Activity)(view.getContext())).finish()}"
if the databinding owner is fragment, may choose another method
At first, define your ViewModel class
public static class ViewModel extends AndroidViewModel {
public ViewModel(#NonNull Application application) {
super(application);
}
public void onFinish(View v) {
((DialogFragment) DataBindingUtil.findBinding(v).getLifecycleOwner()).dismissAllowingStateLoss();
}
}
then in your databinding layout
<data>
<variable
name="viewModel"
type="com.xxxx.settingitemmoretest.MainActivity.ViewModel" />
</data>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".BlankFragment">
<!-- TODO: Update blank fragment layout -->
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Exit"
android:onClick="#{viewModel::onFinish}"/>
</FrameLayout>
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