Coder Social home page Coder Social logo

rosie's Introduction

Karumi logoRosie Maven Central

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 Code: A Handbook of Agile Software Craftsmanship

Introduction

Rosie is an Android framework to create applications following the principles of Clean Architecture.

Rosie divides your application in three layers, view, domain and repository. For each layer, Rosie provides plenty of classes that will make defining and separating these concerns much easier.

  • View: It contains all your presentation logic, implemented through the Model-View-Presenter pattern. Rosie provides classes to represent the main components of this layer like RosieActivity, RosieFragment or RosiePresenter.
  • Domain: Holding all your business logic, its main component is RosieUseCase that gives you an easy way to define your application use cases and execute them in a background thread using the command pattern.
  • Repository: This layer gives you an abstraction of how to retrieve and store data in your application following the Repository pattern. RosieRepository and the multiple DataSource classes gives you the base to start building your own repositories.

Finally, Rosie comes with Dagger to solve Dependency inversion through Dependency Injection.

Screenshots

Screencast

Application UI/UX designs by Luis Herrero.

Data provided by Marvel. © 2016 MARVEL

Usage

First thing you will need to do is to make your own Application instance extend RosieApplication in order to provide your global dependencies module to Dagger:

public class SampleApplication extends RosieApplication {
	@Override protected List<Object> getApplicationModules() {
		return Arrays.asList((Object) new SampleGlobalModule());
	}
}

Extending from RosieApplication is needed to be able to easily use the configuration Rosie provides you related to Dependency Injection. If you do not want to use Dependency Injection in your project, you do not need to extend from RosieApplication.

Rosie provides several base classes to start implementing your architecture separated in three layers, view, domain and repository. Let's explore them in detail.

View

The view package contains all the classes needed to implement your presentation logic following the MVP pattern. To use the view package, make your Activity extend from RosieActivity or your Fragment from RosieFragment and specify the layout that Rosie will automatically inflate for you:

public class SampleActivity extends RosieActivity {
	@Override protected int getLayoutId() {return R.layout.sample_activity;}
	/*...*/
}

Extending from RosieActivity or RosieFragment is not mandatory. If your project is already extending from any other base Activity please review the class PresenterLifeCycleLinker and use it inside your base Activity or your Activities as follow:

public abstract class MyBaseActivity extends FragmentActivity
    implements RosiePresenter.View {

  private PresenterLifeCycleLinker presenterLifeCycleLinker = new PresenterLifeCycleLinker();

  @Override protected void onCreate(Bundle savedInstanceState) {
    ...
    presenterLifeCycleLinker.initializeLifeCycle(this, this);
    ...
  }
  @Override protected void onResume() {
    ...
    presenterLifeCycleLinker.updatePresenters(this);
    ...
  }

  @Override protected void onPause() {
    ...
    presenterLifeCycleLinker.pausePresenters();
    ...
  }

  @Override protected void onDestroy() {
    ...
    super.onDestroy();
    ...
  }

}

Rosie provides you some base classes to be extended and give you a quick access to the Dependency Injection and Model View Presenter features, but the usage of inheritance to use these features is not mandatory.

Dagger

Besides, you can define the Dagger dagger module that will contain the dependencies for your activity by overriding the getActivityScopeModules method:

public class SampleActivity extends RosieActivity {
	@Override protected List<Object> getActivityScopeModules() {
		return Arrays.asList((Object) new SampleModule());
	}
	/*...*/
}

There is also two useful annotations to provide Context dependencies into your classes, one for your Application context and one for your current Activity:

public class InjectedClass {
	@Inject public InjectedClass(@ForApplication Context applicationContext, @ForActivity Context activityContext) {
		/*...*/
	}
}

Presenter

To follow the MVP pattern, Rosie provides a RosiePresenter class that will be responsible for all your presentation logic. Rosie will take care of linking your view (a RosieActivity or RosieFragment implementation) with your presenter and subscribing it to its lifecycle. In order to do that, create a RosiePresenter and inject it into your activity/fragment with the @Presenter annotation:

