Skip to content

Rewrite sample app in Kotlin + Jetpack Compose with Clean Architecture#273

Open
DmBard wants to merge 3 commits intomasterfrom
redesign
Open

Rewrite sample app in Kotlin + Jetpack Compose with Clean Architecture#273
DmBard wants to merge 3 commits intomasterfrom
redesign

Conversation

@DmBard
Copy link
Copy Markdown

@DmBard DmBard commented Apr 20, 2026

Replaces the legacy Java/XML sample app (MainActivity.java, SampleApplication.java, activity_main.xml, PNG launcher icons, w820dp resources) with a modern Kotlin and Jetpack Compose implementation that demonstrates the SumUp Reader SDK using a layered, testable architecture.

Architecture

  • Introduces a Clean Architecture structure with domain/, data/ and presentation/ layers, each holding only framework-appropriate types.
  • Domain: ReaderSdkRepository interface, pure Kotlin models (MerchantInfo, ConnectedReader, ReaderType, OfflineSession, OfflineSdkError, SdkResult, CheckoutConfiguration) and four suspend-enabled use cases (CreateLoginRequestUseCase, CreateCheckoutRequestUseCase, ParseSdkStatusUseCase, ParsePaymentResultUseCase), all returning Result<T> via runCatching.
  • Data: ReaderSdkRepositoryImpl wraps the SumUp SDK callbacks as coroutines and delegates conversion to dedicated mappers (MerchantInfoMapper, ConnectedReaderMapper, OfflineSessionMapper).
  • Presentation: a single MainViewModel exposing StateFlow<UiState> + Channel<UiEvent> drives three Compose screens — WelcomeScreen, CheckoutScreen, SettingsScreen — composed from AmountDisplay, Keypad and CheckoutResultDialog. SdkActionRequest lives in the presentation layer so SumUp SDK types never leak into domain.
  • Navigation uses NavHost with type-safe routes (Welcome, Checkout, Settings) hosted inside a Scaffold whose padding is propagated down.
  • Koin wires the whole graph in di/KoinModules.kt; a coroutine dispatcher abstraction (CoroutinesDispatcherProvider + DefaultCoroutinesDispatcherProvider) keeps the ViewModel testable.

UI / UX

  • Edge-to-edge rendering via enableEdgeToEdge(); screens apply statusBarsPadding() and navigationBarsPadding() and manage system bar icon tint with a SystemBarsAppearance helper.
  • New design system in theme/ (SparkBlue, Violet, LightSand, …) and typography scale (AmountTextStyle, ButtonLabelStyle, …) plugged into MaterialTheme's Typography slot for consistent Material3 defaults.
  • Checkout screen features an auto-sizing amount display, custom keypad, reader-status badge, offline indicator and a Material3 Button whose enabled state is bound to isLoggedIn && amountInCents > 0.
  • Settings screen surfaces tipping configuration (on-reader vs manual), offline-session controls (start/stop, upload transactions, security patch update), developer toggles (result dialog, success screen + custom timeout) and account/logout actions, with preview fixtures for each state.
  • New launcher icon (adaptive ic_launcher + foreground/background vectors), ic_settings_gear, ic_close, ic_location_pin, ic_sumup_logo, and a bg_welcome_map welcome background.

Error handling & observability

  • SafeResult.safely/safelySuspend wrap SDK calls, log failures with a consistent tag, and surface user-facing messages via UiState.latestMessage and a Snackbar.
  • handleFailure extension centralises failure logging and message surfacing while respecting coroutine cancellation.

Build

  • Adds Compose, Navigation, Koin, Material Icons and lifecycle-viewmodel dependencies; enables Compose compiler and removes obsolete dimens/layouts from build.gradle and resources.

Replaces the legacy Java/XML sample app (MainActivity.java, SampleApplication.java,
activity_main.xml, PNG launcher icons, w820dp resources) with a modern Kotlin and
Jetpack Compose implementation that demonstrates the SumUp Reader SDK using a
layered, testable architecture.

Architecture
- Introduces a Clean Architecture structure with domain/, data/ and
  `presentation/` layers, each holding only framework-appropriate types.
