Skip to content

Commit

Permalink
Merge pull request #8 from andrzejchm/feature/parcelable-model
Browse files Browse the repository at this point in the history
Parcelable model option + adding new samples
  • Loading branch information
andrzejchm committed Jul 10, 2016
2 parents 6987ccb + 842e704 commit 818a5e6
Show file tree
Hide file tree
Showing 104 changed files with 3,645 additions and 177 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,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.1'
compile 'com.github.andrzejchm:DroidMVP:0.1.2'
}
```

Expand All @@ -44,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).

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
86 changes: 86 additions & 0 deletions sample-dagger/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright (C) 2016 Appflate.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http:https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

buildscript {
repositories {
mavenCentral()
jcenter()
}

dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}

apply plugin: 'com.android.application'
apply plugin: 'android-apt'
apply from: '../quality_tools/findbugs.gradle'

android {
compileSdkVersion 24
buildToolsVersion "24"

defaultConfig {
applicationId "io.appflate.droidmvp.androidsample"
minSdkVersion 10
targetSdkVersion 24
versionCode 1
versionName "1.0"
testInstrumentationRunner 'io.appflate.droidmvp.androidsample.CustomTestRunner'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

lintOptions {
warningsAsErrors true
abortOnError true
disable 'InvalidPackage'
}
}

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:24.0.0'
compile 'com.android.support:design:24.0.0'
compile 'com.jakewharton:butterknife:7.0.1'
compile 'com.google.dagger:dagger:2.2'
compile 'com.squareup.retrofit2:retrofit:2.0.2'
compile 'com.squareup.retrofit2:converter-gson:2.0.2'
compile 'com.google.code.gson:gson:2.4'
compile 'com.google.code.findbugs:annotations:2.0.3'
provided 'javax.annotation:jsr250-api:1.0'
apt 'com.google.dagger:dagger-compiler:2.2'

//the most important: DroidMVP :)
compile project(':library')

//Test dependencies

testCompile 'com.android.support:appcompat-v7:24.0.0'
testCompile 'com.android.support:design:24.0.0'
testCompile 'junit:junit:4.12'
testCompile "org.robolectric:robolectric:3.0"
testCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
testCompile('com.android.support.test.espresso:espresso-contrib:2.2.2') {
exclude group: 'com.android.support', module: 'appcompat'
exclude group: 'com.android.support', module: 'support-v4'
exclude module: 'recyclerview-v7'
}
}
Loading

0 comments on commit 818a5e6

Please sign in to comment.