public class SamplePresenter extends RosiePresenter<SamplePresenter.View> {
	public interface View extends RosiePresenter.View {
		/*...*/
	}
}
public class SampleActivity extends RosieActivity implements SamplePresenter.View {
	@Inject @Presenter SamplePresenter presenter;
	@Override protected void onPreparePresenter() {/*...*/}
}

Once both, view and presenter, are linked you can react to your view lifecycle directly from the presenter. The onPreparePresenter method gives you the opportunity to configure your presenter before any of these methods are called (e.g. with your intent parameters). You will be also able to call your view easily from the presenter:

public class SamplePresenter extends RosiePresenter<SamplePresenter.View> {
	protected void initialize() {/*...*/}
	protected void update() {/*...*/}
	protected void pause() {/*...*/}
	protected void destroy() {/*...*/}
	
	private void sampleMethod() {
		View view = getView(); // Get the view linked to the presenter
		view.foo();
	}
	
	public interface View extends RosiePresenter.View {
		void foo();
	}
}

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

RosiePresenter Activity Fragment
initialize onCreate onViewCreated
update onResume onResume
pause onPause onPause
destroy onDestroy onDestroy

Domain

The domain package is meant to contain all your business logic that will change from app to app. For that reason, Rosie only provides a single RosieUseCase class that will help you execute your use cases in background following the command pattern.

To start using the Rosie domain package, create your use cases extending RosieUseCase and define the method containing your use case logic. Remember to call notifySuccess or notifyError to get results back to your presenter:

public class DoSomething extends RosieUseCase {
	@UseCase public void doSomething(Object arg) {
		Object response = getData();
		if (response.isOk()) {
			notifySuccess(response.getContent());
		} else {
			notifyError(response.getError());
		}
	}
}

To call your use case, create a UseCaseCall, configure it and execute it. Rosie gives you a fluent API to easily do this from your presenters:

public class SamplePresenter extends RosiePresenter<SamplePresenter.View> {
	public void callUseCase() {
		createUseCaseCall(doSomething)
			.args(arg /*, arg2, arg3, ...*/)
			.onSuccess(new OnSuccessCallback() {
				/*...*/
			})
			.onError(new OnErrorCallback() {
				/*...*/
			})
			.execute();
	}
	/*...*/
}

All the configuration calls are optional and can be omitted when not needed but keep in mind that provided arguments must match the ones declared in your UseCase method or an error will be raised. It is important to keep in mind that, by default, your callback methods will be executed in the main thread so you can easily update your UI from them.

Named use cases

Sometimes you need to specify use cases that are very similar to each other. To avoid creating multiple classes representing every use case configuration, you can create a single RosieUseCase class with multiple methods. Rosie will be able to identify the use case being called by matching its input parameters. If you create two methods with the @UseCase annotation that have the same input parameters, you can provide a name to each of them in order to identify them:

public class DoSomething extends RosieUseCase {
	public static final String USE_CASE_NAME = "UseCaseName";
	public static final String OTHER_USE_CASE_NAME = "OtherUseCaseName";
	@UseCase(name = USE_CASE_NAME) public void doSomething(Object arg) {/*...*/}
	@UseCase(name = OTHER_USE_CASE_NAME) public void doSomethingElse(Object arg) {/*...*/}
}

Even though using names is not mandatory when the method input parameters are different, it's highly recommended to use them just to make its usage more readable.

To call a named use case just configure its name:

public class SamplePresenter extends RosiePresenter<SamplePresenter.View> {
	public void callUseCase() {
		createUseCaseCall(doSomething)
			.args(arg)
			.useCaseName(DoSomething.USE_CASE_NAME)
			.execute();
	}
	/*...*/
}

Error handling

