I currently started to use the Data Binding Library and in my viewmodel I have an extended LiveData object:
public class ScannerViewModel extends AndroidViewModel {
/** MutableLiveData containing the scanner state to notify MainActivity. */
public ScannerLiveData scannerLiveData;
ScannerLiveData:
public class ScannerLiveData extends MutableLiveData<ScannerLiveData> {
public boolean mScanningStarted;
public boolean mBluetoothEnabled;
public boolean mLocationEnabled;
public ScannerLiveData(final boolean bluetoothEnabled, final boolean locationEnabled) {
mScanningStarted = false;
mBluetoothEnabled = bluetoothEnabled;
mLocationEnabled = locationEnabled;
postValue(this);
}
...
}
activity_scanner.xml:
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/layout_test">
<data>
<import type="android.view.View" />
<variable
name="livedata"
type="de.datacollect.ecount.ui.scanner.ScannerLiveData" />
</data>
...
<android.support.constraint.ConstraintLayout
<ProgressBar
android:id="#+id/state_scanning"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="-7dp"
android:layout_marginTop="-8dp"
android:indeterminate="true"
android:indeterminateTint="#android:color/white"
android:visibility="#{livedata.mBluetoothEnabled ? View.VISIBLE : View.INVISIBLE}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/recycler_view_ble_devices" />
...
ScannerActivity:
#Override
protected void onCreate(#Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityScannerBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_scanner);
// Create view model containing utility methods for scanning
mScannerViewModel = ViewModelProviders.of(this).get(ScannerViewModel.class);
mScannerViewModel.getScannerState().observe(this, this::startScan);
binding.setLifecycleOwner(this);
binding.setLivedata(mScannerViewModel.scannerLiveData);
...
How can I bind to the mBluetoothEnabled property? I get an unclassified error on build. I already used the search but didn't find something about it. Every help would be appreciate.
Btw:
Updates to Data Binding:
You can now use a LiveData object as an observable field in data binding expressions. The ViewDataBinding class now includes a new setLifecycle method that you need to use to use to observe LiveData objects.
Instead of binding the LiveData object bind your viewmodel. So change livedata variable in activity_scanner.xml to
<variable
name="viewModel"
type="de.datacollect.ecount.ui.scanner.ScannerViewModel" />
and in ScannerActivity
binding.setViewModel(mScannerViewModel);
Then your binding expression will become
android:visibility="#{viewModel.scannerLiveData.mBluetoothEnabled ? View.VISIBLE : View.INVISIBLE}"
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 have a simple databinding setup:
My ViewModel:
public class MyViewModel {
public ObservableField<Integer> viewVisibility = new ObservableField<>(View.VISIBLE);
public void buttonClicked() {
if (viewVisibility.get() == View.GONE) {
viewVisibility.set(View.VISIBLE);
} else {
viewVisibility.set(View.GONE);
}
}
}
and the layout:
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="viewModel"
type="com.example.fweigl.playground.MyViewModel" />
</data>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="64dp">
<View
android:visibility="#{viewModel.viewVisibility}"
android:layout_width="200dp"
android:layout_height="200dp"
android:background="#00ff00" />
<Button
android:text="click me"
android:onClick="#{() -> viewModel.buttonClicked()}"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</layout>
As you can see, every click on the button switches the ObservableField<Integer> viewVisibility on the viewmodel, which in turn switches the visibility of a green rectangle. This works fine.
Now I want to do the same but using an interface as a viewmodel:
public interface IMyViewModel {
public void buttonClicked();
public ObservableField<Integer> viewVisibility = new ObservableField<>(View.VISIBLE);
}
the viewmodel:
public class MyViewModel implements IMyViewModel {
#Override
public void buttonClicked() {
if (viewVisibility.get() == View.GONE) {
viewVisibility.set(View.VISIBLE);
} else {
viewVisibility.set(View.GONE);
}
}
}
and in the layout, I import the interface instead of the implementation:
<data>
<variable
name="viewModel"
type="com.example.fweigl.playground.IMyViewModel" />
</data>
What works is the binding for the button click, buttonClicked is called and the value of viewVisibility is changed.
What doesn't work is the changing of the green rectangle view's visibility. Changes of the viewVisibility value are not reflected in the layout.
Am I doing something wrong or does databinding not (fully) work with interfaces as viewmodels?
Id you'd wrap whatever variable you'd like to bind to your view in a LiveData<>, Android will automatically unbox the data and bind it to the view
Data binding needs getter and setter to make work done, it does not access your field directly. So this will also not work without getter setter
public class MyViewModel {
public ObservableField<Integer> viewVisibility = new ObservableField<>(View.VISIBLE);
public void buttonClicked() {
if (viewVisibility.get() == View.GONE) {
viewVisibility.set(View.VISIBLE);
} else {
viewVisibility.set(View.GONE);
}
}
}
So because interface does not have getter setter, so they can not be used as model.
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
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);