Skip to content
This repository has been archived by the owner on Aug 13, 2020. It is now read-only.

Commit

Permalink
Merge pull request #3 from d4rken/dev
Browse files Browse the repository at this point in the history
  • Loading branch information
d4rken committed Dec 9, 2017
2 parents 690484d + 732ec81 commit 76dac18
Show file tree
Hide file tree
Showing 41 changed files with 695 additions and 703 deletions.
11 changes: 6 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,17 @@ env:

android:
components:
- platform-tools
- tools
- build-tools-27.0.1
- build-tools-27.0.2
- android-27
- sys-img-armeabi-v7a-android-27
- extra-android-support
- extra-android-m2repository
- sys-img-armeabi-v7a-android-27
licenses:
- 'android-sdk-preview-license-.+'
- 'android-sdk-license-.+'
- 'google-gdk-license-.+'
- 'android-sdk-preview-license-.+'
- 'android-sdk-license-.+'
- 'google-gdk-license-.+'

before_install:
- chmod +x gradlew
Expand Down
129 changes: 123 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,136 @@
[![Coverage Status](https://coveralls.io/repos/github/d4rken/ommvplib/badge.svg)](https://coveralls.io/github/d4rken/ommvplib)
[![Build Status](https://travis-ci.org/d4rken/ommvplib.svg?branch=master)](https://travis-ci.org/d4rken/ommvplib)

This library offers a structure for building Android apps follow MVP principles. Adhering to this structure yields a modular and well testable app.
This library offers a structure that helps build Android apps that follow MVP principles.

The core concept consists of Dagger2's `AndroidInjector` while using Android's `Loader` to retain presenters and components across lifecycle events
The core concept utilizes a `Loader` to store the `Presenter` during configuration (e.g. rotation) changes. This can be seamlessly combined with Dagger2's `AndroidInjector`.

The demo application shows how to use the library and includes examples on how to use unit and instrumentation tests with it.

## Quickstart
1. Add the library
Add the library:
```groovy
implementation 'eu.darken.ommvplib:library:0.2.0'
```
### Without Dagger
The `Presenter` and the `View` that our `Activity` will implement.
```java
@MainComponent.Scope
public class MainPresenter extends Presenter<MainPresenter.View, MainComponent> {

@Inject
MainPresenter() {
}

@Override
public void onBindChange(@Nullable View view) {
super.onBindChange(view);
onView(v -> doSomething());
}

public interface View extends Presenter.View {
void doSomething();
}
}

```

The process of `attach`ing initialises the `Loader`, checks for any existing `Presenter` and creates one if necessary.
```java
public class MainActivity extends AppCompatActivity implements MainPresenter.View {

MainPresenter presenter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
OMMVPLib.<MainPresenter.View, MainPresenter>builder()
.statePublisher(statePublisher)
.presenterCallback(new LoaderFactory.Callback<MainPresenter.View, MainPresenter>() {
@Override
public void onPresenterReady(MainPresenter presenter) {
MainActivity.this.presenter = presenter;
}

@Override
public void onPresenterDestroyed() {

}
})
.presenterSource(new PresenterSource<MainPresenter>() {
@Override
public MainPresenter create() {
return new MainPresenter();
}
})
.attach(this);
setContentView(R.layout.activity_main);
}
}
```
implementation 'eu.darken.ommvplib:library:0.1.0'

### With Dagger
The component for our injection.
```java
@MainComponent.Scope
@Subcomponent(modules = {AndroidSupportInjectionModule.class})
public interface MainComponent extends ActivityComponent<MainActivity>, PresenterComponent<MainPresenter.View, MainPresenter> {

@Subcomponent.Builder
abstract class Builder extends ActivityComponent.Builder<MainActivity, MainComponent> {}

@javax.inject.Scope
@Retention(RetentionPolicy.RUNTIME)
@interface Scope {}
}
```

The `Presenter` that will be injected, including the `View` that our `Activity` will implement.

```java
@MainComponent.Scope
public class MainPresenter extends ComponentPresenter<MainPresenter.View, MainComponent> {

@Inject
MainPresenter() {
}

@Override
public void onBindChange(@Nullable View view) {
super.onBindChange(view);
onView(v -> doSomething());
}

public interface View extends Presenter.View {
void doSomething();
}
}

```

Now we just need to attach the presenter.

```java
public class MainActivity extends AppCompatActivity implements MainPresenter.View {

@Inject MainPresenter presenter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
OMMVPLib.<MainPresenter.View, MainPresenter>builder()
.statePublisher(statePublisher)
.presenterCallback(new PresenterInjectionCallback<>(this))
.presenterSource(new InjectedPresenter<>(this))
.attach(this);
setContentView(R.layout.activity_main);
}
}
```
2. There is no Quickstart. Look at the example app. It's not as complicated as it looks.


## Acknowledgements
This library combines and refactored several other concepts. It was based on:
This library combines multiple concepts:

* [tomorrow-mvp](https://github.com/michal-luszczuk/tomorrow-mvp) by Michał Łuszczuk
* [toegether-mvp](https://github.com/laenger/together-mvp) by Christian Langer
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ allprojects {
ext {
minSdkVersion = 16
compileSdkVersion = 27
buildToolsVersion = '27.0.1'
buildToolsVersion = '27.0.2'
targetSdkVersion = 27

supportVersion = '27.0.2'
Expand Down
34 changes: 17 additions & 17 deletions example/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,45 +37,45 @@ repositories {
}
}
dependencies {
compile project(':library')
implementation project(':library')

testCompile 'junit:junit:4.12'
testImplementation 'junit:junit:4.12'

compile "com.android.support:design:${supportVersion}"
compile "com.android.support:preference-v14:${supportVersion}"
implementation "com.android.support:design:${supportVersion}"
implementation "com.android.support:preference-v14:${supportVersion}"

compile "com.google.dagger:dagger:${daggerVersion}"
implementation "com.google.dagger:dagger:${daggerVersion}"
annotationProcessor "com.google.dagger:dagger-compiler:${daggerVersion}"
androidTestAnnotationProcessor "com.google.dagger:dagger-compiler:${daggerVersion}"

compile "com.google.dagger:dagger-android:${daggerVersion}"
compile "com.google.dagger:dagger-android-support:${daggerVersion}"
implementation "com.google.dagger:dagger-android:${daggerVersion}"
implementation "com.google.dagger:dagger-android-support:${daggerVersion}"

annotationProcessor "com.google.dagger:dagger-android-processor:${daggerVersion}"
androidTestAnnotationProcessor "com.google.dagger:dagger-android-processor:${daggerVersion}"

compile "com.jakewharton:butterknife:${butterknifeVersion}"
implementation "com.jakewharton:butterknife:${butterknifeVersion}"
annotationProcessor "com.jakewharton:butterknife-compiler:${butterknifeVersion}"

compile 'com.jakewharton.timber:timber:4.6.0'
implementation 'com.jakewharton.timber:timber:4.6.0'

androidTestCompile 'org.mockito:mockito-core:2.8.9'
androidTestCompile 'com.linkedin.dexmaker:dexmaker-mockito:2.2.0'
testCompile 'org.mockito:mockito-core:2.8.9'
testCompile 'com.linkedin.dexmaker:dexmaker-mockito:2.2.0'
androidTestImplementation 'org.mockito:mockito-core:2.8.9'
androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito:2.2.0'
testImplementation 'org.mockito:mockito-core:2.8.9'
testImplementation 'com.linkedin.dexmaker:dexmaker-mockito:2.2.0'

androidTestCompile "com.google.code.findbugs:jsr305:3.0.1"
androidTestImplementation "com.google.code.findbugs:jsr305:3.0.1"

// Android runner and rules support
androidTestCompile('com.android.support.test:runner:0.5', {
androidTestImplementation('com.android.support.test:runner:0.5', {
exclude group: 'com.android.support', module: 'support-annotations'
})
androidTestCompile('com.android.support.test:rules:0.5', {
androidTestImplementation('com.android.support.test:rules:0.5', {
exclude group: 'com.android.support', module: 'support-annotations'
})

// Espresso support
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class CountingFragmentTest {
Expand Down Expand Up @@ -57,6 +58,7 @@ public void inject(Fragment fragment) {
@Before
public void setUp() {
doAnswer(invocationOnMock -> null).when(presenter).onBindChange(any());
when(presenter.getComponent()).thenReturn(component);
fragmentRule.setManualInjector(injector);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import android.view.ViewGroup;
import android.widget.TextView;

Expand All @@ -11,25 +12,24 @@

import butterknife.BindView;
import butterknife.ButterKnife;
import eu.darken.ommvplib.base.InstanceStatePublisher;
import eu.darken.ommvplib.base.OMMVPLib;
import eu.darken.ommvplib.example.R;
import eu.darken.ommvplib.injection.ComponentPresenterActivity;
import eu.darken.ommvplib.injection.ComponentSource;
import eu.darken.ommvplib.injection.InjectedPresenter;
import eu.darken.ommvplib.injection.ManualInjector;
import eu.darken.ommvplib.injection.PresenterInjectionCallback;
import eu.darken.ommvplib.injection.fragment.support.HasManualSupportFragmentInjector;


public class MainActivity extends ComponentPresenterActivity<MainPresenter.View, MainPresenter, MainComponent>
implements MainPresenter.View, HasManualSupportFragmentInjector {
public class MainActivity extends AppCompatActivity implements MainPresenter.View, HasManualSupportFragmentInjector {

@Inject ComponentSource<Fragment> componentSource;

@BindView(R.id.container) ViewGroup container;
@BindView(R.id.bindcounter) TextView bindCounter;

@Override
public Class<? extends MainPresenter> getTypeClazz() {
return MainPresenter.class;
}
private InstanceStatePublisher statePublisher;

@Override
public ManualInjector<Fragment> supportFragmentInjector() {
Expand All @@ -39,14 +39,30 @@ public ManualInjector<Fragment> supportFragmentInjector() {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
statePublisher = new InstanceStatePublisher();
statePublisher.onCreate(savedInstanceState);
OMMVPLib.<MainPresenter.View, MainPresenter>builder()
.statePublisher(statePublisher)
.presenterCallback(new PresenterInjectionCallback<>(this))
.presenterSource(new InjectedPresenter<>(this))
.attach(this);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
}

@Override
protected void onSaveInstanceState(Bundle outState) {
statePublisher.onSaveInstanceState(outState);
super.onSaveInstanceState(outState);
}

@Override
public void showFragment(Class<? extends Fragment> fragmentClass) {
final Fragment fragment = Fragment.instantiate(this, fragmentClass.getName());
getSupportFragmentManager().beginTransaction().add(R.id.container, fragment).commitNow();
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.container);
if (fragment == null) {
fragment = Fragment.instantiate(this, fragmentClass.getName());
getSupportFragmentManager().beginTransaction().add(R.id.container, fragment).commitNow();
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,9 @@
public interface MainComponent extends ActivityComponent<MainActivity>, PresenterComponent<MainPresenter.View, MainPresenter> {

@Subcomponent.Builder
abstract class Builder extends ActivityComponent.Builder<MainActivity, MainComponent> {
}
abstract class Builder extends ActivityComponent.Builder<MainActivity, MainComponent> {}

@javax.inject.Scope
@Retention(RetentionPolicy.RUNTIME)
@interface Scope {
}
@interface Scope {}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package eu.darken.ommvplib.example.screens;

import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;

Expand All @@ -21,6 +23,18 @@ public class MainPresenter extends ComponentPresenter<MainPresenter.View, MainCo
this.startingFragment = startingFragment;
}

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) bindCounter = savedInstanceState.getInt("counter");
}

@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("counter", bindCounter);
}

@Override
public void onBindChange(@Nullable View view) {
super.onBindChange(view);
Expand Down

0 comments on commit 76dac18

Please sign in to comment.