Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OnModelCheckedChangeListener #725

Merged
merged 3 commits into from
Apr 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions epoxy-adapter/src/main/java/com/airbnb/epoxy/ListenersUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.airbnb.epoxy;

import android.view.View;
import android.view.ViewParent;

import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;

public class ListenersUtils {

@Nullable
static EpoxyViewHolder getEpoxyHolderForChildView(View v) {
RecyclerView recyclerView = findParentRecyclerView(v);
if (recyclerView == null) {
return null;
}

ViewHolder viewHolder = recyclerView.findContainingViewHolder(v);
if (viewHolder == null) {
return null;
}

if (!(viewHolder instanceof EpoxyViewHolder)) {
return null;
}

return (EpoxyViewHolder) viewHolder;
}

@Nullable
private static RecyclerView findParentRecyclerView(@Nullable View v) {
if (v == null) {
return null;
}

ViewParent parent = v.getParent();
if (parent instanceof RecyclerView) {
return (RecyclerView) parent;
}

if (parent instanceof View) {
return findParentRecyclerView((View) parent);
}

return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.airbnb.epoxy;

import android.widget.CompoundButton;

public interface OnModelCheckedChangeListener<T extends EpoxyModel<?>, V> {
/**
* Called when the view bound to the model is checked.
*
* @param model The model that the view is bound to.
* @param parentView The view bound to the model which received the click.
* @param clickedView The view that received the click. This is either a child of the parentView
* or the parentView itself
* @param isChecked The new value for isChecked property.
* @param position The position of the model in the adapter.
*/
void onChecked(T model, V parentView,
CompoundButton checkedView, boolean isChecked, int position);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.airbnb.epoxy;

import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;

import androidx.recyclerview.widget.RecyclerView;

/**
* Used in the generated models to transform normal checked change listener to model
* checked change.
*/
public class WrappedEpoxyModelCheckedChangeListener<T extends EpoxyModel<?>, V>
implements OnCheckedChangeListener {

private final OnModelCheckedChangeListener<T, V> originalCheckedChangeListener;

public WrappedEpoxyModelCheckedChangeListener(
OnModelCheckedChangeListener<T, V> checkedListener
) {
if (checkedListener == null) {
throw new IllegalArgumentException("Checked change listener cannot be null");
}

this.originalCheckedChangeListener = checkedListener;
}

@Override
public void onCheckedChanged(CompoundButton button, boolean isChecked) {
EpoxyViewHolder epoxyHolder = ListenersUtils.getEpoxyHolderForChildView(button);
if (epoxyHolder == null) {
throw new IllegalStateException("Could not find RecyclerView holder for clicked view");
}

final int adapterPosition = epoxyHolder.getAdapterPosition();
if (adapterPosition != RecyclerView.NO_POSITION) {
originalCheckedChangeListener
.onChecked((T) epoxyHolder.getModel(), (V) epoxyHolder.objectToBind(), button,
isChecked, adapterPosition);
}
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof WrappedEpoxyModelCheckedChangeListener)) {
return false;
}

WrappedEpoxyModelCheckedChangeListener<?, ?>
that = (WrappedEpoxyModelCheckedChangeListener<?, ?>) o;

return originalCheckedChangeListener.equals(that.originalCheckedChangeListener);
}

@Override
public int hashCode() {
return originalCheckedChangeListener.hashCode();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,8 @@
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnLongClickListener;
import android.view.ViewParent;

import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;

/**
* Used in the generated models to transform normal view click listeners to model click
Expand Down Expand Up @@ -40,7 +37,7 @@ public WrappedEpoxyModelClickListener(OnModelLongClickListener<T, V> clickListen

@Override
public void onClick(View v) {
EpoxyViewHolder epoxyHolder = getEpoxyHolderForChildView(v);
EpoxyViewHolder epoxyHolder = ListenersUtils.getEpoxyHolderForChildView(v);
if (epoxyHolder == null) {
throw new IllegalStateException("Could not find RecyclerView holder for clicked view");
}
Expand All @@ -55,7 +52,7 @@ public void onClick(View v) {

@Override
public boolean onLongClick(View v) {
EpoxyViewHolder epoxyHolder = getEpoxyHolderForChildView(v);
EpoxyViewHolder epoxyHolder = ListenersUtils.getEpoxyHolderForChildView(v);
if (epoxyHolder == null) {
throw new IllegalStateException("Could not find RecyclerView holder for clicked view");
}
Expand All @@ -71,43 +68,6 @@ public boolean onLongClick(View v) {
return false;
}

@Nullable
private static EpoxyViewHolder getEpoxyHolderForChildView(View v) {
RecyclerView recyclerView = findParentRecyclerView(v);
if (recyclerView == null) {
return null;
}

ViewHolder viewHolder = recyclerView.findContainingViewHolder(v);
if (viewHolder == null) {
return null;
}

if (!(viewHolder instanceof EpoxyViewHolder)) {
return null;
}

return (EpoxyViewHolder) viewHolder;
}

@Nullable
private static RecyclerView findParentRecyclerView(@Nullable View v) {
if (v == null) {
return null;
}

ViewParent parent = v.getParent();
if (parent instanceof RecyclerView) {
return (RecyclerView) parent;
}

if (parent instanceof View) {
return findParentRecyclerView((View) parent);
}

return null;
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.airbnb.epoxy.integrationtest;

import android.view.View;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;

import com.airbnb.epoxy.EpoxyAttribute;
import com.airbnb.epoxy.EpoxyModel;

import androidx.annotation.NonNull;

public class ModelWithCheckedChangeListener extends EpoxyModel<View> {

@EpoxyAttribute OnCheckedChangeListener checkedChangeListener;

@Override
protected int getDefaultLayout() {
return R.layout.model_with_checked_change;
}

@Override
public void bind(@NonNull View view) {
if (view instanceof CompoundButton) {
((CompoundButton) view).setOnCheckedChangeListener(checkedChangeListener);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<CheckBox xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent">

</CheckBox>
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class BindDiffTest {

@Test
fun propGroupChangedFromOneAttributeToAnother() {
val clickListener = View.OnClickListener { v -> }
val clickListener = View.OnClickListener {}
validateDiff(
model1Props = {
requiredText("hello")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import android.view.View;
import android.view.View.OnClickListener;
import android.widget.CompoundButton;

import com.airbnb.epoxy.integrationtest.BuildConfig;
import com.airbnb.epoxy.integrationtest.ModelWithCheckedChangeListener_;
import com.airbnb.epoxy.integrationtest.ModelWithClickListener_;
import com.airbnb.epoxy.integrationtest.ModelWithLongClickListener_;

Expand Down Expand Up @@ -69,6 +71,17 @@ public boolean onLongClick(ModelWithLongClickListener_ model, View view, View v,
}
}

static class ModelCheckedChangeListener
implements OnModelCheckedChangeListener<ModelWithCheckedChangeListener_, View> {
boolean checked;

@Override
public void onChecked(ModelWithCheckedChangeListener_ model, View parentView,
CompoundButton checkedView, boolean isChecked, int position) {
checked = true;
}
}

static class ViewClickListener implements OnClickListener {
boolean clicked;

Expand Down Expand Up @@ -137,6 +150,46 @@ public void basicModelLongClickListener() {
verify(modelClickListener).onLongClick(eq(model), any(View.class), eq(viewMock), eq(1));
}

@Test
public void basicModelCheckedChangeListener() {
final ModelWithCheckedChangeListener_ model = new ModelWithCheckedChangeListener_();
ModelCheckedChangeListener modelCheckedChangeListener = spy(new ModelCheckedChangeListener());
model.checkedChangeListener(modelCheckedChangeListener);

TestController controller = new TestController();
controller.setModel(model);

lifecycleHelper.buildModelsAndBind(controller);

CompoundButton compoundMock = mockCompoundButtonForClicking(model);

model.checkedChangeListener().onCheckedChanged(compoundMock, true);
assertTrue(modelCheckedChangeListener.checked);

verify(modelCheckedChangeListener).onChecked(eq(model), any(View.class), any(CompoundButton.class), eq(true), eq(1));
}

private CompoundButton mockCompoundButtonForClicking(EpoxyModel model) {
CompoundButton mockedView = mock(CompoundButton.class);
RecyclerView recyclerMock = mock(RecyclerView.class);
EpoxyViewHolder holderMock = mock(EpoxyViewHolder.class);

when(holderMock.getAdapterPosition()).thenReturn(1);
doReturn(recyclerMock).when(mockedView).getParent();
doReturn(holderMock).when(recyclerMock).findContainingViewHolder(mockedView);
doReturn(model).when(holderMock).getModel();

when(mockedView.getParent()).thenReturn(recyclerMock);
when(recyclerMock.findContainingViewHolder(mockedView)).thenReturn(holderMock);
when(holderMock.getAdapterPosition()).thenReturn(1);
when(holderMock.getModel()).thenReturn(model);

View parentView = mock(View.class);
when(holderMock.objectToBind()).thenReturn(parentView);
doReturn(parentView).when(holderMock).objectToBind();
return mockedView;
}

@Test
public void modelClickListenerOverridesViewClickListener() {
final ModelWithClickListener_ model = new ModelWithClickListener_();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.airbnb.epoxy
import com.airbnb.epoxy.GeneratedModelInfo.AttributeGroup
import com.airbnb.epoxy.Utils.EPOXY_MODEL_TYPE
import com.airbnb.epoxy.Utils.isSubtypeOfType
import com.airbnb.epoxy.Utils.isViewCheckedChangeListenerType
import com.airbnb.epoxy.Utils.isViewClickListenerType
import com.airbnb.epoxy.Utils.isViewLongClickListenerType
import com.squareup.javapoet.AnnotationSpec
Expand Down Expand Up @@ -104,6 +105,9 @@ internal abstract class AttributeInfo {
val isViewLongClickListener: Boolean
get() = isViewLongClickListenerType(typeMirror)

val isViewCheckedChangeListener: Boolean
get() = isViewCheckedChangeListenerType(typeMirror)

val isBoolean: Boolean
get() = Utils.isBooleanType(typeMirror)

Expand Down
Loading