Giter VIP home page Giter VIP logo

passosdoimpeachment-app's Introduction

This project is deprecated

This project is being deprecated. I have no more plans to update it.

I have been working on a new project that is better organized and "cleaner". I may in the future decouple its framework and make a new opensource project with it. If you have any ideas, shout it to me ^^;

Thank you o7

Passos do Impeachment (Android app)

A full demo Android app showcasing data syncing with a RESTful server. Android SyncAdapter is used to schedule the appropriate time to sync the data, a process done with Retrofit. StorIO caches the data for offline usage. To keep devices up-to-date, the server issues GCM messages to the client when things changes. Kotlin and RxJava makes everything beautiful.

In short, the following awesome projects were used:

The app has an accompanying server to sync the data with. Check it here: PassosDoImpeachment-server

Phone1

Phone2

Phone3

Tablet1


Architecture

The architecture adopted in this project was heavily based on the Android-CleanArchitecture project. A more in-depth view of that architecture can be found in:

A layered approach is used to better understand the architecture. The following image shows the three layers (like in an onion) used in this app:

Layers

A quick description of each layer will be given soon. But, the important thing to take from this image is how the dependency arrow only points inwards. In other words, the domain layer doesn't known a thing about the presentation layer. However, the presentation depends on the domain layer.

Each layer can have many modules (as in a gradle module), and those modules are written in the image above. For instance, the domain layer has just the domain module. However, the app layer has the app, db and cloud modules.

domain layer

This is where the business logic resides. Most of the logic on how entities talk with each other and data manipulations happens here.

One concrete example: You got a list of responses from the cloud as json objects and you want to cache it with the app's local storage. The domain layer will provide a use case (interactors) to the rest of the application with that functionality, as in this piece of code.

Important points:

  • No dependencies to others layers
  • Try to keep as minimum as possible the dependencies to others frameworks/libraries/tech

By not adding dependencies here you can delay the decision on what framework/libraries/tech to use. The decision on what database (Realm, SQLite, etc) should not matter at this point. And, if one doesn't work well for your project, switching it around will be less painful.

However, there are times when you need some functionality from a framework (Android for example). And when these times come, you invert the dependency (a.k.a. Dependency Inversion Principle).

In practice, let's say you want to share a text. The domain layer can just create an interface, as in this piece of code, and use it when needed without worrying what is implementing it. So another layer, the app for instance, can then step up and implement it, like this.

presentation layer

The presentation layer depends on the domain layer. But, as in the domain layer, the presentation also tries to keep the number of dependencies to a minimum. With that, the presentation does not depends on any framework (like Android).

This layer is mainly made of two types of objects:

  • Presenter: A bridge between the MvpView and the domain layer. It holds the logic to control the view, and uses the domain layer to get the data. It also has logic to deal with inputs from users (can be a person, or any external system).

  • MvpView: Interface or abstract class with no logic at all. All it does is to describe all the actions the presenter can do on a view, and what external events it can receive. In practice, this view will be implemented by the app layer.

app layer

The app layer is where we start to see more specific implementations. This layer has three modules:

  • db: Implements the repositories.
  • cloud: Implements the rest clients.
  • app: The app module is where most of the code goes. In here we will find the MainActivity, Android services (GCMRegistration, SyncAdapters, etc) and Android views.

Architecture Diagram

The following diagram shows in more detail the different parts of the architecture and how they relate to each other:

Diagram

domain module

In the domain layer/module we have many interfaces. As commented early, its so we don't worry about specific implementations here.

The use cases are concrete classes, and they are the ones that will interact with the rest of the system. All use cases implements an abstract UseCase. This implementation was heavily based on the one used by Fernando Cejas.

I really liked his design. Instead of the use case returning an Observable, he forces the user to pass a subscriber instead. This way, if the user decides to do some more complex logic, it will be kind awkward, so it is better to just create another use case.

cloud and db modules

Those two modules are really boring. All they do is implement some of the interfaces found in the domain layer.

The db module has two implementations, one using Content Providers (example) and another In-memory (example). The cloud module also has two implementations, one using Retrofit (example) and another mock implementation that only replays what we want (example).

presentation module

The presentation module is based on the MVP design pattern. Presenters will mostly call use cases and control the MvpView. Sometimes the presenter may use a device, but its communication is mostly limited to the view and the use cases.

Each presenter will have an interface describing the events it can receive from the system. While the view will have an interface describing what it can show on screen (eg. ListStepsMvp). Presenters and Views are grouped together based on the "contract" pattern used by the android-architecture project (tip by @hussam789, thanks!).

The "Clean-way Android architecture" implementation I mentioned earlier keeps presentation + app on the same module. But, why put the presentation module on a different module than the app? To me it was because my presentation module has no dependencies to the Android framework. Also, keeping things separated was much easier to keep track of things and have an idea of what every screen and view will have to do.

app module

