Skip to content

dinocata/rxswift-mvvm-demo

Repository files navigation

RxSwift Clean Architecture demo

This project is based on https://github.com/sergdort/CleanArchitectureRxSwift with some significant differences:

  • Project is split into 3 main target modules: Presentation, Data and Domain
  • Presentation contains UI-specific implementation and dependency containers
  • Data contains platform-specific data providers (networking and local persistence) and repository implementations
  • Domain contains entities, use cases, use case implementations and repository definitions

It utilizes automatic dependency injection through Sourcery annotations and Swinject, thus eliminating the boiler plate of writing instance providers.

Routing is handled globally through a Coordinator, while view controllers are abstracted into Scenes.

This is an ongoing project, so some things (like tests, storage and local persistence examples) are on the TODO list.

HOW TO BUILD THE PROJECT:

  • Install Sourcery via brew install sourcery
  • Inside the project's root directory run the following command: sourcery --config .sourceryInit.yml. This will generate annotated code and link the generated files into the project. You only need to run this command once, since the code will be automatically generated each time you build the project through a build phase run script.

ANNOTATIONS

There are 3 main Sourcery annotations which define how will your boiler plate code be generated:

  • // sourcery: injectable

Registers a protocol or class into the instance container. Example:

// sourcery: injectable
protocol Service {}

class ServiceImpl: Service {
  // Some implementation
}

The above code will register Service into a global instance container through Swinject and resolve it as ServiceImpl, provided you have a defined implementation class. If a class is annotated, the class itself will be resolved.

Now comes the magic part. After a type is annotated with injectable, it will be automatically injected in any other injectable type when used as a dependency.

For example, the following code will inject ServiceImpl into RepositoryImpl:

// sourcery: injectable
protocol Repository {}

class RepositoryImpl: Repository {
  
  var service: Service!
}

Important note: All dependencies should be defined as force-unwrapped variables, because Sourcery is injecting the dependencies AFTER the instance is initialized.

You can also define a implementation class explicitly with annotation parameter:

// sourcery: injectable = Dog
protocol Animal {}

class Dog: Animal {
  // Some implementation
}

// sourcery: injectable = Owner
protocol Person {}

// Owner will have a pet Dog
class Owner: Person {
  var pet: Animal!
}

You can even explicitly choose which implementation to inject with, if you have multiple types conforming to the same protocol, for example:

// sourcery: injectable
class Dog: Animal {}

// sourcery: injectable
class Cat: Animal {}

class Owner: Person {
  // sourcery: inject = Cat
  var pet: Animal!
}

You can always manually resolve an instance, same as Sourcery does it under-the-hood. For example, InstanceContainer.instance.resolve(Person.self)! for the above example will return an instance of Owner, who has a pet Cat. But you should rarely, if ever need to do this.

  • // sourcery: singleton

Registers a protocol or class into the instance container as a singleton when combined with injectable. In other words, it does exactly the same as injectable, but the instance is resolved from a singleton AppContainer instead. Example:

// sourcery: injectable, singleton
protocol Repository {}

class RepositoryImpl: Repository {
  // Some implementation
}
  • // sourcery: scene

Defines a Scene for the annotated view controller.

All view controllers are mapped into an abstract Scene enum, which is code-generated. Scenes are used to perform coordinator transitions. You can always resolve a new view controller instance mapped to a Scene with Scene.someScene.viewController if you need to do something specific with a view controller other than navigating. For example, adding it as a child view controller to a tab view controller.

Important note: All view controllers should subclass either a MVVMController or CoordinatorVC in order for this annotation to work.

Example usage:

// sourcery: scene = dashboard, transition = present, navigation
class DashboardViewController: CoordinatorVC<DashboardViewModel> {
  // Some implementation
}

The above will register a DashboardViewController to a ViewControllerContainer and generate a new Scene enum case dashboard which can be used to perform navigation with a coordinator. Every view controller subclassing CoordinatorVC has a reference to a global coordinator. There are two optional parameters:

  • transition - defines transition type when navigating (can be root, push, present or modal)
  • navigation - wraps the view controller into a UINavigationController

You can now navigate to this view controller with:

self.coordinator.transition(to: .dashboard)

You can also explicitly define a transition type and a completion block:

self.coordinator.transition(to: .dashboard, type: .push) { print("Transition done!") }

If you need to pass some parameters or a delegate to the view controller, you can use parameter annotation and Sourcery will automatically define these parameters as associated values in the enum case:

// sourcery: scene = dashboard, transition = present, navigation
class DashboardViewController: CoordinatorVC<DashboardViewModel> {
  // sourcery:begin: parameter
  var someParam: String!
  var anotherParam: Int!
  weak var delegate: SomeDelegate?
  // sourcery:end
}

// When navigating:
self.coordinator.transition(to: .dashboard(someParam: "Hello World!", anotherParam: 5, delegate: self))

...and lastly:

This is NOT a library or a framework. It is an example iOS project written in RxSwift and following Clean Architecture patterns with minimal boiler plate. You are free to use the Sourcery .stencil templates found in the Templates/ directory inside the root folder and modify them however you like. The annotation feature is completely custom and architecture-independent. You only need Sourcery and Swinject in your project for it to work. Rest is just an example usage.

If you have questions, suggestions or any feedback, feel free to send them at dino.chata@gmail.com

About

RxSwift Clean Architecture demo

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published