Skip to content

Commit

Permalink
Merge pull request #9 from andrzejchm/feature/parcelable-model
Browse files Browse the repository at this point in the history
0.1.2 release
  • Loading branch information
andrzejchm committed Jul 10, 2016
2 parents ba5b3b7 + 842e704 commit 35d2f3f
Show file tree
Hide file tree
Showing 108 changed files with 3,780 additions and 223 deletions.
34 changes: 14 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# DroidMVP
[![Release](https://jitpack.io/v/andrzejchm/DroidMVP.svg)](https://jitpack.io/#andrzejchm/DroidMVP)[![CircleCI](https://circleci.com/gh/andrzejchm/DroidMVP/tree/develop.svg?style=svg)](https://circleci.com/gh/andrzejchm/DroidMVP/tree/develop)

<p align="center">
<img align="cetnter" src="droidMVP.png" alt="mvp diagram" />
</p>

[![Release](https://jitpack.io/v/andrzejchm/DroidMVP.svg)](https://jitpack.io/#andrzejchm/DroidMVP) [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-DroidMVP-green.svg?style=true)](https://android-arsenal.com/details/1/3776)
[![CircleCI](https://circleci.com/gh/andrzejchm/DroidMVP/tree/develop.svg?style=svg)](https://circleci.com/gh/andrzejchm/DroidMVP/tree/develop)

##About
DroidMVP is a small Android library to help you incorporate the [**MVP pattern**](http:https://antonioleiva.com/mvp-android/) along with [**Passive View**](http:https://martinfowler.com/eaaDev/PassiveScreen.html) and [**Presentation Model**](http:https://martinfowler.com/eaaDev/PresentationModel.html) (yes, those can be combined together :) ) within your Android project.
Expand All @@ -8,23 +14,7 @@ DroidMVP is a small Android library to help you incorporate the [**MVP pattern**
<p align="center">
<img align="cetnter" src="mvp-diagram.png" alt="mvp diagram" />
</p>

**Pasive View**

> A screen and components with all application specific behavior extracted into a controller so that the widgets have their state controlled entirely by controller. - **Martin Fowler**
--
**Presentation Model**

> Represent the state and behavior of the presentation independently of the GUI controls used in the interface - **Martin Fowler**
--

#####Model and Presenter
In our case a controller will be our presenter, which stores the view state within the Presentation Model. All the state manipulation happens within the Model class itself, but it is the Presenter who initiates those modifications.

#####View
Our passive view is the activity or fragment, which will be treated as a widgets' (like TextView, ImageView etc.) container with the ability to present different states driven by the presenter. All user interaction should be routed to the presenter.
A short explanation of PassiveView, PresentationModel and how to use it with DroidMVP can be found [in this article](https://medium.com/@andrzejchm/presentation-model-and-passive-view-in-mvp-the-android-way-fdba56a35b1e)

##Setup

Expand All @@ -42,7 +32,7 @@ Add it in your root `build.gradle` at the end of repositories:
Add the dependency to your app's `build.gradle`
```groovy
dependencies {
compile 'com.github.andrzejchm:DroidMVP:0.1.0'
compile 'com.github.andrzejchm:DroidMVP:0.1.2'
}
```

Expand All @@ -54,5 +44,9 @@ This library makes it easy to use it with dependency injection frameworks like [

##Sample Project
A small android app which uses Dependency Injection along with **DroidMVP** can be found
[**here**](/sample)
[**here**](/sample-dagger)

Without dagger can be found [**here**](/sample).

With `Parcelable` PresentationModel can be found [**here**](/sample-parcelable).

Binary file added droidMVP.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion library/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ android {
minSdkVersion 10
targetSdkVersion 24
versionCode 1
versionName "0.1.1"
versionName "0.1.2"
}
buildTypes {
release {
Expand Down
10 changes: 8 additions & 2 deletions library/src/main/java/io/appflate/droidmvp/DroidMVPActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import android.support.v7.app.AppCompatActivity;
import java.io.Serializable;

public abstract class DroidMVPActivity<M extends Serializable, V extends DroidMVPView, P extends DroidMVPPresenter<V, M>>
public abstract class DroidMVPActivity<M, V extends DroidMVPView, P extends DroidMVPPresenter<V, M>>
extends AppCompatActivity implements DroidMVPView {

private DroidMVPViewDelegate<M, V, P> mvpDelegate = new DroidMVPViewDelegate<M, V, P>() {
Expand Down Expand Up @@ -61,6 +61,10 @@ public abstract class DroidMVPActivity<M extends Serializable, V extends DroidMV
mvpDelegate.onStart((V) this);
}

@NonNull protected P getPresenter() {
return mvpDelegate.getPresenter();
}

/**
* Used for performing field injection trough various dependency injection frameworks like
* Dagger. The injection is performed just before the #createPresenter() or
Expand All @@ -70,7 +74,8 @@ public abstract class DroidMVPActivity<M extends Serializable, V extends DroidMV
protected abstract void performFieldInjection();

/**
* Used for creating the presenter instance, called in #onCreate(Bundle) method.
* Used for creating the presenter instance, called in #onCreate(Bundle) method.
*
* @return an instance of your Presenter.
*/
@NonNull protected abstract P createPresenter();
Expand All @@ -84,6 +89,7 @@ public abstract class DroidMVPActivity<M extends Serializable, V extends DroidMV
*
* You can retrieve the arguments from your Intent's extra and pass it
* to your Presentation's model constructor.
*
* @return Presentation Model instance used by your Presenter.
*/
@NonNull protected abstract M createPresentationModel();
Expand Down
12 changes: 8 additions & 4 deletions library/src/main/java/io/appflate/droidmvp/DroidMVPFragment.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import java.io.Serializable;

public abstract class DroidMVPFragment<M extends Serializable, V extends DroidMVPView, P extends DroidMVPPresenter<V, M>>
public abstract class DroidMVPFragment<M, V extends DroidMVPView, P extends DroidMVPPresenter<V, M>>
extends Fragment implements DroidMVPView {

private DroidMVPViewDelegate<M, V, P> mvpDelegate = new DroidMVPViewDelegate<M, V, P>() {
Expand All @@ -37,7 +36,7 @@ public abstract class DroidMVPFragment<M extends Serializable, V extends DroidMV

@Override public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
performFieldInection();
performFieldInjection();
mvpDelegate.onCreate(this, savedInstanceState);
}

Expand All @@ -61,13 +60,17 @@ public abstract class DroidMVPFragment<M extends Serializable, V extends DroidMV
mvpDelegate.onDestroy();
}

@NonNull protected P getPresenter() {
return mvpDelegate.getPresenter();
}

/**
* Used for performing field injection trough various dependency injection frameworks like
* Dagger. The injection is performed just before the #createPresenter() or
* #createPresentationModel() methods are called, so you can
* have your presenter and/or Presentation Model being injected by Dagger.
*/
protected abstract void performFieldInection();
protected abstract void performFieldInjection();

/**
* Used for creating the presenter instance, called in #onCreate(Bundle) method.
Expand All @@ -85,6 +88,7 @@ public abstract class DroidMVPFragment<M extends Serializable, V extends DroidMV
*
* You can retrieve the arguments from #getArguments() method of your
* fragment and pass it to your Presentation's model constructor.
*
* @return Presentation Model instance used by your Presenter.
*/
@NonNull protected abstract M createPresentationModel();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,15 @@

package io.appflate.droidmvp;

import java.io.Serializable;

/**
* Every presenter in the app must either implement this interface or extend SimpleDroidMVPPresenter
* Every presenter in the app must either implement this interface or extend
* SimpleDroidMVPPresenter
* indicating the DroidMVPView type that wants to be attached with.
*/
public interface DroidMVPPresenter<V extends DroidMVPView, M extends Serializable> {
public interface DroidMVPPresenter<V extends DroidMVPView, M> {

void attachView(V mvpView, M presentationModel);


void detachView();

void onDestroy();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,53 +17,60 @@
package io.appflate.droidmvp;

import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.Serializable;

/**
* Class that makes it possible to write your own custom MVP Views that will fit in the DroidMVP structure.
* It is tightly coupled with the common android component's lifecycle, like onCreate, onStart, onStop,
* onSaveInstanceState and onDestroy to properly recover the model on configuration changes and making sure
* Class that makes it possible to write your own custom MVP Views that will fit in the DroidMVP
* structure.
* It is tightly coupled with the common android component's lifecycle, like onCreate, onStart,
* onStop,
* onSaveInstanceState and onDestroy to properly recover the model on configuration changes and
* making sure
* your presenters won't leak your views when those are supposed to be destroyed.
*/
public abstract class DroidMVPViewDelegate<M extends Serializable, V extends DroidMVPView, P extends DroidMVPPresenter<V, M>> {
public abstract class DroidMVPViewDelegate<M, V extends DroidMVPView, P extends DroidMVPPresenter<V, M>> {

private P presenter;
private M presentationModel;

private String presentationModelKey;

/**
* Commonly called from fragment's/activity's onCreate. Presentation Model is either created or restored
* Commonly called from fragment's/activity's onCreate. Presentation Model is either created or
* restored
* here.
*
* @param mvpView the MVP view that is being created.
* @param savedInstanceState instanceState provided by android framework in which we store the
* Presentation Model
*/
public void onCreate(DroidMVPView mvpView, @Nullable Bundle savedInstanceState) {
presentationModelKey = mvpView.getClass().getCanonicalName() + "$PresentationModel";
presentationModel = restorePresentationModel(getClass(), savedInstanceState);
presentationModel = restorePresentationModel(savedInstanceState);
if (presentationModel == null) {
presentationModel = createPresentationModel();
}
this.presenter = createPresenter();
}

/**
* Commonly called from fragment's/activity's onStart. Used for attaching the view to presenter,
* Commonly called from fragment's/activity's onStart. Used for attaching the view to
* presenter,
* so the presenter can start to update the view's state
*/
@SuppressFBWarnings("UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR")
public void onStart(V mvpView) {
@SuppressFBWarnings("UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR") public void onStart(V mvpView) {
checkPresenter();
checkPresentationModel();
presenter.attachView(mvpView, presentationModel);
}

/**
* Commonly called from fragment's/activity's onStop. Used for detaching the view from presenter, so no
* Commonly called from fragment's/activity's onStop. Used for detaching the view from
* presenter, so no
* memory leaks happen.
*/
@SuppressFBWarnings("UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR") public void onStop() {
Expand All @@ -72,8 +79,10 @@ public void onStart(V mvpView) {
}

/**
* Commonly called from fragment's/activity's onDestroy. Used to notify the presenter that the view
* will no longer be attached to the presenter, so all the long running tasks have to be terminated
* Commonly called from fragment's/activity's onDestroy. Used to notify the presenter that the
* view
* will no longer be attached to the presenter, so all the long running tasks have to be
* terminated
* and the context should be cleared.
*/
@SuppressFBWarnings("UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR") public void onDestroy() {
Expand All @@ -83,10 +92,18 @@ public void onStart(V mvpView) {

/**
* Used by the delegate to persist current Presentation Model due to configuration change.
* @param outState
*/
public void onSaveInstanceState(Bundle outState) {
outState.putSerializable(presentationModelKey, presentationModel);
@SuppressFBWarnings("UWF_FIELD_NOT_INITIALIZED_IN_CONSTRUCTOR") public void onSaveInstanceState(
Bundle outState) {
if (presentationModel instanceof Parcelable) {
outState.putParcelable(presentationModelKey, (Parcelable) presentationModel);
} else if (presentationModel instanceof Serializable) {
outState.putSerializable(presentationModelKey, (Serializable) presentationModel);
} else {
throw new IllegalArgumentException(
"Your presentation model must either implement Parcelable or Serializable interface: "
+ presentationModel.getClass().getCanonicalName());
}
}

public P getPresenter() {
Expand All @@ -111,10 +128,9 @@ private void checkPresentationModel() {
}
}

private M restorePresentationModel(Class stateViewClass, @Nullable Bundle savedInstanceState) {
private M restorePresentationModel(@Nullable Bundle savedInstanceState) {
if (savedInstanceState != null) {
Serializable potentialPresentationModel =
savedInstanceState.getSerializable(presentationModelKey);
Object potentialPresentationModel = savedInstanceState.get(presentationModelKey);
try {
return (M) potentialPresentationModel;
} catch (ClassCastException ex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,23 @@

package io.appflate.droidmvp;

import java.io.Serializable;

/**
* Base class that implements the DroidMVPPresenter interface and provides a base implementation for
* Base class that implements the DroidMVPPresenter interface and provides a base implementation
* for
* attachView() and detachView(). It also handles keeping a reference to the mvpView that
* can be accessed from the child classes by calling getMvpView().
*/
public abstract class SimpleDroidMVPPresenter<V extends DroidMVPView, M extends Serializable>
implements DroidMVPPresenter<V,M> {
public abstract class SimpleDroidMVPPresenter<V extends DroidMVPView, M>
implements DroidMVPPresenter<V, M> {

private M presentationModel;
private V mvpView;
private V mvpView;

@Override public void attachView(V mvpView, M presentationModel) {
this.mvpView = mvpView;
this.presentationModel = presentationModel;
}

public M getPresentationModel() {
return presentationModel;
}

@Override public void detachView() {
mvpView = null;
}
Expand All @@ -50,6 +45,10 @@ public M getPresentationModel() {

}

public M getPresentationModel() {
return presentationModel;
}

protected V getMvpView() {
return mvpView;
}
Expand Down
5 changes: 5 additions & 0 deletions quality_tools/findbugs-filter.xml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
<Match>
<Class name="~.*TestUtils"/>
</Match>

<!-- Do not check TestUtils class -->
<Match>
<Class name="~.*TestMocks"/>
</Match>
<!-- fields gets initialized in onCreate mostly in fragments -->
<Match>
<Class name="~.*Fragment"/>
Expand Down
Loading

0 comments on commit 35d2f3f

Please sign in to comment.