Errors can be either manually reported or implicitly notified when an exception is thrown from your use case context. To handle errors, there is a capturing event system that iterates over all your registered OnErrorCallback implementations and notifies them of the issue. Every callback needs to return a boolean value to inform whether the error has been handled and needs no further management. Callbacks are always called in this specific order:

  1. Use case specific callback; the ones you register while calling a use case.
  2. Global callbacks; the ones you can register from your presenter constructor.

With this approach you can create an error callback in your presenter that shows a generic error message to your user and create multiple other listeners for specific use cases and/or errors.

To register global callbacks from your presenter, call registerOnErrorCallback in the constructor:

public class SamplePresenter extends RosiePresenter<SamplePresenter.View> implements OnErrorCallback {
	public SamplePresenter(UseCaseHandler useCaseHandler) {
		super(useCaseHandler);
		registerOnErrorCallback(this);
	}

	@Override public boolean onError(Error error) {
		getView().showGenericError();
		return true;
	}
}

As explained in the UseCase section, you can also register error callbacks for a use case call. Rosie will assign those a higher priority:

public class SamplePresenter extends RosiePresenter<SamplePresenter.View> {
	public void callUseCase() {
		createUseCaseCall(doSomething)
			.args(arg)
			.onError(new OnErrorCallback() {
				getView().showSpecificError();
				// The error has been handled
				// Avoid calling any other error callback by returning true
				return true;
			})
			.execute();
	}
	/*...*/
}

You can create your own ErrorFactory implementation to map Exceptions to Errors. In your implementation you can unify your error handling.

public class CustomErrorFactory extends ErrorFactory {

	@Inject public CustomErrorFactory() {
	}

	@Override public Error create(Exception exception) {
		if (targetException instanceof MyConnectionException) {
			return new ConnectionError();
		}
		return new UnknownError();
  }
}

Remember to provide your ErrorFactory implementation inside a Dagger module.

	@Provides public ErrorHandler providesErrorHandler(CustomErrorFactory errorFactory) {
		return new ErrorHandler(errorFactory);
	}

Repository

The third layer is meant to encapsulate your data sources. To start using it just extend RosieRepository and configure its data sources in its constructor:

public class SampleRepository extends RosieRepository<Key, Value> {
	@Inject public SampleRepository(SampleApiDataSource sampleApiDataSource,
			SampleCacheDataSource sampleCacheDataSource) {
		addReadableDataSources(sampleApiDataSource);
		addCacheDataSources(sampleCacheDataSource);
	}
}

There are three different types of data sources and each one has its own interface that you can implement to use it in your repository:

  • ReadableDataSource<K, V>: Defines data sources where you can read values by a given key or retrieving them all.
  • WriteableDataSource<K, V>: Defines data sources where you can persist data with operations to add, update or delete values.
  • CacheDataSource<K, V>: Defines a mix of readable and writeable data sources to speed up access to your values.

There are empty implementations of each data source to simplify your own subclasses by overriding only the methods that make sense in your context. Besides, there is a generic InMemoryCacheDataSource to store recent values up to a configurable time.

public class SampleRepository extends RosieRepository<Key, Value> {
	@Inject public SampleRepository() {
	PaginatedCacheDataSource<Key, Value> inMemoryCacheDataSource =
		new InMemoryCacheDataSource<>(new TimeProvider(), MINUTES.toMillis(5));
	addCacheDataSources(inMemoryCacheDataSource);
  }
}

Users of the repositories can retrieve or modify data by using one of the multiple methods available for that matter. There are multiple ways to retrieve and store data using repositories, the following snippet shows some of the most useful:

// Initialization
Key key = /*...*/; Value value; Collection<Value> values;
RosieRepository<Key, Value> repository = /*...*/;

// Get a value by its key
value = repository.getByKey(key);
// Get a value by its key using only the defined cache
value = repository.getByKey(key, ReadPolicy.CACHE_ONLY);
// Get all the available values
values = repository.getAll();
// Get all the available values using only readable sources
values = repository.getAll(ReadPolicy.READABLE_ONLY);
// Add a new value
repository.addOrUpdate(value);
// Add a new value only to the first writeable data source that works
repository.addOrUpdate(value, WritePolicy.WRITE_ONCE);
// Delete a value by its key
repository.deleteByKey(key);
// Delete all values stored in the repository
repository.deleteAll();