- Domain: `ReaderSdkRepository` interface, pure Kotlin models (`MerchantInfo`,
  `ConnectedReader`, `ReaderType`, `OfflineSession`, `OfflineSdkError`,
  `SdkResult`, `CheckoutConfiguration`) and four `suspend`-enabled use cases
  (`CreateLoginRequestUseCase`, `CreateCheckoutRequestUseCase`,
  `ParseSdkStatusUseCase`, `ParsePaymentResultUseCase`), all returning
  `Result<T>` via `runCatching`.
- Data: `ReaderSdkRepositoryImpl` wraps the SumUp SDK callbacks as coroutines
  and delegates conversion to dedicated mappers (`MerchantInfoMapper`,
  `ConnectedReaderMapper`, `OfflineSessionMapper`).
- Presentation: a single `MainViewModel` exposing `StateFlow<UiState>` +
  `Channel<UiEvent>` drives three Compose screens — `WelcomeScreen`,
  `CheckoutScreen`, `SettingsScreen` — composed from `AmountDisplay`,
  `Keypad` and `CheckoutResultDialog`. `SdkActionRequest` lives in the
  presentation layer so SumUp SDK types never leak into domain.
- Navigation uses `NavHost` with type-safe routes (`Welcome`, `Checkout`,
  `Settings`) hosted inside a `Scaffold` whose padding is propagated down.
- Koin wires the whole graph in `di/KoinModules.kt`; a coroutine dispatcher
  abstraction (`CoroutinesDispatcherProvider` +
  `DefaultCoroutinesDispatcherProvider`) keeps the ViewModel testable.

UI / UX
- Edge-to-edge rendering via `enableEdgeToEdge()`; screens apply
  `statusBarsPadding()` and `navigationBarsPadding()` and manage system bar
  icon tint with a `SystemBarsAppearance` helper.
- New design system in `theme/` (`SparkBlue`, `Violet`, `LightSand`, …) and
  typography scale (`AmountTextStyle`, `ButtonLabelStyle`, …) plugged into
  `MaterialTheme`'s `Typography` slot for consistent Material3 defaults.
- Checkout screen features an auto-sizing amount display, custom keypad,
  reader-status badge, offline indicator and a Material3 `Button` whose
  `enabled` state is bound to `isLoggedIn && amountInCents > 0`.
- Settings screen surfaces tipping configuration (on-reader vs manual),
  offline-session controls (start/stop, upload transactions, security
  patch update), developer toggles (result dialog, success screen + custom
  timeout) and account/logout actions, with preview fixtures for each state.
- New launcher icon (adaptive `ic_launcher` + foreground/background vectors),
  `ic_settings_gear`, `ic_close`, `ic_location_pin`, `ic_sumup_logo`, and a
  `bg_welcome_map` welcome background.

Error handling & observability
- `SafeResult.safely`/`safelySuspend` wrap SDK calls, log failures with a
  consistent tag, and surface user-facing messages via `UiState.latestMessage`
  and a Snackbar.
- `handleFailure` extension centralises failure logging and message surfacing
  while respecting coroutine cancellation.

Build
- Adds Compose, Navigation, Koin, Material Icons and lifecycle-viewmodel
  dependencies; enables Compose compiler and removes obsolete dimens/layouts
  from `build.gradle` and resources.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Rewrites the sample app from legacy Java/XML to a Kotlin + Jetpack Compose implementation that demonstrates the SumUp Reader SDK using a Clean Architecture split (domain/data/presentation) and Koin wiring.

Changes:

  • Migrates UI to Compose with navigation, theming, and three primary screens (Welcome/Checkout/Settings).
  • Introduces domain models + use-cases, plus a repository implementation that wraps SumUp SDK callbacks into coroutines with mappers.
  • Updates build + resources (Compose/Koin deps, adaptive launcher icons, new strings, removes legacy layouts/dimens).

Reviewed changes

