Android Architecture Components: dataBinding not changing TextView value - android

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);

Related

Same databinding layout in different fragment

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() {
...
}
}

Databinding via Interface

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.

Fixing data binding errors in Android for linking a button with a function

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

Android - data binding on a property of an LiveData extended object

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}"

Android DataBinding Activity finish()

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>

Categories

Resources