The app module is composed of a single activity named MainActivity. It uses Conductor to control the screens (the views the users can see). With Conductor, a controller can be like a fragment or an activity. A controller will have a presenter (but will not communicate with it) and will implement a view.

The controller is like a bridge that connects the MvpView from the presentation layer to Android Views (like a ViewPager or a TextView).

The only way for a controller to communicate with a presenter is through listeners in the view, this way the presenter can keep track of what is going on.

The services that run on background, like synchronization and GCM registration, will mostly delegate its work to use cases. Also in this layer you will find the implementations for the devices interfaces.


Reactive

The entire architecture is built from the ground up to support reactive programming. Let's see how the list of news is automatically update when the data changes.

First the domain declares repositories that return Observables (NewsRepository).

The GetNewsUseCase will just return the same observable from the repository. So the repository implementation must be aware of it. For the Content Provider version StorIO was used as it provides reactive queries. For the In-memory db, the behavior was simulated using BehaviorSubjects.

The ListNewsPresenter will subscribe to the GetNewsUseCase and group the news by date prior to sending them to the MvpView. This operation is reactively done every time the data changes.

The MvpView is implemented by the ListNewsController, and when it gets a request to render news, all it does is update the data in the RecyclerView.Adapter and call notifyDataSetChanged().


Dependency Injection

The example above shows one simple presenter, the ListNewsPresenter.

It has one problem though, that list of parameters in its constructor. This is one problem with "dependency injection", where the objects require the instance of many others in order for it to function.

Dagger2 to the rescue \o/. Why? well, instead of manually entering all those parameters, we can declare the presenter to be injected then ask Dagger2 to inject it for us. And that is it! \o/. Dagger2 just figured out how to build all the parameters of the ListNewsPresenter, then it instantiated a ListNewsPresenter for us, and put it nicelly in our parameter. Isn't Dagger2 a sweet little.. amm thing??

Now, the question on how Dagger2 actually know what object to instantiate is more complex and will not be covered here, but it is mostly just about components and modules.


Automated Testing

domain module

Testing in the domain module is pretty straightforward because it only contains use cases as concrete classes. We can assume everything else is working perfectly.

Although, use cases depend on repositories, rest services and etc. That is where Mockito comes into play. Mockito is awesome, it allows us to quickly fake a repository (for example the when keyword is part of Mockito), and focus on the functionality of the use case being tested.

cloud module

The cloud module implements the REST client using Retrofit. This client will do HTTP requests. To properly test it, the MockWebServer project is used to create a local mock server. MockWebServer lets us quickly test the behavior of our client with different responses and can even simulate bad connections.

Here is a code example.

db module

The db module implements the repositories interfaces using Content Providers and SQLite, part of the Android Framework. But, thanks to the Robolectric project we don't have to run these tests on an Android emulator or a real device \o/. Robolectric will fake a Content Provider and SQLite database for us \o/.

Robolectric is pretty simple too, just run your tests on its environment, and use the functions as one would in Android. Here is a code example. Note how you can get a ContentResolver from the runtime environment :)

presentation module

One of the main advantages of having a separate presentation module is to better test the MVP.

Unfortunately, the app module existed long before this separation was introduced in this project, and most tests were already created for the app module. The presentation module was them created to pass these tests.

Thus, this module ended up without tests. In an app still in development, it is highly recommended to test this module.

app module

The app module is where the application is built, so testing it is a bit less straighforward. First, the app will have full syncing capabilities. It will try connect to the cloud and access the actual database on the device. Because content from the cloud is not predictable, we can't use it for testing.

This is where Dagger2 makes things a lot easier. We can create a Dagger2 component telling what kind of implementations we want use for the repositories/rest services interfaces. For testing, we want to use an in-memory implementation, and insert into it what we want just for the tests. This is the component/module that does it.

In our tests we can just use that component instead of the usual one that uses real implementations: code

With the mock implementations injected into our app, we can then populate the fake database with a predictable content: like this.

The actual testing of the app is done with Espresso, a view hierarchy based testing framework. Most of the time, we must first find a view in the hierarchy and then do some action on it, like a click or check if it is visible. This is where a predictable content comes in handy, because we can then find the view using its text, like this. Because we have many views with id R.id.step_title_tv, we can differentiate between them using its text content.

In short, the app module testing is all about mocking the data, and test a bunch of actions and check if the screen is showing what we expect it to show :)


Where are all the comments?

After applying this architecture, and making sure all the rules are followed and everything named properly, I found almost no reason to add comments in code.


Running

To get things started:

  1. Get a GCM configuration file and place it in the app folder.
    1. GCM file from here.
  2. Open the project in Android Studio and just run it.
  3. By default, the app will fetch data from the internet on first run (or when data is outdated). You can easily change the REST config in the file RestConfig.kt to use your local server.

License

Passos do Impeachment (app) is published under the Apache 2.0 license.