Copilot reviewed 53 out of 60 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
build.gradle Updates build tooling dependencies (AGP/Kotlin plugin).
app/build.gradle Enables Kotlin/Compose and adds Compose, Navigation, Koin, lifecycle, coroutines deps.
app/src/main/AndroidManifest.xml Updates application/activity wiring for new Kotlin application + Compose activity and launcher icons.
app/src/main/java/com/sumup/app/SampleApplication.kt New Application that initializes SumUpState and starts Koin.
app/src/main/java/com/sumup/sdksampleapp/SampleApplication.java Removes legacy Java Application class.
app/src/main/java/com/sumup/sdksampleapp/MainActivity.java Removes legacy Java/XML Activity implementation.
app/src/main/res/layout/activity_main.xml Removes legacy XML layout.
app/src/main/res/values/strings.xml Adds Compose UI strings for screens/settings/dialogs.
app/src/main/res/values/dimens.xml Removes legacy dimens resource.
app/src/main/res/values-w820dp/dimens.xml Removes tablet-specific legacy dimens override.
app/src/main/res/values/colors.xml Adds launcher background color resource.
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml Adds adaptive launcher icon definition.
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml Adds adaptive round launcher icon definition.
app/src/main/res/drawable/ic_launcher_background.xml Adds new launcher icon background drawable.
app/src/main/res/drawable/ic_launcher_foreground.xml Adds new launcher icon foreground vector.
app/src/main/res/drawable/ic_sumup_logo.xml Adds SumUp logo vector used on welcome screen.
app/src/main/res/drawable/ic_settings_gear.xml Adds settings icon for checkout top bar.
app/src/main/res/drawable/ic_location_pin.xml Adds location pin vector used on welcome screen.
app/src/main/res/drawable/ic_close.xml Adds close icon vector (resource set).
app/src/main/res/drawable-nodpi/bg_welcome_map.webp Adds welcome screen background image asset.
app/src/main/java/com/sumup/app/di/KoinModules.kt Defines DI graph: dispatchers, mappers, repository, use cases, ViewModel.
app/src/main/java/com/sumup/app/util/CoroutinesDispatcherProvider.kt Adds dispatcher abstraction for testability.
app/src/main/java/com/sumup/app/util/SafeResult.kt Adds safe Result wrappers for logging + cancellation-aware error handling.
app/src/main/java/com/sumup/app/util/ResultExt.kt Adds Result failure handling helper to surface messages.
app/src/main/java/com/sumup/app/data/repository/ReaderSdkRepositoryImpl.kt Implements SDK repository, wrapping callbacks into suspend functions and mapping SDK models.
app/src/main/java/com/sumup/app/data/mapper/MerchantInfoMapper.kt Maps SDK merchant to domain MerchantInfo.
app/src/main/java/com/sumup/app/data/mapper/ConnectedReaderMapper.kt Maps SDK reader details to domain ConnectedReader/ReaderType.
app/src/main/java/com/sumup/app/data/mapper/OfflineSessionMapper.kt Maps SDK offline session state to domain OfflineSession.
app/src/main/java/com/sumup/app/domain/repository/ReaderSdkRepository.kt Introduces domain repository interface for SDK operations.
app/src/main/java/com/sumup/app/domain/model/MerchantInfo.kt Adds pure domain model for merchant info.
app/src/main/java/com/sumup/app/domain/model/ConnectedReader.kt Adds pure domain model for connected reader info.
app/src/main/java/com/sumup/app/domain/model/ReaderType.kt Adds domain enum for reader type display mapping.
app/src/main/java/com/sumup/app/domain/model/CheckoutConfiguration.kt Adds domain config model for checkout behavior and tipping.
app/src/main/java/com/sumup/app/domain/model/OfflineSession.kt Adds domain model for offline session state + UI cap constant.
app/src/main/java/com/sumup/app/domain/model/OfflineSdkError.kt Adds typed exceptions for offline operation failures.
app/src/main/java/com/sumup/app/domain/model/SdkResult.kt Adds unified domain representation of SDK activity results.
app/src/main/java/com/sumup/app/domain/usecase/CreateLoginRequestUseCase.kt Creates SumUpLogin request as a use case.
app/src/main/java/com/sumup/app/domain/usecase/CreateCheckoutRequestUseCase.kt Builds SumUpPayment request from UI state + config.
app/src/main/java/com/sumup/app/domain/usecase/ParseSdkStatusUseCase.kt Parses login/card-reader-page status bundles into SdkResult.Status.
app/src/main/java/com/sumup/app/domain/usecase/ParsePaymentResultUseCase.kt Parses checkout result bundles into SdkResult.Payment.
app/src/main/java/com/sumup/app/presentation/model/UiState.kt Adds single StateFlow-backed UI state for app screens.
app/src/main/java/com/sumup/app/presentation/model/UiEvent.kt Adds UI events for navigation and executing SDK actions.
app/src/main/java/com/sumup/app/presentation/model/SdkActionRequest.kt Defines presentation-level requests for SDK operations (login/checkout/card reader page).
app/src/main/java/com/sumup/app/presentation/navigation/Route.kt Defines type-safe route constants for Compose navigation.
app/src/main/java/com/sumup/app/presentation/MainViewModel.kt Implements app ViewModel: state, events, SDK result parsing, offline operations, settings.
app/src/main/java/com/sumup/app/presentation/MainActivity.kt Hosts Compose UI, collects events, drives navigation and SDK action dispatch.
app/src/main/java/com/sumup/app/presentation/theme/Theme.kt Adds Material3 color scheme + typography mapping into MaterialTheme.
app/src/main/java/com/sumup/app/presentation/theme/Type.kt Defines custom text styles used across screens/components.
app/src/main/java/com/sumup/app/presentation/theme/SystemBars.kt Adds helper to control system bar icon appearance.
app/src/main/java/com/sumup/app/presentation/screen/WelcomeScreen.kt Implements welcome screen UI.
app/src/main/java/com/sumup/app/presentation/screen/CheckoutScreen.kt Implements checkout screen UI (amount, keypad, status badges, result dialog).
app/src/main/java/com/sumup/app/presentation/screen/SettingsScreen.kt Implements settings UI (tipping/offline/dev options/account).
app/src/main/java/com/sumup/app/presentation/screen/components/AmountDisplay.kt Adds autosizing amount display component.
app/src/main/java/com/sumup/app/presentation/screen/components/Keypad.kt Adds custom keypad component.
app/src/main/java/com/sumup/app/presentation/screen/components/CheckoutResultDialog.kt Adds debug/result dialog component for SDK outcomes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/src/main/java/com/sumup/app/presentation/theme/SystemBars.kt
Comment thread app/src/main/java/com/sumup/app/presentation/screen/SettingsScreen.kt Outdated
Comment thread app/src/main/java/com/sumup/app/presentation/screen/CheckoutScreen.kt Outdated
Comment thread app/src/main/java/com/sumup/app/presentation/MainActivity.kt
Comment thread build.gradle
}
dependencies {
classpath 'com.android.tools.build:gradle:8.8.2'
classpath 'com.android.tools.build:gradle:8.9.1'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use kotlin for all gradle files?

Comment thread app/build.gradle

android {
namespace "com.sumup.sdksampleapp"
namespace "com.sumup.app"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to make sure it does not clash with any Sumup package names, maybe: com.sumup.sample.app

Comment thread app/build.gradle

dependencies {
implementation 'com.google.android.gms:play-services-location:21.3.0'
implementation platform('androidx.compose:compose-bom:2024.10.01')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use latest dependencies for the sample app?

Comment thread app/build.gradle
debug {
// All ProGuard rules required by the SumUp SDK are packaged with the library
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we replace with proguard-android-optimize.txt?

Comment thread build.gradle
dependencies {
classpath 'com.android.tools.build:gradle:8.8.2'
classpath 'com.android.tools.build:gradle:8.9.1'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.25'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we also use the latest version for gradle and kotlin plugins?

.getOrDefault(false)
if (loggedIn) {
refreshMerchantStateInternal()
_events.trySend(UiEvent.NavigateToCheckout)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can change all trySends to send(), as trySend might fail silently.

import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module

internal val appModule = module {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since there is only one module for the whole app, we can rename the file to KoinModule.

internal val appModule = module {
single<CoroutinesDispatcherProvider> { DefaultCoroutinesDispatcherProvider() }

single { OfflineSessionMapper() }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: As these are stateless mappers, we can change to factory

import java.math.BigDecimal
import java.util.UUID

internal class CreateCheckoutRequestUseCase(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Random place: Should we also cover the main logics with tests?

onBackspace: () -> Unit,
onDoubleZeroPressed: () -> Unit,
) {
val rows = listOf(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The list is rebuilt with each recomposition. Maybe hoisting it?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants