Skip to content

Multi‐module

Viacheslav Ivanovichev edited this page Nov 2, 2023 · 7 revisions

As mentioned in Concept, any application element can be treated as a black-box component. These components can range from small buttons to full-screen views or even a combination of views structured for navigation.

When we look at these components from a higher-level perspective, grouping them by specific domains, we create what we call a Feature. A Feature serves as another level of abstraction within the framework.

  • Definition: A Feature can be thought of as a black-box with a well-defined interface encompassing inputs (io), configurations (config), and dependencies.

Distinguishing points of a Feature black-box

  1. Unlike the View black-box, which is represented by a @Composable function, a Feature black-box offers more than just visual representation. And its API is much broader than just returning a Single @Composable view.
  2. A Feature can consist of a single screen or multiple screens, and it can also include multiple flows, or it might solely represent the core functionality of the app. Generally, Feature API boils down to three possible factory methods:
FeatureApi(config, dependencies, io) {
   @Composable
   fun view(config, dependencies, io)
   @Composable
   fun flow(config, dependencies, io)
   fun coreDependency(config, dependencies, io) -> SomeFeatureCoreDependency
} 

The approach of modularising the App Features is encouraged by Blackbox framework as it is essential for better manageability, parallel development, code reusability, testing isolation, build optimization, and clear ownership.

To achieve this, it is important to emphasize the principles of low coupling and high cohesion among black-box modules. These principles help ensure that each Feature can act independently and efficiently within the broader application context.

  • Low Coupling: This principle entails minimizing interdependencies between modules. Features should communicate through well-defined black-box interfaces and avoid direct reliance on the internal details of other modules. By adhering to low coupling, changes within one module are less likely to impact other modules, promoting flexibility and maintainability.
  • High Cohesion: High cohesion involves keeping related functionality within the same module. Each Feature should encapsulate a cohesive set of functionalities that work together to achieve a specific goal. This ensures that modules as well as black-boxes have a clear and well-defined purpose, making the codebase more organized and understandable.

As an example, let’s have a look on Ticketing Feature cohered into independent module within sample MovieApp. The Feature has multiple entry points created based on a specific user-scenario:

  1. Ticketing flow launched every time user presses Get Ticket button.
  2. Orders is a tab on main bottom navigation, that displays a list of purchased tickets via Ticketing Flow.
@Immutable
class TicketingConfig(val movieName: String)

@Immutable
class TicketingDependencies(httpClient: HttpClient)

@Immutable
class TicketingFactory(private val dependencies: TicketingDependencies) {
    private val orderRepository by lazy { OrderRepositoryImpl() }

    @Composable
    fun CreateTicketingFlow(
        modifier: Modifier,
        io: TicketingFlowIO,
        config: TicketingConfig
    ) = TicketingFlow(modifier, TicketingFlowDependencies(orderRepository), io, config)

    @Composable
    fun Orders(modifier: Modifier) = Orders(
        modifier = modifier,
        OrdersDependencies(orderRepository)
    )
}
Clone this wiki locally