A full-featured framework that allows building android applications following the principles of Clean Architecture.

Overview

EasyMVP

Build Status Download Android Arsenal

A powerful, and very simple MVP library with annotation processing and bytecode weaving.

EasyMVP eliminates the boilerplate code for dealing with MVP implementation.

📖 Chinese Readme 中文文档

Features

  • Easy integration
  • Less boilerplate
  • Composition over inheritance
  • Implement MVP with just few annotations
  • Use Loaders to preserve presenters across configurations changes
  • Support Clean Architecture approach.

Installation

Configure your project-level build.gradle to include the 'easymvp' plugin:

buildscript {
  repositories {
    ...
    maven { url  "http://dl.bintray.com/6thsolution/easymvp" }
   }
  dependencies {
    classpath 'com.sixthsolution.easymvp:easymvp-plugin:1.2.0-beta10'
  }
}
allprojects {
  repositories {
      ...
      maven { url  "http://dl.bintray.com/6thsolution/easymvp" }
  }
}

Then, apply the 'easymvp' plugin in your module-level build.gradle:

apply plugin: 'easymvp'

android {
  ...
}

There is no need for android-apt plugin for android gradle plugin version 2.2.0-alpha1 or higher. But if your are using it, please apply easymvp plugin after android-apt plugin.

apply plugin: 'com.neenbedankt.android-apt'
apply plugin: 'easymvp'

For reactive API, simply apply the 'easymvp-rx' plugin in your module-level build.gradle and then add the RxJava dependency:

apply plugin: 'easymvp-rx'

dependencies {
  compile 'io.reactivex:rxjava:x.y.z'
}

Also EasyMVP supports RxJava2:

apply plugin: 'easymvp-rx2'

dependencies {
  compile 'io.reactivex.rxjava2:rxjava:x.y.z'
}

Note: All snapshot versions are available on jfrog

Usage

First thing you will need to do is to create your view interface.

public interface MyView {
    void showResult(String resultText);

    void showError(String errorText);
}

Then you should implement MyView in your Activity, Fragment or CustomView. But why?

  • Improve unit testability. You can test your presenter without any android SDK dependencies.
  • Decouple the code from the implementation view.
  • Easy stubbing. For example, you can replace your Activity with a Fragment without any changes in your presenter.
  • High level details (such as the presenter), can't depend on low level concrete details like the implementation view.

Presenter

Presenter acts as the middle man. It retrieves data from the data-layer and shows it in the View.

You can create a presenter class by extending of the AbstractPresenter or RxPresenter (available in reactive API).

public class MyPresenter extends AbstractPresenter<MyView> {

}

To understand when the lifecycle methods of the presenter are called take a look at the following table:

Presenter Activity Fragment View
onViewAttached onStart onResume onAttachedToWindow
onViewDetached onStop onPause onDetachedFromWindow
onDestroyed onDestroy onDestroy onDetachedFromWindow

View Annotations

Well, here is the magic part. There is no need for any extra inheritance in your Activity, Fragment or View classes to bind the presenter lifecycle.

Presenter's creation, lifecycle-binding, caching and destruction gets handled automatically by these annotations.

For injecting presenter into your activity/fragment/view, you can use @Presenter annotation. Also during configuration changes, previous instance of the presenter will be injected.

EasyMVP uses Loaders to preserve presenters across configurations changes.

Presenter instance will be set to null, after onDestroyed method injection.

@ActivityView example:

@ActivityView(layout = R.layout.my_activity, presenter = MyPresenter.class)
public class MyActivity extends AppCompatActivity implements MyView {

    @Presenter
    MyPresenter presenter;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    protected void onStart() {
        super.onStart();
        // Now presenter is injected.
    }
    
    @Override
    public void showResult(String resultText) {
        //do stuff
    }
    
    @Override
    public void showError(String errorText) {
        //do stuff
    }
}
  • You can specify the layout in @ActivityView#layout and EasyMVP will automatically inflate it for you.

@FragmentView example:

@FragmentView(presenter = MyPresenter.class)
public class MyFragment extends Fragment implements MyView {

    @Presenter
    MyPresenter presenter;
    
    @Override
    public void onResume() {
        super.onResume();
        // Now presenter is injected.
    }
    
    @Override
    public void showResult(String resultText) {
        //do stuff
    }
    
    @Override
    public void showError(String errorText) {
        //do stuff
    }
}

@CustomView example:

@CustomView(presenter = MyPresenter.class)
public class MyCustomView extends View implements MyView {

    @Presenter
    MyPresenter presenter;
    
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        // Now presenter is injected.
    }
    
    @Override
    public void showResult(String resultText) {
        //do stuff
    }
    
    @Override
    public void showError(String errorText) {
        //do stuff
    }
}

Injecting with Dagger

@Presenter annotation will instantiate your presenter class by calling its default constructor, So you can't pass any objects to the constructor.

But if you are using Dagger, you can use its constructor injection feature to inject your presenter.

So what you need is make your presenter injectable and add @Inject annotation before @Presenter. Here is an example:

public class MyPresenter extends AbstractPresenter<MyView> {

    @Inject
    public MyPresenter(UseCase1 useCase1, UseCase2 useCase2){
    
    }
}

@ActivityView(layout = R.layout.my_activity, presenter = MyPresenter.class)
public class MyActivity extends AppCompatActivity implements MyView {

    @Inject
    @Presenter
    MyPresenter presenter;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        SomeDaggerComponent.injectTo(this);
        super.onCreate(savedInstanceState);
     }
        
    //...
}

