Skip to content

Instantly share code, notes, and snippets.

@talosdev
Created August 13, 2016 12:09
Show Gist options
  • Save talosdev/1e6533743b1062aafccfb188494e6c9f to your computer and use it in GitHub Desktop.
Save talosdev/1e6533743b1062aafccfb188494e6c9f to your computer and use it in GitHub Desktop.
package com.listhi.features.discos;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.google.firebase.database.Query;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import app.we.go.firebase.rx.SingleChildEventObservable;
import rx.Observable;
import rx.functions.Action0;
import rx.functions.Func1;
/**
* Created by Aristides Papadopoulos (github:talosdev).
*/
public abstract class FirebaseAdapter<FM, VM, VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> {
private final Class<FM> firebaseModelClass;
private final Class<VM> viewModelClass;
private final int modelLayout;
private final Class<VH> viewHolderClass;
private final Query ref;
private List<VM> data;
/**
* @param firebaseModelClass Firebase will marshall the data at a location into an instance of a class that you provide
* @param modelLayout This is the layout used to represent a single item in the list. You will be responsible for populating an
* instance of the corresponding view with the data from an instance of firebaseModelClass.
* @param viewHolderClass The class that hold references to all sub-views in an instance modelLayout.
* @param ref The Firebase location to watch for data changes. Can also be a slice of a location, using some
* combination of <code>limit()</code>, <code>startAt()</code>, and <code>endAt()</code>
*/
public FirebaseAdapter(Class<FM> firebaseModelClass, Class<VM> viewModelClass, int modelLayout, Class<VH> viewHolderClass, Query ref) {
this.firebaseModelClass = firebaseModelClass;
this.viewModelClass = viewModelClass;
this.modelLayout = modelLayout;
this.viewHolderClass = viewHolderClass;
this.ref = ref;
data = new ArrayList<>();
}
@Override
public VH onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View view = inflater.inflate(viewType, parent, false);
try {
Constructor<VH> constructor = viewHolderClass.getConstructor(View.class);
return constructor.newInstance(view);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
/**
* Initialization call that triggers the adapter to load its data. Calls
* {@link Observable#doOnCompleted(Action0)} when done.
* @return
*/
public Observable<Void> loadData() {
return loadFirebaseModelData()
.flatMap(transformToViewModelData())
.toList()
.map(new Func1<List<VM>, Void>() {
@Override
public Void call(List<VM> vms) {
data.addAll(vms);
notifyDataSetChanged();
return null;
}
});
}
public List<VM> getData() {
return data;
}
/**
* Loads the data from firebase.
* @return
*/
protected Observable<FM> loadFirebaseModelData() {
return new SingleChildEventObservable(ref)
.get(firebaseModelClass);
}
/**
* Transforms the firebase model data to view model data. This is to compensate for the cases
* when the data fetched by the firebase reference is not complete (due to normalization/duplication)
* and therefore more queries must be made. Subclasses can override this method and perform
* all the additional data loading necessary.
* This implementation will simply try to cast firebase model object to the view model object
* and will only work if the view model class is the same as (or assignable from) the firebase
* model class.
*
* @return
*
*/
protected Func1<FM, Observable<VM>> transformToViewModelData() {
return new Func1<FM, Observable<VM>>() {
@Override
public Observable<VM> call(FM fm) {
try {
return Observable.just((VM) fm);
} catch (ClassCastException e) {
throw new RuntimeException("Firebase model class is not castable to View model " +
"class. You must provide a transform function.", e);
}
}
};
}
@Override
public void onBindViewHolder(VH viewHolder, int position) {
VM model = getItem(position);
populateViewHolder(viewHolder, model, position);
}
public VM getItem(int position) {
return data.get(position);
}
@Override
public int getItemCount() {
return data.size();
}
@Override
public int getItemViewType(int position) {
return modelLayout;
}
/**
* Your implementation should populate the view using the data contained in the model.
*
* @param viewHolder The view to populate
* @param model The object containing the data used to populate the view
* @param position The position in the list of the view being populated
*/
abstract protected void populateViewHolder(VH viewHolder, VM model, int position);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment