diff --git a/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/brick/ViewModelBrickTest.kt b/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/brick/ViewModelBrickTest.kt index c32ebba3..26faad8c 100644 --- a/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/brick/ViewModelBrickTest.kt +++ b/BrickKit/bricks/src/androidTest/java/com/wayfair/brickkit/brick/ViewModelBrickTest.kt @@ -1,6 +1,5 @@ package com.wayfair.brickkit.brick -import android.util.SparseArray import android.view.LayoutInflater import android.widget.LinearLayout import androidx.databinding.Bindable @@ -100,22 +99,7 @@ class ViewModelBrickTest { viewModelBrick.addViewModel(BR.text, TextViewModel(TextDataModel(TEXT_2))) - assertEquals(2, viewModelBrick.getViewModels().size()) - } - - @Test - fun testSetViewModels() { - val viewModelBrick = ViewModelBrick.Builder(R.layout.text_brick_vm) - .addViewModel(BR.viewModel, TextViewModel(TextDataModel(TEXT))) - .build() - - viewModelBrick.setViewModels( - SparseArray>().apply { - put(BR.text, TextViewModel(TextDataModel(TEXT_2))) - } - ) - - assertEquals(1, viewModelBrick.getViewModels().size()) + assertEquals(2, viewModelBrick.viewModels.size()) } @Test @@ -199,17 +183,6 @@ class ViewModelBrickTest { assertEquals(viewModel, viewModelBrick.viewModels[BR.viewModel]) } - @Test - fun testBuilder_setViewModels() { - val viewModel = mock>() - - val viewModelBrick = ViewModelBrick.Builder(R.layout.text_brick_vm) - .setViewModels(SparseArray>().apply { put(BR.viewModel, viewModel) }) - .build() - - assertEquals(viewModel, viewModelBrick.viewModels[BR.viewModel]) - } - @Test fun testBuilder_addNullViewModel() { val viewModelBrick = ViewModelBrick.Builder(R.layout.text_brick_vm) diff --git a/BrickKit/bricks/src/main/java/com/wayfair/brickkit/brick/ViewModelBrick.java b/BrickKit/bricks/src/main/java/com/wayfair/brickkit/brick/ViewModelBrick.java deleted file mode 100644 index aef50004..00000000 --- a/BrickKit/bricks/src/main/java/com/wayfair/brickkit/brick/ViewModelBrick.java +++ /dev/null @@ -1,306 +0,0 @@ -package com.wayfair.brickkit.brick; - -import android.util.SparseArray; -import android.view.View; - -import com.wayfair.brickkit.padding.ZeroBrickPadding; -import com.wayfair.brickkit.size.FullWidthBrickSize; -import com.wayfair.brickkit.viewholder.BrickViewHolder; -import com.wayfair.brickkit.padding.BrickPadding; -import com.wayfair.brickkit.size.BrickSize; - -import androidx.annotation.LayoutRes; -import androidx.databinding.DataBindingUtil; -import androidx.databinding.ViewDataBinding; - -/** - * This class is used as a Generic Brick that can take in any XML Layout and use DataBinding to - * insert information from a {@link ViewModel}. - */ -public final class ViewModelBrick extends BaseBrick implements ViewModel.ViewModelUpdateListener { - @LayoutRes - private final int layoutId; - @LayoutRes - private final int placeholderLayoutId; - protected final SparseArray viewModels; - - /** - * Private constructor that the {@link Builder} class uses. - * - * @param builder the builder - */ - private ViewModelBrick(Builder builder) { - super(builder.spanSize, builder.padding); - - this.layoutId = builder.layoutId; - this.placeholderLayoutId = builder.placeholderLayoutId; - this.viewModels = builder.viewModels; - - for (int i = 0; i < viewModels.size(); i++) { - viewModels.valueAt(i).addUpdateListener(this); - } - } - - /** - * Gets the appropriate {@link ViewModel} for the given binding id. - * - * @param bindId the binding id - * @return a {@link ViewModel} for the binding id - */ - public ViewModel getViewModel(int bindId) { - return viewModels.get(bindId); - } - - /** - * Gets all {@link ViewModel}s. - * - * @return a {@link ViewModel} for the binding id - */ - public SparseArray getViewModels() { - return viewModels; - } - - /** - * Add a view model to the Brick. - * - * @param bindingId the binding ID of the view model - * @param viewModel the view model - */ - public void addViewModel(int bindingId, ViewModel viewModel) { - viewModel.addUpdateListener(this); - this.viewModels.put(bindingId, viewModel); - onChange(); - } - - /** - * Replace all the view models with these. - * - * @param viewModels the view models to replace existing view models - */ - public void setViewModels(SparseArray viewModels) { - this.viewModels.clear(); - for (int i = 0; i < viewModels.size(); i++) { - viewModels.valueAt(i).addUpdateListener(this); - this.viewModels.put(viewModels.keyAt(i), viewModels.valueAt(i)); - } - onChange(); - } - - /** - * {@inheritDoc} - */ - @Override - public void onBindData(BrickViewHolder holder) { - ViewModelBrickViewHolder viewModelBrickViewHolder = (ViewModelBrickViewHolder) holder; - - for (int i = 0; i < viewModels.size(); i++) { - viewModelBrickViewHolder.bind(viewModels.keyAt(i), viewModels.valueAt(i)); - } - - viewModelBrickViewHolder.getViewDataBinding().executePendingBindings(); - } - - /** - * {@inheritDoc} - */ - @Override - public int getLayout() { - return layoutId; - } - - /** - * {@inheritDoc} - */ - @Override - public int getPlaceholderLayout() { - return placeholderLayoutId; - } - - /** - * {@inheritDoc} - */ - @Override - public BrickViewHolder createViewHolder(View itemView) { - return new ViewModelBrickViewHolder(DataBindingUtil.bind(itemView)); - } - - /** - * {@inheritDoc} - */ - @Override - public void onChange() { - setHidden(!isDataReady()); - - refreshItem(); - } - - /** - * {@inheritDoc} - */ - @Override - public boolean isDataReady() { - boolean isDataReady = viewModels.size() != 0; - - for (int i = 0; isDataReady && i < viewModels.size(); i++) { - isDataReady = viewModels.valueAt(i).isDataModelReady(); - } - - return isDataReady; - } - - @Override - public int hashCode() { - return super.hashCode(); - } - - @Override - public boolean equals(Object obj) { - boolean areContentsTheSame = true; - - if (obj instanceof ViewModelBrick) { - for (int i = 0; i < getViewModels().size(); i++) { - for (int j = 0; j < ((ViewModelBrick) obj).getViewModels().size(); j++) { - if (getViewModels().keyAt(i) == ((ViewModelBrick) obj).getViewModels().keyAt(j)) { - if (!getViewModels().valueAt(i).equals(((ViewModelBrick) obj).getViewModels().valueAt(j))) { - areContentsTheSame = false; - } - } - } - } - } else { - areContentsTheSame = false; - } - - return areContentsTheSame; - } - - /** - * A builder class for {@link ViewModelBrick}, this makes it clearer what is required and what you are actually doing when creating - * {@link ViewModelBrick}s. - */ - public static class Builder { - @LayoutRes - int layoutId; - @LayoutRes - int placeholderLayoutId; - SparseArray viewModels = new SparseArray<>(); - BrickSize spanSize = new FullWidthBrickSize(); - BrickPadding padding = new ZeroBrickPadding(); - - /** - * Builder constructor, requires only a {@link LayoutRes} to work. - * - * @param layoutId a {@link LayoutRes} to use as a brick - */ - public Builder(@LayoutRes int layoutId) { - this.layoutId = layoutId; - } - - /** - * Set the placeholder for this brick. - * - * @param placeholderLayoutId the placeholder layout id to be used - * @return the builder - */ - public Builder setPlaceholder(@LayoutRes int placeholderLayoutId) { - this.placeholderLayoutId = placeholderLayoutId; - return this; - } - - /** - * Add a {@link ViewModel} with a binding Id for the layout already defined. - * - * @param bindingId the binding Id of the view model - * @param viewModel the view model to be bound, extends {@link ViewModel} - * @return the builder - */ - public Builder addViewModel(int bindingId, ViewModel viewModel) { - if (viewModel != null) { - this.viewModels.put(bindingId, viewModel); - } - return this; - } - - /** - * Add a set of {@link ViewModel}s and their binding Ids. - * - * @param viewModels a {@link SparseArray} of binding Ids and {@link ViewModel}s - * @return the builder - */ - public Builder setViewModels(SparseArray viewModels) { - this.viewModels = viewModels; - return this; - } - - /** - * Set the {@link BrickSize}. - * - * @param spanSize the {@link BrickSize} - * @return the builder - */ - public Builder setSpanSize(BrickSize spanSize) { - this.spanSize = spanSize; - return this; - } - - /** - * Set the {@link BrickPadding}. - * - * @param padding the {@link BrickPadding} - * @return the builder - */ - public Builder setPadding(BrickPadding padding) { - this.padding = padding; - return this; - } - - /** - * Assemble the {@link ViewModelBrick}. - * - * @return the complete {@link ViewModelBrick} - */ - public ViewModelBrick build() { - return new ViewModelBrick(this); - } - } - - /** - * A special {@link BrickViewHolder} that can handle binding {@link ViewModel}s to layouts. - */ - public static final class ViewModelBrickViewHolder extends BrickViewHolder { - public final ViewDataBinding viewDataBinding; - - /** - * Constructor to set up the {@link BrickViewHolder} with the {@link ViewDataBinding} - * from the right item view. - * - * @param viewDataBinding the {@link ViewDataBinding} object - * from {@link #createViewHolder(View)} - */ - public ViewModelBrickViewHolder(ViewDataBinding viewDataBinding) { - super(viewDataBinding.getRoot()); - - this.viewDataBinding = viewDataBinding; - } - - /** - * Sets the {@link ViewModel} to be bound for the given id. - * - * @param bindId the id - * @param viewModel the {@link ViewModel} - */ - void bind(int bindId, ViewModel viewModel) { - viewDataBinding.setVariable(bindId, viewModel); - } - - /** - * gets the {@link ViewDataBinding} object in order to execute them - * {@link #onBindData(BrickViewHolder)}. - * - * @return the {@link ViewDataBinding} - */ - ViewDataBinding getViewDataBinding() { - return viewDataBinding; - } - } -} diff --git a/BrickKit/bricks/src/main/java/com/wayfair/brickkit/brick/ViewModelBrick.kt b/BrickKit/bricks/src/main/java/com/wayfair/brickkit/brick/ViewModelBrick.kt new file mode 100644 index 00000000..bf1e42a2 --- /dev/null +++ b/BrickKit/bricks/src/main/java/com/wayfair/brickkit/brick/ViewModelBrick.kt @@ -0,0 +1,188 @@ +package com.wayfair.brickkit.brick + +import android.util.SparseArray +import android.view.View +import androidx.annotation.LayoutRes +import androidx.core.util.isNotEmpty +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding +import com.wayfair.brickkit.padding.BrickPadding +import com.wayfair.brickkit.padding.ZeroBrickPadding +import com.wayfair.brickkit.size.BrickSize +import com.wayfair.brickkit.size.FullWidthBrickSize +import com.wayfair.brickkit.viewholder.BrickViewHolder + +/** + * This class is used as a Generic Brick that can take in any XML Layout and use DataBinding to + * insert information from a [ViewModel]. + */ +class ViewModelBrick private constructor( + private val layoutId: Int, + private val placeholderLayoutId: Int, + val viewModels: SparseArray>, + spanSize: BrickSize, + padding: BrickPadding +) : BaseBrick(spanSize, padding), ViewModel.ViewModelUpdateListener { + + init { + (0 until viewModels.size()).forEach { i -> viewModels.valueAt(i).addUpdateListener(this) } + } + + /** + * Gets the appropriate [ViewModel] for the given binding id. + * + * @param bindId the binding id + * @return a [ViewModel] for the binding id + */ + fun getViewModel(bindId: Int): ViewModel<*> = viewModels[bindId] + + /** + * Add a view model to the Brick. + * + * @param bindingId the binding ID of the view model + * @param viewModel the view model + */ + fun addViewModel(bindingId: Int, viewModel: ViewModel<*>) { + viewModel.addUpdateListener(this) + viewModels.put(bindingId, viewModel) + onChange() + } + + /** + * {@inheritDoc} + */ + override fun onBindData(holder: BrickViewHolder) { + val binding = (holder as ViewModelBrickViewHolder).viewDataBinding + + (0 until viewModels.size()).forEach { i -> + binding.setVariable(viewModels.keyAt(i), viewModels.valueAt(i)) + } + + binding.executePendingBindings() + } + + /** + * {@inheritDoc} + */ + override fun getLayout(): Int = layoutId + + /** + * {@inheritDoc} + */ + override fun getPlaceholderLayout(): Int = placeholderLayoutId + + /** + * {@inheritDoc} + */ + override fun createViewHolder(itemView: View): BrickViewHolder = ViewModelBrickViewHolder(DataBindingUtil.bind(itemView)!!) + + /** + * {@inheritDoc} + */ + override fun onChange() { + isHidden = !isDataReady + refreshItem() + } + + /** + * {@inheritDoc} + */ + override fun isDataReady(): Boolean { + var isDataReady = viewModels.isNotEmpty() + + var i = 0 + while (isDataReady && i < viewModels.size()) { + isDataReady = viewModels.valueAt(i++).isDataModelReady + } + + return isDataReady + } + + override fun hashCode(): Int = super.hashCode() + + override fun equals(other: Any?): Boolean { + var areContentsTheSame = true + if (other is ViewModelBrick) { + (0 until viewModels.size()).forEach { i -> + (0 until other.viewModels.size()).forEach { j -> + if (viewModels.keyAt(i) == other.viewModels.keyAt(j) && viewModels.valueAt(i) != other.viewModels.valueAt(j)) { + areContentsTheSame = false + } + } + } + } else { + areContentsTheSame = false + } + return areContentsTheSame + } + + /** + * A builder class for [ViewModelBrick], this makes it clearer what is required and what you are actually doing when creating + * [ViewModelBrick]s. + */ + class Builder(@LayoutRes private val layoutId: Int) { + private var placeholderLayoutId = 0 + private var viewModels = SparseArray>() + private var spanSize: BrickSize = FullWidthBrickSize() + private var padding: BrickPadding = ZeroBrickPadding() + + /** + * Set the placeholder for this brick. + * + * @param placeholderLayoutId the placeholder layout id to be used + * @return the builder + */ + fun setPlaceholder(@LayoutRes placeholderLayoutId: Int): Builder { + this.placeholderLayoutId = placeholderLayoutId + return this + } + + /** + * Add a [ViewModel] with a binding Id for the layout already defined. + * + * @param bindingId the binding Id of the view model + * @param viewModel the view model to be bound, extends [ViewModel] + * @return the builder + */ + fun addViewModel(bindingId: Int, viewModel: ViewModel<*>?): Builder { + if (viewModel != null) { + viewModels.put(bindingId, viewModel) + } + return this + } + + /** + * Set the [BrickSize]. + * + * @param spanSize the [BrickSize] + * @return the builder + */ + fun setSpanSize(spanSize: BrickSize): Builder { + this.spanSize = spanSize + return this + } + + /** + * Set the [BrickPadding]. + * + * @param padding the [BrickPadding] + * @return the builder + */ + fun setPadding(padding: BrickPadding): Builder { + this.padding = padding + return this + } + + /** + * Assemble the [ViewModelBrick]. + * + * @return the complete [ViewModelBrick] + */ + fun build(): ViewModelBrick = ViewModelBrick(layoutId, placeholderLayoutId, viewModels, spanSize, padding) + } + + /** + * A special [BrickViewHolder] that can handle binding [ViewModel]s to layouts. + */ + private class ViewModelBrickViewHolder(val viewDataBinding: ViewDataBinding) : BrickViewHolder(viewDataBinding.root) +}