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: [email protected]
    

    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)
Android Clean Architecture๐Ÿ’Ž Base Project Android with Kotlin and MVVM applying clean architecture

Android Clean Architecture?? Base Project Android with Kotlin and MVVM applying clean architecture

Mina Mikhail 102 Nov 19, 2022
Group Messaging Chat (Discord Clone :eyes:) App Using Firebase Cloud-Firestore following MVVM Architecture

Nit Talk Nit Talk is a Group Chat Messaging (Discord Clone) App based on Modern Android Application tech-stacks and MVVM architecture. Techs Used ?? K

Rohit Sharma 27 Jul 22, 2022
Chat App MVVM + Clean ArchitectureChat App MVVM + Clean Architecture

Chat App MVVM + Clean Architecture This Android application built using MVVM + Clean Architecture architecture approach and is written 100% in Kotlin.

null 2 Nov 18, 2022
Repository that showcases 3 Android app architectures: "Standard Android", MVP and MVVM. The exact same app is built 3 times following the different patterns.

Archi This repository showcases and compares different architectural patterns that can be used to build Android apps. The exact same sample app is bui

Ivรกn Carballo 3.4k Nov 29, 2022
Full Management is an application that helps you manage your tasks effectively. built with the latest tachs like Compose UI, Jetpack libraries, and MVVM design pattern.

Full Management is an application that helps you manage your tasks effectively. built with the latest tachs like Compose UI, Jetpack libraries, and MVVM design pattern.

Amr algnyat 4 Nov 1, 2022
An Android Template with MVVM and Clean Architecture

MVVMTemplate ??โ€ A simple Android template that lets you create an Android project quickly. How to use ?? Just click on button to create a new repo st

Hossein Abbasi 525 Nov 25, 2022
Template for using Clean Architecture Concepts in Android

AndroidCleanArchitecture Template for using Clean Architecture Concepts in Android We have separated the layers into three main group Presentation/App

Stephen Siapno 2 Apr 4, 2022
JeTaxi is built on Clean Architecture-MVVM with Kotlin and follows modern android development trends.

JeTaxi is built on Clean Architecture-MVVM with Kotlin and follows modern android development trends. Also, It uses some of Jetpack and popular libraries. These are Kotlin Coroutine-Flow, kotlinx.serialization, Hilt, Compose, Accompanist, Retrofit2, OkHttp3, Chucker, MockWebServer, Truth.

Tolga Bolatcan 13 Nov 2, 2022
Movie app that receives popular movies and allows the user to search for the specific movie through the Rest API with help of retrofit library &MVVM architecture.

MovieClue Millions of movies, TV shows and people to discover. Explore now Movie app that recieves popular movies and allow the user to search for spe

Shubham Tomar 6 Mar 31, 2022
AAC MVVM + Clean Architecture + Coroutine Flow

GithubBrowser MVVM Clean Architecture Sample AAC MVVM + Clean Architecture + Coroutine Flow ๋ณธ ์ƒ˜ํ”Œ ํ”„๋กœ์ ํŠธ๋Š” https://github.com/omjoonkim/GitHubBrowserApp ์„

Jeonguk-JayDev 18 May 25, 2022
Clean Architecture - Kotlin, MVVM, Use cases

CleanArchitecture Is Clean Architecture only MVVM ? NO, MVVM is a part of clean architecture. MVVM includes Model, View and ViewModel and in addition

Deepanshi bajaj 24 Oct 24, 2022
A simple Cat Search app, performed the search by cat breed name, using MVVM clean Architecture.

CatSearchApp A simple Cat Search app, performed the search by cat breed name, using MVVM clean Architecture. The App is using the The Cat Api for sear

Mansingh Bhati 2 Oct 20, 2022
Image Search used MVVM with Clean Architecture

ImageSearchCleanArch Readme In this project I used MVVM with Clean Architecture. MVVM provides a clean separation of concerns between user interface a

null 2 Jun 14, 2022
Retrieve Data from an API using MVVM Clean Architecture and Jetpack Compose

MVVM Clean Architecture Demo Retrieve Data from an API using MVVM Clean Architecture and Jetpack Compose. It simply shows a list of movies fetched fro

Daniel Kago 2 Sep 16, 2022
Template for MVVM Architecture Clean Code.

Template for MVVM Architecture. This is basic template for android app follwing MVVM architecture and latest tech stack. You don't need to create and add basic code and depency for start project using MVVM architecture. Just use this template and boost your productivity.

Rohit Jakhar 31 Aug 18, 2022
Followed best practices, MVVM, clean architecture and coroutines.

GitHub Profiles Video Walktrough Using Github public API allows users to search Github profiles through the username. It shows the necessary informati

Vishal Sengar 2 Sep 6, 2022
Sanju S 835 Nov 23, 2022
๐Ÿ“Š A Minimal Expense Tracker App built to demonstrate the use of modern android architecture component with MVVM Architecture

Expenso ?? A Simple Expense Tracker App ?? built to demonstrate the use of modern android architecture component with MVVM Architecture ?? . Made with

Sanju S 798 Nov 20, 2022
Playground project built with MVVM with Clean Artchitect to try out new tech in Android ๐ŸŒ

Clean-MVVM-Playground Playground project built with MVVM with Clean Artchitect to try out new tech in Android ?? Features ?? 100% Kotlin Following MVV

Somesh Kumar 7 Oct 9, 2022