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>
Related
I have a test project where I want to bind the press of a button to trigger a function via the DataBinding Libray and add:command.
Unfortunately, I'm getting the error:
Found data binding errors.
****/ data binding error ****msg:Could not resolve com.example.ckleineidam.testproject.ViewModel.testButton as an accessor or listener on the attribute.
MainActivity:
public class MainActivity extends AppCompatActivity {
ViewModel mModel;
ActivityMainBinding binding;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_main);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
mModel = new ViewModel(this);
binding.setViewModel(mModel);
}
}
ViewModel:
public class ViewModel extends BaseObservable {
private static final String TAG = "VIEW_MODEL";
private Context mActivity;
public ViewModel(Context context) {
this.mActivity=context;
}
public void testButton(){
Log.i(TAG, "Button Click");
}
}
activity_main.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=".MainActivity">
<data>
<variable
name="ViewModel"
type="com.example.ckleineidam.testproject.ViewModel" />
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="title"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/activation_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Test Button"
android:background="?android:attr/selectableItemBackground"
app:command="#{ViewModel.testButton}"
app:layout_constraintTop_toBottomOf="#id/title" />
</android.support.constraint.ConstraintLayout >
</layout>
The code is also as an example project at Github.
You are getting this error because there is no attribute app:command on a button.
If you are trying to achieve an onClick functionality, you can use android:onClick="#{ViewModel.testButton}" and change your function signature to void testButton(View view).
To use custom attributes, you need to define a binding adapter
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
My question is simple but I can't seem to do what I want to.
So In my activity I have this method.
public void performButtonClick(View view)
{
Log.i("INTRO", "OK");
}
Which is called from a button click event defined in xml like so
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="performButtonClick"
android:text="#string/start"/>
Now I've heard that this approach is using reflection which slows down performance so I'm trying to replace the onClick event with databinding.
I tried various combinations like
android:onClick="#{performButtonClick}"
or
android:onClick="#{(v) -> performButtonClick(v)}"
or
android:onClick="#{(v) -> MainActivity::performButtonClick}"
but None of these worked.
Could you help me out?
Ok answering to my own question.
I am new to DataBinding but I do not understand why I have to use copy-pasted solutions which make use of an accessory Handlers class and end up with more boilerplate code than I used to have.
All I wanted is an equivalent to android:onClick="performButtonClick" So here is my solution:
activity_main.xml
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="activity"
type="com.example.mydatabinding.MainActivity"/>
</data>
<LinearLayout
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/bg"
android:orientation="vertical">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="#{activity::performButtonClick}"
android:text="#string/start"/>
</LinearLayout>
MainActivity.java
public final class MainActivity extends AppCompatActivity
{
ActivityMainBinding binding;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setActivity(this);
}
public void performButtonClick(View view)
{
Log.i("INTRO", "OK");
}
}
Hi I'm giving a try on new Architecture Components for Android, I've built a simple app and now I want to bind a Texview to a variable in my ViewModel, I did this before but I wasn't using the Components. now The getter is not called and the value of the TextView is not changed.
MainActivity:
public class MainActivity extends AppCompatActivity implements LifecycleOwner {
public MainViewModel viewModel;
MainViewModel:
public class MainViewModel extends ViewModel {
private LatLng origin;
public LatLng getOrigin() {
return origin;
}
activity_main.xml:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context="jacopo.com.carbook.MainActivity">
<data>
<variable name="viewModel" type="jacopo.com.carbook.MainViewModel" />
</data>
<TextView
android:id="#+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{Double.toString(viewModel.origin.latitude)}"/>
</layout
dataBinding is enabled in the gradle file so I'm not sure what's going on here as it's the same code of the previous version of the app.
did you add this lines to your activity (according ti your file's names):
binding = DataBindingUtil.setContentView(this, R.layout.activity_xml);
viewModel = ViewModelProviders.of(this).get(ActivityViewModel.class);
binding.setLifecycleOwner(this);
binding.setViewModel(viewModel);
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