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
Related
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 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'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 have simple layout and viewModel. I want to connect them with each other but they dont connect.Problem of above problem is in logs there is no error and my app also doesn't crash.
Here my layout:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="progressView"
type="uz.iutlab.ictnews.viewModel.ProgressViewModel" />
<variable
name="viewModel"
type="uz.iutlab.ictnews.viewModel.DetailFragmentViewModel" />
</data>
<RelativeLayout
android:id="#+id/activity_detail"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
tools:context="uz.iutlab.ictnews.view.fragment.DetailFragment">
<RelativeLayout
android:id="#+id/fragment_detail_post_root"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="#dimen/collapsing_image_height">
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.makeramen.roundedimageview.RoundedImageView
android:id="#+id/fragment_detail_image_post"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
android:src="#{viewModel.image}"
app:layout_collapseMode="parallax"
app:setContext="#{viewModel.context}" />
<android.support.v7.widget.Toolbar
android:id="#+id/fragment_detail_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_collapseMode="pin" />
<TextView
android:id="#+id/fragment_detail_title_post"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:padding="#dimen/spacing_view_large"
android:text="#{viewModel.title}"
android:textColor="#android:color/white"
android:textSize="#dimen/font_size_title_huge" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
</RelativeLayout>
</RelativeLayout>
Here is my ViewModel class
public class DetailFragmentViewModel extends BaseObservable {
private Post mPost;
private Context mContext;
public DetailFragmentViewModel(Post mPost,Context mContext) {
this.mPost = mPost;
this.mContext = mContext;
}
public String getTitle() {
return mPost.getTitle().getRendered();
}
public Context getContext () {
return mContext;
}
public String getImage() {
if (mPost.getMedia().getSource_url() != null) {
return mPost.getMedia().getSource_url();
} else {
return null;
}
}
#BindingAdapter({"android:src","setContext"})
public static void downloadImage (RoundedImageView imageView,String url,Context context) {
if (url!=null) {
Picasso.with(context).load(url).into(imageView);
} else {
imageView.setImageResource(R.drawable.placeholder);
}
}
}
There is no error, There is no crashes. App works normally but there is no any title , any image.I tried this one instead overriding its method writing own but doesn't work.
#BindingAdapter({"bind:imageUrl","setContext"})
public static void downloadImage (RoundedImageView imageView,String url,Context context) {
if (url!=null) {
Picasso.with(context).load(url).into(imageView);
} else {
imageView.setImageResource(R.drawable.placeholder);
}
}
In addition to above . I also check it with debug, above methods are not called.
You should better remove app:setContext="#{viewModel.context}" and get it from the view in your adapter. Also you need to use attribute names without a namespace; so instead of bind:imageUrl only use imageUrl.
#BindingAdapter("imageUrl")
public static void downloadImage (RoundedImageView imageView, String url) {
if (url != null) {
Picasso.with(imageView.getContext()).load(url).into(imageView);
} else {
imageView.setImageResource(R.drawable.placeholder);
}
}
But since Picasso works asynchronously, you might end up with an image after you already set it to R.drawable.placeholder again.
Eventually you could also have a look at the generated java sources for the binding and see if your BindingAdapter is called somewhere.
try like this:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:bind="http://schemas.android.com/apk/res-auto"
>
.....
<com.makeramen.roundedimageview.RoundedImageView
android:id="#+id/fragment_detail_image_post"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
bind:src="#{viewModel.image}"
app:layout_collapseMode="parallax"
bind:setContext="#{viewModel.context}" />
.....
</layout>
#BindingAdapter({"bind:loadUrl"})
public static void downloadImage(RoundedImageViewimageView, String url) {
if (url != null) {
Picasso.with(imageView.getContext()).load(url).into(imageView);
} else {
imageView.setImageResource(R.mipmap.ic_launcher);
}
}
The problem was not in my ViewModel or in my xml file everything is correct there is no syntax error.Problem is here this is fragment and I have connected them together my fragment with my viewModel.I made mistake in creating binding here you can see it. This is my not working one
FragmentStudentLifeBinding binding = DataBindingUtil.inflate(inflater,R.layout.fragment_student_life,container,false);
Here is correct one
FragmentStudentLifeBinding binding = DataBindingUtil.inflate(inflater,R.layout.fragment_student_life,container,true);
I missed to attach fragment to activity that is why my binding hadn't been working for 21 days.Hope it will help to someone.
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);
}