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.
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'm beginner to data binding.I'm trying to get spinner items from my view model using data binding and set the values in android:entries ,But the thing is my spinner didn't showing items.
Here is my layout
<layout>
<data>
<import type="com.saddan.sanidadvegetalsyscomed.viewmodel.TableDataViewModel"/>
<variable
name="model"
type="TableDataViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="#ffffff"
android:layout_margin="20dp"
android:padding="5dp"
tools:context=".Fragment.First_Form_Fragment">
<Spinner
style="#android:style/Widget.TextView.SpinnerItem"
android:id="#+id/ext_tipo_inspection"
android:layout_width="320dp"
android:layout_height="#dimen/EditTextFieldHeight"
android:entries="#{model.spinerInspecion}"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Here is my view model class
public class TableDataViewModel extends AndroidViewModel
{
private String TAG=getClass().getSimpleName();
private UserAccessToken userAccessToken;
private SanidadDAO sanidadDAO;
private Context context;
private LiveData<List<String>> spinnerItem;
private LiveData<List<String>> spinerInspecion;
public TableDataViewModel(#NonNull Application application)
{
super(application);
userAccessToken=new UserAccessToken(application);
SanidadVegetalDatabase database = SanidadVegetalDatabase.getDatabase(application);
sanidadDAO = database.getDao();
context=application;
spinerInspecion=getmCommonData("tipoinspeccion");
if(spinerInspecion!=null)
{
Log.d(TAG, "TableDataViewModel: yrs");
}
}
public LiveData<List<String>> getmCommonData(String queryType)
{
spinnerItem=sanidadDAO.getType(queryType);
//Toast.makeText(context, ""+mCommonData, Toast.LENGTH_SHORT).show();
return spinnerItem;
}
public LiveData<List<String>> getSpinerInspecion()
{
return spinerInspecion;
}
public void setSpinerInspecion(LiveData<List<String>> spinerInspecion)
{
this.spinerInspecion = spinerInspecion;
}
#Override
protected void onCleared()
{
super.onCleared();
}
}
and here is my Fragment's onViewCreated method where I initialize the binding
tableDataViewModel= new ViewModelProvider(getActivityNonNull()).get(TableDataViewModel.class);
binding.setModel(tableDataViewModel);
Problem here is you are not setting value to spinerInspecion you are assigning it a new LiveData. This is why observer never get called on the instance. Modify your method as:-
public List<String> getmCommonData(String queryType) {
spinnerItem=sanidadDAO.getType(queryType);
return spinnerItem;
}
and then set value as :-
spinerInspecion.setValue(getmCommonData("tipoinspeccion"));
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}"
I am trying to figure out how to bind a viewmodel to a bottomsheet in a way that I can have the bottomsheet both expand, collapse, and hide using observable fields on the viewmodel.
Thank you!
You should use custom BindingAdapter.
#BindingAdapter("bottomSheetBehaviorState")
public static void setState(View v, int bottomSheetBehaviorState) {
BottomSheetBehavior<View> viewBottomSheetBehavior = BottomSheetBehavior.from(v);
viewBottomSheetBehavior.setState(bottomSheetBehaviorState);
}
Bind it in xml to your view:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
(...)
<android.support.v4.widget.NestedScrollView
android:id="#+id/group_bottom_sheet"
bottomSheetBehaviorState="#{viewModel.bottomSheetBehaviorState}"
android:layout_width="match_parent"
android:layout_height="250dp"
android:background="#android:color/holo_blue_bright"
app:behavior_hideable="true"
app:behavior_peekHeight="50dp"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior"/>
(...)
</layout>
And change state in ViewModel. Related code from my ViewModel:
public final ObservableInt bottomSheetBehaviorState = new ObservableInt(BottomSheetBehavior.STATE_HIDDEN);
#Override
public void onAction(boolean show){
bottomSheetBehaviorState.set(show? BottomSheetBehavior.STATE_COLLAPSED : BottomSheetBehavior.STATE_HIDDEN);
}
I'm trying to handle clickable TextView on data binding method, but i get this error:
Cannot find the setter for attribute 'android:clickable' with parameter type lambda on android.widget.TextView
my TextView widgets must be clickable and i show simple Toast, how can i set text to that such as android:text="#string/my_text and can be clickable?
ActivityRegister:
public class ActivityRegister extends BaseActivities
implements ActivityRegisterContract.View{
private ActivityRegisterBinding binding;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_register);
ActivityRegisterPresenter mainActivityPresenter = new ActivityRegisterPresenter(this);
ActivityRegisterData viewModel = new ActivityRegisterData();
viewModel.setReadContactPermission(Utils.getString(R.string.get_read_contact_permission, context));
binding.setPresenter(mainActivityPresenter);
}
#Override
public void getReadContactsPermission() {
Utils.toast("CLICKED", context);
}
}
presenter:
public class ActivityRegisterPresenter {
private ActivityRegisterContract.View view;
public ActivityRegisterPresenter(ActivityRegisterContract.View mView) {
view = mView;
}
public void getReadContactsPermission(){
view.getReadContactsPermission();
}
}
ActivityRegisterContract
public interface ActivityRegisterContract {
public interface View {
void getReadContactsPermission();
}
}
and then ActivityRegisterData
public class ActivityRegisterData extends BaseObservable {
private String readContactPermission;
public ActivityRegisterData() {
}
#Bindable
public String getReadContactPermission() {
return readContactPermission;
}
public void setReadContactPermission(String readContactPermission) {
this.readContactPermission = readContactPermission;
notifyPropertyChanged(BR.readContactPermission);
}
}
my layout:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:slidingLayer="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="com.example.Ui.Register.Model.ActivityRegisterData"/>
<variable
name="presenter"
type="com.example.Ui.Register.Presenter.ActivityRegisterPresenter"/>
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#d1d1d1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="#+id/permission_for_read_contacts"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="#string/permission_for_read_contacts"
android:textColor="#color/white"/>
<TextView
android:layout_width="match_parent"
android:layout_height="#dimen/default_textview_height"
android:background="#drawable/selector_blue_buttons"
android:clickable="#{()->presenter.getReadContactsPermission()}"
android:text="#{viewModel.readContactPermission}"
android:textColor="#color/white"/>
</LinearLayout>
</LinearLayout>
</FrameLayout>
</layout>
problem is for this line on layout:
<TextView
android:layout_width="match_parent"
android:layout_height="#dimen/default_textview_height"
android:background="#drawable/selector_blue_buttons"
android:clickable="#{()->presenter.getReadContactsPermission()}"
android:text="#{viewModel.readContactPermission}"
android:textColor="#color/white"/>
I dont know about your code.but to make a clickable text, you just have to add listener to it or you can define onClick attribute in Xml and define that method in Activity to handle click event.
Use this:
android:onclick="doSomething"
And in activity
public void doSomething(View v){
//Write your code here
}