Paginated repositories

Finally, Rosie gives you support for pagination in repositories. If your data is paginated, just extend PaginatedRosieRepository instead of RosieRepository. You will also need to implement your paginated data sources, PaginatedReadableDataSource<V> and PaginatedCacheDataSource<K, V>. Once your paginated repository is completely defined and configured you will be able to use it just as a regular repository with additional pagination-related methods:

// Initialization
Key key = /*...*/; PaginatedCollection<Value> page;
PaginatedRosieRepository<Key, Value> repository = /*...*/;

// Get a value by its key just as with a regular repository
Value value = repository.getByKey(key);
// Get a page
page = repository.getPage(Page.withOffsetAndLimit(offset, limit));
// Get a page using only the cache data source
page = repository.getPage(Page.withOffsetAndLimit(offset, limit), ReadPolicy.CACHE_ONLY);

To run the application using real data obtained from the Marvel API create a marvel.properties file inside the sample directory and add your public and private key there as follows:

MARVEL_PUBLIC_KEY="YOUR_MARVEL_PUBLIC_KEY"
MARVEL_PRIVATE_KEY="YOUR_MARVEL_PRIVATE_KEY"

Add it to your project

Include the library in your build.gradle

dependencies{
    compile 'com.karumi.rosie:rosie:3.0.2'
}

or to your pom.xml if you are using Maven

<dependency>
    <groupId>com.karumi.rosie</groupId>
    <artifactId>rosie</artifactId>
    <version>3.0.2</version>
    <type>aar</type>
</dependency>

More information?

We are writing some blog posts to explain the main motivations behind Rosie and some desing considerations:

Related projects

Do you want to contribute?

Feel free to report us or add any useful feature to the library, we will be glad to improve it with your help.

Keep in mind that your PRs must be validated by Travis-CI. Please, run a local build with ./gradlew checkstyle build connectedCheck before submitting your code.

Libraries used in this project

License

Copyright 2015 Karumi

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.

rosie's People

Contributors

aracem avatar davideme avatar dmitriyzaitsev avatar flipper83 avatar jcdom avatar libanezm5 avatar luis-ibanez avatar mohsenoid avatar pakoito avatar pedrovgs avatar radarhere avatar sargunv avatar serchinastico avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rosie's Issues

Rosie incompatible with android.arch.lifecycle ¿?

Hi I'm trying to implement a Lifecycle observer using android arch components and it crashes, seems the cause is RosieApplication, android.arch is unable to get ApplicationContext, so crash because is null.

 java.lang.RuntimeException: Unable to get provider android.arch.lifecycle.ProcessLifecycleOwnerInitializer: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.app.Application.registerActivityLifecycleCallbacks(android.app.Application$ActivityLifecycleCallbacks)' on a null object reference
                                                          at android.app.ActivityThread.installProvider(ActivityThread.java:5156)
                                                          at android.app.ActivityThread.installContentProviders(ActivityThread.java:4748)
                                                          at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4688)
                                                          at android.app.ActivityThread.-wrap1(ActivityThread.java)
                                                          at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1405)
                                                          at android.os.Handler.dispatchMessage(Handler.java:102)
                                                          at android.os.Looper.loop(Looper.java:148)
                                                          at android.app.ActivityThread.main(ActivityThread.java:5417)
                                                          at java.lang.reflect.Method.invoke(Native Method)
                                                          at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
                                                          at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
                                                       Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.app.Application.registerActivityLifecycleCallbacks(android.app.Application$ActivityLifecycleCallbacks)' on a null object reference
                                                          at android.arch.lifecycle.LifecycleDispatcher.init(LifecycleDispatcher.java:59)
                                                          at android.arch.lifecycle.ProcessLifecycleOwnerInitializer.onCreate(ProcessLifecycleOwnerInitializer.java:35)
                                                          at android.content.ContentProvider.attachInfo(ContentProvider.java:1748)
                                                          at android.content.ContentProvider.attachInfo(ContentProvider.java:1723)
                                                          at android.app.ActivityThread.installProvider(ActivityThread.java:5153)
                                                          at android.app.ActivityThread.installContentProviders(ActivityThread.java:4748) 
                                                          at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4688) 
                                                          at android.app.ActivityThread.-wrap1(ActivityThread.java) 
                                                          at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1405) 
                                                          at android.os.Handler.dispatchMessage(Handler.java:102) 
                                                          at android.os.Looper.loop(Looper.java:148) 
                                                          at android.app.ActivityThread.main(ActivityThread.java:5417) 
                                                          at java.lang.reflect.Method.invoke(Native Method) 
                                                          at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726) 
                                                          at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) 