Don't inject dependencies after super.onCreate(savedInstanceState); in activities, super.onActivityCreated(bundle); in fragments and super.onAttachedToWindow(); in custom views.

Clean Architecture Usage

You can follow the principles of Clean Architecture by applying 'easymvp-rx' plugin. Previous part was all about the presentation-layer, Now lets talk about the domain-layer.

Domain Layer holds all your business logic, it encapsulates and implements all of the use cases of the system. This layer is a pure java module without any android SDK dependencies.

UseCase

UseCases are the entry points to the domain layer. These use cases represent all the possible actions a developer can perform from the presentation layer.

Each use case should run off the main thread(UI thread), to avoid reinventing the wheel, EasyMVP uses RxJava to achieve this.

You can create a use case class by extending of the following classes:

public class SuggestPlaces extends ObservableUseCase<List<Place>, String> {

    private final SearchRepository searchRepository;

    public SuggestPlaces(SearchRepository searchRepository, 
                         UseCaseExecutor useCaseExecutor,
                         PostExecutionThread postExecutionThread) {
        super(useCaseExecutor, postExecutionThread);
        this.searchRepository = searchRepository;
    }

    @Override
    protected Observable<List<Place>> interact(@NonNull String query) {
        return searchRepository.suggestPlacesByName(query);
    }
}
public class InstallTheme extends CompletableUseCase<File> {

    private final ThemeManager themeManager;
    private final FileManager fileManager;
    
    public InstallTheme(ThemeManager themeManager,
                           FileManager fileManager,
                           UseCaseExecutor useCaseExecutor,
                           PostExecutionThread postExecutionThread) {
        super(useCaseExecutor, postExecutionThread);
        this.themeManager = themeManager;
        this.fileManager = fileManager;
    }

    @Override
    protected Completable interact(@NonNull File themePath) {
        return themeManager.install(themePath)
                .andThen(fileManager.remove(themePath))
                .toCompletable();
    }

}

And the implementations of UseCaseExecutor and PostExecutionThread are:

public class UIThread implements PostExecutionThread {
    
    @Override
    public Scheduler getScheduler() {
        return AndroidSchedulers.mainThread();
    }
}

public class BackgroundThread implements UseCaseExecutor {

    @Override
    public Scheduler getScheduler() {
        return Schedulers.io();
    }
}

DataMapper

Each DataMapper transforms entities from the format most convenient for the use cases, to the format most convenient for the presentation layer.

But, why is it useful?

Let's see SuggestPlaces use case again. Assume that you passed the Mon query to this use case and it emitted:

  • Montreal
  • Monterrey
  • Montpellier

But you want to bold the Mon part of each suggestion like:

  • Montreal
  • Monterrey
  • Montpellier

So, you can use a data mapper to transform the Place object to the format most convenient for your presentation layer.

public class PlaceSuggestionMapper extends DataMapper<List<SuggestedPlace>, List<Place>> {
    
    @Override
    public List<SuggestedPlace> call(List<Place> places) {
        //TODO for each Place object, use SpannableStringBuilder to make a partial bold effect
    }
}

Note that Place entity lives in the domain layer but SuggestedPlace entity lives in the presentation layer.

So, How to bind DataMapper to ObservableUseCase?

public class MyPresenter extends RxPresenter<MyView> {

    private SuggestPlace suggestPlace;
    private SuggestPlaceMapper suggestPlaceMapper;
    
    @Inject
    public MyPresenter(SuggestPlace suggestPlace, SuggestPlaceMapper suggestPlaceMapper){
        this.suggestPlace = suggestPlace;
        this.suggestPlaceMapper = suggestPlaceMapper;
    }
    
    void suggestPlaces(String query){
        addSubscription(
                       suggestPlace.execute(query)
                                     .map(suggetsPlaceMapper)
                                     .subscribe(suggestedPlaces->{
                                           //do-stuff
                                      })
                        );
    }
}

FAQ

How does EasyMVP work under the hood?

  • For each annotated class with @ActivityView, @FragmentView or @CustomView, EasyMVP generates *_ViewDelegate class in the same package. These classes are responsible for binding presenter's lifecycle.
  • EasyMVP uses bytecode weaving to call delegate classes inside your view implementation classes. You can find these manipulated classes in build/weaver folder.

Is there any restrictions on using EasyMVP?

Does it support kotlin?

  • Yes, See this issue for details.

Documentations

EasyMVP API: Javadocs for the current API release

EasyMVP RX-API: Javadocs for the current RX-API (Clean Architecture API) release

EasyMVP RX2-API: Javadocs for the current RX2-API (Clean Architecture API) release

Demo

CleanTvMaze Shows how to use EasyMVP with Kotlin

TVProgram_Android Shows how to use EasyMVP with Java

Author

Saeed Masoumi

License

