Many developers learn Uncle Bob’s Component Principles from theory but struggle to understand where they apply in real projects.
Let’s take a simple Android BLE Scanner application as an example.
Imagine our project has two modules:
core-ble-scanner
feature-ble-scanner
core-ble-scanner
Contains:
BLE scanning logic
Scan manager
Bluetooth APIs
Device discovery logic
feature-ble-scanner
Contains:
Scan screen
RecyclerView
Compose UI
Device list display
User interactions
Architecture:
feature-ble-scanner
↓
core-ble-scanner
The UI depends on the scanner logic.
_
1. Reuse/Release Equivalence Principle (REP)
Idea
If something can be reused, it should be packaged as a reusable component.
Bad Example
BLE scanning code is written directly inside the UI module.
feature-ble-scanner
├── ScanScreen
├── DeviceList
└── BluetoothScanner
Now every feature that needs BLE scanning must copy the code.
Better Example
Move scanner logic to a separate component.
core-ble-scanner
├── BleScanner
├── ScanManager
└── BluetoothRepository
Now another feature can reuse it.
feature-ble-scanner
feature-device-pairing
feature-device-diagnostics
All use the same scanner component.
Lesson
Reusable code should live in its own reusable component.
2. Common Closure Principle (CCP)
Idea
Things that change together should stay together.
Example
Suppose Android introduces a new BLE scanning API.
What changes?
BleScanner
ScanManager
BluetoothRepository
All these classes belong to scanning.
They change for the same reason.
Therefore they belong together.
core-ble-scanner
├── BleScanner
├── ScanManager
└── BluetoothRepository
The UI doesn’t change.
So it should stay separate.
Lesson
Keep BLE logic together because it changes together.
3. Common Reuse Principle (CRP)
Idea
Don’t force modules to depend on things they don’t need.
Bad Example
core-ble-scanner
├── BleScanner
├── ScanManager
├── ScanScreen
├── DeviceAdapter
└── DeviceCard
Now another feature only needs scanning.
But it must also depend on:
Compose UI
RecyclerView
UI resources
which it doesn’t need.
Better Example
core-ble-scanner
├── BleScanner
└── ScanManager
feature-ble-scanner
├── ScanScreen
├── DeviceCard
└── DeviceList
Each module depends only on what it needs.
Lesson
Separate reusable logic from UI.
4. Acyclic Dependencies Principle (ADP)
Idea
Dependencies should not form circles.
Bad Example
feature-ble-scanner
↓
core-ble-scanner
↓
feature-ble-scanner
How can this happen?
Become a Medium member
Suppose:
_core-ble-scanner
_calls
ScanScreen.showError()
Now the core module depends on the feature module.
And the feature module already depends on core.
We created a cycle.
Better Example
feature-ble-scanner
↓
core-ble-scanner
Only one direction.
Core never knows about UI.
Lesson
Core modules should not depend on feature modules.
5. Stable Dependencies Principle (SDP)
Idea
Depend on stable things.
Example
UI changes frequently.
Maybe today:
RecyclerView
Tomorrow:
Jetpack Compose
Next month:
Different design
UI is unstable.
BLE scanning logic changes less often.
BleScanner
ScanManager
BluetoothRepository
are relatively stable.
Therefore:
feature-ble-scanner
↓
core-ble-scanner
is correct.
Not:
core-ble-scanner
↓
feature-ble-scanner
Lesson
Unstable UI should depend on stable business logic.
6. Stable Abstractions Principle (SAP)
Idea
Stable modules should expose abstractions.
Example
Instead of exposing implementation:
class AndroidBleScanner
Expose an interface.
interface BleScanner {
fun startScan()
fun stopScan()
}
Implementation:
class AndroidBleScanner : BleScanner
Now future implementations can be added.
FakeBleScanner
MockBleScanner
AndroidBleScanner
without changing consumers.
Lesson
Stable components should expose interfaces, not concrete implementations.
Final Architecture
core-ble-scanner
│
├── BleScanner
├── AndroidBleScanner
├── ScanManager
├── BluetoothRepository
└── Models
feature-ble-scanner
│
├── ScanScreen
├── DeviceList
├── DeviceCard
└── ViewModel
Dependency direction:
feature-ble-scanner
↓
core-ble-scanner
This architecture follows all six of Uncle Bob’s Component Principles:
- Reusable scanner logic is isolated
- BLE-related changes stay together
- UI is not forced on other features
- No circular dependencies
- UI depends on stable logic
- Stable logic exposes abstractions
And that’s exactly why many modern Android projects separate core modules from feature modules. This isn’t just about keeping code organized — it makes the application easier to maintain, test, and extend as new BLE features are added.
Top comments (0)