Android MVP base classes (android-arch-sample)
import android.os.Bundle;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.remind101.archexample.presenters.BasePresenter;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
// берет на себя заботу об удержании Р при смене ориентации
public class PresenterManager {
private static final String SIS_KEY_PRESENTER_ID = "presenter_id";
private static PresenterManager instance;
private final AtomicLong currentId;
private final Cache<Long, BasePresenter<?, ?>> presenters;
PresenterManager(long maxSize, long expirationValue, TimeUnit expirationUnit) {
currentId = new AtomicLong();
presenters = CacheBuilder.newBuilder()
.maximumSize(maxSize)
.expireAfterWrite(expirationValue, expirationUnit)
.build();
}
public static PresenterManager getInstance() {
if (instance == null) {
instance = new PresenterManager(10, 30, TimeUnit.SECONDS);
}
return instance;
}
public <P extends BasePresenter<?, ?>> P restorePresenter(Bundle savedInstanceState) {
Long presenterId = savedInstanceState.getLong(SIS_KEY_PRESENTER_ID);
P presenter = (P) presenters.getIfPresent(presenterId);
presenters.invalidate(presenterId);
return presenter;
}
public void savePresenter(BasePresenter<?, ?> presenter, Bundle outState) {
long presenterId = currentId.incrementAndGet();
presenters.put(presenterId, presenter);
outState.putLong(SIS_KEY_PRESENTER_ID, presenterId);
}
}
import com.remind101.archexample.presenters.BasePresenter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public abstract class MvpRecyclerListAdapter<M, P extends BasePresenter, VH extends MvpViewHolder<P>> extends MvpRecyclerAdapter<M, P, VH> {
private final List<M> models;
public MvpRecyclerListAdapter() {
models = new ArrayList<>();
}
public void clearAndAddAll(Collection<M> data) {
models.clear();
presenters.clear();
for (M item : data) {
addInternal(item);
}
notifyDataSetChanged();
}
public void addAll(Collection<M> data) {
for (M item : data) {
addInternal(item);
}
int addedSize = data.size();
int oldSize = models.size() - addedSize;
notifyItemRangeInserted(oldSize, addedSize);
}
public void addItem(M item) {
addInternal(item);
notifyItemInserted(models.size());
}
public void updateItem(M item) {
Object modelId = getModelId(item);
// Swap the model
int position = getItemPosition(item);
if (position >= 0) {
models.remove(position);
models.add(position, item);
}
// Swap the presenter
P existingPresenter = presenters.get(modelId);
if (existingPresenter != null) {
existingPresenter.setModel(item);
}
if (position >= 0) {
notifyItemChanged(position);
}
}
public void removeItem(M item) {
int position = getItemPosition(item);
if (position >= 0) {
models.remove(item);
}
presenters.remove(getModelId(item));
if (position >= 0) {
notifyItemRemoved(position);
}
}
private int getItemPosition(M item) {
Object modelId = getModelId(item);
int position = -1;
for (int i = 0; i < models.size(); i++) {
M model = models.get(i);
if (getModelId(model).equals(modelId)) {
position = i;
break;
}
}
return position;
}
private void addInternal(M item) {
System.err.println("Adding item " + getModelId(item));
models.add(item);
presenters.put(getModelId(item), createPresenter(item));
}
@Override
public int getItemCount() {
return models.size();
}
@Override
protected M getItem(int position) {
return models.get(position);
}
}
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import com.remind101.archexample.presenters.BasePresenter;
import java.util.HashMap;
import java.util.Map;
public abstract class MvpRecyclerAdapter<M, P extends BasePresenter, VH extends MvpViewHolder> extends RecyclerView.Adapter<VH> {
final Map<Object, P> presenters;
public MvpRecyclerAdapter() {
presenters = new HashMap<>();
}
@NonNull protected P getPresenter(@NonNull M model) {
System.err.println("Getting presenter for item " + getModelId(model));
return presenters.get(getModelId(model));
}
@NonNull protected abstract P createPresenter(@NonNull M model);
@NonNull protected abstract Object getModelId(@NonNull M model);
@Override
public void onViewRecycled(VH holder) {
super.onViewRecycled(holder);
holder.unbindPresenter();
}
@Override
public void onBindViewHolder(VH holder, int position) {
holder.bindPresenter(getPresenter(getItem(position)));
}
protected abstract M getItem(int position);
}
import android.os.AsyncTask;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import com.remind101.archexample.CounterDatabase;
import com.remind101.archexample.models.Counter;
import com.remind101.archexample.views.MainView;
import java.util.List;
/**
* Presenter methods should mostly be callbacks,
* as the View is reporting events for the
* Presenter to evaluate
*/
public class MainPresenter extends BasePresenter<List<Counter>, MainView> {
private boolean isLoadingData = false;
@Override
protected void updateView() {
// Business logic is in the presenter
if (model.size() == 0) {
view().showEmpty();
} else {
view().showCounters(model);
}
}
@Override
public void bindView(@NonNull MainView view) {
super.bindView(view);
// Let's not reload data if it's already here
if (model == null && !isLoadingData) {
view().showLoading();
loadData();
}
}
private void loadData() {
isLoadingData = true;
new LoadDataTask().execute();
}
// Input events methods
public void onAddCounterClicked() {
Counter counter = new Counter();
counter.setName("New Counter");
counter.setValue(0);
// Update view immediately
model.add(counter);
CounterDatabase.getInstance().saveCounter(counter);
updateView();
}
// It's OK for this class not to be static and to keep a reference to the Presenter, as this
// is retained during orientation changes and is lightweight (has no activity/view reference)
private class LoadDataTask extends AsyncTask<Void, Void, Void> {
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(3000);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
setModel(CounterDatabase.getInstance().getAllCounters());
isLoadingData = false;
}
}
}
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.ViewAnimator;
import com.remind101.archexample.models.Counter;
import com.remind101.archexample.presenters.MainPresenter;
import com.remind101.archexample.views.MainView;
import java.util.List;
public class MainActivity extends AppCompatActivity implements MainView {
private static final int POSITION_LIST = 0;
private static final int POSITION_LOADING = 1;
private static final int POSITION_EMPTY = 2;
private ViewAnimator animator;
private CounterAdapter adapter;
private MainPresenter presenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState == null) {
presenter = new MainPresenter();
} else {
presenter = PresenterManager.getInstance().restorePresenter(savedInstanceState);
}
setContentView(R.layout.activity_list);
animator = (ViewAnimator) findViewById(R.id.animator);
RecyclerView recyclerView = (RecyclerView) animator.getChildAt(POSITION_LIST);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
adapter = new CounterAdapter();
recyclerView.setAdapter(adapter);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.add_counter:
presenter.onAddCounterClicked();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
protected void onResume() {
super.onResume();
presenter.bindView(this);
}
@Override
protected void onPause() {
super.onPause();
presenter.unbindView();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
PresenterManager.getInstance().savePresenter(presenter, outState);
}
@Override
public void showCounters(List<Counter> counters) {
adapter.clearAndAddAll(counters);
animator.setDisplayedChild(POSITION_LIST);
}
@Override
public void showLoading() {
animator.setDisplayedChild(POSITION_LOADING);
}
@Override
public void showEmpty() {
animator.setDisplayedChild(POSITION_EMPTY);
}
}
import android.support.annotation.NonNull;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import com.remind101.archexample.models.Counter;
import com.remind101.archexample.presenters.CounterPresenter;
import com.remind101.archexample.views.CounterViewHolder;
public class CounterAdapter extends MvpRecyclerListAdapter<Counter, CounterPresenter, CounterViewHolder> {
@Override
public CounterViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new CounterViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.counter_row, parent, false));
}
@NonNull
@Override
protected CounterPresenter createPresenter(@NonNull Counter counter) {
CounterPresenter presenter = new CounterPresenter();
presenter.setModel(counter);
return presenter;
}
@NonNull
@Override
protected Object getModelId(@NonNull Counter model) {
return model.getId();
}
}
import android.support.v7.widget.RecyclerView;
import android.view.View;
import com.remind101.archexample.presenters.BasePresenter;
public abstract class MvpViewHolder<P extends BasePresenter> extends RecyclerView.ViewHolder {
protected P presenter;
public MvpViewHolder(View itemView) {
super(itemView);
}
public void bindPresenter(P presenter) {
this.presenter = presenter;
presenter.bindView(this);
}
public void unbindPresenter() {
presenter = null;
}
}
import android.support.annotation.NonNull;
import java.lang.ref.WeakReference;
public abstract class BasePresenter<M, V> {
M model;
private WeakReference<V> view;
public void setModel(M model) {
resetState();
this.model = model;
if (setupDone()) {
updateView();
}
}
private void resetState() {
}
public void bindView(@NonNull V view) {
this.view = new WeakReference<>(view);
if (setupDone()) {
updateView();
}
}
public void unbindView() {
this.view = null;
}
protected V view() {
if (view == null) {
return null;
} else {
return view.get();
}
}
protected abstract void updateView();
boolean setupDone() {
return view() != null && model != null;
}
}