Copyright 2016-2017 6thSolution Technologies Inc.

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://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.
Comments
  • Same instance of Presenter injected in all instances of a class

    Same instance of Presenter injected in all instances of a class

    Problem I've encountered a pretty big bug (or maybe not-thought feature?).
    Let's say we have the following presenter class:

    public class Presenter extends AbstractPresenter<View> {
        private static int counter = 0;
        private int count = counter++;
    
        public void setupView(){
            getView().setLabelText(String.valueOf(count));
        }
    
    }
    

    Which is used by the following view class:

    @CustomView(presenter = Presenter.class)
    public class ViewImpl extends android.View implements View {
        @Presenter Presenter presenter;
        @BindView(R2.id.icon_label_textview) TextView labelTextView;
    
        public ViewImpl(Context context){
            View view = LayoutInflater.from(context).inflate(R.layout.view_layout, this);
            ButterKnife.bind(this, view);
        }
    
        @Override
        protected void onAttachedToWindow() {
            super.onAttachedToWindow();
            presenter.setupView();
        }
    
        @Override
        public void setLabelText(String text) {
            labelTextView.setText(text);
        }
    

    That implements the followin interface:

    public interface IconView {
        void setLabelText(String text);
    

    Now, inside another class (let's say a fragment), we exceute the following piece of code:

    for (int i = 0 ; i < 10; i ++){
       View view = new View(getActivity());  // Create a new View instance
       addView(view);  // Adds the view to the layout
    }
    

    What will happen is that the first View that gets created triggers the Presenter creation, and thus will correctly show a label displaying the number 0 (initial value of counter). But then, starting from the second icon onwards, the Presenter instances associated with the new View instances will all refer the first Presenter instance, thus showing other 9 labels all with the value 0 instead of the supposed increasing 1, 2, 3, ..., 9.

    Solution I think that this issue might be solved by adding a field to the @Presenter annotation, let's call it refresh which, if set to true performs the injection each time that a new instance of the class referring that field is created, otherwise it will remain all the same. Let's have a look at how it could be used in both cases:

    @CustomView(presenter = Presenter.class)
    public class ViewImpl implements View{
        @Presenter(refresh = true) // This instance will be re-injected each time that a ViewImpl istance gets created
        Presenter presenter;
        
        ...
    }
    
    @CustomView(presenter = Presenter.class)
    public class ViewImpl implements View{
        // @Presenter(refresh = false) if the same of writing @Presenter, false is the default value
        @Presenter(refresh = false) // This instance will not be re-injected each time that a ViewImpl istance gets created
        Presenter presenter;
        
        ...
    }
    
    bug 
    opened by RiccardoM 10
  • Presenter null

    Presenter null

    Hi, im trying implement EasyMvp to my project here are my Fragment

    @FragmentView(presenter = LoginPresenter.class) public class LoginFragment extends Fragment
        implements LoginView {
    
      @Presenter LoginPresenter presenter;
    
      public LoginFragment() {
        // Required empty public constructor
      }
    
      public static LoginFragment newInstance() {
        Bundle args = new Bundle();
        LoginFragment fragment = new LoginFragment();
        fragment.setArguments(args);
        return fragment;
      }
    
      @Override public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
      }
    
      @Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
          Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_login, container, false);
      }
    
      @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        ButterKnife.bind(this, view);
      }
    
      @Override public void onStart() {
        super.onStart();
      }
    
      @Override public void onStop() {
        super.onStop();
      }
    
      @OnClick(R.id.btnLogin) void onLoginClick() {
        presenter.onLoginClick();
      }
    
      @OnClick(R.id.btnForgotPassword) void onForgotPassword() {
        EventBus.getDefault().post(new ClickEvent(ClickEvent.Event.FORGOT_PASSWORD));
      }
    
      @OnClick(R.id.btnSignUp) void onSignUpClick() {
        EventBus.getDefault().post(new ClickEvent(ClickEvent.Event.REGISTER));
      }
    
      @Override public String getUsername() {
        return null;
      }
    
      @Override public void showEmptyUsername() {
    
      }
    
      @Override public String getPassword() {
        return null;
      }
    
      @Override public void showEmptyPassword() {
    
      }
    
      @Override public void loginSuccesfull() {
    
      }
    
      @Override public void loginFailed() {
    
      }
    
      @Override public void showMessage(String message) {
        Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show();
      }
    }
    

    and for my Presenter

    public class LoginPresenter extends RxPresenter<LoginView> {
      private LoginRepository repository;
    
      public LoginPresenter() {
      }
    
      @Override public void onViewAttached(LoginView view) {
        super.onViewAttached(view);
      }
    
      @Override public void onViewDetached() {
        super.onViewDetached();
      }
    
      public void onLoginClick() {
        final String username = getView().getUsername();
        final String password = getView().getPassword();
        if (username.isEmpty()) {
          getView().showEmptyUsername();
        } else if (password.isEmpty()) {
          getView().showEmptyPassword();
        } else {
          getView().loginSuccesfull();
        }
      }
    }
    

    but when im trying access my presenter its still null, i have been add

    apply plugin: 'easymvp'
    apply plugin: 'easymvp-rx'
    

    to my build.gradle

    bug 
    opened by pratamawijaya 6
  • Mocking views in presenter testing

    Mocking views in presenter testing

    When writing unit tests I can't change the view object in the presenter without calling onViewAttached I think it would be nice if there was a method (with @VisibleForTesting annotation maybe) to change the view object of the presenter without calling any extra method. This way we can easily mock views in presenter objects and write unit tests for any presenter and test them separately like MVP pattern suggests. P.s: RxJava 2 API hasn't been uploaded to jcenter yet: http://jcenter.bintray.com/com/sixthsolution/easymvp/

    opened by mohamad-amin 4
  • How to work with ButterKnife

    How to work with ButterKnife

    mudule-level:

    build.gradle

    apply plugin: 'com.android.application'
    apply plugin: 'easymvp'
    apply plugin: 'android-apt'
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
    
        compile 'com.jakewharton:butterknife:8.4.0'
        apt 'com.jakewharton:butterknife-compiler:8.4.0'
    
        compile 'com.android.support:appcompat-v7:24.2.1'
    }
    

    the activity:

    MainActivity.java

    @ActivityView(layout = R.layout.activity_main, presenter = MainPresenter.class)
    public class MainActivity extends AppCompatActivity implements MainView{
    
        @Presenter
        MainPresenter mMainPresenter;
    
        @BindView(R.id.pb_loading)
        ProgressBar mPbLoading;
    
        @BindView(R.id.tv_content)
        TextView mTvContent;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            ButterKnife.bind(this);
        }
    }
    

    it doesn't work, and i can't find the _ViewDelegate class in build/generated/source/apt/.

    opened by znyang 4
  • Support bluelinelabs/Conductor

    Support bluelinelabs/Conductor

    https://github.com/bluelinelabs/Conductor Conductor is great View-based approach but there view is wrapped in Controller. Add Controller annotation so @Controller(layout = R.layout.my_view, presenter = MyPresenter.class)

    feature 
    opened by erva 4
  • Cannot access ParametersAreNullableByDefault

    Cannot access ParametersAreNullableByDefault

    I've tried to create the following presenter:

    public class MainPresenter extends RxPresenter<MainView> {
    
        private MainView view;
    
        private GetRandomContactUseCase getRandomContact;
    
        @Inject
        public MainPresenter(GetRandomContactUseCase getRandomContact){
            this.getRandomContact = getRandomContact;
        }
    
        ...
    
    }
    

    With java GetRandomContactUseCase being:

    public class GetRandomContactUseCase extends ObservableUseCase<String, Void> {
    
        ContactsDataSource source;
    
        @Inject
        public GetRandomContactUseCase(ContactsDataSource source,
                                       UseCaseExecutor executor,
                                       PostExecutionThread executionThread){
            super(executor, executionThread);
            this.source = source;
        }
    
        @Override
        protected Observable<String> interact(Void param) {
            return Observable.just(source.getRandomContact());
        }
    }
    

    When I try to compile this, i get the following error:

    Error:cannot access ParametersAreNullableByDefault  
    Error:Execution failed for task ':module:compileReleaseJavaWithJavac'.  
    > Compilation failed; see the compiler error output for details.
    

    Using the following UseCase, which does not extend ObservableUseCase, however, I don't get any error:

    public class GetRandomContactUseCase {
    
        ContactsDataSource source;
    
        @Inject
        public GetRandomContactUseCase(ContactsDataSource source,
                                       UseCaseExecutor executor,
                                       PostExecutionThread executionThread){
            this.source = source;
        }
    

    I think that this is due to ObservableUseCase having no default constructor, or no constructor marked as @Inject. Is there a way you can fix this issue, maybe creating a plugin which has all classes with a constructor marked with @Inject if that is the error, or having an empty constructor?

    opened by RiccardoM 3
  • A question about ButterKnife

    A question about ButterKnife

    I added compile 'com.jakewharton:butterknife:8.5.0' annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' to my application module, and called method ButterKnife.bind(this); in method onCreate,but widgets is still null, how could solution it?

    opened by bptx0114 2
  • Support presenters for RecyclerView.Adapter

    Support presenters for RecyclerView.Adapter

    Right now there is now way to use the @Presenter annotation inside a class which extends from RecyclerView.Adapter, preventing the use of the library while working with this kind of objects. I think that this should be supported as adapters are becoming nowadays a major part of Android application, and having the possibility to divide the view and the model part inside them could lead to a great improvement of code quality.

    Possible solution

    Fragment class.

    @FragmentView(
       presenter = FragmentWithAdapterPresenter.class,
       adapter = FragmentAdapter.class  // Tells the adapter class
    )
    public class FragmentWithAdapter extends Fragment implements FragmentView {
      @Presenter
      FragmentWithAdapterPresenter presenter;
    
      // Bind the adapter
      @Adapter
      FragmentAdapter adapter;
    
    }
    

    Adapter class

    @Adapter(presenter = FragmentAdapterPresenter.class)
    public class FragmentAdapter extends AbstractMVPAdapter<FragmentView> implements AdapterView {
      @Presenter // Binds the presenter to the adapter
      FragmentAdapterPresenter presenter;
    
      private FragmentView view;
    
      // Called after Fragment#onCreateView(), Activity#onCreate() or View#onDraw() has finished
      @Override
      protected void onViewAttached(View view){
        this.view = view;
        super.onViewAttached(view);
      }
    }
    

    AbstractMVPAdapter class

    public abstract class AbstractMVPAdapter<V>{
      protected void onViewAttached(V view);  // Called when the Activity, Fragment or View gets created
      protected void onViewDetached();  // Called when the Activity, Fragment or View gets destroyed
    
      protected V getView();  // Returns the bound view
    }
    

    FragmentAdapterPresenter class

    public class FragmentAdapterPresenter extends BaseAdapterPresenter<AdapterView>{
      // Methods of a common presenter
    }
    

    BaseAdapterPresenter class

    public abstract class BaseAdapterPresenter<V>{
      Same methods as the actual AbstractPresenter
    }
    

    How it works

    Note. The following working mechanism will use Activityas a reference, but it may be also Fragment or even View.

    Creation lifecycle.

    1. When an Activity gets created, after the onCreate() method, it calls the AbstractMVPAdapter#onViewAttached(V1 view), which stores a WeakReference to the interface that the activity implements (V1).

    2. AbstractMVPAdapter#onViewAttached(V1 view) calls BaseAdapterPresenter#onViewAttached(V2 view), which stores inside the adapter's presenter, a WeakReference to the interface that the adapter implements (V2).

    Destruction lifecycle

    1. When an Activity gets destroyed, after the onDestroy() method, it calls the AbstractMVPAdapter#onViewDetached(V1 view), which deletes the WeakReference to the interface implemented by the activity (V1).
    2. Before destroying the WeakReference to V1, AbstractMVPAdapter#onViewDetached(V1 view) calls BaseAdapterPresenter#onViewAttached(V2 view) which tells the adapter's presenter to delete the WeakReference to V2, which is the view interface implemented by the adapter.

    Conclusions

    Following this path should garantee that each component (activity, adapter and the adapter's presenter) is separated and does not depend on its dependencies. Also, when the Activity gets destroyed, it triggers the deletion of all its dependencies and the dependencies' dependencies too, avoiding memory leaks.

    feature 
    opened by RiccardoM 2
  • Disable / remove getSupportLoaderManager() dependency ?

    Disable / remove getSupportLoaderManager() dependency ?

    Hi, your library looks amazing !

    Is it possible to get rid of getSupportLoaderManager() dependency ? I need to extend an android.appActivity (third party library) and EasyMVP generates code that refer to the getSupportLoaderManager() method which cannot be resolved.

    private LoaderManager getLoaderManager(GVRMainActivity view) {
       return view.getSupportLoaderManager(); // Symbol not found
    }
    
    opened by amallo 2
  • Forgetting @FragmentView makes Gradle crash without any error

    Forgetting @FragmentView makes Gradle crash without any error

    Bug If you forget the @FragmentView on a Fragment class, but you annotate with @Presenter a field, Gradle will crash while compiling without giving any error.

    How to replicate 1. Create the following class

    public class MyFragment extent Fragment extends MyView {
        @Presenter MyPresenter presenter;
    
    }
    

    where MyPresenter and MyView are the presenter and view of this fragment.

    2. Try to build the project.

    Verified behaviour The build craches telling Cannot write ( null )

    Expected behaviour Throw some kind of exception

    opened by RiccardoM 1
  • [Discussion] Improving @PresenterId annotation

    [Discussion] Improving @PresenterId annotation

    From the last comment on #28:

    @RiccardoM Thanks, EasyMVP calls getLoaderManager().initLoader(ID, ... , ... ); method, by default the ID is a simple counter object so when you create multiple instances of a class it will assign same id to them. There will be better solutions maybe, I think it can be simplified with putting ids in Bundle, but i encountered some bugs in custom Views.

    What about using UUIDs as the presenteres' id?

    Instead of passing the presenter id to its constructor and then associating it to the @PresenterId annotated object, a solution I came up with would be to have the same annotation (i.e. @Presenter) with a new boolean parameter (let's say keepInstance) that will act as follows:

    • if keepInstance is set to true, no action will be taken, and everything will be the same;
    • if keepInstance is set to false, the presenter id will be generated each time that a new instance of the view is created, using UUID.randomUUID().toString().replace("-", "");

    I think that the resulting code inside the generator class would be something along the lines of this:

    if (keepInstance) {
         presenterId = LOADER_ID.incrementAndGet() + "";
    } else {
        presenterId = "view." + UUID.randomUUID().toString().replace("-", "");
    }
    

    This method shouldn't have problem with custom views or stuff like that, as the UUID is indipendent from the object that creates it.

    What are your thoughts on this solution @SaeedMasoumi?

    opened by RiccardoM 1
  • Updating com.android.support:design to 27.1.1 causes null presenter

    Updating com.android.support:design to 27.1.1 causes null presenter

    Hi, I implemented easy MVP in my application. Everything used to work fine.

    But I have to update com.android.support:design to 27.1.1.

    When I do this my presenter is not created in my activity :

    @ActivityView(layout = R.layout.activity_login, presenter = LoginPresenter.class)
    public class LoginActivity extends AppCompatActivity implements LoginActivityContract.View {
     @Override
        protected void onStart() {
            super.onStart();
            // Now presenter is injected.
    
            SharedPreferences pref = PreferenceManager
                    .getDefaultSharedPreferences(this.getBaseContext());
            this.presenter.init(pref, this.queue); ---> here presenter is null
        }
    

    What am I doing wrong ?

    opened by arthurChennetier 0
  • Activity view's presenter is not attached on first creation

    Activity view's presenter is not attached on first creation

    Hi,

    I ran into this weird problem where the Activity view's presenter is created but not attached the first time the activity is created. After that, if for example I press the home button and return to the activity everything works fine and the view is attached to the presenter with onStart().

    Here is my code:

    @ActivityView(layout = R.layout.activity_wallet_new, presenter = TestPresenter::class)
    class TestActivity : AppCompatActivity(), TestView {
    
        @Presenter
        @JvmField
        var presenter: TestPresenter? = null
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            Horizon.zero(this).statusBarColor(R.color.statusBarColor).dawn()
        }
    
        override fun onStart() {
            super.onStart()
            Timber.d("onStart: presenter=%s", presenter)
        }
    }
    

    presenter:

    class TestPresenter: AbstractPresenter<TestView>() {
    
        init {
            Timber.d("init: test presenter created")
        }
    
        override fun onViewAttached(view: TestView?) {
            super.onViewAttached(view)
            Timber.d("onViewAttached: called")
        }
    
        override fun onViewDetached() {
            super.onViewDetached()
            Timber.d("onViewDetached: called.")
        }
    }
    

    view interface:

    interface TestView {
    }
    

    build.gradle:

    android {
        compileSdkVersion 27
        defaultConfig {
            applicationId "com.exmample.foo"
            minSdkVersion 19
            targetSdkVersion 27
            versionCode 6
            versionName "1.0.6"
            testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
            vectorDrawables.useSupportLibrary = true
        }
        buildTypes {
            release {
                minifyEnabled true
                shrinkResources true
                proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            }
        }
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
        buildToolsVersion '28.0.1'
    }
    

    easymvp version:

            classpath 'com.sixthsolution.easymvp:easymvp-plugin:1.2.0-beta10'
    

    Note that the presenter constructor is called even on the first creation, it is just that it isn't attached (i.e. onViewAttached is not called and presenter is null in onStart() of the activity)

    I tried rewriting everything in Java just to make sure it's not some freaky kapt problem. I also tried using an injected presenter and everything was the same.

    Any ideas?

    UPDATE:

    here's the log output. I just realized when it's on Java, neither attach or detach is called the first time. On Kotlin, it even calls onViewDetached the first time but onViewAttached is only called from the second time on, here's the log output for Kotlin:

    D/TestActivity: onStart: presenter=null
    D/TestPresenter: init: test presenter created
    

    [HIT HOME BUTTON AND RETURN TO ACTIVITY]

    D/TestPresenter: onViewDetached: called.
    D/TestPresenter: onViewAttached: called
    D/TestActivity: onStart: presenter=com.example.foo.domain.TestPresenter@9c8b130
    

    Now I'm really confused.

    opened by blackvvine 0
  • NullPointerException in ViewDelegates

    NullPointerException in ViewDelegates

    In a legacy project using EasyMVP have a @CustomView extending LinearLayout and for some reason it throws NPE in attachView (everything extends AppCompatActivity, so it's not related to #34) and in PresenterLoaderCallbacks.onLoaderReset

    Is this project still alive? Could You provide me some help with that? It's using version 1.0.5 of easyMVP

    The app returns to screen containing this custom view after opening another activity for result (it might be some hint)

    Custom view presenter:

    It has actually one method doin thing specific for our model, so i'll hide that one for readability, but the body of presenter:

    public class AddressPresenter extends BasePresenter<AddressView> {
    
        public AddressPresenter() {
        }
    
        public void geocode(Context context, double lat, double lng) {
            // RxJava stuff
        }
    
    }
    

    Custom view:

    As above, just the body, to keep it clean:

    @CustomView(presenter = AddressPresenter.class)
    public class AddressForm extends LinearLayout implements AddressView {
    
    }
    

    Attach view NPE

    Line of code causing crash:

        presenter.onViewAttached((AddressView)view);
    

    Sony Xperia Z5 Compact (E5823), 2048MB RAM, Android 7.1

    java.lang.RuntimeException:

      at android.app.ActivityThread.performLaunchActivity (ActivityThread.java:2720)
      at android.app.ActivityThread.handleLaunchActivity (ActivityThread.java:2781)
      at android.app.ActivityThread.handleRelaunchActivity (ActivityThread.java:4615)
      at android.app.ActivityThread.-wrap19 (ActivityThread.java)
      at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1514)
      at android.os.Handler.dispatchMessage (Handler.java:102)
      at android.os.Looper.loop (Looper.java:241)
      at android.app.ActivityThread.main (ActivityThread.java:6274)
      at java.lang.reflect.Method.invoke (Native Method)
      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run (ZygoteInit.java:886)
      at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:776)
    

    Caused by: java.lang.NullPointerException:

      at xx.xx.mobile.ui.widget.address.AddressForm_ViewDelegate.attachView (AddressForm_ViewDelegate.java:33)
      at xx.xx.mobile.ui.widget.address.AddressForm.onAttachedToWindow (AddressForm.java)
      at android.view.View.dispatchAttachedToWindow (View.java:15561)
      at android.view.ViewGroup.dispatchAttachedToWindow (ViewGroup.java:2916)
      at android.view.ViewGroup.dispatchAttachedToWindow (ViewGroup.java:2923)
      at android.view.ViewGroup.dispatchAttachedToWindow (ViewGroup.java:2923)
      at android.view.ViewGroup.dispatchAttachedToWindow (ViewGroup.java:2923)
      at android.view.ViewGroup.dispatchAttachedToWindow (ViewGroup.java:2923)
      at android.view.ViewGroup.addViewInner (ViewGroup.java:4456)
      at android.view.ViewGroup.addView (ViewGroup.java:4258)
      at android.view.ViewGroup.addView (ViewGroup.java:4198)
      at android.view.ViewGroup.addView (ViewGroup.java:4171)
      at android.support.v4.app.FragmentManagerImpl.moveToState (FragmentManager.java:1309)
      at android.support.v4.app.FragmentManagerImpl.moveFragmentToExpectedState (FragmentManager.java:1528)
      at android.support.v4.app.FragmentManagerImpl.moveToState (FragmentManager.java:1595)
      at android.support.v4.app.FragmentManagerImpl.dispatchActivityCreated (FragmentManager.java:2900)
      at android.support.v4.app.FragmentController.dispatchActivityCreated (FragmentController.java:201)
      at android.support.v4.app.FragmentActivity.onStart (FragmentActivity.java:603)
      at android.support.v7.app.AppCompatActivity.onStart (AppCompatActivity.java:178)
      at android.app.Instrumentation.callActivityOnStart (Instrumentation.java:1249)
      at android.app.Activity.performStart (Activity.java:6737)
      at android.app.ActivityThread.performLaunchActivity (ActivityThread.java:2683)
    

    onLoaderReset NPE

    Line of code causing crash:

          delegate.get().detachView();
    

    Samsung Galaxy S7 Edge (hero2lte), 4096MB RAM, Android 8.0

    java.lang.RuntimeException:

      at android.app.ActivityThread.performDestroyActivity (ActivityThread.java:4605)
      at android.app.ActivityThread.handleDestroyActivity (ActivityThread.java:4623)
      at android.app.ActivityThread.-wrap5 (Unknown Source)
      at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1757)
      at android.os.Handler.dispatchMessage (Handler.java:105)
      at android.os.Looper.loop (Looper.java:164)
      at android.app.ActivityThread.main (ActivityThread.java:6944)
      at java.lang.reflect.Method.invoke (Native Method)
      at com.android.internal.os.Zygote$MethodAndArgsCaller.run (Zygote.java:327)
      at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1374)
    

    Caused by: java.lang.NullPointerException:

      at xx.xx.mobile.ui.widget.address.AddressForm_ViewDelegate$PresenterLoaderCallbacks.onLoaderReset (AddressForm_ViewDelegate.java:96)
      at android.support.v4.app.LoaderManagerImpl$LoaderInfo.destroy (LoaderManager.java:357)
      at android.support.v4.app.LoaderManagerImpl.doDestroy (LoaderManager.java:832)
      at android.support.v4.app.FragmentHostCallback.doLoaderDestroy (FragmentHostCallback.java:285)
      at android.support.v4.app.FragmentController.doLoaderDestroy (FragmentController.java:420)
      at android.support.v4.app.FragmentActivity.onDestroy (FragmentActivity.java:391)
      at android.support.v7.app.AppCompatActivity.onDestroy (AppCompatActivity.java:209)
      at android.app.Activity.performDestroy (Activity.java:7470)
      at android.app.Instrumentation.callActivityOnDestroy (Instrumentation.java:1255)
      at android.app.ActivityThread.performDestroyActivity (ActivityThread.java:4592)
    
    opened by Nocri 0
  • EasyMVP and Robolectric annotations problem

    EasyMVP and Robolectric annotations problem

    I have problem while trying to use Robolectric to test activity which is using EasyMvp. All of the classes are written in Kotlin.

    Part of Activity:

    @ActivityView(layout = R.layout.activity_access, presenter = AccessPresenterImpl::class)
    class AccessActivity : BaseActivity(), AccessView {
    
    @Presenter
    lateinit var presenter: AccessPresenter
    
    override fun providePresenter(): BasePresenter? {
        return presenter
    }
    
    

    I was trying to introduce Robolectric tests in my app.

    var activity: AccessActivity? = null
    var loginEditText: EditText? = null
    var passwordEditText: EditText? = null
    
    @Before
    fun initData() {
        activity = Robolectric.setupActivity(AccessActivity::class.java)
        loginEditText = activity?.findViewById(R.id.loginEditText)
        passwordEditText = activity?.findViewById(R.id.passwordEditText)
    }
    

    But while Running the test, I am always getting error:

    kotlin.UninitializedPropertyAccessException: lateinit property presenter has not been initialized I tried numerous of ideas like changing presenter to nullable object, but EasyMVP do not compile with it.

    Any solution for that?

    opened by bardss 0
  • Injection is broken with the latest Dagger 2 release

    Injection is broken with the latest Dagger 2 release

    EasyMVP works fine with Dagger 2.11. It is broken when migrating to Dagger 2.12. You can easily replicate this by compiling the sample tvProgram_android project against this Dagger version. When the view tries to inject the presenter you will get an error like:

    java.lang.NullPointerException: Attempt to invoke interface method 'java.lang.Object javax.inject.Provider.get()' on a null object reference at easymvp.loader.SupportPresenterLoader.onForceLoad(SupportPresenterLoader.java:32) at android.support.v4.content.Loader.forceLoad(Loader.java:329) at easymvp.loader.SupportPresenterLoader.onStartLoading(SupportPresenterLoader.java:26) at android.support.v4.content.Loader.startLoading(Loader.java:272) at android.support.v4.app.LoaderManagerImpl$LoaderInfo.start(LoaderManager.java:270) at android.support.v4.app.LoaderManagerImpl.doStart(LoaderManager.java:770) at android.support.v4.app.FragmentHostCallback.doLoaderStart(FragmentHostCallback.java:243) at android.support.v4.app.FragmentController.doLoaderStart(FragmentController.java:386) at android.support.v4.app.FragmentActivity.onStart(FragmentActivity.java:566) at android.support.v7.app.AppCompatActivity.onStart(AppCompatActivity.java:177) at com.leonard.www.tvprog.feature.channelList.view.ChannelListActivity.onStart(ChannelListActivity.java:0)

    This is because the providerFactory passed to the view delegate class is null. I suspect this may be caused by a change to the way Dagger is generating its internal classes. Therefore the Dagger2Extension.apply() method is failing to correctly detect the Dagger classes.

    The simply fix would be to update this class to support the new class format, however a better solution would be to rewrite the class such that its detection is based on the public annotations, etc rather than inferring information from the internal Dagger implementation.

    bug 
    opened by Xcalllibur 1
Releases(1.2.0-beta8)
Rosie is an Android framework to create applications following the principles of Clean Architecture.

Rosie The only way to make the deadline—the only way to go fast—is to keep the code as clean as possible at all times. — Robert C. Martin in Clean Cod

Karumi 1.8k Dec 28, 2022
kotlin-core - A full framework for making Android apps. Based on Anko and Kotson.

kotlin-core This package is not Android-specific, and can be used across platforms. However, for a good example of use in Android, take a look at kotl

Lightning Kite 36 Oct 3, 2022
A modern framework for full stack web apps in Kotlin

Kobweb is an opinionated Kotlin framework for creating websites and web apps, built on top of Web Compose and inspired by Next.js and Chakra UI.

Varabyte 425 Jan 8, 2023
Cross-platform framework for building truly native mobile apps with Java or Kotlin. Write Once Run Anywhere support for iOS, Android, Desktop & Web.

Codename One - Cross Platform Native Apps with Java or Kotlin Codename One is a mobile first cross platform environment for Java and Kotlin developers

Codename One 1.4k Dec 23, 2022
Pet project using Clean Architecture + MVVM + Reactive Extensions + Android Architecture Components. The data are fetched from LondonTheatreDirect API. 🎭

Theatre Pet project using Clean Architecture + MVVM + Reactive Extensions + Android Architecture Components. The data is fetched from LondonTheatreDir

André Mion 646 Jan 9, 2023
Source++ is an open-source live coding platform. Add breakpoints, logs, metrics, and tracing to live production applications

Source++ is an open-source live coding platform. Add breakpoints, logs, metrics, and distributed tracing to live production software in real-time on-d

Source++ 40 Dec 14, 2022
Android app built with MVP architectural approach and uses Marvel Comics API that allows developers everywhere to access information about Marvel's vast library of comics. :zap:

Villains & Heroes Android app built with MVP architectural approach and uses Marvel Comics API that allows developers everywhere to access information

André Mion 53 Jul 13, 2022
LiteOrm is a fast, small, powerful ORM framework for Android. LiteOrm makes you do CRUD operarions on SQLite database with a sigle line of code efficiently.

#LiteOrm:Android高性能数据库框架 A fast, small, powerful ORM framework for Android. LiteOrm makes you do CRUD operarions on SQLite database with a sigle line

马天宇 1.5k Nov 19, 2022
A data-binding Presentation Model(MVVM) framework for the Android platform.

PLEASE NOTE, THIS PROJECT IS NO LONGER BEING MAINTAINED. As personal time contraints, I am currently unable to keep up. Please use official android da

RoboBinding open source 1.3k Dec 9, 2022
Android Plugin Framework

Android Plugin Framework This project is pre-mature and may be changed very frequently. Introduction Android Plugin Framework (APF) aims to providing

Umeng Limited 322 Nov 17, 2022
MVVM framework for Android

RoboMVVM - MVVM Framework For Android RoboMVVM is an open source library that facilitates the use of the MVVM pattern in Android apps. The MVVM patter

Debdatta Basu 55 Nov 24, 2020
🔥 Android component-based routing framework

README-CN Latest version module krouter-core krouter-compiler krouter-annotation krouter-plugin version Features 支持通过路由获取intent 支持方法注解,通过路由调用方法 支持给fra

Jiaming Gu 6 Jun 24, 2022
VasSonic is a lightweight and high-performance Hybrid framework developed by tencent VAS team, which is intended to speed up the first screen of websites working on Android and iOS platform.

VasSonic: A Lightweight And High-performance Hybrid Framework VasSonic is a lightweight and high-performance Hybrid framework developed by tencent VAS

Tencent 11.6k Dec 30, 2022
🔪 AOP development framework implemented through *Annotation + ASM + Gradle Transform API* for Android🤖

?? AOP development framework implemented through *Annotation + ASM + Gradle Transform API* for Android??

Pumpkin 325 Nov 22, 2022
UltimateAndroid is a rapid development framework for developing your apps

UltimateAndroid Version:0.10.2 UltimateAndroid is a rapid development framework for developing apps Master branch: Dev branch: V0.7.0 Ui Demo screensh

MarshalChen 2.1k Dec 26, 2022
A framework for hook java methods.

Legend Projects are out of date, plese move to: Whale Hook What is Legend? Legend is a Hook framework for Android Development, it allows you to Hook J

Lody 1.6k Dec 15, 2022
Android part of the Android Studio(IntellijIDEA) OkHttp Profiler plugin

OkHttpProfiler Android Library Created by LocaleBro.com - Android Localization Platform The OkHttp Profiler plugin can show requests from the OkHttp l

Ievgenii 261 Dec 8, 2022
Android common lib, include ImageCache, HttpCache, DropDownListView, DownloadManager, Utils and so on

android-common-lib 关于我,欢迎关注 微博:Trinea 主页:trinea.cn 邮箱:trinea.cn#gmail.com 微信:codek2 主要包括:缓存(图片缓存、预取缓存、网络缓存)、公共View(下拉及底部加载更多ListView、底部加载更多ScrollView、

Trinea 5k Dec 30, 2022
A Model-View-Presenter / Model-View-Intent library for modern Android apps

Mosby A Model-View-Presenter and Model-View-Intent library for Android apps. Dependency dependencies { compile 'com.hannesdorfmann.mosby3:mvi:3.1.1

Hannes Dorfmann 5.5k Dec 25, 2022