passosdoimpeachment-app's People

Contributors

allanhasegawa avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

passosdoimpeachment-app's Issues

Navigation

you wrote a very good project overview but its not clear how Navigation works in this app.
i know that based on MVP the view listens for click events and tells the presenter about these events and then the presenter asks the view to launch new app.

another question: why you don't have presenter interface for each feature that describes the actions that can be started from the View?

One-way MVP with a Passive View

#3 grouped Views and Presenters together, however, by doing so I introduced a two-way communication between the View and the Presenter. This means the View must know how to interact with the presenter and it also need to hold a reference to it.

This goes against the MVP pattern and the idea of a "Passive View":

Things to consider:

  1. Testing a presenter with a passive view looks simpler (to try)
  2. What is the best approach to let the presenter listen for events on the view? Before was implemented like below, and the presenter would just assign a new function to the buttonClickListener when bound to the view.
    1. Problem: Controller, Fragments etc cant implement this view
    2. The above point can be solved by using an unnamed class implementing this abstract view
    3. Only one listener per View (do I even something other than the presenter to listen for events?)

 abstract class View : MvpView {
     var buttonClickListener: (Int) -> Unit = {buttonId: Int -> Unit}
 }
  1. Another option for listener is to use maybe observables?
    1. Should we use shared subjects? (side-effects :( )
    2. Wrap observables around listeners instances? can we automate it somehow?
    3. If using observables, should the view return one Observable<Any> with many different messages ? or many different Observable<...>s for each type of event? Or a single Observable<...> with a data class containing all the possible events?
    4. The presenter will also have to clean up the subscription for these observables.

TDD?

did you use Test Driven Development in this project? can you describe the flow of writing tests for this application? i mean which module you started writing tests and did you write the tests for each usecase across all the project layer (app, presentation, db...etc)?

Single module

Resources:

My arguments:

Pros of a single module structure

  1. build system: Faster compilation times, specially when using Kotlin and code generation. Any time a code generation must be performed, a full build is required and a multi module system is slow. The build system of a single module is also much simpler, specially when dependencies version clash.
  2. ???

Cons of a single module structure

  1. dependencies: not all parts of the system requires all of the dependencies of the app.
  2. testing: I found it much easier to test each module separated from each other. Just assume the others modules works perfectly and mock them :)
  3. clear separation of concerns: It is very clear how different the domain module is from the app module. When a new functionality must be added, reasoning about where it should be is less confusing.

I'm leaning more towards a multi module structure, however, keeping this issue open to remind me to check what others devs more experienced than me thinks about it...

Entities should be immutable

Entities in this project only hold data. They are implemented using Kotlin's data class.

To make sure we don't accidentally insert side-effects when working with entities, we could make all entities immutable.

Kotlin has a very cool and straightforward way of dealing with immutable data class:
the copy function

Group Presenter and MvpView together

Each MvpView is associated with a Presenter. For this codebase, I could easily group these together.

Example:

class CreditsMvp {
    abstract class View : MvpView {
        var haseEmailTouchListener: () -> Unit = {}
        var haseGitHubTouchListener: () -> Unit = {}
    }
    class Presenter(...) : Presenter<CreditsMvp.View> {
        ....
     }
}

Enhance Sync Mechanism

currently all sync calls trigger full data sync for all the entities.
but in most cases you want to sync only type of business entity. for example: swipe to refresh on the news list, you want to sync only the news.

in this case do you encourage adding a field to the SyncEntitycalled ActionType?
in the SyncAdapter's onPerformSync you get all list of pendingSyncEntities and it performs the intended sync action, onSuccess you mark it as Successful SyncEntity.

this also would be useful for Sync-To-Server type of Actions

Review lifetime of objects inside Controllers

With the new Conductor 2.0, Controllers are kept in memory inside a "Back Stack" even when not on screen or being used. Controllers should be light weight objects, so this is not an issue. However, this also means we shouldn't keep strong references to heavy objects inside Controllers when not needed.

Things to do:

  • Update project to use Conductor 2.0
  • Add LeakCanary and reference watchers to the lifecycle of Controllers
  • Review the lifetime of objects created by the Controllers
  • Remove strong references to objects that are not needed (like Presenters after the View is destroyed)

Package by feature

Resources:

Right now the project uses an organization like this:

+ usecases
 |-- GetNewsUseCase
 |-- AddNewsUseCase
 |-- GetStepsUseCase
 |-- AddStepsUseCase
+ repositories
 |-- NewsRepository
 |-- StepsRepository

A better approach may be to package by feature and then by layer? Something like this:

+ news
|-- + usecases
|-- |-- GetNewsUseCase
|-- |-- AddNewsUseCase
|-- NewsRepository
+ steps
|-- + usecases
|-- |-- GetStepsUseCase
|-- |-- AddStepsUseCase
|-- StepsRepository

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.