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"));
Related
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 use data binding.
Here parent adapter:
public abstract class PreviewSortAdapter extends RealmRecyclerViewAdapter {
protected Context context;
#BindingAdapter("imageUrl")
public static void loadImage(ImageView view, String imageUrl) {
Glide.with(view.getContext()).load(imageUrl)
.apply(RequestOptions.bitmapTransform(
new GlideRoundedCornersTransformation(view.getContext(), (int) AndroidUtil.dpToPx(view.getContext(),
view.getContext().getResources().getInteger(R.integer.image_rounded_corner_radius_dp)),
0, GlideRoundedCornersTransformation.CornerType.TOP)))
.into(view);
}
}
Here my child adapter:
public class MapListSortAdapter extends PreviewSortAdapter {
public MapListSortAdapter(Context context, OrderedRealmCollection<Merchant> data) {
super(context, data, true);
}
#BindingAdapter("imageUrl")
public static void loadImage(ImageView view, String imageUrl) {
Debug.d(TAG, "loadImage: ");
Glide.with(view.getContext()).load(imageUrl)
.into(view);
}
#Override
protected int getLayoutForPosition(int position) {
return R.layout.map_list_item;
}
}
As you can see in my child adapter I override method loadImage(). I want to call method loadImage() from child adapter.
Herer map_list_item.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">
<data>
<variable
name="item"
type="com.myproject.android.customer.api.model.Merchant" />
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#android:color/white"
android:minHeight="90dp">
<ImageView
android:id="#+id/imageViewPhoto"
android:layout_width="90dp"
android:layout_height="90dp"
android:scaleType="centerCrop"
app:imageUrl="#{item.preview.formats.reference.url}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
</layout>
As you can see I use custom tag app:imageUrl to call method loadImage().
The problem is that method is call but it call of parent adapter - PreviewSortAdapter.loadImage().
But I need to call this method in child adapter: MapListSortAdapter.loadImage().
Methods anotated with the static modifier doesn't have hieritance. Just remove the static modifier and it should work
I am trying to use MVVM pattern with android databinding.
I have an edittext and a textview that should show the same text when I type in the edittext field by binding to the model via the observablefield with a LocationType model object.
From testing both fields are set to "hello" when I start the app, as expected.
But when I type in the edittextfield the textview does not update, even tough the model object gets updated correctly as can be seen with by debug.
When I use an:
observablefield<String>
drop using the model and just set to some text and update the xml to use this field. It works as intended.
model:
public class LocationType {
private String locationType;
public String getLocationType() {
return locationType;
}
public void setLocationType(String locationType) {
this.locationType = locationType;
}
}
modelView:
public class LocationTypeViewModel extends BaseObservable {
#Bindable
private ObservableField<LocationType> locationTypeObservableField = new ObservableField<>();
private Context context;
LocationType locationType;
public LocationTypeViewModel(Context context) {
this.context = context;
locationType = new LocationType();
locationType.setLocationType("Hello");
locationTypeObservableField.set(locationType);
}
public ObservableField<LocationType> getLocationTypeObservableField() {
Log.d("CALLED GET", locationType.getLocationType());
return locationTypeObservableField;
}
}
XML:
<layout xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View"/>
<variable
name="viewModel"
type="fragment.LocationType.viewmodel.LocationTypeViewModel"/>
</data>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="#{viewModel.locationTypeObservableField.locationType}"
android:id="#+id/edittext1"/>
<TextView
android:layout_below="#+id/edittext1"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:text="#{viewModel.locationTypeObservableField.locationType}"
android:id="#+id/text"
/>
</RelativeLayout>
</layout>
android:text="#={viewModel.locationTypeObservableField.locationType}"
You forgot to add "=" .check and compare above line with your code.
Two way databinding should be like this
android:text="#={ProfileViewModel.mobileNumber}"
You forgot to add "="
The problem is you are observing changes to the field inside LocationTypeViewModel which points to a LocationType object. That will only notify changes when the object reference changes. What you want is an observer on the actual String field.
You are also missing an important two-binding symbol on your EditText. You want to use #={obj.fieldName} instead of #{obj.fieldName}. The #= signals that you want changes to propagate back to the source.
Here is your code edited to work as intended:
public class LocationType {
private String location;
public LocationType(String location) {
this.location = location;
}
public String getLocation() {
return location;
}
public void setLocation(String location) {
this.location = location;
}
}
public class LocationTypeViewModel extends BaseObservable {
private final LocationType locationType;
public LocationTypeViewModel() {
locationType = new LocationType("Hello");
}
#Bindable
public String getLocation() {
return locationType.getLocation();
}
public void setLocation(String location) {
locationType.setLocation(location);
notifyPropertyChanged(BR.location);
}
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View"/>
<variable
name="viewModel"
type=".LocationTypeViewModel"/>
</data>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="#={viewModel.location}"
android:id="#+id/edittext1"/>
<TextView
android:layout_below="#+id/edittext1"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:text="#{viewModel.location}"
android:id="#+id/text"
/>
</RelativeLayout>
</layout>
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
}
I would like to create a gridview of movie posters images with using bindings.
My viewmodel looks like that:
public class PopularMoviesViewModel extends BaseObservable {
Movie movie;
Context context;
MovieServiceComponent movieServiceComponent = DaggerMovieServiceComponent.builder()
.contextModule(new ContextModule(context))
.build();
Picasso getPicasso = movieServiceComponent.getPicasso();
public PopularMoviesViewModel(Movie movie, Context context) {
this.movie = movie;
this.context = context;
}
#Bindable
public String getImageUrl(){
return movie.posterPath();
}
#Bindable
public String getTitle(){
return movie.originalTitle();
}
#BindingAdapter({"imageUrl"})
public void setImageUrl(ImageView view, String poserPath){
getPicasso.with(view.getContext()).load("http://image.tmdb.org/t/p/w185"+ poserPath).into(view);
}
}
Layout:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<data class="PopularMoviesBinding">
<variable
name="pmvm"
type="com.hartyandi.oviesm.modelviews.PopularMoviesViewModel"></variable>
</data>
<LinearLayout
android:id="#+id/row"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#FFFFFF"
android:paddingBottom="0dp"
android:paddingTop="5dp"
android:paddingRight="2.5dp"
android:paddingLeft="5dp"
android:orientation="vertical">
<ImageView
app:imageUrl="#{pmvm.imageUrl}"
android:id="#+id/popular_movies_grid_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="0dp"
android:adjustViewBounds="true"
android:elevation="20dp">
</ImageView>
<TextView
android:id="#+id/popular_movies_grid_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{pmvm.title}"
android:textColor="#000000"
android:textSize="12sp"
android:background="#FFFFFF"
>
</TextView>
</LinearLayout>
</layout>
Adapter:
public class PopularMoviesAdapter extends RecyclerView.Adapter<PopularMoviesAdapter.BindingHolder> {
private List<Movie> movies;
private Context context;
public PopularMoviesAdapter(List<Movie> movies, Context context) {
this.movies = movies;
this.context = context;
}
public void add(Movie movie){
movies.add(movie);
}
#Override
public BindingHolder onCreateViewHolder(ViewGroup parent, int viewType) {
PopularMoviesBinding popularMoviesBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()),
R.layout.popular_movies_gridview_row, parent,false);
return new BindingHolder(popularMoviesBinding);
}
#Override
public void onBindViewHolder(PopularMoviesAdapter.BindingHolder holder, int position) {
PopularMoviesBinding popularMoviesBinding = holder.popularMoviesBinding;
popularMoviesBinding.setPmvm(new PopularMoviesViewModel(movies.get(position), context));
}
#Override
public int getItemCount() {
return movies.size();
}
public class BindingHolder extends RecyclerView.ViewHolder{
private PopularMoviesBinding popularMoviesBinding;
public BindingHolder(PopularMoviesBinding popularMoviesBinding) {
super(popularMoviesBinding.getRoot());
this.popularMoviesBinding = popularMoviesBinding;
}
}
}
I get the following error:
java.lang.IllegalStateException: Required DataBindingComponent is null in class PopularMoviesBinding.A BindingAdapter in modelviews.PopularMoviesViewModel is not static and requires an object to use, retrieved from the DataBindingComponent.
I tried to change my implementation just like this stackoverflow post suggest and I got the same error message.
I also used the following code as example.
Could someone explain what the problem with the code, and how to solve it?
You probably didn't intend to use an instance method for the BindingAdapter.
If you do, you must provide a DataBindingComponent so that the generated Binding class knows which instance to use.
You have two options -- provide a DataBindingComponent or just pass the required context as an attribute to a static binding adapter method. The second is a bit easier to understand, so I'll start with that:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<data class="PopularMoviesBinding">
<variable name="pmvm"
type="com.hartyandi.oviesm.modelviews.PopularMoviesViewModel"/>
<variable name="picasso" type="com.whatever.Picasso"/>
</data>
<!-- ... -->
<ImageView
app:imageUrl="#{pmvm.imageUrl}"
app:picasso="#{picasso}"
... />
</ImageView>
</layout>
Then in your BindingAdapter:
#BindingAdapter({"imageUrl", "picasso"})
public static void setImageUrl(ImageView view, String poserPath, Picasso picasso){
picasso.with(view.getContext()).load("http://image.tmdb.org/t/p/w185"+ poserPath).into(view);
}
Note that setImageUrl is now static.
Alternatively, since your the Picasso instance is also on the ViewModel, you can just pass the instance by adding a getter for the picasso:
<ImageView
app:imageUrl="#{pmvm.imageUrl}"
app:picasso="#{pmvm.picasso}"
... />
and the method in your ViewModel:
public Picasso getPicasso() { return this.getPicasso; }
The other way means that you implement a DataBindingComponent. When you create an instance BindingAdapter method, the generated interface will have a getter for your class. You'll need to create a class to implement that interface:
public class MyDataBindingComponent implements DataBindingComponent {
public PopularMoviesViewModel getPopularMoviesViewModel() {
return whateverIDoToCreateOrGetThisBindingAdapterInstance();
}
}
Then you pass the instance when you inflate or bind:
PopularMoviesBinding popularMoviesBinding =
DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()),
R.layout.popular_movies_gridview_row, parent,false,
new MyDataBindingComponent());
just make the setImageUrl() method static to be like this code
#BindingAdapter({"imageUrl"})
public void setImageUrl(ImageView view, String poserPath){
getPicasso.with(view.getContext()).load("http://image.tmdb.org/t/p/w185"+
poserPath).into(view);
}