Do you know how can I integrate both parts?

Error when running a signed APK

Hi,
I do not understand what is happening.

  • When I install the app from Android Studio with the debugkeystore by default, there is no problem.
  • When I sign the app with the Android Studio tool and my keystore, then I pass it to the phone, install the app, execute and ERROR: This object does not contain any use case to execute. Did you forget to add the @usecase annotation?

Why these two different behaviors with the same code?

Has something similar happened to you?

Thanks in advance

Create base class for custom views

This has become specially important for memory management. We recently discovered that if views keep references to fragments or activities they will be leaked because there won't be any call to onPause or onDestroy to free those resources.

These are the two different approaches we though, both having their pros & cons:

  1. RosieCustomView interface, make custom views implement it and make the wiring inside the view, downcasting the context param in the constructor to RosieFragment or RosieActivity.
  2. RosieCustomView interface with one single method to return the presenter associated to the view. Annotate views that you want to link with the lifecycle. RosieFragment and RosieActivity take care of wiring the presenter lifecycles depending on the annotations.

I like the second one more but it makes mandatory to add the annotation even if you don't need to use the view for anything else. On the other hand, it makes more sense to me because it keeps the wiring top-to-down, from the activities to the views and not the other way around. This is great because we can make rosie views and use them in non-rosie projects.

Searching in repositories

How do you implement searches in repositories (eg paginated results where user can filter by a text expression)?

Is the intended usage to use a repository factory that creates a repository for search result?
If this is the intended usage how do you implement caching? Did you use some type of repository caching, manually create appropriate caching datasources?
Or simple recreate all and try to move caching to upper levels like http request/response caching.

I think that this is a pretty common repository use case and will be nice to have some support in the framework and example usage in the sample.

Rethink use cases

We have found several issues with use cases. We should rethink how to execute use cases to simplify their usage

AppCompatActivity

RosieActivity should extend from AppCompatActivity (or make a new RosieAppCompatActivity) to allow using things like setSupportActionBar()

I think there is no downside to changing RosieActivity to extend AppCompatActivity since both extend FragmentActivity and Rosie already depends on the support library.

Add support for persistency

With the coming of N we'll all have to deal with configurationChanges when windows get resized, on top of the rotation ones. The framework doesn't currently support any way of persisting tasks or state on those configuration changes, leaving what's arguably the complicated bit to the user.

Can it be addressed by Rosie, or should we consider a 3rd party alternative? What's the architectural suggestion to maintain a Clean Architecture?

Service injection

Make service injection available, just as we have with Activities/Fragments.

OnErrorCallback null pointer exception with Marshmallow devices

Every time I call the method notifyError(new Error("Error")) in a UseCase class I'm getting a null pointer exception. It seems that when I create the " .onError(new OnErrorCallback() {...}" in the presenter something goes wrong."I have tried with two different devices using Marshmallow version and in both cases the result is:

java.lang.NullPointerException: Attempt to invoke interface method 'boolean com.karumi.rosie.domain.usecase.error.OnErrorCallback.onError(java.lang.Error)' on a null object reference at com.karumi.rosie.domain.usecase.RosieUseCase.notifyError(RosieUseCase.java:77)

