Skip to main content

Overview

NetPOS is built on modern Android architecture principles using MVVM (Model-View-ViewModel) pattern with clean architecture separation. The application leverages Dagger Hilt for dependency injection, RxJava2 for reactive programming, and Room for local persistence.

Core Architecture Patterns

MVVM Pattern

The app follows the Model-View-ViewModel architecture:
  • Models: Data entities in com.woleapp.netpos.model
  • Views: Activities and Fragments in com.woleapp.netpos.ui
  • ViewModels: Business logic in com.woleapp.netpos.viewmodels
// MainActivity.kt - View observes ViewModel
class MainActivity : AppCompatActivity() {
    private val viewModel: DashBoardViewModel by viewModels()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        observeViewModel()
    }
}

Application Components

1. Application Class

The NetPosApp class initializes core services at startup:
app/NetPosApp.kt
@HiltAndroidApp
class NetPosApp : Application() {
    override fun onCreate() {
        super.onCreate()
        Timber.plant(Timber.DebugTree())
        FirebaseApp.initializeApp(this)
        
        // Initialize NetPos SDK
        NetPosSdk.init()
        NetPosSdk.loadProvidedCapksAndAids()
        
        // Subscribe to Firebase topics
        Firebase.messaging.subscribeToTopic("netpos_campaign")
    }
}
  • Timber: Logging framework initialization
  • Firebase: Push notifications and analytics
  • SharedPreferences: Using Prefs library for simple key-value storage
  • NetPos SDK: Card reader and terminal configuration
  • RxJava Error Handler: Global error handling

2. Dependency Injection

NetPOS uses Dagger Hilt for dependency injection, configured in di/Module.kt:38:
@Module
@InstallIn(SingletonComponent::class)
object Module {
    @Provides @Singleton
    @Named("defaultOkHttpClient")
    fun providesDefaultOkHttpClient(
        @Named("loginInterceptor") loggingInterceptor: Interceptor
    ): OkHttpClient =
        OkHttpClient().newBuilder()
            .connectTimeout(120, TimeUnit.SECONDS)
            .readTimeout(120, TimeUnit.SECONDS)
            .addInterceptor(loggingInterceptor)
            .build()
}

3. Network Layer

Multiple API services configured with different base URLs:
Main backend API for authentication, transactions, and merchant data
  • Base URL: Configured via BuildConfig.STRING_DEFAULT_BASE_URL
  • Endpoints: /api/auth, /api/token, /api/agents/{stormId}
Zenith Bank pay-by-transfer integration
  • Base URL: BuildConfig.STRING_ZENITH_BASE_URL
  • Endpoints: /api/getUserAccount/{terminalId}, /api/queryTransactions
Contactless QR payment processing
  • Endpoint: /contactlessQr
Payment checkout operations
Customer complaints and feedback
Providus Bank merchant account integration
FCMB Bank merchant account integration

4. Reactive Programming

NetPOS extensively uses RxJava2 for asynchronous operations:
viewmodels/AuthViewModel.kt:79
private fun auth(username: String, password: String) {
    authInProgress.value = true
    stormApiService.userToken(credentials)
        .flatMap { response ->
            if (!response.success) {
                throw Exception("Login Failed")
            }
            val userToken = response.token
            Prefs.putString(PREF_USER_TOKEN, userToken)
            Single.just(parseUserFromToken(userToken))
        }
        .subscribeOn(Schedulers.io())
        .doFinally { authInProgress.postValue(false) }
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe { res, error ->
            // Handle response
        }
        .disposeWith(disposables)
}
All RxJava subscriptions are disposed in ViewModel’s onCleared() to prevent memory leaks.

Data Flow Architecture

Example: Transaction Flow

1

User initiates transaction

User taps on transaction type in MainActivity
2

ViewModel processes request

TransactionsViewModel validates input and creates transaction request
3

Transaction processor

TransactionProcessor communicates with card reader via NIBSS EPMS library
4

Network call

Result logged to backend via StormApiService.logTransactionBeforeConnectingToNibss()
5

Local persistence

Transaction saved to Room database via TransactionResponseDao
6

UI update

LiveData observers update UI with transaction result

Key Components

Activities

AuthenticationActivity

Entry point and user authentication
  • Launched on app start with LAUNCHER intent
  • Handles login and password reset

MainActivity

Main dashboard and transaction hub
  • Fragment container for all transaction flows
  • Navigation between features

Services & Receivers

  • MyFirebaseMessagingService: Push notifications from Firebase Cloud Messaging
  • BatteryReceiver: Monitors battery status and power connection
  • BootReceiver: Handles device boot and shutdown events

Configuration Management

NetPOS supports multiple product flavors (white-label configurations):
build.gradle:104
flavorDimensions "whiteLabels"
productFlavors {
    netpos { dimension "whiteLabels" }
    zenith { 
        dimension "whiteLabels"
        applicationIdSuffix ".zenith"
    }
    wema { applicationIdSuffix ".wema" }
    providus { applicationIdSuffix ".providus" }
    konga { applicationIdSuffix ".konga" }
    heritage { applicationIdSuffix ".heritage" }
}

Threading Model

@Provides @Singleton
@Named("io-scheduler")
fun providesIoScheduler(): Scheduler = Schedulers.io()
Network operations and database queries always run on IO scheduler, with results observed on main thread.

State Management

ViewModel state is managed using LiveData and MutableLiveData:
viewmodels/TransactionsViewModel.kt:32
val lastTransactionResponse = MutableLiveData<TransactionResponse>()
private val _message = MutableLiveData<Event<String>>()
val message: LiveData<Event<String>> get() = _message

val inProgress = MutableLiveData(false)
val done: LiveData<Boolean> get() = _done
Single-use events are wrapped in Event<T> class to prevent re-emission on configuration changes.

Build Variants

The application supports two build types:
  • Debug: Development with verbose logging, signing config enabled
  • Release: Production with minification and ProGuard
Combined with 12 product flavors, this creates 24 possible build variants.

External Dependencies

  • Kotlin: 1.8+ with coroutines support
  • AndroidX: AppCompat, Core-KTX, ConstraintLayout
  • Material Components: Material Design UI components
  • Lifecycle: ViewModel, LiveData (v2.6.1)
  • Room: 2.5+ for local database
  • Paging: 2.1.2 for paginated transaction lists
  • Dagger Hilt: 2.48.1
  • Retrofit: 2.9.0 with RxJava2 adapter
  • OkHttp: 5.0.0-alpha.2 with logging interceptor
  • Gson: JSON serialization
  • RxJava2: 2.2.21
  • RxAndroid: 2.1.1
  • RxKotlin: 2.4.0

Next Steps

Database Schema

Explore Room database entities and relationships

ViewModels

Detailed ViewModel implementations

Services

API services and network layer

Security

Security implementation and encryption

Build docs developers (and LLMs) love