Versions under Marshmallow works perfectly. Any tip about what is causing this error?

Thanks in advance

Remove ButterKnife and Renderers library.

In order to reduce the number of dependencies of this framework I suggest to remove ButterKnife and Renderers libraries. @Serchinastico, do you agree? @luis-ibanez instead of update ButterKnife we should delete it and keep the code related to this library and the renderers library just in the example project.

Add a new interface extending from RosiePresenter.View

Some of the views we have to implement have a loading state. We are going to create a RosiePresenter.ViewWithLoading interface with two methods: showLoading and hideLoading to avoid code duplicity in all the library client code related to this classes.

Uses cases from sync service (question)

Hi,
I'm developing an app that must have a data repository synced with server to allow the user make some operations even without internet connection.
I plan to use SyncAdapter to make sync tasks with the server. But, It has emerged me a doubt, how can I call a usecase (exampe SyncCategoriesRepositoryUseCase) from a service (SyncAdapter) if not exits a presenter to call it (createUseCaseCall)?

Thanks.

NullPointerException sometimes in RosieUseCase.notifyError()

I have found a problem when RosieUseCase.notifyError is called, a null pointer exception sometimes occurs in RosieUseCase L.77. The exception is only thrown when onErrorCallback.get() returns null, because this weak reference is empty.

I have been looking into and I have been able to see that the only class that call setOnErrorCallback() is UseCaseHandler when it is executed, and the method is called with a OnErrorCallbackToErrorHandlerAdapter, which is instantiated in the execute method and it is not saved in a strong reference.

I have been able to fix this problem with some changes in UseCaseHandler, I have created this Pull Request: #84

To fix this in my application I have replaced UseCaseHandler with a class called com.karumi.rosie.domain.usecase.MyUseCaseHandler, which extends UseCaseHandler but it does not call UseCaseHandler methods.

There is an open issue about a NullPointerException in use case notifyError, but I do not know if it is the same problem, because my problem occurs in all android versions and only sometimes.

I am using Rosie in an app available in Google Play, everything is working fine except for a few crashes caused by this issue.

Communication between presenters

First of all, thanks for share with community this great framework.

I've a doubt about how can I communicate two presenters without using Event Bus solution.
I've got an Editor activity with a presenter and multiple fragments, each one of them with a presenter, that be added to this activity in different steps of the process.
How can the activity's presenter know when the user has clicked on fragment list item, if user's click event is handled by fragment's presenter?

Thanks

Create a Realm Data Source

Create a realm data source that implement Rosie Data source, for people can use directly Realm on his projects. this dataSource implementation must be out of rosie default implementation.

Review copyright/license usage.

I've noticed the current project license is using Copyright © 2014 flipper83 and this is not the correct one. The copyright should be Copyright © 2015 Karumi. We should review all the license project and copyright. Here is a sample

Review Repository/PaginatedRepository use.

To use a PaginatedRepository implementation we have to extend from PaginatedRepository and write code like this:

  @Inject public CharactersRepository(CharacterDataSourceFactory characterDataSourceFactory,
      @Named(ApplicationModule.CHARACTERS_PAGE_IN_MEMORY_CACHE)
      PaginatedCacheDataSource<String, Character> inMemoryPaginatedCache,
      @Named(ApplicationModule.CHARACTERS_IN_MEMORY_CACHE)
      CacheDataSource<String, Character> inMemoryCache) {
    addCacheDataSources(inMemoryCache);
    addPaginatedCacheDataSources(inMemoryPaginatedCache);

    CharacterDataSource characterDataSource = characterDataSourceFactory.createDataSource();
    addReadableDataSources(characterDataSource);
    addPaginatedReadableDataSources(characterDataSource);
  }

We should review this implementation to do not need to add the data source to the cache data sources list and the paginated list. We should just configure the same data source once because a paginated data source should be used as a normal data source.

The usage of the repository should be something like:

  @Inject public CharactersRepository(CharacterDataSourceFactory characterDataSourceFactory,
      @Named(ApplicationModule.CHARACTERS_PAGE_IN_MEMORY_CACHE)
      PaginatedCacheDataSource<String, Character> inMemoryPaginatedCache) {
    addPaginatedCacheDataSources(inMemoryPaginatedCache);

    CharacterDataSource characterDataSource = characterDataSourceFactory.createDataSource();
    addPaginatedReadableDataSources(characterDataSource);
  }

RosieSupportFragment

I think there should be a RosieSupportFragment like the RosieFragment, because support fragments are needed for things like the TabLayout in the material design library. This is a similar issue to #46.

Rethink repositories

We have found several issues while implementing repositories in the ECI project, we should think about them again, try to make them even easier to use, segregate readability/writeability/cacheability, etc.

Add instrumentation tests

To be able to know if the framework is working as expected we should add at least one instrumentation test to be sure the application is not crashing with some error not detected by the unit tests we used to write.

RuntimeException with 2 or more presenters

If there are two or more presenters and the view is destroyed, when the getView () method is invoked the proxy works for a presenter but not for more.
If two or more are implemented, only the last one works.

Example:

_public class CharacterDetailsActivity extends MarvelActivity
    implements **CharacterCrashDetailsPresenter.View**, CharacterDetailsPresenter.View {...}_

Process: com.karumi.rosie.sample, PID: 20529
                                                   java.lang.RuntimeException: Internal error invoking the success object
                                                       at com.karumi.rosie.domain.usecase.RosieUseCase$1.run(RosieUseCase.java:114)
                                                       at android.os.Handler.handleCallback(Handler.java:739)
                                                       at android.os.Handler.dispatchMessage(Handler.java:95)
                                                       at android.os.Looper.loop(Looper.java:234)
                                                       at android.app.ActivityThread.main(ActivityThread.java:5526)
                                                       at java.lang.reflect.Method.invoke(Native Method)
                                                       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
                                                       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
                                                    Caused by: java.lang.reflect.InvocationTargetException
                                                       at java.lang.reflect.Method.invoke(Native Method)
                                                       at com.karumi.rosie.domain.usecase.RosieUseCase$1.run(RosieUseCase.java:112)
                                                       at android.os.Handler.handleCallback(Handler.java:739)
                                                       at android.os.Handler.dispatchMessage(Handler.java:95)
                                                       at android.os.Looper.loop(Looper.java:234)
                                                       at android.app.ActivityThread.main(ActivityThread.java:5526)
                                                       at java.lang.reflect.Method.invoke(Native Method)
                                                       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
                                                       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
                                                    Caused by: java.lang.ClassCastException: $Proxy5 cannot be cast to com.karumi.rosie.sample.characters.view.presenter.CharacterCrashDetailsPresenter$View
                                                       at com.karumi.rosie.sample.characters.view.presenter.CharacterCrashDetailsPresenter$2.onCharacterDetailsLoaded(CharacterCrashDetailsPresenter.java:62)
                                                       at java.lang.reflect.Method.invoke(Native Method)
                                                       at com.karumi.rosie.domain.usecase.RosieUseCase$1.run(RosieUseCase.java:112)
                                                       at android.os.Handler.handleCallback(Handler.java:739)
                                                       at android.os.Handler.dispatchMessage(Handler.java:95)
                                                       at android.os.Looper.loop(Looper.java:234)
                                                       at android.app.ActivityThread.main(ActivityThread.java:5526)
                                                       at java.lang.reflect.Method.invoke(Native Method)
                                                       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
                                                       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616) 

Thanks in advance.

How to connect a Activity to its Presenter without Injection?

I am not able to link my Activity witch extends from RosieAppCompatActivity, to my Presenter.

When i try

@Inject @Presenter
WelcomePresenter presenter;

I always get the error "The presenter instance to be registered can't be null".

How do i link my activity to its presenter without the use of Injection?

Extending RosieApplication

The documentation states "If you do not want to use Dependency Injection in your project, you do not need to extend from RosieApplication", but I get a ClassCastException when I don't extend RosieApplication:

Caused by: java.lang.ClassCastException: android.app.Application cannot be cast to com.karumi.rosie.application.RosieApplication
   at com.karumi.rosie.view.RosieAppCompatActivity.injectActivityModules(RosieAppCompatActivity.java:133)
   at com.karumi.rosie.view.RosieAppCompatActivity.onCreate(RosieAppCompatActivity.java:46)
   at me.sargunvohra.android.diningcourts.MainActivity.onCreate(MainActivity.kt:17)
   at android.app.Activity.performCreate(Activity.java:6876)
   at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1135)
   at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3207)
   at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3350) 
   at android.app.ActivityThread.access$1100(ActivityThread.java:222) 
   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1795) 
   at android.os.Handler.dispatchMessage(Handler.java:102) 
   at android.os.Looper.loop(Looper.java:158) 
   at android.app.ActivityThread.main(ActivityThread.java:7229) 
   at java.lang.reflect.Method.invoke(Native Method) 
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230) 
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120) 

Stabilize Travis-CI builds

As we've seen for the last months Travis-CI is failing constantly due to problems initializing the Android emulator, even when our tests are not flacky at all. Travis-CI is one of the core components in this project, so we should try to stabilize our build in Travis-CI environments using this tool

Release 2.1.1 - ButterKnife changes

When you upgrade version 2.1.0 to 2.1.1, you have to add code to keep ButterKnife running, because ButterKnife and Renderer libraries has been moved to the sample application in this commit: 0ab7fc3

First, you have to add ButterKnife dependency:
compile 'com.jakewharton:butterknife:7.0.1'

Second, you have to add ButterKnife.bind(this); to your activities or base activity (and fragments).
Rosie provides a new method to do it:

@Override
protected void onPrepareActivity() {
    super.onPrepareActivity();
    ButterKnife.bind(this);
}

@flipper83 Maybe, this should be added to the release changes

Extract caching logic from RosieRepository.java

I think It's not cool to put the caching logic directly into the repository (RosieRepository.java) because it violates the Single Responsibility Principle. Now, the concerns of data access and caching policy is in the same class, and if either of these changes, you'll need to change the class.

A better approach would be to use a Proxy pattern to apply the caching logic in a separate type like CachedRosieRepository.

NoSuchMethodError. No ButterKnife.bind(Activity) method

Hi,

I got weird crash when extending RosieAppCompatActivity. http://crashes.to/s/129c15fd43c

Fatal Exception: java.lang.NoSuchMethodError: No static method bind(Landroid/app/Activity;)V in class Lbutterknife/ButterKnife; or its super classes (declaration of 'butterknife.ButterKnife' appears in /data/app/com.anotherdev.photos500.debug-1/base.apk)
       at com.karumi.rosie.view.RosieAppCompatActivity.onCreate(RosieAppCompatActivity.java:50)

Except from my dependencies:

compile 'com.jakewharton:butterknife:8.0.1'
apt 'com.jakewharton:butterknife-compiler:8.0.1'
compile 'com.karumi.rosie:rosie:2.1.0'

Use a Page object to define pages

Using primitive types will prevent us from extending the library in that part, we should group offset + limit in a Pageclass to be able to modify how pages work while being backwards compatible.

Add realm migration DB

On current realm data source implementation the migration policy is remove the previous database values and create the table with empty values. That could be good for some project but someone it's posible that have other requirement, could be good can configure the migration Policy from outside without override the method getRealm.

Reflection Performance

Hi guys, I've been reading several articles on how and when to use reflection as well as I have also read that it can cause many problems in an application performance so too I have been studying this repository and I think Rosie Framework is an excellent contribution but I have some questions about the use of reflection So far I've seen two uses @usecase and @presenter .

How Rosie Framework affect applications performance with the reflector use?
Did you do any test performance?

Thank you for your great contributions!

I've really learned a lot thank you !!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.